diff --git a/MAINTAINERS b/MAINTAINERS index adbe6d9c5658..a9fe96417547 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21907,7 +21907,7 @@ F: tools/lib/perf/ F: tools/perf/ PERFORMANCE EVENTS TOOLING ARM64 -R: John Garry +R: John Garry R: Will Deacon R: James Clark R: Mike Leach diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h index 9998252ea8ec..65ee09bd1463 100644 --- a/tools/arch/arm64/include/uapi/asm/kvm.h +++ b/tools/arch/arm64/include/uapi/asm/kvm.h @@ -111,6 +111,7 @@ struct kvm_regs { #define KVM_ARM_VCPU_PTRAUTH_GENERIC 6 /* VCPU uses generic authentication */ #define KVM_ARM_VCPU_HAS_EL2 7 /* Support nested virtualization */ #define KVM_ARM_VCPU_HAS_EL2_E2H0 8 /* Limit NV support to E2H RES0 */ +#define KVM_ARM_VCPU_PMU_V3_STRICT 9 /* No default PMU creation */ struct kvm_vcpu_init { __u32 target; diff --git a/tools/arch/powerpc/include/uapi/asm/kvm.h b/tools/arch/powerpc/include/uapi/asm/kvm.h index 077c5437f521..913a64b901a3 100644 --- a/tools/arch/powerpc/include/uapi/asm/kvm.h +++ b/tools/arch/powerpc/include/uapi/asm/kvm.h @@ -437,6 +437,24 @@ struct kvm_ppc_cpu_char { __u64 behaviour_mask; /* valid bits in behaviour */ }; +/* For KVM_PPC_GET_COMPAT_CAPS */ +struct kvm_ppc_compat_caps { + __u64 size; /* Size of this structure */ + __u64 flags; /* Reserved for future use */ + __u64 compat_capabilities; /* Capabilities supported by the host */ +}; +#define KVM_PPC_COMPAT_CAPS_SIZE_VER0 24 /* sizeof first published struct */ + +/* + * Capability bits for compat_capabilities field in kvm_ppc_compat_caps. + * These bits indicate which processor compatibility modes are supported. + */ +#define KVM_PPC_COMPAT_CAP_POWER9 (1ULL << 62) +#define KVM_PPC_COMPAT_CAP_POWER10 (1ULL << 61) +#define KVM_PPC_COMPAT_CAP_POWER11 (1ULL << 60) +#define KVM_PPC_COMPAT_BITMASK (KVM_PPC_COMPAT_CAP_POWER9 | \ + KVM_PPC_COMPAT_CAP_POWER10 | \ + KVM_PPC_COMPAT_CAP_POWER11) /* * Values for character and character_mask. * These are identical to the values used by H_GET_CPU_CHARACTERISTICS. diff --git a/tools/arch/x86/include/asm/cpufeatures.h b/tools/arch/x86/include/asm/cpufeatures.h index 6547c0aee45b..fe84cf4e97d3 100644 --- a/tools/arch/x86/include/asm/cpufeatures.h +++ b/tools/arch/x86/include/asm/cpufeatures.h @@ -136,8 +136,12 @@ #define X86_FEATURE_HYPERVISOR ( 4*32+31) /* "hypervisor" Running on a hypervisor */ /* VIA/Cyrix/Centaur-defined CPU features, CPUID level 0xC0000001, word 5 */ +#define X86_FEATURE_SM2 ( 5*32+ 0) /* "sm2" SM2 algorithm */ +#define X86_FEATURE_SM2_EN ( 5*32+ 1) /* "sm2_en" SM2 enabled */ #define X86_FEATURE_XSTORE ( 5*32+ 2) /* "rng" RNG present (xstore) */ #define X86_FEATURE_XSTORE_EN ( 5*32+ 3) /* "rng_en" RNG enabled */ +#define X86_FEATURE_CCS ( 5*32+ 4) /* "ccs" SM3 + SM4 instructions */ +#define X86_FEATURE_CCS_EN ( 5*32+ 5) /* "ccs_en" CCS enabled */ #define X86_FEATURE_XCRYPT ( 5*32+ 6) /* "ace" on-CPU crypto (xcrypt) */ #define X86_FEATURE_XCRYPT_EN ( 5*32+ 7) /* "ace_en" on-CPU crypto enabled */ #define X86_FEATURE_ACE2 ( 5*32+ 8) /* "ace2" Advanced Cryptography Engine v2 */ @@ -146,6 +150,12 @@ #define X86_FEATURE_PHE_EN ( 5*32+11) /* "phe_en" PHE enabled */ #define X86_FEATURE_PMM ( 5*32+12) /* "pmm" PadLock Montgomery Multiplier */ #define X86_FEATURE_PMM_EN ( 5*32+13) /* "pmm_en" PMM enabled */ +#define X86_FEATURE_RNG2 ( 5*32+22) /* "rng2" RNG v2 */ +#define X86_FEATURE_RNG2_EN ( 5*32+23) /* "rng2_en" RNG2 enabled */ +#define X86_FEATURE_PHE2 ( 5*32+25) /* "phe2" PadLock Hash Engine v2 */ +#define X86_FEATURE_PHE2_EN ( 5*32+26) /* "phe2_en" PHE2 enabled */ +#define X86_FEATURE_RSA ( 5*32+27) /* "rsa" Big-number arithmetic */ +#define X86_FEATURE_RSA_EN ( 5*32+28) /* "rsa_en" RSA enabled */ /* More extended AMD flags: CPUID level 0x80000001, ECX, word 6 */ #define X86_FEATURE_LAHF_LM ( 6*32+ 0) /* "lahf_lm" LAHF/SAHF in long mode */ @@ -365,6 +375,8 @@ #define X86_FEATURE_HWP_HIGHEST_PERF_CHANGE (14*32+15) /* HWP Highest perf change */ #define X86_FEATURE_HFI (14*32+19) /* "hfi" Hardware Feedback Interface */ +#define X86_FEATURE_DPTI (14*32+24) /* Intel Directed Package Thermal Interrupt */ + /* AMD SVM Feature Identification, CPUID level 0x8000000a (EDX), word 15 */ #define X86_FEATURE_NPT (15*32+ 0) /* "npt" Nested Page Table support */ #define X86_FEATURE_LBRV (15*32+ 1) /* "lbrv" LBR Virtualization support */ @@ -465,6 +477,8 @@ #define X86_FEATURE_NO_NESTED_DATA_BP (20*32+ 0) /* No Nested Data Breakpoints */ #define X86_FEATURE_WRMSR_XX_BASE_NS (20*32+ 1) /* WRMSR to {FS,GS,KERNEL_GS}_BASE is non-serializing */ #define X86_FEATURE_LFENCE_RDTSC (20*32+ 2) /* LFENCE always serializing / synchronizes RDTSC */ + +#define X86_FEATURE_TLBSYNC_SFW_NO (20*32+ 4) /* TLBI Erratum #1718 fixed in microcode */ #define X86_FEATURE_VERW_CLEAR (20*32+ 5) /* The memory form of VERW mitigates TSA */ #define X86_FEATURE_NULL_SEL_CLR_BASE (20*32+ 6) /* Null Selector Clears Base */ @@ -575,4 +589,5 @@ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ #define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ +#define X86_BUG_TLBI X86_BUG( 1*32+12) /* "tlbi" AMD TLBI Erratum #1718 */ #endif /* _ASM_X86_CPUFEATURES_H */ diff --git a/tools/arch/x86/include/asm/msr-index.h b/tools/arch/x86/include/asm/msr-index.h index 18c4be75e927..9c26d4075c5f 100644 --- a/tools/arch/x86/include/asm/msr-index.h +++ b/tools/arch/x86/include/asm/msr-index.h @@ -350,6 +350,13 @@ #define ARCH_PEBS_LBR_SHIFT 40 #define ARCH_PEBS_LBR (0x3ull << ARCH_PEBS_LBR_SHIFT) #define ARCH_PEBS_VECR_XMM BIT_ULL(49) +#define ARCH_PEBS_VECR_YMMH BIT_ULL(50) +#define ARCH_PEBS_VECR_EGPRS BIT_ULL(51) +#define ARCH_PEBS_VECR_OPMASK BIT_ULL(53) +#define ARCH_PEBS_VECR_ZMMH BIT_ULL(54) +#define ARCH_PEBS_VECR_H16ZMM BIT_ULL(55) +#define ARCH_PEBS_VECR_EXT_SHIFT 49 +#define ARCH_PEBS_VECR_EXT (0x7full << ARCH_PEBS_VECR_EXT_SHIFT) #define ARCH_PEBS_GPR BIT_ULL(61) #define ARCH_PEBS_AUX BIT_ULL(62) #define ARCH_PEBS_EN BIT_ULL(63) diff --git a/tools/arch/x86/include/uapi/asm/perf_regs.h b/tools/arch/x86/include/uapi/asm/perf_regs.h index 7c9d2bb3833b..faaa82df688d 100644 --- a/tools/arch/x86/include/uapi/asm/perf_regs.h +++ b/tools/arch/x86/include/uapi/asm/perf_regs.h @@ -2,6 +2,8 @@ #ifndef _ASM_X86_PERF_REGS_H #define _ASM_X86_PERF_REGS_H +#include + enum perf_event_x86_regs { PERF_REG_X86_AX, PERF_REG_X86_BX, @@ -27,9 +29,35 @@ enum perf_event_x86_regs { PERF_REG_X86_R13, PERF_REG_X86_R14, PERF_REG_X86_R15, + /* + * The eGPRs/SSP and XMM have overlaps. Only one can be used + * at a time. The ABI PERF_SAMPLE_REGS_ABI_SIMD is used to + * distinguish which one is used. If PERF_SAMPLE_REGS_ABI_SIMD + * is set, then eGPRs/SSP is used, otherwise, XMM is used. + * + * Extended GPRs (eGPRs) + */ + PERF_REG_X86_R16, + PERF_REG_X86_R17, + PERF_REG_X86_R18, + PERF_REG_X86_R19, + PERF_REG_X86_R20, + PERF_REG_X86_R21, + PERF_REG_X86_R22, + PERF_REG_X86_R23, + PERF_REG_X86_R24, + PERF_REG_X86_R25, + PERF_REG_X86_R26, + PERF_REG_X86_R27, + PERF_REG_X86_R28, + PERF_REG_X86_R29, + PERF_REG_X86_R30, + PERF_REG_X86_R31, + PERF_REG_X86_SSP, /* These are the limits for the GPRs. */ PERF_REG_X86_32_MAX = PERF_REG_X86_GS + 1, PERF_REG_X86_64_MAX = PERF_REG_X86_R15 + 1, + PERF_REG_MISC_MAX = PERF_REG_X86_SSP + 1, /* These all need two bits set because they are 128bit */ PERF_REG_X86_XMM0 = 32, @@ -54,5 +82,30 @@ enum perf_event_x86_regs { }; #define PERF_REG_EXTENDED_MASK (~((1ULL << PERF_REG_X86_XMM0) - 1)) +#define PERF_X86_EGPRS_MASK __GENMASK_ULL(PERF_REG_X86_R31, PERF_REG_X86_R16) + +enum { + PERF_X86_SIMD_XMM_REGS = 16, + PERF_X86_SIMD_YMM_REGS = 16, + PERF_X86_SIMD_ZMM_REGS = 32, + PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_ZMM_REGS, + + PERF_X86_SIMD_OPMASK_REGS = 8, + PERF_X86_SIMD_PRED_REGS_MAX = PERF_X86_SIMD_OPMASK_REGS, +}; + +#define PERF_X86_SIMD_PRED_MASK __GENMASK(PERF_X86_SIMD_PRED_REGS_MAX - 1, 0) +#define PERF_X86_SIMD_VEC_MASK __GENMASK_ULL(PERF_X86_SIMD_VEC_REGS_MAX - 1, 0) + +#define PERF_X86_H16ZMM_BASE 16 + +enum { + /* 1 qword = 8 bytes */ + PERF_X86_OPMASK_QWORDS = 1, + PERF_X86_XMM_QWORDS = 2, + PERF_X86_YMM_QWORDS = 4, + PERF_X86_ZMM_QWORDS = 8, + PERF_X86_SIMD_QWORDS_MAX = PERF_X86_ZMM_QWORDS, +}; #endif /* _ASM_X86_PERF_REGS_H */ diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature index 99eb0ea09537..4d37d11f674c 100644 --- a/tools/build/Makefile.feature +++ b/tools/build/Makefile.feature @@ -79,12 +79,13 @@ FEATURE_TESTS_BASIC := \ libelf-zstd \ libnuma \ numa_num_possible_cpus \ - libpython \ + python-module \ libslang \ libtraceevent \ libcpupower \ pthread-attr-setaffinity-np \ pthread-barrier \ + pthread-mutex-robust \ reallocarray \ stackprotector-all \ timerfd \ @@ -113,8 +114,7 @@ FEATURE_TESTS_EXTRA := \ compile-x32 \ cplus-demangle \ cxa-demangle \ - gtk2 \ - gtk2-infobar \ + gtk4 \ hello \ babeltrace2-ctf-writer \ libcapstone \ @@ -122,7 +122,6 @@ FEATURE_TESTS_EXTRA := \ libbfd-liberty \ libbfd-liberty-z \ libopencsd \ - libperl \ llvm \ libbpf \ libpfm4 \ @@ -146,7 +145,7 @@ FEATURE_DISPLAY ?= \ libelf \ libnuma \ numa_num_possible_cpus \ - libpython \ + python-module \ libcapstone \ llvm-perf \ zlib \ diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile index 7d165018116a..10340a537224 100644 --- a/tools/build/feature/Makefile +++ b/tools/build/feature/Makefile @@ -11,8 +11,7 @@ FILES= \ test-eventfd.bin \ test-fortify-source.bin \ test-glibc.bin \ - test-gtk2.bin \ - test-gtk2-infobar.bin \ + test-gtk4.bin \ test-hello.bin \ test-libbfd.bin \ test-libbfd-threadsafe.bin \ @@ -32,8 +31,7 @@ FILES= \ test-libdebuginfod.bin \ test-libnuma.bin \ test-numa_num_possible_cpus.bin \ - test-libperl.bin \ - test-libpython.bin \ + test-python-module.bin \ test-libslang.bin \ test-libtraceevent.bin \ test-libcpupower.bin \ @@ -43,6 +41,7 @@ FILES= \ $(foreach arch,$(LIBUNWIND_ARCHS),test-libunwind-$(arch).bin test-libunwind-debug-frame-$(arch).bin) \ test-pthread-attr-setaffinity-np.bin \ test-pthread-barrier.bin \ + test-pthread-mutex-robust.bin \ test-stackprotector-all.bin \ test-timerfd.bin \ test-babeltrace2-ctf-writer.bin \ @@ -130,6 +129,9 @@ $(OUTPUT)test-pthread-attr-setaffinity-np.bin: $(OUTPUT)test-pthread-barrier.bin: $(BUILD) -lpthread +$(OUTPUT)test-pthread-mutex-robust.bin: + $(BUILD) -lpthread + $(OUTPUT)test-stackprotector-all.bin: $(BUILD) -fstack-protector-all @@ -240,46 +242,24 @@ $(OUTPUT)test-libcpupower.bin: $(OUTPUT)test-libtracefs.bin: $(BUILD) $(shell $(PKG_CONFIG) --cflags libtracefs 2>/dev/null) -ltracefs -$(OUTPUT)test-gtk2.bin: - $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null) -Wno-deprecated-declarations +$(OUTPUT)test-gtk4.bin: + $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk4 2>/dev/null) -$(OUTPUT)test-gtk2-infobar.bin: - $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null) - -grep-libs = $(filter -l%,$(1)) -strip-libs = $(filter-out -l%,$(1)) - -PERL_EMBED_LDOPTS = $(shell perl -MExtUtils::Embed -e ldopts 2>/dev/null) -PERL_EMBED_LDFLAGS = $(call strip-libs,$(PERL_EMBED_LDOPTS)) -PERL_EMBED_LIBADD = $(call grep-libs,$(PERL_EMBED_LDOPTS)) -PERL_EMBED_CCOPTS = $(shell perl -MExtUtils::Embed -e ccopts 2>/dev/null) -FLAGS_PERL_EMBED=$(PERL_EMBED_CCOPTS) $(PERL_EMBED_LDOPTS) - -ifeq ($(CC_NO_CLANG), 0) - PERL_EMBED_LDOPTS := $(filter-out -specs=%,$(PERL_EMBED_LDOPTS)) - PERL_EMBED_CCOPTS := $(filter-out -flto=auto -ffat-lto-objects, $(PERL_EMBED_CCOPTS)) - PERL_EMBED_CCOPTS := $(filter-out -specs=%,$(PERL_EMBED_CCOPTS)) - FLAGS_PERL_EMBED += -Wno-compound-token-split-by-macro -endif - -$(OUTPUT)test-libperl.bin: - $(BUILD) $(FLAGS_PERL_EMBED) - -$(OUTPUT)test-libpython.bin: +$(OUTPUT)test-python-module.bin: $(BUILD) $(FLAGS_PYTHON_EMBED) $(OUTPUT)test-libbfd.bin: - $(BUILD_BFD) + $(BUILD_BFD) || $(BUILD_BFD) -lzstd $(OUTPUT)test-libbfd-threadsafe.bin: - $(BUILD_BFD) || $(BUILD_BFD) -liberty || $(BUILD_BFD) -liberty -lz + $(BUILD_BFD) || $(BUILD_BFD) -lzstd || $(BUILD_BFD) -liberty || $(BUILD_BFD) -liberty -lz $(OUTPUT)test-disassembler-four-args.bin: - $(BUILD_BFD) -lopcodes || $(BUILD_BFD) -lopcodes -liberty || \ + $(BUILD_BFD) -lopcodes || $(BUILD_BFD) -lopcodes -lzstd || $(BUILD_BFD) -lopcodes -liberty || \ $(BUILD_BFD) -lopcodes -liberty -lz $(OUTPUT)test-disassembler-init-styled.bin: - $(BUILD_BFD) -lopcodes || $(BUILD_BFD) -lopcodes -liberty || \ + $(BUILD_BFD) -lopcodes || $(BUILD_BFD) -lopcodes -liberty || \ $(BUILD_BFD) -lopcodes -liberty -lz $(OUTPUT)test-reallocarray.bin: diff --git a/tools/build/feature/test-all.c b/tools/build/feature/test-all.c index 544563d62950..4a6890290f74 100644 --- a/tools/build/feature/test-all.c +++ b/tools/build/feature/test-all.c @@ -10,8 +10,8 @@ * Quirk: Python headers cannot be in arbitrary places, so keep this testcase at * the top: */ -#define main main_test_libpython -# include "test-libpython.c" +#define main main_test_python_module +# include "test-python-module.c" #undef main #define main main_test_hello @@ -90,6 +90,10 @@ # include "test-pthread-barrier.c" #undef main +#define main main_test_pthread_mutex_robust +# include "test-pthread-mutex-robust.c" +#undef main + #define main main_test_scandirat # include "test-scandirat.c" #undef main @@ -148,7 +152,7 @@ int main(int argc, char *argv[]) { - main_test_libpython(); + main_test_python_module(); main_test_hello(); main_test_libelf(); main_test_gettid(); @@ -167,6 +171,7 @@ int main(int argc, char *argv[]) main_test_zlib(); main_test_pthread_attr_setaffinity_np(); main_test_pthread_barrier(); + main_test_pthread_mutex_robust(); main_test_lzma(); main_test_bpf(); main_test_scandirat(); diff --git a/tools/build/feature/test-gettid.c b/tools/build/feature/test-gettid.c index ef24e42d3f1b..a8fdb2619669 100644 --- a/tools/build/feature/test-gettid.c +++ b/tools/build/feature/test-gettid.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2019, Red Hat Inc, Arnaldo Carvalho de Melo +#undef _GNU_SOURCE #define _GNU_SOURCE #include diff --git a/tools/build/feature/test-gtk2-infobar.c b/tools/build/feature/test-gtk2-infobar.c deleted file mode 100644 index b1b716dd5733..000000000000 --- a/tools/build/feature/test-gtk2-infobar.c +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#pragma GCC diagnostic ignored "-Wstrict-prototypes" -#include -#pragma GCC diagnostic error "-Wstrict-prototypes" - -int main(int argc, char *argv[]) -{ - gtk_init(&argc, &argv); - gtk_info_bar_new(); - - return 0; -} diff --git a/tools/build/feature/test-gtk2.c b/tools/build/feature/test-gtk4.c similarity index 76% rename from tools/build/feature/test-gtk2.c rename to tools/build/feature/test-gtk4.c index 2aaf4bfc2055..b9520e7408b9 100644 --- a/tools/build/feature/test-gtk2.c +++ b/tools/build/feature/test-gtk4.c @@ -3,9 +3,9 @@ #include #pragma GCC diagnostic error "-Wstrict-prototypes" -int main(int argc, char *argv[]) +int main(void) { - gtk_init(&argc, &argv); + gtk_init(); return 0; } diff --git a/tools/build/feature/test-libdw.c b/tools/build/feature/test-libdw.c index aabd63ca76b4..46f75cde5947 100644 --- a/tools/build/feature/test-libdw.c +++ b/tools/build/feature/test-libdw.c @@ -49,8 +49,16 @@ int test_elfutils(void) return 0; } +/* Needs elfutils 0.160: taking its address fails the probe, not the link. */ +int test_libdw_cu_getdwarf(void) +{ + void *sym = (void *)dwarf_cu_getdwarf; + + return sym == NULL; +} + int main(void) { return test_libdw() + test_libdw_unwind() + test_libdw_getlocations() + - test_libdw_getcfi() + test_elfutils(); + test_libdw_getcfi() + test_libdw_cu_getdwarf() + test_elfutils(); } diff --git a/tools/build/feature/test-libperl.c b/tools/build/feature/test-libperl.c deleted file mode 100644 index 0415f437eb31..000000000000 --- a/tools/build/feature/test-libperl.c +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include -#include - -int main(void) -{ - perl_alloc(); - - return 0; -} diff --git a/tools/build/feature/test-libpython.c b/tools/build/feature/test-libpython.c deleted file mode 100644 index 371c9113e49d..000000000000 --- a/tools/build/feature/test-libpython.c +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include - -int main(void) -{ - Py_Initialize(); - - return 0; -} -#undef _GNU_SOURCE diff --git a/tools/build/feature/test-pthread-mutex-robust.c b/tools/build/feature/test-pthread-mutex-robust.c new file mode 100644 index 000000000000..d8409074713d --- /dev/null +++ b/tools/build/feature/test-pthread-mutex-robust.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +int main(void) +{ + pthread_mutexattr_t attr; + pthread_mutex_t mtx; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(&mtx, &attr); + pthread_mutex_consistent(&mtx); + pthread_mutex_destroy(&mtx); + pthread_mutexattr_destroy(&attr); + + return 0; +} diff --git a/tools/build/feature/test-python-module.c b/tools/build/feature/test-python-module.c new file mode 100644 index 000000000000..50e9e5062feb --- /dev/null +++ b/tools/build/feature/test-python-module.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +int main(void) +{ + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "test", + }; + PyObject *module = PyModule_Create(&moduledef); + + return module ? 0 : -1; +} diff --git a/tools/build/feature/test-timerfd.c b/tools/build/feature/test-timerfd.c index 9c72c697a9df..f88e8cc63ef3 100644 --- a/tools/build/feature/test-timerfd.c +++ b/tools/build/feature/test-timerfd.c @@ -6,7 +6,7 @@ int main(void) { - struct itimerspec new_value; + struct itimerspec new_value = {}; int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if (fd < 0) diff --git a/tools/include/tools/dis-asm-compat.h b/tools/include/tools/dis-asm-compat.h index 70f331e23ed3..1e0586d8451b 100644 --- a/tools/include/tools/dis-asm-compat.h +++ b/tools/include/tools/dis-asm-compat.h @@ -2,6 +2,7 @@ #ifndef _TOOLS_DIS_ASM_COMPAT_H #define _TOOLS_DIS_ASM_COMPAT_H +#include #include #include diff --git a/tools/include/uapi/linux/if_addr.h b/tools/include/uapi/linux/if_addr.h index aa7958b4e41d..7fb630b7fe31 100644 --- a/tools/include/uapi/linux/if_addr.h +++ b/tools/include/uapi/linux/if_addr.h @@ -36,6 +36,7 @@ enum { IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ IFA_TARGET_NETNSID, IFA_PROTO, /* u8, address protocol */ + IFA_MC_USERS, /* u32, multicast group users */ __IFA_MAX, }; diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h index 419011097fa8..ac2d77d14963 100644 --- a/tools/include/uapi/linux/kvm.h +++ b/tools/include/uapi/linux/kvm.h @@ -997,6 +997,8 @@ struct kvm_enable_cap { #define KVM_CAP_S390_KEYOP 247 #define KVM_CAP_S390_VSIE_ESAMODE 248 #define KVM_CAP_S390_HPAGE_2G 249 +#define KVM_CAP_PPC_COMPAT_CAPS 250 +#define KVM_CAP_ARM_PMU_V3_STRICT 251 struct kvm_irq_routing_irqchip { __u32 irqchip; @@ -1341,6 +1343,8 @@ struct kvm_s390_keyop { /* Available with KVM_CAP_COUNTER_OFFSET */ #define KVM_ARM_SET_COUNTER_OFFSET _IOW(KVMIO, 0xb5, struct kvm_arm_counter_offset) #define KVM_ARM_GET_REG_WRITABLE_MASKS _IOR(KVMIO, 0xb6, struct reg_mask_range) +/* Available with KVM_CAP_PPC_COMPAT_CAPS */ +#define KVM_PPC_GET_COMPAT_CAPS _IO(KVMIO, 0xb8) /* ioctl for vm fd */ #define KVM_CREATE_DEVICE _IOWR(KVMIO, 0xe0, struct kvm_create_device) diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h index fd10aa8d697f..c49fc76292f7 100644 --- a/tools/include/uapi/linux/perf_event.h +++ b/tools/include/uapi/linux/perf_event.h @@ -314,8 +314,9 @@ enum { */ enum perf_sample_regs_abi { PERF_SAMPLE_REGS_ABI_NONE = 0, - PERF_SAMPLE_REGS_ABI_32 = 1, - PERF_SAMPLE_REGS_ABI_64 = 2, + PERF_SAMPLE_REGS_ABI_32 = (1 << 0), + PERF_SAMPLE_REGS_ABI_64 = (1 << 1), + PERF_SAMPLE_REGS_ABI_SIMD = (1 << 2), }; /* @@ -383,6 +384,7 @@ enum perf_event_read_format { #define PERF_ATTR_SIZE_VER7 128 /* Add: sig_data */ #define PERF_ATTR_SIZE_VER8 136 /* Add: config3 */ #define PERF_ATTR_SIZE_VER9 144 /* add: config4 */ +#define PERF_ATTR_SIZE_VER10 176 /* Add: sample_simd_{vec|pred}_reg_* */ /* * 'struct perf_event_attr' contains various attributes that define @@ -547,6 +549,29 @@ struct perf_event_attr { __u64 config3; /* extension of config2 */ __u64 config4; /* extension of config3 */ + + /* + * Defines the sampling SIMD/PRED(predicate) registers bitmap and + * qwords (8 bytes) length. + * + * sample_simd_regs_enabled != 0 indicates there are SIMD/PRED + * registers to be sampled, the SIMD/PRED registers bitmap and + * qwords length are represented in + * sample_simd_{vec|pred}_reg_{intr|user} and + * sample_simd_{vec|pred}_reg_qwords fields separately. + * + * sample_simd_regs_enabled == 0 indicates no SIMD/PRED registers + * are sampled. + */ + __u16 sample_simd_regs_enabled; + __u16 sample_simd_pred_reg_qwords; + __u16 sample_simd_vec_reg_qwords; + __u16 __reserved_4; + + __u32 sample_simd_pred_reg_intr; + __u32 sample_simd_pred_reg_user; + __u64 sample_simd_vec_reg_intr; + __u64 sample_simd_vec_reg_user; }; /* @@ -1020,7 +1045,15 @@ enum perf_event_type { * } && PERF_SAMPLE_BRANCH_STACK * * { u64 abi; # enum perf_sample_regs_abi - * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER + * u64 regs[weight(mask)]; + * struct { + * u64 nr_vectors; # 0 ... weight(sample_simd_vec_reg_user) + * u64 vector_qwords; # 0 ... sample_simd_vec_reg_qwords + * u64 nr_pred; # 0 ... weight(sample_simd_pred_reg_user) + * u64 pred_qwords; # 0 ... sample_simd_pred_reg_qwords + * u64 data[nr_vectors * vector_qwords + nr_pred * pred_qwords]; + * } && (abi & PERF_SAMPLE_REGS_ABI_SIMD) + * } && PERF_SAMPLE_REGS_USER * * { u64 size; * char data[size]; @@ -1047,7 +1080,15 @@ enum perf_event_type { * { u64 data_src; } && PERF_SAMPLE_DATA_SRC * { u64 transaction; } && PERF_SAMPLE_TRANSACTION * { u64 abi; # enum perf_sample_regs_abi - * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_INTR + * u64 regs[weight(mask)]; + * struct { + * u64 nr_vectors; # 0 ... weight(sample_simd_vec_reg_intr) + * u64 vector_qwords; # 0 ... sample_simd_vec_reg_qwords + * u64 nr_pred; # 0 ... weight(sample_simd_pred_reg_intr) + * u64 pred_qwords; # 0 ... sample_simd_pred_reg_qwords + * u64 data[nr_vectors * vector_qwords + nr_pred * pred_qwords]; + * } && (abi & PERF_SAMPLE_REGS_ABI_SIMD) + * } && PERF_SAMPLE_REGS_INTR * { u64 phys_addr;} && PERF_SAMPLE_PHYS_ADDR * { u64 cgroup;} && PERF_SAMPLE_CGROUP * { u64 data_page_size;} && PERF_SAMPLE_DATA_PAGE_SIZE diff --git a/tools/include/uapi/linux/rtnetlink.h b/tools/include/uapi/linux/rtnetlink.h index 27265fd31e5f..3988b51db539 100644 --- a/tools/include/uapi/linux/rtnetlink.h +++ b/tools/include/uapi/linux/rtnetlink.h @@ -399,6 +399,7 @@ enum rtattr_type_t { RTA_DPORT, RTA_NH_ID, RTA_FLOWLABEL, + RTA_DEL_REASON, __RTA_MAX }; @@ -407,6 +408,30 @@ enum rtattr_type_t { #define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) +/* RTA_DEL_REASON: why the kernel deleted the route. u32. + * Emitted only on RTM_DELROUTE notifications, and only when the deletion + * path records a cause. Absence means either an older kernel or a + * deletion path that does not (yet) record its cause - consumers must + * treat "absent" and "unspec" identically. New causes may be appended. + * Currently only IPv6 deletion paths record a cause. + * + * The attribute is notification-only: the kernel rejects it in + * requests, so a notification must not be echoed back verbatim. + * + * The value space is family-agnostic: a value must never be + * reinterpreted per address family. A cause that only one family can + * produce still gets its own value rather than reusing another + * family's. + */ +enum rt_del_reason { + RT_DEL_REASON_UNSPEC, /* cause not recorded */ + RT_DEL_REASON_EXPIRED, /* RTF_EXPIRES lifetime ran out (GC) */ + RT_DEL_REASON_RA_WITHDRAWN, /* zero-lifetime RA / PIO / RIO */ + __RT_DEL_REASON_MAX +}; + +#define RT_DEL_REASON_MAX (__RT_DEL_REASON_MAX - 1) + /* RTM_MULTIPATH --- array of struct rtnexthop. * * "struct rtnexthop" describes all necessary nexthop information, diff --git a/tools/lib/api/fd/array.c b/tools/lib/api/fd/array.c index 67b73481df27..3200746d1657 100644 --- a/tools/lib/api/fd/array.c +++ b/tools/lib/api/fd/array.c @@ -116,6 +116,10 @@ int fdarray__filter(struct fdarray *fda, short revents, return 0; for (fd = 0; fd < fda->nr; ++fd) { + /* + * Explicitly bypass nonfilterable items (e.g. control descriptors). + * This ensures their fd, events, and revents remain completely untouched. + */ if (fda->priv[fd].flags & fdarray_flag__nonfilterable) continue; diff --git a/tools/lib/perf/evlist.c b/tools/lib/perf/evlist.c index 1f210dadd666..c1c5b0684eed 100644 --- a/tools/lib/perf/evlist.c +++ b/tools/lib/perf/evlist.c @@ -79,8 +79,9 @@ static void __perf_evlist__propagate_maps(struct perf_evlist *evlist, } } - /* Ensure cpus only references valid PMU CPUs. */ - if (!perf_cpu_map__has_any_cpu(evsel->cpus) && + /* Ensure cpus only references valid PMU CPUs, except for uncore PMUs. */ + if (!evsel->is_pmu_uncore && + !perf_cpu_map__has_any_cpu(evsel->cpus) && !perf_cpu_map__is_subset(evsel->pmu_cpus, evsel->cpus)) { struct perf_cpu_map *tmp = perf_cpu_map__intersect(evsel->pmu_cpus, evsel->cpus); diff --git a/tools/lib/perf/include/internal/evsel.h b/tools/lib/perf/include/internal/evsel.h index b988034f1371..35447bed0f95 100644 --- a/tools/lib/perf/include/internal/evsel.h +++ b/tools/lib/perf/include/internal/evsel.h @@ -128,6 +128,8 @@ struct perf_evsel { bool requires_cpu; /** Is the PMU for the event a core one? Effects the handling of own_cpus. */ bool is_pmu_core; + /** Is the PMU for the event an uncore one? */ + bool is_pmu_uncore; /** Does the evsel on read on the first CPU index such as tool time events? */ bool reads_only_on_cpu_idx0; int idx; diff --git a/tools/lib/perf/mmap.c b/tools/lib/perf/mmap.c index ec124eb0ec0a..ab3adf1d8baf 100644 --- a/tools/lib/perf/mmap.c +++ b/tools/lib/perf/mmap.c @@ -200,7 +200,7 @@ void perf_mmap__read_done(struct perf_mmap *map) /* * Check if event was unmapped due to a POLLHUP/POLLERR. */ - if (!refcount_read(&map->refcnt)) + if (!refcount_read(&map->refcnt) || !map->overwrite) return; map->prev = perf_mmap__read_head(map); diff --git a/tools/perf/.clang-format b/tools/perf/.clang-format index e4cff363a603..7cc11f84da88 100644 --- a/tools/perf/.clang-format +++ b/tools/perf/.clang-format @@ -21,3 +21,4 @@ IncludeCategories: # Your Project's Other Headers - Regex: '^".*"' Priority: 4 +AlignArrayOfStructures: Left diff --git a/tools/perf/Build b/tools/perf/Build index e18c80a5c1bc..dcba5ba55206 100644 --- a/tools/perf/Build +++ b/tools/perf/Build @@ -61,7 +61,6 @@ perf-util-y += arch/ perf-y += arch/ perf-test-y += arch/ perf-ui-y += ui/ -perf-util-y += scripts/ gtk-y += ui/gtk/ @@ -77,7 +76,9 @@ $(OUTPUT)%.shellcheck_log: % $(call rule_mkdir) $(Q)$(call echo-cmd,test)$(SHELLCHECK) "$<" > $@ || (cat $@ && rm $@ && false) -perf-y += $(SHELL_TEST_LOGS) +.PHONY: shellcheck +shellcheck: $(addprefix $(OUTPUT),$(SHELL_TEST_LOGS)) + @: ifdef MYPY PY_TESTS := $(shell find python -type f -name '*.py') @@ -88,9 +89,11 @@ endif $(OUTPUT)%.mypy_log: % $(call rule_mkdir) - $(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false) + $(Q)$(call echo-cmd,test)$(MYPY) "$<" > $@ || (cat $@ && rm $@ && false) -perf-y += $(MYPY_TEST_LOGS) +.PHONY: mypy +mypy: $(addprefix $(OUTPUT),$(MYPY_TEST_LOGS)) + @: ifdef PYLINT PY_TESTS := $(shell find python -type f -name '*.py') @@ -101,6 +104,8 @@ endif $(OUTPUT)%.pylint_log: % $(call rule_mkdir) - $(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false) + $(Q)$(call echo-cmd,test)$(PYLINT) "$<" > $@ || (cat $@ && rm $@ && false) -perf-y += $(PYLINT_TEST_LOGS) +.PHONY: pylint +pylint: $(addprefix $(OUTPUT),$(PYLINT_TEST_LOGS)) + @: diff --git a/tools/perf/Documentation/build-docdep.perl b/tools/perf/Documentation/build-docdep.perl index ba4205e0302a..f19f5107fe0f 100755 --- a/tools/perf/Documentation/build-docdep.perl +++ b/tools/perf/Documentation/build-docdep.perl @@ -41,6 +41,6 @@ while ($changed) { while (my ($text, $included) = each %include) { if (! exists $included{$text} && (my $base = $text) =~ s/\.txt$//) { - print "$base.html $base.xml : ", join(" ", keys %$included), "\n"; + print "\$(OUTPUT)$base.html \$(OUTPUT)$base.xml : ", join(" ", keys %$included), "\n"; } } diff --git a/tools/perf/Documentation/db-export.txt b/tools/perf/Documentation/db-export.txt index 52ffccb02d55..a736be3b6e84 100644 --- a/tools/perf/Documentation/db-export.txt +++ b/tools/perf/Documentation/db-export.txt @@ -1,14 +1,14 @@ Database Export =============== -perf tool's python scripting engine: +perf tool's python module: - tools/perf/util/scripting-engines/trace-event-python.c + tools/perf/util/python.c -supports scripts: +supports standalone scripts: - tools/perf/scripts/python/export-to-sqlite.py - tools/perf/scripts/python/export-to-postgresql.py + tools/perf/python/export-to-sqlite.py + tools/perf/python/export-to-postgresql.py which export data to a SQLite3 or PostgreSQL database. @@ -31,11 +31,5 @@ backward compatibility by testing for the presence of new tables and columns before using them. e.g. function IsSelectable() in exported-sql-viewer.py 4. The export scripts themselves maintain forward compatibility (i.e. an existing -script will continue to work with new versions of perf) by accepting a variable -number of arguments (e.g. def call_return_table(*x)) i.e. perf can pass more -arguments which old scripts will ignore. - -5. The scripting engine tests for the existence of script handler functions -before calling them. The scripting engine can also test for the support of new -or optional features by checking for the existence and value of script global -variables. +script will continue to work with new versions of perf) by querying event and +sample attributes dynamically from the perf Python module. diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt index a688738809c4..1a90b09a12d5 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -95,6 +95,9 @@ include::itrace.txt[] --gtk:: Use the GTK interface. +--weights:: Show or hide (with `--no-weights`) weight columns in annotation output. + By default, weight columns are shown when samples contain non-zero weights. + -C:: --cpu=:: Only report samples for the list of CPUs provided. Multiple CPUs can be provided as a comma-separated list with no space: 0,1. Ranges of diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt index c5913cf59c98..7f820f69740f 100644 --- a/tools/perf/Documentation/perf-bench.txt +++ b/tools/perf/Documentation/perf-bench.txt @@ -58,6 +58,9 @@ SUBSYSTEM 'numa':: NUMA scheduling and MM benchmarks. +'atomic':: + Atomic operation benchmarks. + 'futex':: Futex stressing benchmarks. @@ -283,6 +286,41 @@ SUITES FOR 'numa' *mem*:: Suite for evaluating NUMA workloads. +SUITES FOR 'atomic' +~~~~~~~~~~~~~~~~~~~ +*cas*:: +Suite for evaluating compare-and-swap (CAS) atomic operations under +multi-threaded contention. + +*inc*:: +Suite for evaluating atomic fetch-add (increment) operations under +multi-threaded contention. + +Options of *cas* and *inc* +^^^^^^^^^^^^^^^^^^^^^^^^^^ +-T:: +--threads=:: +Number of threads contending for the shared counter (default: 2, or +1 on a single-CPU machine). + +-R:: +--runtime=:: +Runtime of each repeat in seconds (default: 2). + +The per-thread fastest/slowest/avg ops summary is only printed when +running with more than one thread. + +The average throughput is annotated with its relative deviation +("+- x%"), i.e. the standard error of the mean over the measured +repeats. A small value means the average is reproducible; it does not +reflect per-thread fairness, which the per-thread ops summary shows. + +Note that the cas benchmark counts successful compare-and-swap +operations only; retries and the loads between them are additional +atomic instructions not included in the ops/sec count, so cas and +inc throughputs are not directly comparable at the instruction +level. + SUITES FOR 'futex' ~~~~~~~~~~~~~~~~~~ *hash*:: diff --git a/tools/perf/Documentation/perf-c2c.txt b/tools/perf/Documentation/perf-c2c.txt index 8775889bc0a3..7a0cf31be7ed 100644 --- a/tools/perf/Documentation/perf-c2c.txt +++ b/tools/perf/Documentation/perf-c2c.txt @@ -107,6 +107,10 @@ REPORT OPTIONS --stats:: Display only statistic tables and force stdio mode. +--function:: + Display the function view and force stdio mode. This requires `iaddr` + in the cacheline coalescing fields and cannot be used with `--stats`. + --full-symbols:: Display full length of symbols. @@ -341,7 +345,7 @@ output fields set for cacheline offsets output: Code address, Code symbol, Shared Object, Source line dso - coalesced by shared object -By default the coalescing is setup with 'pid,iaddr'. +By default, coalescing uses `iaddr`. STDIO OUTPUT ------------ @@ -360,6 +364,10 @@ Following tables are displayed: Shared Cache Line Distribution Pareto - list of all accessed offsets for each cacheline +With `--function`, the cacheline and Pareto tables are replaced by a fully +expanded Shared Data Functions Table. Its three levels are the read-side +function, contending writer, and shared cacheline, as detailed below. + TUI OUTPUT ---------- The TUI output provides interactive interface to navigate @@ -374,8 +382,8 @@ Verbose mode also includes code addresses in function rows, and code addresses remain available in the per-cacheline detail view ('d'). The function view requires `iaddr` in the cacheline coalescing fields. If -`--coalesce` omits it, TAB reports that the view is unavailable rather than -attributing already-coalesced samples to an arbitrary function. +`--coalesce` omits it, TAB or `--function` reports that the view is unavailable +rather than attributing already-coalesced samples to an arbitrary function. Level 1: the read-side function, sorted by Cycles % (estimated load cycles: HITM, peer-snoop and other-load cycles) diff --git a/tools/perf/Documentation/perf-check.txt b/tools/perf/Documentation/perf-check.txt index 09e1d35677f5..1406a41c5d09 100644 --- a/tools/perf/Documentation/perf-check.txt +++ b/tools/perf/Documentation/perf-check.txt @@ -58,14 +58,13 @@ feature:: libLLVM / HAVE_LIBLLVM_SUPPORT libnuma / HAVE_LIBNUMA_SUPPORT libopencsd / HAVE_CSTRACE_SUPPORT - libperl / HAVE_LIBPERL_SUPPORT libpfm4 / HAVE_LIBPFM - libpython / HAVE_LIBPYTHON_SUPPORT libslang / HAVE_SLANG_SUPPORT libtraceevent / HAVE_LIBTRACEEVENT libunwind / HAVE_LIBUNWIND_SUPPORT lzma / HAVE_LZMA_SUPPORT numa_num_possible_cpus / HAVE_LIBNUMA_SUPPORT + python-module / HAVE_PYTHON_MODULE_SUPPORT zlib / HAVE_ZLIB_SUPPORT zstd / HAVE_ZSTD_SUPPORT diff --git a/tools/perf/Documentation/perf-config.txt b/tools/perf/Documentation/perf-config.txt index 9b223f892829..6cff6f26af8d 100644 --- a/tools/perf/Documentation/perf-config.txt +++ b/tools/perf/Documentation/perf-config.txt @@ -217,6 +217,16 @@ core.*:: Sets a timeout (in milliseconds) for parsing 'addr2line' output. The default timeout is 5s. + hybrid-merge:: + Merge matching events from all hybrid core PMUs into one display + by default. For example, if a wildcard expands to run on both p-cores and + e-cores, this aggregates them into a single view. This applies to + 'perf report' and 'perf top', it is ignored with '--hierarchy' and + doesn't alter 'perf stat' where '--hybrid-merge' must be given + explicitly. As a default it is silently ignored when there is + nothing to merge, such as on a machine with a single core PMU, + whereas an explicit '--hybrid-merge' warns. + tui.*, gtk.*:: Subcommands that can be configured here are 'top', 'report' and 'annotate'. These values are booleans, for example: diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt index a4378a0cd914..17e582998474 100644 --- a/tools/perf/Documentation/perf-list.txt +++ b/tools/perf/Documentation/perf-list.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'perf list' [] [hw|sw|cache|tracepoint|pmu|sdt|metric|metricgroup|event_glob] +'perf list' --tui [-- ] DESCRIPTION ----------- @@ -51,6 +52,15 @@ Output in JSON format. --output=:: Output file name. By default output is written to stdout. +--tui:: +Rather than printing the events, interactively browse the PMUs, events and +metrics in the terminal using the 'ilist' python script (see +linkperf:perf-script[1]). Selecting an event or metric shows its description +and counts it on each CPU. The script requires the perf python module and the +python 'textual' library. Other options are ignored, arguments after '--' are +passed to the script, for example 'perf list --tui -- -I 0.5' to update the +counts every 0.5 seconds. + [[EVENT_MODIFIERS]] EVENT MODIFIERS --------------- diff --git a/tools/perf/Documentation/perf-mem.txt b/tools/perf/Documentation/perf-mem.txt index 4d164836d094..fe51c5e3333d 100644 --- a/tools/perf/Documentation/perf-mem.txt +++ b/tools/perf/Documentation/perf-mem.txt @@ -14,6 +14,10 @@ DESCRIPTION ----------- "perf mem record" runs a command and gathers memory operation data from it, into perf.data. Perf record options are accepted and are passed through. +It also requests the address (-d), the weight (-W, where supported) and the +CPU id (--sample-cpu) of every sampled access by default; the CPU id is what +lets per-sample analysis tell reads and writes to the same data from +different cores apart from same-CPU traffic. "perf mem report" displays the result. It invokes perf report with the right set of options to display a memory access profile. By default, loads diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index 178f483140ed..2351f7b4f5fc 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -212,7 +212,7 @@ OPTIONS The can be one of: (for any term) na, load, store, pfetch, exec (for mem_op) - l1, l2, l3, l4, cxl, io, any_cache, lfb, ram, pmem (for mem_lvl) + l0, l1, l2, l3, l4, cxl, io, any_cache, lfb, ram, pmem (for mem_lvl) na, none, hit, miss, hitm, fwd, peer (for mem_snoop) remote (for mem_remote) na, locked (for mem_locked) @@ -293,7 +293,7 @@ OPTIONS --call-graph:: Setup and enable call-graph (stack chain/backtrace) recording, - implies -g. Default is "fp" (for user space). + implies -g. Default is "fp" for user space (s390 uses "dwarf"). The unwinding method used for kernel space is dependent on the unwinder used by the active kernel configuration, i.e @@ -329,6 +329,11 @@ OPTIONS enable deferred user callchain which will collect user-space callchains when the thread returns to the user space. + On s390, the "fp" method uses back chain instead of frame pointers, + which requires user space to maintain a back chain (e.g. compiler + option -mbackchain). Without a maintained back chain this produces + bogus call graphs. In that case use "dwarf" instead. + -q:: --quiet:: Don't print any warnings or messages, useful for scripting. @@ -480,9 +485,11 @@ Note that this feature may not be available on all processors. -W:: --weight:: -Enable weightened sampling. An additional weight is recorded per sample and can be -displayed with the weight and local_weight sort keys. This currently works for TSX -abort events and some memory events in precise mode on modern Intel CPUs. +Enable weighted sampling. One or more additional weights can be recorded per sample +and can be displayed with the weight, weight2, weight3 and local_weight sort keys. +Possible uses for weights are memory or instruction retirement latencies. +Support depends on the PMU and the event, as well as its configuration. Typically +it requires use of 'p'. --namespaces:: Record events of type PERF_RECORD_NAMESPACES. This enables 'cgroup_id' sort key. @@ -513,12 +520,16 @@ Capture machine state (registers) at interrupt, i.e., on counter overflows for each sample. List of captured registers depends on the architecture. This option is off by default. It is possible to select the registers to sample using their symbolic names, e.g. on x86, ax, si. To list the available registers use ---intr-regs=\?. To name registers, pass a comma separated list such as ---intr-regs=ax,bx. The list of register is architecture dependent. +--intr-regs=\?. On supported architectures, SIMD registers are displayed as +groups (e.g., on x86: XMM0-15,YMM0-15,ZMM0-31). To name registers, pass a comma +separated list such as --intr-regs=ax,bx,zmm. Please notice SIMD registers must +be assigned as a complete set, sampling individual SIMD registers (e.g., zmm0) +is not supported. The list of register is architecture dependent. --user-regs:: Similar to -I, but capture user registers at sample time. To list the available -user registers use --user-regs=\?. +user registers use --user-regs=\?. For SIMD registers, only complete register +sets are allowed like -I. --running-time:: Record running and enabled time for read events (:S) diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 22f87eaa3279..e145124d6c9a 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -29,6 +29,25 @@ OPTIONS --quiet:: Do not show any warnings or messages. (Suppress -v) +--progress:: + Show progress for each of the processing phases, printing the + percentage done and the current/total counts: the first phase + counts the bytes of the perf.data file processed so far, the + merge and sort phases count hist entries, e.g.: + + Processing events... [ 42.3%] 4G / 10G + Merging related events... [ 7.1%] 1024 / 14387 + Sorting events for output... [ 98.2%] 14132 / 14387 + + It is a no-op when using the TUI or GTK browsers, that already + present progress information, or when --quiet is used, that asks + for no messages at all. + +--no-progress:: + Do not show progress while processing the perf.data file. It + also turns off the progress the TUI and GTK browsers present, + which is their own. + -n:: --show-nr-samples:: Show the number of samples for each symbol @@ -64,6 +83,9 @@ OPTIONS --symbol-filter=:: Only show symbols that match (partially) with this filter. +--weights:: Show or hide (with `--no-weights`) weight columns in annotation output. + By default, weight columns are shown when samples contain non-zero weights. + -U:: --hide-unresolved:: Only display entries resolved to a symbol. @@ -351,7 +373,10 @@ OPTIONS requires a tty, if one is not present, as when piping to other commands, the stdio interface is used. ---gtk:: Use the GTK2 interface. +--gtk:: Use the GTK4 interface. + +--weights:: Show or hide (with `--no-weights`) weight columns in annotation output. + By default, weight columns are shown when samples contain non-zero weights. -k:: --vmlinux=:: @@ -572,6 +597,11 @@ include::itrace.txt[] --raw-trace:: When displaying traceevent output, do not use print fmt or plugins. +--hybrid-merge:: + Merge matching events from all hybrid core PMUs into one + display. For example, if a wildcard expands to run on both p-cores and + e-cores, this aggregates them into a single view. + -H:: --hierarchy:: Enable hierarchical output. In the hierarchy mode, each sort key groups diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt index 4da06215163a..fe0abef2bd13 100644 --- a/tools/perf/Documentation/perf-sched.txt +++ b/tools/perf/Documentation/perf-sched.txt @@ -95,8 +95,10 @@ There are several variants of 'perf sched': events, ``try_to_wakeup()`` call among others. This is useful in understanding the scheduler behavior for the workload. - Note: The tool will not give correct results if there is topological reordering or - online/offline of cpus in between capturing snapshots of `/proc/schedstat`. + Note: Reports reject incomplete snapshots and mismatched CPU or domain IDs. + Topology changes that retain the same IDs, including CPUs going offline + and returning online between snapshots, cannot be detected and may still + produce incorrect results. Example usage: perf sched stats record -- sleep 1 diff --git a/tools/perf/Documentation/perf-script-perl.txt b/tools/perf/Documentation/perf-script-perl.txt deleted file mode 100644 index 5b479f5e62ff..000000000000 --- a/tools/perf/Documentation/perf-script-perl.txt +++ /dev/null @@ -1,216 +0,0 @@ -perf-script-perl(1) -=================== - -NAME ----- -perf-script-perl - Process trace data with a Perl script - -SYNOPSIS --------- -[verse] -'perf script' [-s [Perl]:script[.pl] ] - -DESCRIPTION ------------ - -This perf script option is used to process perf script data using perf's -built-in Perl interpreter. It reads and processes the input file and -displays the results of the trace analysis implemented in the given -Perl script, if any. - -STARTER SCRIPTS ---------------- - -You can avoid reading the rest of this document by running 'perf script --g perl' in the same directory as an existing perf.data trace file. -That will generate a starter script containing a handler for each of -the event types in the trace file; it simply prints every available -field for each event in the trace file. - -You can also look at the existing scripts in -~/libexec/perf-core/scripts/perl for typical examples showing how to -do basic things like aggregate event data, print results, etc. Also, -the check-perf-script.pl script, while not interesting for its results, -attempts to exercise all of the main scripting features. - -EVENT HANDLERS --------------- - -When perf script is invoked using a trace script, a user-defined -'handler function' is called for each event in the trace. If there's -no handler function defined for a given event type, the event is -ignored (or passed to a 'trace_unhandled' function, see below) and the -next event is processed. - -Most of the event's field values are passed as arguments to the -handler function; some of the less common ones aren't - those are -available as calls back into the perf executable (see below). - -As an example, the following perf record command can be used to record -all sched_wakeup events in the system: - - # perf record -a -e sched:sched_wakeup - -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/tracing/events/sched/sched_wakeup/format): - ----- - format: - field:unsigned short common_type; - field:unsigned char common_flags; - field:unsigned char common_preempt_count; - field:int common_pid; - - field:char comm[TASK_COMM_LEN]; - field:pid_t pid; - field:int prio; - field:int success; - field:int target_cpu; ----- - -The handler function for this event would be defined as: - ----- -sub sched::sched_wakeup -{ - my ($event_name, $context, $common_cpu, $common_secs, - $common_nsecs, $common_pid, $common_comm, - $comm, $pid, $prio, $success, $target_cpu) = @_; -} ----- - -The handler function takes the form subsystem::event_name. - -The $common_* arguments in the handler's argument list are the set of -arguments passed to all event handlers; some of the fields correspond -to the common_* fields in the format file, but some are synthesized, -and some of the common_* fields aren't common enough to to be passed -to every event as arguments but are available as library functions. - -Here's a brief description of each of the invariant event args: - - $event_name the name of the event as text - $context an opaque 'cookie' used in calls back into perf - $common_cpu the cpu the event occurred on - $common_secs the secs portion of the event timestamp - $common_nsecs the nsecs portion of the event timestamp - $common_pid the pid of the current task - $common_comm the name of the current process - -All of the remaining fields in the event's format file have -counterparts as handler function arguments of the same name, as can be -seen in the example above. - -The above provides the basics needed to directly access every field of -every event in a trace, which covers 90% of what you need to know to -write a useful trace script. The sections below cover the rest. - -SCRIPT LAYOUT -------------- - -Every perf script Perl script should start by setting up a Perl module -search path and 'use'ing a few support modules (see module -descriptions below): - ----- - use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; - use lib "./Perf-Trace-Util/lib"; - use Perf::Trace::Core; - use Perf::Trace::Context; - use Perf::Trace::Util; ----- - -The rest of the script can contain handler functions and support -functions in any order. - -Aside from the event handler functions discussed above, every script -can implement a set of optional functions: - -*trace_begin*, if defined, is called before any event is processed and -gives scripts a chance to do setup tasks: - ----- - sub trace_begin - { - } ----- - -*trace_end*, if defined, is called after all events have been - processed and gives scripts a chance to do end-of-script tasks, such - as display results: - ----- -sub trace_end -{ -} ----- - -*trace_unhandled*, if defined, is called after for any event that - doesn't have a handler explicitly defined for it. The standard set - of common arguments are passed into it: - ----- -sub trace_unhandled -{ - my ($event_name, $context, $common_cpu, $common_secs, - $common_nsecs, $common_pid, $common_comm) = @_; -} ----- - -The remaining sections provide descriptions of each of the available -built-in perf script Perl modules and their associated functions. - -AVAILABLE MODULES AND FUNCTIONS -------------------------------- - -The following sections describe the functions and variables available -via the various Perf::Trace::* Perl modules. To use the functions and -variables from the given module, add the corresponding 'use -Perf::Trace::XXX' line to your perf script script. - -Perf::Trace::Core Module -~~~~~~~~~~~~~~~~~~~~~~~~ - -These functions provide some essential functions to user scripts. - -The *flag_str* and *symbol_str* functions provide human-readable -strings for flag and symbolic fields. These correspond to the strings -and values parsed from the 'print fmt' fields of the event format -files: - - flag_str($event_name, $field_name, $field_value) - returns the string representation corresponding to $field_value for the flag field $field_name of event $event_name - symbol_str($event_name, $field_name, $field_value) - returns the string representation corresponding to $field_value for the symbolic field $field_name of event $event_name - -Perf::Trace::Context Module -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some of the 'common' fields in the event format file aren't all that -common, but need to be made accessible to user scripts nonetheless. - -Perf::Trace::Context defines a set of functions that can be used to -access this data in the context of the current event. Each of these -functions expects a $context variable, which is the same as the -$context variable passed into every event handler as the second -argument. - - common_pc($context) - returns common_preempt count for the current event - common_flags($context) - returns common_flags for the current event - common_lock_depth($context) - returns common_lock_depth for the current event - -Perf::Trace::Util Module -~~~~~~~~~~~~~~~~~~~~~~~~ - -Various utility functions for use with perf script: - - nsecs($secs, $nsecs) - returns total nsecs given secs/nsecs pair - nsecs_secs($nsecs) - returns whole secs portion given nsecs - nsecs_nsecs($nsecs) - returns nsecs remainder given nsecs - nsecs_str($nsecs) - returns printable string in the form secs.nsecs - avg($total, $n) - returns average given a sum and a total number of values - -SEE ALSO --------- -linkperf:perf-script[1] diff --git a/tools/perf/Documentation/perf-script-python.txt b/tools/perf/Documentation/perf-script-python.txt index 27a1cac6fe76..cbf50b3e5f06 100644 --- a/tools/perf/Documentation/perf-script-python.txt +++ b/tools/perf/Documentation/perf-script-python.txt @@ -3,676 +3,182 @@ perf-script-python(1) NAME ---- -perf-script-python - Process trace data with a Python script +perf-script-python - Process trace data with a Python script using perf module SYNOPSIS -------- [verse] -'perf script' [-s [Python]:script[.py] ] +'perf script' + + + +""" + +class Node: + """A node in the flame graph tree.""" + def __init__(self, name: str, libtype: str): + self.name = name + self.libtype = libtype + self.value: int = 0 + self.children: dict[str, Node] = {} + + def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]: + """Convert the node to a JSON-serializable dictionary.""" + return { + "n": self.name, + "l": self.libtype, + "v": self.value, + "c": [x.to_json() for x in self.children.values()] + } + + +class FlameGraphCLI: + """Command-line interface for generating flame graphs.""" + def __init__(self, args): + self.args = args + self.stack = Node("all", "root") + self.session = None + + @staticmethod + def get_libtype_from_dso(dso: Optional[str]) -> str: + """Determine the library type from the DSO name.""" + if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux") or dso == "[kernel]"): + return "kernel" + return "" + + @staticmethod + def find_or_create_node(node: Node, name: str, libtype: str) -> Node: + """Find a child node with the given name or create a new one.""" + if name in node.children: + return node.children[name] + child = Node(name, libtype) + node.children[name] = child + return child + + def process_event(self, sample) -> None: + """Process a single perf sample event.""" + if self.args.event_name and self.args.event_name not in str(sample.evsel): + return + + pid = sample.sample_pid + dso_type = "" + try: + thread = self.session.find_thread(sample.sample_pid, sample.sample_tid) + comm = (thread.comm() if thread else None) or "[unknown]" + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + comm = "[unknown]" + + if pid == 0: + comm = comm if comm != "[unknown]" else "swapper" + dso_type = "kernel" + else: + comm = f"{comm} ({pid})" + + node = self.find_or_create_node(self.stack, comm, dso_type) + + callchain = sample.callchain + if callchain: + # We want to traverse from root to leaf. + # perf callchain iterator gives leaf to root. + # We collect them and reverse. + frames = list(callchain) + for entry in reversed(frames): + name = entry.symbol or "[unknown]" + libtype = self.get_libtype_from_dso(entry.dso) + node = self.find_or_create_node(node, name, libtype) + else: + # Fallback if no callchain + name = (sample.symbol or '[unknown]') + libtype = self.get_libtype_from_dso((sample.dso or '[unknown]')) + node = self.find_or_create_node(node, name, libtype) + + node.value += 1 + + def get_report_header(self) -> str: + """Get the header from the perf report.""" + try: + input_file = self.args.input or "perf.data" + if input_file == "-": + return "" + mode = os.stat(input_file).st_mode + import stat + if stat.S_ISFIFO(mode) or stat.S_ISCHR(mode): + return "" + output = subprocess.check_output(["perf", "report", "--header-only", "-i", input_file]) + result = output.decode("utf-8") + if self.args.event_name: + result += "\nFocused event: " + self.args.event_name + return result + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError, + subprocess.CalledProcessError): + return "" + + def run(self) -> None: + """Run the flame graph generation.""" + input_file = self.args.input or "perf.data" + if input_file != "-" and not os.path.exists(input_file): + print(f"Error: {input_file} not found. (try 'perf record' first)", file=sys.stderr) + sys.exit(1) + + try: + self.session = perf.session(perf.data(input_file), + sample=self.process_event) + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError) as e: + print(f"Error opening session: {e}", file=sys.stderr) + sys.exit(1) + + try: + self.session.process_events() + finally: + # perf.session is a C extension type that holds a strong reference to the + # bound method self.process_event (which references self) and does not + # implement cyclic GC (tp_traverse). Explicitly clear self.session to break + # the reference cycle and free the underlying session resources deterministically. + self.session = None + + stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) + # Escape HTML special characters to prevent XSS + stacks_json = stacks_json.replace("<", "\\u003c") \ + .replace(">", "\\u003e").replace("&", "\\u0026") + + if self.args.format == "html": + report_header = self.get_report_header() + options = { + "colorscheme": self.args.colorscheme, + "context": report_header + } + options_json = json.dumps(options) + options_json = options_json.replace("<", "\\u003c") \ + .replace(">", "\\u003e").replace("&", "\\u0026") + + template = self.args.template + template_sha256sum = None + output_str = None + + if not os.path.isfile(template): + if template.startswith("http://") or template.startswith("https://"): + if not self.args.allow_download: + print("Warning: Downloading templates is disabled. " + "Use --allow-download.", file=sys.stderr) + template = None + else: + print(f"Warning: Template file '{template}' not found.", file=sys.stderr) + if self.args.allow_download: + print("Using default CDN template.", file=sys.stderr) + template = ( + "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/" + "d3-flamegraph-base.html" + ) + template_sha256sum = ( + "f6a4aa7edffda4fb9bd71eb0eb75bc44d0bb34cd9efbd053f6095bc5c28d702b" + ) + else: + template = None + + use_minimal = False + try: + if not template: + use_minimal = True + elif template.startswith(("http://", "https://")): + with urllib.request.urlopen(template) as url_template: + output_str = "".join([l.decode("utf-8") for l in url_template.readlines()]) + else: + with open(template, "r", encoding="utf-8") as f: + output_str = f.read() + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError) as err: + print(f"Error reading template {template}: {err}\n", file=sys.stderr) + use_minimal = True + + if use_minimal: + print("Using internal minimal HTML that refers to d3's web site. JavaScript " + + "loaded this way from a local file may be blocked unless your " + + "browser has relaxed permissions. Run with '--allow-download' to fetch " + + "the full D3 HTML template.", file=sys.stderr) + output_str = MINIMAL_HTML + + elif template_sha256sum: + assert output_str is not None + download_sha256sum = hashlib.sha256( + output_str.encode("utf-8") + ).hexdigest() + if download_sha256sum != template_sha256sum: + s = None + while s not in ["y", "n"]: + try: + s = input(f"""Unexpected template sha256sum. +{download_sha256sum} != {template_sha256sum}, for: +{template} +continue?[yn] """).lower() + except EOFError: + s = "n" + if s == "n": + sys.exit(1) + + assert output_str is not None + replacements = { + "/** @options_json **/": options_json, + "/** @flamegraph_json **/": stacks_json, + } + # Use a lambda replacement so re.sub does not interpret backslashes + # in the JSON strings (such as \n or \u003c) as regex escape sequences. + output_str = re.sub( + r"/\*\* @(?:options_json|flamegraph_json) \*\*/", + lambda m: replacements[m.group(0)], + output_str, + ) + output_fn = self.args.output or "flamegraph.html" + else: + output_str = stacks_json + output_fn = self.args.output or "stacks.json" + + if output_fn == "-": + with open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: + out.write(output_str) + else: + print(f"dumping data to {output_fn}") + with open(output_fn, "w", encoding="utf-8") as out: + out.write(output_str) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Create flame graphs using perf python module.") + parser.add_argument("-f", "--format", default="html", choices=["json", "html"], + help="output file format") + parser.add_argument("-o", "--output", help="output file name") + parser.add_argument("--template", + default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", + help="path to flame graph HTML template") + parser.add_argument("--colorscheme", default="blue-green", + help="flame graph color scheme", choices=["blue-green", "orange"]) + parser.add_argument("-i", "--input", help="input perf.data file") + parser.add_argument("--allow-download", default=False, action="store_true", + help="allow unprompted downloading of HTML template") + parser.add_argument("-e", "--event", default="", dest="event_name", type=str, + help="specify the event to generate flamegraph for") + + cli_args = parser.parse_args() + cli = FlameGraphCLI(cli_args) + cli.run() diff --git a/tools/perf/python/futex-contention.py b/tools/perf/python/futex-contention.py new file mode 100755 index 000000000000..097663d929b7 --- /dev/null +++ b/tools/perf/python/futex-contention.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +"""Measures futex contention.""" +from __future__ import annotations + +import argparse +from collections import defaultdict +from typing import Dict, Tuple +import perf + +class LockStats: + """Aggregate lock contention information.""" + def __init__(self) -> None: + self.count = 0 + self.total_time = 0 + self.min_time = 0 + self.max_time = 0 + + def add(self, duration: int) -> None: + """Add a new duration measurement.""" + self.count += 1 + self.total_time += duration + if self.count == 1: + self.min_time = duration + self.max_time = duration + else: + self.min_time = min(self.min_time, duration) + self.max_time = max(self.max_time, duration) + + def avg(self) -> float: + """Return average duration.""" + return self.total_time / self.count if self.count > 0 else 0.0 + +process_names: Dict[int, str] = {} +start_times: Dict[int, Tuple[int, int]] = {} +session = None +durations: Dict[Tuple[int, int], LockStats] = defaultdict(LockStats) + +FUTEX_WAIT = 0 +FUTEX_PRIVATE_FLAG = 128 +FUTEX_CLOCK_REALTIME = 256 +# Mask out FUTEX_PRIVATE_FLAG and FUTEX_CLOCK_REALTIME so variants such as +# FUTEX_WAIT_PRIVATE (0 | 128) match the base FUTEX_WAIT command. +FUTEX_CMD_MASK = ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) + + +def handle_start(pid: int, tid: int, uaddr: int, op: int, start_time: int) -> None: + """Handle a futex sys_enter event.""" + if (op & FUTEX_CMD_MASK) != FUTEX_WAIT: + return + try: + if session: + process = session.find_thread(pid, tid) + if process: + process_names[tid] = process.comm() or "unknown" + except (TypeError, AttributeError): + pass + if tid not in process_names: + process_names[tid] = "unknown" + + start_times[tid] = (uaddr, start_time) + +def handle_end(tid: int, end_time: int) -> None: + """Handle a futex sys_exit event.""" + if tid not in start_times: + return + (uaddr, start_time) = start_times[tid] + del start_times[tid] + durations[(tid, uaddr)].add(end_time - start_time) + +def process_event(sample: perf.sample_event) -> None: + """Process a single sample event.""" + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + if event_name.startswith("syscalls:sys_enter_futex"): + uaddr = getattr(sample, "uaddr", None) + op = getattr(sample, "op", None) + if uaddr is None or op is None: + return # Tracepoint fields missing, skip silent attribution to 0 + handle_start(getattr(sample, "sample_pid", sample.sample_tid), + sample.sample_tid, uaddr, op, sample.sample_time) + elif event_name.startswith("syscalls:sys_exit_futex"): + handle_end(sample.sample_tid, sample.sample_time) + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description="Measure futex contention") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + args = ap.parse_args() + + try: + session = perf.session(perf.data(args.input), sample=process_event) + try: + session.process_events() + except KeyboardInterrupt: + pass + + for ((t, u), stats) in sorted(durations.items()): + avg_ns = stats.avg() + print(f"{process_names.get(t, 'unknown')}[{t}] lock {u:x} contended " + f"{stats.count} times, {avg_ns:.0f} avg ns " + f"[max: {stats.max_time} ns, min {stats.min_time} ns]") + finally: + # Break the reference cycle between the global session and the + # process_event callback so the C perf.session object is freed. + session = None diff --git a/tools/perf/python/gecko.py b/tools/perf/python/gecko.py new file mode 100755 index 000000000000..3427e0770156 --- /dev/null +++ b/tools/perf/python/gecko.py @@ -0,0 +1,411 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +gecko.py - Convert perf record output to Firefox's gecko profile format +""" +from __future__ import annotations + +import argparse +import functools +import json +import os +import sys +import tempfile +import threading +import urllib.parse +import uuid +import webbrowser +from dataclasses import dataclass, field +from http.server import HTTPServer, SimpleHTTPRequestHandler +from typing import Dict, List, NamedTuple, Optional, Tuple + +import perf + + +# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156 +class Frame(NamedTuple): + """A single stack frame in the gecko profile format.""" + string_id: int + relevantForJS: bool + innerWindowID: int + implementation: None + optimizations: None + line: None + column: None + category: int + subcategory: Optional[int] + + +# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216 +class Stack(NamedTuple): + """A single stack in the gecko profile format.""" + prefix_id: Optional[int] + frame_id: int + + +# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90 +class Sample(NamedTuple): + """A single sample in the gecko profile format.""" + stack_id: Optional[int] + time_ms: float + responsiveness: int + + +@dataclass +class Tables: + """Interned tables for the gecko profile format.""" + frame_table: List[Frame] = field(default_factory=list) + string_table: List[str] = field(default_factory=list) + string_map: Dict[str, int] = field(default_factory=dict) + stack_table: List[Stack] = field(default_factory=list) + stack_map: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict) + frame_map: Dict[str, int] = field(default_factory=dict) + + +@dataclass +class Thread: + """A builder for a profile of the thread.""" + comm: str + pid: int + tid: int + user_category: int + kernel_category: int + samples: List[Sample] = field(default_factory=list) + tables: Tables = field(default_factory=Tables) + + def _intern_stack(self, frame_id: int, prefix_id: Optional[int]) -> int: + """Gets a matching stack, or saves the new stack. Returns a Stack ID.""" + key = (prefix_id, frame_id) + stack_id = self.tables.stack_map.get(key) + if stack_id is None: + stack_id = len(self.tables.stack_table) + self.tables.stack_table.append(Stack(prefix_id=prefix_id, frame_id=frame_id)) + self.tables.stack_map[key] = stack_id + return stack_id + + def _intern_string(self, string: str) -> int: + """Gets a matching string, or saves the new string. Returns a String ID.""" + string_id = self.tables.string_map.get(string) + if string_id is not None: + return string_id + string_id = len(self.tables.string_table) + self.tables.string_table.append(string) + self.tables.string_map[string] = string_id + return string_id + + def _intern_frame(self, frame_str: str) -> int: + """Gets a matching stack frame, or saves the new frame. Returns a Frame ID.""" + frame_id = self.tables.frame_map.get(frame_str) + if frame_id is not None: + return frame_id + frame_id = len(self.tables.frame_table) + self.tables.frame_map[frame_str] = frame_id + string_id = self._intern_string(frame_str) + + category = self.user_category + if (frame_str.find('kallsyms') != -1 or + frame_str.find('/vmlinux') != -1 or + frame_str.endswith('.ko)')): + category = self.kernel_category + + self.tables.frame_table.append(Frame( + string_id=string_id, + relevantForJS=False, + innerWindowID=0, + implementation=None, + optimizations=None, + line=None, + column=None, + category=category, + subcategory=None, + )) + return frame_id + + def add_sample(self, comm: str, stack: List[str], time_ms: float) -> None: + """Add a timestamped stack trace sample to the thread builder.""" + if self.comm != comm: + self.comm = comm + + prefix_stack_id: Optional[int] = None + for frame in stack: + frame_id = self._intern_frame(frame) + prefix_stack_id = self._intern_stack(frame_id, prefix_stack_id) + + if prefix_stack_id is not None: + self.samples.append(Sample(stack_id=prefix_stack_id, + time_ms=time_ms, + responsiveness=0)) + + def to_json_dict(self) -> Dict: + """Converts current Thread to GeckoThread JSON format.""" + return { + "tid": self.tid, + "pid": self.pid, + "name": self.comm, + "markers": { + "schema": { + "name": 0, + "startTime": 1, + "endTime": 2, + "phase": 3, + "category": 4, + "data": 5, + }, + "data": [], + }, + "samples": { + "schema": { + "stack": 0, + "time": 1, + "responsiveness": 2, + }, + "data": self.samples + }, + "frameTable": { + "schema": { + "location": 0, + "relevantForJS": 1, + "innerWindowID": 2, + "implementation": 3, + "optimizations": 4, + "line": 5, + "column": 6, + "category": 7, + "subcategory": 8, + }, + "data": self.tables.frame_table, + }, + "stackTable": { + "schema": { + "prefix": 0, + "frame": 1, + }, + "data": self.tables.stack_table, + }, + "stringTable": self.tables.string_table, + "registerTime": 0, + "unregisterTime": None, + "processType": "default", + } + + +class CORSRequestHandler(SimpleHTTPRequestHandler): + """Enable CORS for requests from profiler.firefox.com.""" + def __init__(self, *args, allowed_file: Optional[str] = None, **kwargs): + self.allowed_file = allowed_file + super().__init__(*args, **kwargs) + + def end_headers(self): + self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com') + super().end_headers() + + def do_GET(self): + if self.allowed_file and self.path.split('?')[0] != f'/{self.allowed_file}': + self.send_error(403, "Access denied") + return + super().do_GET() + + def list_directory(self, path): + self.send_error(403, "Directory listing forbidden") + return None + + +@dataclass +class CategoryData: + """Category configuration for the gecko profile.""" + user_index: int = 0 + kernel_index: int = 1 + categories: List[Dict] = field(default_factory=list) + + +class GeckoCLI: + """Command-line interface for converting perf data to Gecko format.""" + def __init__(self, args: argparse.Namespace) -> None: + self.args = args + self.tid_to_thread: Dict[int, Thread] = {} + self.start_time_ms: Optional[float] = None + self.session: Optional[perf.session] = None + self.product = args.product + self.cat_data = CategoryData( + categories=[ + { + "name": 'User', + "color": args.user_color, + "subcategories": ['Other'] + }, + { + "name": 'Kernel', + "color": args.kernel_color, + "subcategories": ['Other'] + }, + ] + ) + + def process_event(self, sample) -> None: + """Process a single perf sample event.""" + if self.args.event_name and self.args.event_name not in str(sample.evsel): + return + + # sample_time is in nanoseconds. Gecko wants milliseconds. + time_ms = sample.sample_time / 1000000.0 + pid = sample.sample_pid + tid = sample.sample_tid + + if self.start_time_ms is None: + self.start_time_ms = time_ms + + try: + thread_info = self.session.find_thread(sample.sample_pid, sample.sample_tid) if self.session else None + comm = (thread_info.comm() if thread_info is not None else None) or "[unknown]" + except AttributeError: + comm = "[unknown]" + + stack = [] + callchain = sample.callchain + if callchain: + for entry in callchain: + symbol = entry.symbol or "[unknown]" + dso = entry.dso or "[unknown]" + stack.append(f"{symbol} (in {dso})") + # Reverse because Gecko wants root first. + stack.reverse() + else: + symbol = sample.symbol or "[unknown]" + dso = sample.dso or "[unknown]" + stack.append(f"{symbol} (in {dso})") + + thread = self.tid_to_thread.get(tid) + if thread is None: + thread = Thread(comm=comm, pid=pid, tid=tid, + user_category=self.cat_data.user_index, + kernel_category=self.cat_data.kernel_index) + self.tid_to_thread[tid] = thread + thread.add_sample(comm=comm, stack=stack, time_ms=time_ms) + + def run(self) -> None: + """Run the conversion process.""" + input_file = self.args.input or "perf.data" + if input_file != "-" and not os.path.exists(input_file): + print(f"Error: {input_file} not found.", file=sys.stderr) + sys.exit(1) + + try: + self.session = perf.session(perf.data(input_file), sample=self.process_event) + except (OSError, RuntimeError, ValueError) as e: + print(f"Error opening session: {e}", file=sys.stderr) + sys.exit(1) + + if self.session: + try: + self.session.process_events() + finally: + # Break the reference cycle between perf.session and self.process_event + # because perf.session lacks cyclic GC support (tp_traverse). + self.session = None + + threads = [t.to_json_dict() for t in self.tid_to_thread.values()] + + gecko_profile = { + "meta": { + "interval": 1, + "processType": 0, + "product": self.product, + "stackwalk": 1, + "debug": 0, + "gcpoison": 0, + "asyncstack": 1, + "startTime": self.start_time_ms or 0, + "shutdownTime": None, + "version": 24, + "presymbolicated": True, + "categories": self.cat_data.categories, + "markerSchema": [], + }, + "libs": [], + "threads": threads, + "processes": [], + "pausedRanges": [], + } + + output_file = self.args.save_only + if output_file is None: + self._write_and_launch(gecko_profile) + else: + print(f'[ perf gecko: Captured and wrote into {output_file} ]') + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(gecko_profile, f, indent=2) + + def _write_and_launch(self, profile: Dict) -> None: + """Write the profile to a file and launch the Firefox profiler.""" + print("Starting Firefox Profiler on your default browser...") + + with tempfile.TemporaryDirectory() as tmp_dir_name: + profile_name = f'gecko_profile_{uuid.uuid4().hex}.json' + filename = os.path.join(tmp_dir_name, profile_name) + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(profile, f, indent=2) + + handler = functools.partial( + CORSRequestHandler, directory=tmp_dir_name, allowed_file=profile_name + ) + try: + httpd = HTTPServer(('127.0.0.1', 0), handler) + except OSError as e: + print(f"Error starting HTTP server: {e}", file=sys.stderr) + sys.exit(1) + + port = httpd.server_port + + def start_server(): + httpd.serve_forever() + + thread = threading.Thread(target=start_server, daemon=True) + thread.start() + + safe_string = urllib.parse.quote_plus(f'http://127.0.0.1:{port}/{profile_name}') + url = f'https://profiler.firefox.com/from-url/{safe_string}' + print(f"Please open the following URL in your browser to view the profile:\n\n{url}\n") + launch_html = os.path.join(tmp_dir_name, 'launch.html') + with open(launch_html, 'w', encoding='utf-8') as f: + f.write(f'' + f'' + f'' + f'Redirecting to Firefox Profiler...') + if not webbrowser.open(f'file://{launch_html}'): + print("Failed to open browser, please open the URL manually.") + + print(f'[ perf gecko: Captured and wrote into {filename} ]') + print("Press Ctrl+C to stop the local server.") + try: + # Keep the main thread alive so the daemon thread can serve requests + stop_event = threading.Event() + while True: + stop_event.wait(1) + except KeyboardInterrupt: + print("\nStopping server...") + httpd.shutdown() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Convert perf.data to Firefox's Gecko Profile format" + ) + parser.add_argument('--product', default='perf', help='Product name (e.g. perf)') + parser.add_argument('--user-color', default='yellow', + help='Color for the User category', + choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', + 'grey', 'magenta']) + parser.add_argument('--kernel-color', default='orange', + help='Color for the Kernel category', + choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', + 'grey', 'magenta']) + parser.add_argument('--save-only', + help='Save the output to a file instead of opening Firefox\'s profiler') + parser.add_argument("-i", "--input", help="input perf.data file") + parser.add_argument("-e", "--event", default="", dest="event_name", type=str, + help="specify the event to generate gecko profile for") + + cli_args = parser.parse_args() + cli = GeckoCLI(cli_args) + cli.run() diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py index 0d757ddb4795..86bd65452e6e 100755 --- a/tools/perf/python/ilist.py +++ b/tools/perf/python/ilist.py @@ -2,11 +2,13 @@ # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) """Interactive perf list.""" -from abc import ABC, abstractmethod +from __future__ import annotations + import argparse +from abc import ABC, abstractmethod from dataclasses import dataclass import math -from typing import Any, Dict, Optional, Tuple +from typing import Any, Optional import perf from textual import on from textual.app import App, ComposeResult @@ -19,7 +21,7 @@ from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Sta from textual.widgets.tree import TreeNode -def get_info(info: Dict[str, str], key: str): +def get_info(info: dict[str, Any], key: str): return (info[key] + "\n") if key in info else "" @@ -80,7 +82,7 @@ class Metric(TreeValue): try: val = evlist.compute_metric(self.metric_name, cpu, thread) return 0 if math.isnan(val) else val - except: + except (OSError, ValueError, RuntimeError, TypeError): # Be tolerant of failures to compute metrics on particular CPUs/threads. return 0 @@ -140,7 +142,7 @@ class ErrorScreen(ModalScreen[bool]): def compose(self) -> ComposeResult: yield Button(f"Error: {self.error}", variant="primary", id="error") - def on_button_pressed(self, event: Button.Pressed) -> None: + def on_button_pressed(self) -> None: self.dismiss(True) @@ -234,7 +236,7 @@ class IListApp(App): def __init__(self, interval: float) -> None: self.interval = interval - self.evlist = None + self.evlist: Optional[perf.evlist] = None self.selected: Optional[TreeValue] = None self.search_results: list[TreeNode[TreeValue]] = [] self.cur_search_result: TreeNode[TreeValue] | None = None @@ -284,18 +286,18 @@ class IListApp(App): """Sets the focus after the SearchScreen is dismissed.""" search_label = self.query_one("#active_search", Label) - search_label.display = True if event else False + search_label.display = bool(event) if not event: return event = event.lower() search_label.update(f'Searching for events matching "{event}"') - tree: Tree[str] = self.query_one("#root", Tree) + tree: Tree[TreeValue] = self.query_one("#root", Tree) - def find_search_results(event: str, node: TreeNode[str], + def find_search_results(event: str, node: TreeNode[TreeValue], cursor_seen: bool = False, - match_after_cursor: Optional[TreeNode[str]] = None - ) -> Tuple[bool, Optional[TreeNode[str]]]: + match_after_cursor: Optional[TreeNode[TreeValue]] = None + ) -> tuple[bool, Optional[TreeNode[TreeValue]]]: """Find nodes that match the search remembering the one after the cursor.""" if not cursor_seen and node == tree.cursor_node: cursor_seen = True @@ -332,7 +334,7 @@ class IListApp(App): def action_collapse(self) -> None: """Collapse the part of the tree currently on.""" - tree: Tree[str] = self.query_one("#root", Tree) + tree: Tree[TreeValue] = self.query_one("#root", Tree) node = tree.cursor_node if node and node.parent: node.parent.collapse_all() @@ -343,31 +345,34 @@ class IListApp(App): if not self.selected or not self.evlist: return - def update_count(cpu: int, count: int): + def update_count(cpu: int, count: int | float): # Update the raw count display. - counter: Label = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total") - if not counter: + counter_query = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total") + if not counter_query: return - counter = counter.first(Label) + counter: Label = counter_query.first(Label) counter.update(str(count)) # Update the sparkline. - line: Sparkline = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total") - if not line: + line_query = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total") + if not line_query: return - line = line.first(Sparkline) + line: Sparkline = line_query.first(Sparkline) # If there are more events than the width, remove the front event. - if len(line.data) > line.size.width: - line.data.pop(0) - line.data.append(count) - line.mutate_reactive(Sparkline.data) + if line.data is not None: + if len(line.data) > line.size.width: + line.data = line.data[1:] + line.data = list(line.data) + [float(count)] + else: + line.data = [float(count)] + line.refresh() # Update the total and each CPU counts, assume there's just 1 evsel. - total = 0 + total: float = 0.0 self.evlist.disable() for evsel in self.evlist: for cpu in evsel.cpus(): - aggr = 0 + aggr: float = 0.0 for thread in evsel.threads(): aggr += self.selected.value(self.evlist, evsel, cpu, thread) update_count(cpu, aggr) @@ -414,7 +419,7 @@ class IListApp(App): if self.evlist: self.evlist.open() self.evlist.enable() - except: + except (OSError, ValueError, RuntimeError): self.evlist = None if not self.evlist: @@ -424,16 +429,16 @@ class IListApp(App): # Add spark lines for all the CPUs. Note, must be done after # open so that the evlist CPUs have been computed by propagate # maps. - line = CounterSparkline(cpu=-1) - lines.mount(line) + line_sp = CounterSparkline(cpu=-1) + lines.mount(line_sp) for cpu in self.evlist.all_cpus(): - line = CounterSparkline(cpu) - lines.mount(line) - line = Counter(cpu=-1) - lines.mount(line) + c_sp = CounterSparkline(cpu) + lines.mount(c_sp) + c_val = Counter(cpu=-1) + lines.mount(c_val) for cpu in self.evlist.all_cpus(): - line = Counter(cpu) - lines.mount(line) + c_val2 = Counter(cpu) + lines.mount(c_val2) def compose(self) -> ComposeResult: """Draws the app.""" @@ -445,7 +450,7 @@ class IListApp(App): pmu_name = pmu.name().lower() pmu_node = pmus.add(pmu_name) try: - for event in sorted(pmu.events(), key=lambda x: x["name"]): + for event in sorted(pmu.events(), key=lambda x: x.get("name", "")): if "deprecated" in event: continue if "name" in event: @@ -455,21 +460,22 @@ class IListApp(App): data=PmuEvent(pmu_name, e)) else: pmu_node.add_leaf(e, data=PmuEvent(pmu_name, e)) - except: + except (OSError, ValueError, RuntimeError, KeyError, TypeError): # Reading events may fail with EPERM, ignore. pass metrics = tree.root.add("Metrics") - groups = set() + groups: set[str] = set() for metric in perf.metrics(): groups.update(metric["MetricGroup"]) - def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: str = None): + def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: Optional[str] = None): for metric in sorted(perf.metrics(), key=lambda x: x["MetricName"]): - metric_pmu = metric.get('PMU') + metric_pmu_raw = metric.get('PMU') + metric_pmu = str(metric_pmu_raw) if metric_pmu_raw else '' if pmu and metric_pmu and metric_pmu != pmu: continue if parent in metric["MetricGroup"]: - name = metric["MetricName"] + name = str(metric["MetricName"]) display_name = name if metric_pmu: display_name += f" ({metric_pmu})" diff --git a/tools/perf/python/intel-pt-events.py b/tools/perf/python/intel-pt-events.py new file mode 100755 index 000000000000..c233a29eb8f9 --- /dev/null +++ b/tools/perf/python/intel-pt-events.py @@ -0,0 +1,632 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Print Intel PT Events including Power Events and PTWRITE. +Ported from tools/perf/scripts/python/intel-pt-events.py +""" +from __future__ import annotations + +import argparse +from typing import Dict, List, Tuple +import contextlib +from ctypes import addressof, create_string_buffer +import io +import os +import struct +import sys +from typing import Any, Optional +import typing +import perf + +try: + from libxed import LibXED as _LibXED + LibXED: Optional[type[_LibXED]] = _LibXED +except ImportError: + LibXED = None + + +def sample_flags_to_name(flags: int) -> str: + """Implement perf's sample_flags_to_name.""" + if not isinstance(flags, int) or flags == 0: + return "".ljust(21) + # PERF_IP_FLAG_* bit positions (from tools/perf/util/event.h): + # bit 0: PERF_IP_FLAG_BRANCH + # bit 1: PERF_IP_FLAG_CALL + # bit 2: PERF_IP_FLAG_RETURN + # bit 3: PERF_IP_FLAG_CONDITIONAL + # bit 4: PERF_IP_FLAG_SYSCALLRET + # bit 5: PERF_IP_FLAG_ASYNC + # bit 6: PERF_IP_FLAG_INTERRUPT + # bit 7: PERF_IP_FLAG_TX_ABORT + # bit 8: PERF_IP_FLAG_TRACE_BEGIN + # bit 9: PERF_IP_FLAG_TRACE_END + # bit 10: PERF_IP_FLAG_IN_TX + # bit 11: PERF_IP_FLAG_VMENTRY + # bit 12: PERF_IP_FLAG_VMEXIT + # bit 13: PERF_IP_FLAG_INTR_DISABLE + # bit 14: PERF_IP_FLAG_INTR_TOGGLE + # bit 15: PERF_IP_FLAG_BRANCH_MISS + # bit 16: PERF_IP_FLAG_NOT_TAKEN + sample_flags = [ + ((1 << 0) | (1 << 1), "call"), + ((1 << 0) | (1 << 2), "return"), + ((1 << 0) | (1 << 3), "jcc"), + ((1 << 0), "jmp"), + ((1 << 0) | (1 << 1) | (1 << 6), "int"), + ((1 << 0) | (1 << 2) | (1 << 6), "iret"), + ((1 << 0) | (1 << 1) | (1 << 4), "syscall"), + ((1 << 0) | (1 << 2) | (1 << 4), "sysret"), + ((1 << 0) | (1 << 5), "async"), + ((1 << 0) | (1 << 1) | (1 << 5) | (1 << 6), "hw int"), + ((1 << 0) | (1 << 7), "tx abrt"), + ((1 << 0) | (1 << 8), "tr strt"), + ((1 << 0) | (1 << 9), "tr end"), + ((1 << 0) | (1 << 1) | (1 << 11), "vmentry"), + ((1 << 0) | (1 << 1) | (1 << 12), "vmexit"), + ] + additional_mask = (1 << 10) | (1 << 13) | (1 << 14) + branch_event_mask = (1 << 15) | (1 << 16) + xf = flags & additional_mask + rem_flags = flags & ~additional_mask + + if rem_flags & (1 << 8): + prefix = "tr strt " + elif rem_flags & (1 << 9): + prefix = "tr end " + else: + prefix = "" + + rem_flags &= ~((1 << 8) | (1 << 9)) + types = rem_flags & ~branch_event_mask + type_name = "" + for f_mask, f_name in sample_flags: + if f_mask == types: + type_name = f_name + break + + s = prefix + type_name + ev_parts = [] + if rem_flags & (1 << 15): + ev_parts.append("miss") + if rem_flags & (1 << 16): + ev_parts.append("not_taken") + if ev_parts: + s += "/" + ",".join(ev_parts) + "/" + + if xf: + xs = "(" + if xf & (1 << 10): + xs += "x" + if xf & (1 << 13): + xs += "D" + if xf & (1 << 14): + xs += "t" + xs += ")" + if len(s) + len(xs) < 21: + s = s + xs.rjust(21 - len(s)) + else: + s = s + " " + xs + return s.ljust(21) + +class IntelPTAnalyzer: + """Analyzes Intel PT events and prints details.""" + + def __init__(self, cfg: argparse.Namespace): + self.args = cfg + self.session: Optional[perf.session] = None + self.insn = False + self.src = False + self.source_file_name: Optional[str] = None + self.line_number: int = 0 + self.dso: Optional[str] = None + self.stash_dict: Dict[int, List[str]] = {} + self.output: Any = None + self.output_pos: int = 0 + self.cpu: int = -1 + self.time: int = 0 + self.switch_str: Dict[int, str] = {} + + if cfg.insn_trace: + print("Intel PT Instruction Trace") + self.insn = True + elif cfg.src_trace: + print("Intel PT Source Trace") + self.insn = True + self.src = True + else: + print("Intel PT Branch Trace, Power Events, Event Trace and PTWRITE") + + self.disassembler: Any = None + self.inst: Any = None + if self.insn and LibXED is not None: + try: + self.disassembler = LibXED() + self.inst = self.disassembler.instruction() + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError, + ImportError) as e: + print(f"Failed to initialize LibXED: {e}") + self.disassembler = None + self.inst = None + + def print_ptwrite(self, raw_buf: bytes) -> None: + """Print PTWRITE data.""" + if not raw_buf: + return + try: + data = struct.unpack_from(" None: + """Print CBR data.""" + if len(raw_buf) < 12: + return + try: + data = struct.unpack_from(" None: + """Print MWAIT data.""" + try: + data = struct.unpack_from("> 32) & 0x3 + print(f"hints: {hints:#x} extensions: {extensions:#x}", end=' ') + + def print_pwre(self, raw_buf: bytes) -> None: + """Print PWRE data.""" + try: + data = struct.unpack_from("> 7) & 1 + cstate = (payload >> 12) & 0xf + subcstate = (payload >> 8) & 0xf + print(f"hw: {hw} cstate: {cstate} sub-cstate: {subcstate}", end=' ') + + def print_exstop(self, raw_buf: bytes) -> None: + """Print EXSTOP data.""" + try: + data = struct.unpack_from(" None: + """Print PWRX data.""" + try: + data = struct.unpack_from("> 4) & 0xf + wake_reason = (payload >> 8) & 0xf + print(f"deepest cstate: {deepest_cstate} last cstate: {last_cstate} " + f"wake reason: {wake_reason:#x}", end=' ') + + def print_psb(self, raw_buf: bytes) -> None: + """Print PSB data.""" + try: + data = struct.unpack_from(" None: + """Print EVT data.""" + glb_cfe = ["", "INTR", "IRET", "SMI", "RSM", "SIPI", "INIT", "VMENTRY", "VMEXIT", + "VMEXIT_INTR", "SHUTDOWN", "", "UINT", "UIRET"] + [""] * 18 + glb_evd = ["", "PFA", "VMXQ", "VMXR"] + [""] * 60 + + try: + data = struct.unpack_from("> 7 + vector = data[1] + evd_cnt = data[2] + s = glb_cfe[typ] + if s: + print(f" cfe: {s} IP: {ip_flag} vector: {vector}", end=' ') + else: + print(f" cfe: {typ} IP: {ip_flag} vector: {vector}", end=' ') + pos = 4 + for _ in range(evd_cnt): + if len(raw_buf) < pos + 16: + break + try: + data = struct.unpack_from(" None: + """Print IFLAG data.""" + try: + data = struct.unpack_from("{iflag} {s} branch", end=' ') + + def common_start_str(self, comm: str, sample: perf.sample_event) -> str: + """Return common start string for display.""" + ts = sample.sample_time + cpu = sample.sample_cpu + pid = sample.sample_pid + tid = sample.sample_tid + machine_pid = getattr(sample, "machine_pid", 0) + if machine_pid: + vcpu = getattr(sample, "vcpu", -1) + return (f"VM:{machine_pid:5d} VCPU:{vcpu:03d} {comm:>16s} {pid:5d}/{tid:<5d} " + f"[{cpu:03d}] {ts // 1000000000:9d}.{ts % 1000000000:09d} ") + return (f"{comm:>16s} {pid:5d}/{tid:<5d} [{cpu:03d}] " + f"{ts // 1000000000:9d}.{ts % 1000000000:09d} ") + + def print_common_start(self, comm: str, sample: perf.sample_event, name: str) -> None: + """Print common start info.""" + flags_disp = sample_flags_to_name(getattr(sample, "flags", 0)) + print(self.common_start_str(comm, sample) + f"{name:>8s} {flags_disp:>21s}", end=' ') + + def print_instructions_start(self, comm: str, sample: perf.sample_event) -> None: + """Print instructions start info.""" + raw_flags = getattr(sample, "flags", 0) + if isinstance(raw_flags, int) and (raw_flags & (1 << 10)): + print(self.common_start_str(comm, sample) + "x", end=' ') + else: + print(self.common_start_str(comm, sample), end=' ') + + def disassem(self, insn: bytes, ip: int) -> Tuple[int, str]: + """Disassemble instruction using LibXED.""" + inst = self.inst if self.inst is not None else self.disassembler.instruction() + is_64_bit = getattr(self.session, "is_64_bit", True) if self.session else True + self.disassembler.set_mode(inst, 0 if is_64_bit else 1) + buf = create_string_buffer(insn) + return self.disassembler.disassemble_one(inst, addressof(buf), len(insn), ip) + + def print_common_ip(self, sample: perf.sample_event, symbol: str, dso: str) -> None: + """Print IP and symbol info.""" + ip = sample.sample_ip + offs = f"+{sample.sym_offset:#x}" if getattr(sample, "sym_offset", None) is not None else "" + cyc_cnt = getattr(sample, "sample_cyc_count", 0) + if cyc_cnt: + insn_cnt = getattr(sample, "sample_insn_count", 0) + ipc_str = f" IPC: {insn_cnt / cyc_cnt:#.2f} ({insn_cnt}/{cyc_cnt})" + else: + ipc_str = "" + + if self.insn and self.disassembler is not None: + try: + insn = sample.insn() + except AttributeError: + insn = None + if insn: + cnt, text = self.disassem(insn, ip) + byte_str = (f"{ip:x}").rjust(16) + for k in range(cnt): + byte_str += f" {insn[k]:02x}" + print(f"{byte_str:<40s} {text:<30s}", end=' ') + else: + print(f"{ip:16x}", end=' ') + print(f"{symbol}{offs} ({dso})", end=' ') + else: + print(f"{ip:16x} {symbol}{offs} ({dso})", end=' ') + + addr = getattr(sample, "sample_addr", getattr(sample, "addr", 0)) + addr_correlates_sym = ( + bool(addr) + or getattr(sample, "addr_symbol", None) is not None + or getattr(sample, "addr_dso", None) is not None + ) + if addr_correlates_sym: + addr_dso = (sample.addr_dso or '[unknown]') + addr_symbol = (sample.addr_symbol or '[unknown]') + addr_offs = (f"+{sample.addr_sym_offset:#x}" + if getattr(sample, "addr_sym_offset", None) is not None else "") + print(f"=> {addr:x} {addr_symbol}{addr_offs} ({addr_dso}){ipc_str}") + else: + print(ipc_str) + + def print_srccode(self, comm: str, sample: perf.sample_event, + symbol: str, dso: str, with_insn: bool) -> None: + """Print source code info.""" + ip = sample.sample_ip + if symbol == "[unknown]": + start_str = self.common_start_str(comm, sample) + (f"{ip:x}").rjust(16).ljust(40) + else: + offs = (f"+{sample.sym_offset:#x}" + if getattr(sample, "sym_offset", None) is not None else "") + start_str = self.common_start_str(comm, sample) + (symbol + offs).ljust(40) + + if with_insn and self.insn and self.disassembler is not None: + try: + insn = sample.insn() + except AttributeError: + insn = None + if insn: + _, text = self.disassem(insn, ip) + start_str += text.ljust(30) + + source_file_name: typing.Any = None + line_number: typing.Any = 0 + source_line: typing.Any = None + try: + res_srcc = sample.srccode() + if res_srcc: + source_file_name, line_number, source_line = res_srcc + except (AttributeError, ValueError, TypeError): + pass + + if source_file_name: + if self.line_number == line_number and self.source_file_name == source_file_name: + src_str = "" + else: + if len(source_file_name) > 40: + src_file = ("..." + source_file_name[-37:]) + " " + else: + src_file = source_file_name.ljust(41) + if source_line is None: + src_str = src_file + str(line_number).rjust(4) + " " + else: + src_str = src_file + str(line_number).rjust(4) + " " + source_line + self.dso = None + elif dso == self.dso: + src_str = "" + else: + src_str = dso + self.dso = dso + + self.line_number = line_number + self.source_file_name = source_file_name + print(start_str, src_str) + + def do_process_event(self, sample: perf.sample_event) -> None: + """Process event and print info.""" + cpu = getattr(sample, "sample_cpu", getattr(sample, "cpu", 0)) + if cpu in self.switch_str: + print(self.switch_str[cpu]) + del self.switch_str[cpu] + + comm = "Unknown" + if hasattr(self, 'session') and self.session: + try: + thread = self.session.find_thread(sample.sample_pid, sample.sample_tid) + if thread: + comm = thread.comm() or "Unknown" + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + pass + # Python < 3.9 compatibility + name = str(sample.evsel) + if name.startswith("evsel("): + name = name[6:-1] + dso = (sample.dso or '[unknown]') + symbol = (sample.symbol or '[unknown]') + + raw_buf = getattr(sample, 'raw_buf', b'') or b'' + + if name.startswith("instructions"): + if self.src: + self.print_srccode(comm, sample, symbol, dso, True) + else: + self.print_instructions_start(comm, sample) + self.print_common_ip(sample, symbol, dso) + elif name.startswith("branches"): + if self.src: + self.print_srccode(comm, sample, symbol, dso, False) + else: + self.print_common_start(comm, sample, name) + self.print_common_ip(sample, symbol, dso) + elif name == "ptwrite": + self.print_common_start(comm, sample, name) + self.print_ptwrite(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "cbr": + self.print_common_start(comm, sample, name) + self.print_cbr(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "mwait": + self.print_common_start(comm, sample, name) + self.print_mwait(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "pwre": + self.print_common_start(comm, sample, name) + self.print_pwre(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "exstop": + self.print_common_start(comm, sample, name) + self.print_exstop(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "pwrx": + self.print_common_start(comm, sample, name) + self.print_pwrx(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "psb": + self.print_common_start(comm, sample, name) + self.print_psb(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "evt": + self.print_common_start(comm, sample, name) + self.print_evt(raw_buf) + self.print_common_ip(sample, symbol, dso) + elif name == "iflag": + self.print_common_start(comm, sample, name) + self.print_iflag(raw_buf) + self.print_common_ip(sample, symbol, dso) + else: + self.print_common_start(comm, sample, name) + self.print_common_ip(sample, symbol, dso) + + def interleave_events(self, sample: perf.sample_event) -> None: + """Interleave output to avoid garbled lines from different CPUs.""" + self.cpu = sample.sample_cpu + ts = sample.sample_time + + if self.time != ts: + self.time = ts + self.flush_stashed_output() + + self.output_pos = 0 + with contextlib.redirect_stdout(io.StringIO()) as self.output: + self.do_process_event(sample) + + self.stash_output() + + def stash_output(self) -> None: + """Stash output for later flushing.""" + output_str = self.output.getvalue()[self.output_pos:] + n = len(output_str) + if n: + self.output_pos += n + if self.cpu not in self.stash_dict: + self.stash_dict[self.cpu] = [] + self.stash_dict[self.cpu].append(output_str) + if len(self.stash_dict[self.cpu]) > 1000: + self.flush_stashed_output() + + def flush_stashed_output(self) -> None: + """Flush stashed output.""" + while self.stash_dict: + cpus = list(self.stash_dict.keys()) + for cpu in cpus: + items = self.stash_dict[cpu] + countdown = self.args.interleave + while len(items) and countdown: + sys.stdout.write(items[0]) + del items[0] + countdown -= 1 + if not items: + del self.stash_dict[cpu] + + def process_context_switch(self, event: perf.switch_event) -> None: + """Process context switch.""" + cpu = getattr(event, "sample_cpu", getattr(event, "cpu", 0)) + pid = getattr(event, "sample_pid", getattr(event, "pid", 0)) + tid = getattr(event, "sample_tid", getattr(event, "tid", 0)) + ts = getattr(event, "sample_time", getattr(event, "time", 0)) + np_pid = getattr(event, "next_prev_pid", None) + np_tid = getattr(event, "next_prev_tid", None) + machine_pid = getattr(event, "machine_pid", -1) + vcpu = getattr(event, "vcpu", -1) + misc = getattr(event, "misc", 0) + out = bool(misc & (1 << 13)) + out_preempt = bool(misc & (1 << 14)) + + if self.args.interleave: + self.flush_stashed_output() + + if out: + out_str = "Switch out " + else: + out_str = "Switch In " + + preempt_str = "preempt" if out_preempt else "" + + if machine_pid == -1: + machine_str = "" + elif vcpu == -1: + machine_str = f"machine PID {machine_pid}" + else: + machine_str = f"machine PID {machine_pid} VCPU {vcpu}" + + np_str = f"{np_pid:5d}/{np_tid:<5d} " if np_pid is not None and np_tid is not None else "" + c_str = (f"{out_str:>16s} {pid:5d}/{tid:<5d} [{cpu:03d}] " + f"{ts // 1000000000:9d}.{ts % 1000000000:09d} " + f"{np_str}{machine_str} {preempt_str}") + + if self.args.all_switch_events: + print(c_str) + else: + self.switch_str[cpu] = c_str + def process_event(self, sample: perf.sample_event) -> None: + """Wrapper to handle interleaving and exceptions.""" + try: + if self.args.interleave: + self.interleave_events(sample) + else: + self.do_process_event(sample) + except BrokenPipeError: + # Stop python printing broken pipe errors and traceback + sys.stdout = open(os.devnull, 'w', encoding='utf-8') + sys.exit(1) + + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("--insn-trace", action='store_true') + ap.add_argument("--src-trace", action='store_true') + ap.add_argument("--all-switch-events", action='store_true') + ap.add_argument("--interleave", type=int, nargs='?', const=4, default=0) + ap.add_argument("--itrace", default=os.environ.get("PERF_ITRACE"), help="itrace options") + args = ap.parse_args() + + if args.itrace is None: + if args.insn_trace or args.src_trace: + args.itrace = "i0nsepwxI" + else: + args.itrace = "bepwxI" + analyzer = IntelPTAnalyzer(args) + + try: + # Note: Python API currently lacks auxtrace_error callbacks affecting + # chronological interleaving + try: + analyzer.session = perf.session( + perf.data(args.input), + sample=analyzer.process_event, + context_switch=analyzer.process_context_switch, + itrace=args.itrace + ) + try: + analyzer.session.process_events() + except KeyboardInterrupt: + pass + finally: + # Break the reference cycle between analyzer.session and the bound + # analyzer.process_event / process_context_switch callbacks so the + # underlying C perf.session object is freed. + analyzer.session = None + if args.interleave: + analyzer.flush_stashed_output() + print("End") + except BrokenPipeError: + sys.exit(0) + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + import traceback + traceback.print_exc() + sys.exit(1) diff --git a/tools/perf/python/libxed.py b/tools/perf/python/libxed.py new file mode 100755 index 000000000000..900bd3e0184b --- /dev/null +++ b/tools/perf/python/libxed.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Python wrapper for libxed.so +Ported from tools/perf/scripts/python/libxed.py +""" +from __future__ import annotations + +from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \ + c_void_p, c_byte, c_int, c_uint, c_ulonglong + +# To use Intel XED, libxed.so must be present. To build and install +# libxed.so: +# git clone https://github.com/intelxed/mbuild.git mbuild +# git clone https://github.com/intelxed/xed +# cd xed +# ./mfile.py --share +# sudo ./mfile.py --prefix=/usr/local install +# sudo ldconfig +# + + +class XedStateT(Structure): + """xed_state_t structure.""" + _fields_ = [ + ("mode", c_int), + ("width", c_int) + ] + + +class XEDInstruction(): + """Represents a decoded instruction.""" + + def __init__(self, libxed): + # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion + xedd_t = c_byte * 512 + self.xedd = xedd_t() + self.xedp = addressof(self.xedd) + libxed.xed_decoded_inst_zero(self.xedp) + self.state = XedStateT() + self.statep = addressof(self.state) + # Buffer for disassembled instruction text + self.buffer = create_string_buffer(256) + self.bufferp = addressof(self.buffer) + + +class LibXED(): + """Wrapper for libxed.so.""" + + def __init__(self): + try: + self.libxed = CDLL("libxed.so") + except OSError: + self.libxed = None + if not self.libxed: + try: + self.libxed = CDLL("/usr/local/lib/libxed.so") + except OSError: + self.libxed = None + + if not self.libxed: + raise ImportError("libxed.so not found. Please install Intel XED.") + + self.xed_tables_init = self.libxed.xed_tables_init + self.xed_tables_init.restype = None + self.xed_tables_init.argtypes = [] + + self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero + self.xed_decoded_inst_zero.restype = None + self.xed_decoded_inst_zero.argtypes = [c_void_p] + + self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode + self.xed_operand_values_set_mode.restype = None + self.xed_operand_values_set_mode.argtypes = [c_void_p, c_void_p] + + self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode + self.xed_decoded_inst_zero_keep_mode.restype = None + self.xed_decoded_inst_zero_keep_mode.argtypes = [c_void_p] + + self.xed_decode = self.libxed.xed_decode + self.xed_decode.restype = c_int + self.xed_decode.argtypes = [c_void_p, c_void_p, c_uint] + + self.xed_format_context = self.libxed.xed_format_context + self.xed_format_context.restype = c_uint + self.xed_format_context.argtypes = [ + c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p + ] + + self.xed_decoded_inst_get_length = self.libxed.xed_decoded_inst_get_length + self.xed_decoded_inst_get_length.restype = c_uint + self.xed_decoded_inst_get_length.argtypes = [c_void_p] + + self.xed_tables_init() + + def instruction(self): + """Create a new XEDInstruction.""" + return XEDInstruction(self) + + def set_mode(self, inst, mode): + """Set 32-bit or 64-bit mode.""" + if mode: + inst.state.mode = 4 # 32-bit + inst.state.width = 4 # 4 bytes + else: + inst.state.mode = 1 # 64-bit + inst.state.width = 8 # 8 bytes + self.xed_operand_values_set_mode(inst.xedp, inst.statep) + + def disassemble_one(self, inst, bytes_ptr, bytes_cnt, ip): + """Disassemble one instruction.""" + self.xed_decoded_inst_zero_keep_mode(inst.xedp) + err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt) + if err: + return 0, "" + # Use AT&T mode (2), alternative is Intel (3) + ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0) + if not ok: + return 0, "" + + result = inst.buffer.value.decode('utf-8') + # Return instruction length and the disassembled instruction text + return self.xed_decoded_inst_get_length(inst.xedp), result diff --git a/tools/perf/python/mem-phys-addr.py b/tools/perf/python/mem-phys-addr.py new file mode 100755 index 000000000000..53ef7e9a14b5 --- /dev/null +++ b/tools/perf/python/mem-phys-addr.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +"""mem-phys-addr.py: Resolve physical address samples""" +from __future__ import annotations +import argparse +import bisect +import collections +from dataclasses import dataclass +import re +from typing import (Dict, List, Optional) + +import perf + +@dataclass(frozen=True) +class IomemEntry: + """Read from a line in /proc/iomem""" + begin: int + end: int + indent: int + label: str + + def __lt__(self, other) -> bool: + if isinstance(other, int): + return self.begin < other + return self.begin < other.begin + + def __gt__(self, other) -> bool: + if isinstance(other, int): + return self.begin > other + return self.begin > other.begin + +# Physical memory layout from /proc/iomem. Key is the indent and then +# a list of ranges. +iomem: Dict[int, List[IomemEntry]] = collections.defaultdict(list) +# Child nodes from the iomem parent. +children: Dict[IomemEntry, List[IomemEntry]] = collections.defaultdict(list) +# Maximum indent seen before an entry in the iomem file. +_STATE: Dict[str, int] = {"max_indent": 0} +# Per-event counts for each range of memory. +event_counts: Dict[str, collections.Counter] = collections.defaultdict(collections.Counter) + +def parse_iomem(iomem_path: str): + """Populate iomem from iomem file""" + with open(iomem_path, 'r', encoding='ascii') as f: + for line in f: + line = line.rstrip('\n') + if not line or line.isspace(): + continue + indent = 0 + while indent < len(line) and line[indent] == ' ': + indent += 1 + _STATE["max_indent"] = max(_STATE["max_indent"], indent) + m = re.split('-|:', line, maxsplit=2) + if len(m) < 3: + continue + begin = int(m[0].strip(), 16) + end = int(m[1].strip(), 16) + label = m[2].strip() + entry = IomemEntry(begin, end, indent, label) + # Before adding entry, search for a parent node using its begin. + if indent > 0: + parent = find_memory_type(begin) + assert parent, f"Given indent expected a parent for {label}" + children[parent].append(entry) + iomem[indent].append(entry) + +def find_memory_type(phys_addr) -> Optional[IomemEntry]: + """Search iomem for the range containing phys_addr with the maximum indent""" + for i in range(_STATE["max_indent"], -1, -1): + if i not in iomem: + continue + position = bisect.bisect_right(iomem[i], phys_addr) + if position == 0: + continue + iomem_entry = iomem[i][position-1] + if iomem_entry.begin <= phys_addr <= iomem_entry.end: + return iomem_entry + return None + +def _print_entries(entries, load_mem_type_cnt, total): + """Print counts from parents down to their children""" + for entry in sorted(entries, + key=lambda e: (-load_mem_type_cnt[e], e.begin)): + count = load_mem_type_cnt[entry] + if count > 0: + mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}" + percent = 100 * count / total + print(f"{mem_type:<40} {count:>10} {percent:>10.1f}") + _print_entries(children[entry], load_mem_type_cnt, total) + +def print_memory_type(): + """Print the resolved memory types and their counts.""" + if not event_counts: + print("No valid physical address samples found in perf data.") + return + + for event_name, load_mem_type_cnt in event_counts.items(): + print(f"Event: {event_name}") + print(f"{'Memory type':<40} {'count':>10} {'percentage':>10}") + print(f"{'-' * 40:<40} {'-' * 10:>10} {'-' * 10:>10}") + total = sum(load_mem_type_cnt.values()) + if total == 0: + continue + + # Add count from children into the parent. + for i in range(_STATE["max_indent"], -1, -1): + if i not in iomem: + continue + for entry in iomem[i]: + for child in children[entry]: + if load_mem_type_cnt[child] > 0: + load_mem_type_cnt[entry] += load_mem_type_cnt[child] + + _print_entries(iomem[0], load_mem_type_cnt, total) + print() +if __name__ == "__main__": + ap = argparse.ArgumentParser(description="Resolve physical address samples") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("--iomem", default="/proc/iomem", help="Path to iomem file") + args = ap.parse_args() + + def process_event(sample): + """Process a single sample event.""" + phys_addr = sample.sample_phys_addr or 0 + if not phys_addr: + return + entry = find_memory_type(phys_addr) + if entry: + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + event_counts[event_name][entry] += 1 + + parse_iomem(args.iomem) + perf.session(perf.data(args.input), sample=process_event).process_events() + print_memory_type() diff --git a/tools/perf/python/net_dropmonitor.py b/tools/perf/python/net_dropmonitor.py new file mode 100755 index 000000000000..fff637b5c7e9 --- /dev/null +++ b/tools/perf/python/net_dropmonitor.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Monitor the system for dropped packets and produce a report of drop locations and counts. +Ported from tools/perf/scripts/python/net_dropmonitor.py +""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import os +import sys +from typing import Tuple +import perf + + +class DropMonitor: + """Monitors dropped packets and aggregates counts by location.""" + + def __init__(self, kallsyms_path: str | None = None) -> None: + self.drop_log: dict[int, int] = defaultdict(int) + self.kallsyms: list[Tuple[int, str]] = [] + self.kallsyms_parsed = False + self.resolved_syms: dict[int, Tuple[str, int]] = {} + self.callchain_syms: dict[int, str] = {} + self.kallsyms_path = ( + kallsyms_path + or os.environ.get("PERF_SYMBOL_KALLSYMS") + or "/proc/kallsyms" + ) + + def _parse_kallsyms(self) -> None: + """Parse the kallsyms file and map kernel addresses to function symbols.""" + self.kallsyms.clear() + self.kallsyms_parsed = True + try: + with open(self.kallsyms_path, "r", encoding="utf-8") as f: + for line in f: + parts = line.split() + if len(parts) >= 3 and parts[1] in ('t', 'T', 'w', 'W'): + addr = int(parts[0], 16) + if addr > 0: + self.kallsyms.append((addr, parts[2])) + self.kallsyms.sort(key=lambda x: x[0]) + except (FileNotFoundError, PermissionError): + print(f"Failed to read {self.kallsyms_path}. Symbols will not be resolved.") + + def _get_sym(self, loc: int) -> Tuple[str, int]: + """Resolve a memory location using session symbols or the kallsyms map.""" + # Priority order: + # 1. Exact symbols with offsets resolved directly from the perf.data session + # (resolved_syms). + # 2. Symbols captured from the sample's callchain in the trace file + # (callchain_syms) before falling back to self.kallsyms, so offline + # trace symbols are not overwritten by the live host's /proc/kallsyms. + # 3. Binary search in self.kallsyms (from --kallsyms or /proc/kallsyms). + if loc in self.resolved_syms: + return self.resolved_syms[loc] + if loc in self.callchain_syms: + res = (self.callchain_syms[loc], 0) + self.resolved_syms[loc] = res + return res + if not self.kallsyms: + return f"{loc:#x}", 0 + + start = 0 + end = len(self.kallsyms) - 1 + while start < end: + mid = (start + end) // 2 + if self.kallsyms[mid][0] <= loc < self.kallsyms[mid+1][0]: + start = mid + break + if loc < self.kallsyms[mid][0]: + end = mid - 1 + else: + start = mid + 1 + + sym_addr, sym_name = self.kallsyms[start] + if loc >= sym_addr: + res = (sym_name, loc - sym_addr) + self.resolved_syms[loc] = res + return res + return f"{loc:#x}", 0 + + def print_drop_table(self) -> None: + """Print aggregated results.""" + if not self.drop_log: + print(f"{'LOCATION':>25} {'OFFSET':>25} {'COUNT':>25}") + return + + if (not self.kallsyms_parsed + and any(loc not in self.resolved_syms and loc not in self.callchain_syms + for loc in self.drop_log)): + print("Gathering kallsyms data") + self._parse_kallsyms() + + print(f"{'LOCATION':>25} {'OFFSET':>25} {'COUNT':>25}") + sorted_keys = sorted(self.drop_log.keys()) + for sloc in sorted_keys: + sym, off = self._get_sym(sloc) + print(f"{sym:>25} {off:>25d} {self.drop_log[sloc]:>25d}") + + def process_event(self, sample: perf.sample_event) -> None: + """Process a single sample event.""" + if "skb:kfree_skb" not in str(sample.evsel): + return + + location = getattr(sample, "location", None) + if location is not None: + self.drop_log[location] += 1 + if location not in self.resolved_syms: + sym = getattr(sample, "symbol", None) + if getattr(sample, "sample_ip", 0) == location and sym and sym != "[unknown]": + self.resolved_syms[location] = ( + sym, + getattr(sample, "sym_offset", 0) or 0, + ) + self.callchain_syms.pop(location, None) + else: + for entry in getattr(sample, "callchain", []) or []: + if isinstance(entry, dict): + entry_ip = entry.get("ip") + sym_info = entry.get("sym") + sym_name = sym_info.get("name") if isinstance(sym_info, dict) else None + sym_start = sym_info.get("start") if isinstance(sym_info, dict) else None + sym_off = (max(0, location - sym_start) + if sym_start is not None else None) + else: + entry_ip = getattr(entry, "ip", None) + entry_sym = getattr(entry, "sym", None) + sym_name = ( + getattr(entry_sym, "name", None) + or getattr(entry, "symbol", None) + ) + sym_off = getattr(entry, "sym_offset", None) + if sym_off is None: + sym_start = getattr(entry_sym, "start", None) + if sym_start is not None: + sym_off = max(0, location - sym_start) + if entry_ip == location and sym_name and sym_name != "[unknown]": + if sym_off is not None: + self.resolved_syms[location] = (sym_name, sym_off) + self.callchain_syms.pop(location, None) + else: + self.callchain_syms[location] = sym_name + break + + +if __name__ == "__main__": + ap = argparse.ArgumentParser( + description="Monitor the system for dropped packets and produce a " + "report of drop locations and counts.") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("-k", "--kallsyms", default=None, + help="Path to kallsyms file for offline symbol resolution") + args = ap.parse_args() + + monitor = DropMonitor(kallsyms_path=args.kallsyms) + session = None + + try: + session = perf.session(perf.data(args.input), sample=monitor.process_event, + kallsyms=args.kallsyms) + session.process_events() + except KeyboardInterrupt: + print("\nStopping trace...") + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError) as e: + print(f"Error processing events: {e}") + sys.exit(1) + finally: + session = None + + monitor.print_drop_table() diff --git a/tools/perf/python/netdev-times.py b/tools/perf/python/netdev-times.py new file mode 100755 index 000000000000..f0f829987447 --- /dev/null +++ b/tools/perf/python/netdev-times.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Display a process of packets and processed time. +It helps us to investigate networking or network device. + +Ported from tools/perf/scripts/python/netdev-times.py +""" + +from __future__ import annotations +import argparse +from collections import defaultdict +import sys +from typing import Optional +import perf + +# Format for displaying rx packet processing +PF_IRQ_ENTRY = " irq_entry(+%.3fmsec irq=%d:%s)" +PF_SOFT_ENTRY = " softirq_entry(+%.3fmsec)" +PF_NAPI_POLL = " napi_poll_exit(+%.3fmsec %s)" +PF_JOINT = " |" +PF_WJOINT = " | |" +PF_NET_RECV = " |---netif_receive_skb(+%.3fmsec skb=%x len=%d)" +PF_NET_RX = " |---netif_rx(+%.3fmsec skb=%x)" +PF_CPY_DGRAM = " | skb_copy_datagram_iovec(+%.3fmsec %d:%s)" +PF_KFREE_SKB = " | kfree_skb(+%.3fmsec location=%x)" +PF_CONS_SKB = " | consume_skb(+%.3fmsec)" + + +class NetDevTimesAnalyzer: + """Analyzes network device events and prints charts.""" + + def __init__(self, cfg: argparse.Namespace): + self.args = cfg + self.session: Optional[perf.session] = None + self.show_tx = cfg.tx or (not cfg.tx and not cfg.rx) + self.show_rx = cfg.rx or (not cfg.tx and not cfg.rx) + self.dev = cfg.dev + self.debug = cfg.debug + self.buffer_budget = 65536 + self.irq_dic: dict[int, list[dict]] = defaultdict(list) + self.net_rx_dic: dict[int, dict] = {} + self.receive_hunk_list: list[dict] = [] + self.rx_skb_list: list[dict] = [] + self.tx_queue_list: list[dict] = [] + self.tx_xmit_list: list[dict] = [] + self.tx_free_list: list[dict] = [] + + self.buffer_budget = 65536 + self.of_count_rx_skb_list = 0 + self.of_count_tx_queue_list = 0 + self.of_count_tx_xmit_list = 0 + + def diff_msec(self, src: int, dst: int) -> float: + """Calculate a time interval(msec) from src(nsec) to dst(nsec).""" + return (dst - src) / 1000000.0 + + def print_transmit(self, hunk: dict) -> None: + """Display a process of transmitting a packet.""" + if self.dev and hunk['dev'].find(self.dev) < 0: + return + queue_t_sec = hunk['queue_t'] // 1000000000 + queue_t_usec = hunk['queue_t'] % 1000000000 // 1000 + print(f"{hunk['dev']:7s} {hunk['len']:5d} " + f"{queue_t_sec:6d}.{queue_t_usec:06d}sec " + f"{self.diff_msec(hunk['queue_t'], hunk['xmit_t']):12.3f}msec " + f"{self.diff_msec(hunk['xmit_t'], hunk['free_t']):12.3f}msec") + + def print_receive(self, hunk: dict) -> None: + """Display a process of received packets and interrupts.""" + show_hunk = False + irq_list = hunk['irq_list'] + if not irq_list: + return + cpu = irq_list[0]['cpu'] + base_t = irq_list[0]['irq_ent_t'] + + if self.dev: + for irq in irq_list: + if irq['name'].find(self.dev) >= 0: + show_hunk = True + break + else: + show_hunk = True + + if not show_hunk: + return + + base_t_sec = base_t // 1000000000 + base_t_usec = base_t % 1000000000 // 1000 + print(f"{base_t_sec}.{base_t_usec:06d}sec cpu={cpu}") + for irq in irq_list: + print(PF_IRQ_ENTRY % + (self.diff_msec(base_t, irq['irq_ent_t']), + irq['irq'], irq['name'])) + print(PF_JOINT) + irq_event_list = irq['event_list'] + for irq_event in irq_event_list: + if irq_event['event'] == 'netif_rx': + print(PF_NET_RX % + (self.diff_msec(base_t, irq_event['time']), + irq_event['skbaddr'])) + print(PF_JOINT) + + print(PF_SOFT_ENTRY % self.diff_msec(base_t, hunk['sirq_ent_t'])) + print(PF_JOINT) + event_list = hunk['event_list'] + for i, event in enumerate(event_list): + if event['event_name'] == 'napi_poll': + print(PF_NAPI_POLL % + (self.diff_msec(base_t, event['event_t']), + event['dev'])) + if i == len(event_list) - 1: + print("") + else: + print(PF_JOINT) + else: + print(PF_NET_RECV % + (self.diff_msec(base_t, event['event_t']), + event['skbaddr'], + event['len'])) + if 'handle' in event: + print(PF_WJOINT) + if event['handle'] == "kfree_skb": + print(PF_KFREE_SKB % + (self.diff_msec(base_t, event['comm_t']), + event['location'])) + elif event['handle'] == "consume_skb": + print(PF_CONS_SKB % + self.diff_msec(base_t, event['comm_t'])) + elif event['handle'] == "skb_copy_datagram_iovec": + print(PF_CPY_DGRAM % + (self.diff_msec(base_t, event['comm_t']), + event['pid'], event['comm'])) + print(PF_JOINT) + + def handle_irq_handler_entry(self, event: dict) -> None: + """Handle irq:irq_handler_entry event.""" + time = event['time'] + cpu = event['cpu'] + irq = event['irq'] + irq_name = event['irq_name'] + irq_record = {'irq': irq, 'name': irq_name, 'cpu': cpu, + 'irq_ent_t': time, 'event_list': []} + self.irq_dic[cpu].append(irq_record) + + def handle_irq_handler_exit(self, event: dict) -> None: + """Handle irq:irq_handler_exit event.""" + time = event['time'] + cpu = event['cpu'] + irq = event['irq'] + if cpu not in self.irq_dic or not self.irq_dic[cpu]: + return + if irq != self.irq_dic[cpu][-1]['irq']: + return + irq_record = self.irq_dic[cpu].pop() + irq_record['irq_ext_t'] = time + # if an irq doesn't include NET_RX softirq, drop. + if irq_record['event_list']: + self.irq_dic[cpu].append(irq_record) + + def handle_irq_softirq_raise(self, event: dict) -> None: + """Handle irq:softirq_raise event.""" + time = event['time'] + cpu = event['cpu'] + if cpu not in self.irq_dic or not self.irq_dic[cpu]: + return + irq_record = self.irq_dic[cpu].pop() + irq_record['event_list'].append({'time': time, 'event': 'sirq_raise'}) + self.irq_dic[cpu].append(irq_record) + + def handle_irq_softirq_entry(self, event: dict) -> None: + """Handle irq:softirq_entry event.""" + time = event['time'] + cpu = event['cpu'] + self.net_rx_dic[cpu] = {'sirq_ent_t': time, 'event_list': []} + + def handle_irq_softirq_exit(self, event: dict) -> None: + """Handle irq:softirq_exit event.""" + time = event['time'] + cpu = event['cpu'] + irq_list = None + event_list = None + sirq_ent_t = None + + if cpu in self.irq_dic: + irq_list = self.irq_dic[cpu] + del self.irq_dic[cpu] + if cpu in self.net_rx_dic: + sirq_ent_t = self.net_rx_dic[cpu]['sirq_ent_t'] + event_list = self.net_rx_dic[cpu]['event_list'] + del self.net_rx_dic[cpu] + if not irq_list or event_list is None or sirq_ent_t is None: + return + rec_data = {'sirq_ent_t': sirq_ent_t, 'sirq_ext_t': time, + 'irq_list': irq_list, 'event_list': event_list} + self.receive_hunk_list.append(rec_data) + + def handle_napi_poll(self, event: dict) -> None: + """Handle napi:napi_poll event.""" + time = event['time'] + cpu = event['cpu'] + dev_name = event['dev_name'] + work = event['work'] + budget = event['budget'] + if cpu in self.net_rx_dic: + event_list = self.net_rx_dic[cpu]['event_list'] + rec_data = {'event_name': 'napi_poll', + 'dev': dev_name, 'event_t': time, + 'work': work, 'budget': budget} + event_list.append(rec_data) + + def handle_netif_rx(self, event: dict) -> None: + """Handle net:netif_rx event.""" + time = event['time'] + cpu = event['cpu'] + skbaddr = event['skbaddr'] + skblen = event['skblen'] + dev_name = event['dev_name'] + if cpu not in self.irq_dic or not self.irq_dic[cpu]: + return + irq_record = self.irq_dic[cpu].pop() + irq_record['event_list'].append({'time': time, 'event': 'netif_rx', + 'skbaddr': skbaddr, 'skblen': skblen, + 'dev_name': dev_name}) + self.irq_dic[cpu].append(irq_record) + + def handle_netif_receive_skb(self, event: dict) -> None: + """Handle net:netif_receive_skb event.""" + time = event['time'] + cpu = event['cpu'] + skbaddr = event['skbaddr'] + skblen = event['skblen'] + if cpu in self.net_rx_dic: + rec_data = {'event_name': 'netif_receive_skb', + 'event_t': time, 'skbaddr': skbaddr, 'len': skblen} + event_list = self.net_rx_dic[cpu]['event_list'] + event_list.append(rec_data) + self.rx_skb_list.insert(0, rec_data) + if len(self.rx_skb_list) > self.buffer_budget: + self.rx_skb_list.pop() + self.of_count_rx_skb_list += 1 + + def handle_net_dev_queue(self, event: dict) -> None: + """Handle net:net_dev_queue event.""" + time = event['time'] + skbaddr = event['skbaddr'] + skblen = event['skblen'] + dev_name = event['dev_name'] + skb = {'dev': dev_name, 'skbaddr': skbaddr, 'len': skblen, 'queue_t': time} + self.tx_queue_list.insert(0, skb) + if len(self.tx_queue_list) > self.buffer_budget: + self.tx_queue_list.pop() + self.of_count_tx_queue_list += 1 + + def handle_net_dev_xmit(self, event: dict) -> None: + """Handle net:net_dev_xmit event.""" + time = event['time'] + skbaddr = event['skbaddr'] + rc = event['rc'] + if rc == 0: # NETDEV_TX_OK + for i, skb in enumerate(self.tx_queue_list): + if skb['skbaddr'] == skbaddr: + skb['xmit_t'] = time + self.tx_xmit_list.insert(0, skb) + del self.tx_queue_list[i] + if len(self.tx_xmit_list) > self.buffer_budget: + self.tx_xmit_list.pop() + self.of_count_tx_xmit_list += 1 + return + + def handle_kfree_skb(self, event: dict) -> None: + """Handle skb:kfree_skb event.""" + time = event['time'] + skbaddr = event['skbaddr'] + comm = event['comm'] + pid = event['pid'] + location = event['location'] + for i, skb in enumerate(self.tx_queue_list): + if skb['skbaddr'] == skbaddr: + del self.tx_queue_list[i] + return + for i, skb in enumerate(self.tx_xmit_list): + if skb['skbaddr'] == skbaddr: + skb['free_t'] = time + self.tx_free_list.append(skb) + del self.tx_xmit_list[i] + return + for i, rec_data in enumerate(self.rx_skb_list): + if rec_data['skbaddr'] == skbaddr: + rec_data.update({'handle': "kfree_skb", + 'comm': comm, 'pid': pid, 'comm_t': time, 'location': location}) + del self.rx_skb_list[i] + return + + def handle_consume_skb(self, event: dict) -> None: + """Handle skb:consume_skb event.""" + time = event['time'] + skbaddr = event['skbaddr'] + for i, skb in enumerate(self.tx_xmit_list): + if skb['skbaddr'] == skbaddr: + skb['free_t'] = time + self.tx_free_list.append(skb) + del self.tx_xmit_list[i] + return + for i, rec_data in enumerate(self.rx_skb_list): + if rec_data['skbaddr'] == skbaddr: + rec_data.update({'handle': "consume_skb", 'comm_t': time}) + del self.rx_skb_list[i] + return + + def handle_skb_copy_datagram_iovec(self, event: dict) -> None: + """Handle skb:skb_copy_datagram_iovec event.""" + time = event['time'] + skbaddr = event['skbaddr'] + comm = event['comm'] + pid = event['pid'] + for i, rec_data in enumerate(self.rx_skb_list): + if skbaddr == rec_data['skbaddr']: + rec_data.update({'handle': "skb_copy_datagram_iovec", + 'comm': comm, 'pid': pid, 'comm_t': time}) + del self.rx_skb_list[i] + return + + def print_summary(self) -> None: + """Print charts.""" + + # display receive hunks + if self.show_rx: + for hunk in self.receive_hunk_list: + self.print_receive(hunk) + + # display transmit hunks + if self.show_tx: + print(" dev len Qdisc " + " netdevice free") + for hunk in self.tx_free_list: + self.print_transmit(hunk) + + if self.debug: + print("debug buffer status") + print("----------------------------") + print(f"xmit Qdisc:remain:{len(self.tx_queue_list)} " + f"overflow:{self.of_count_tx_queue_list}") + print(f"xmit netdevice:remain:{len(self.tx_xmit_list)} " + f"overflow:{self.of_count_tx_xmit_list}") + print(f"receive:remain:{len(self.rx_skb_list)} " + f"overflow:{self.of_count_rx_skb_list}") + + def handle_single_event(self, event: dict) -> None: + """Handle a single processed event.""" + name = event['name'] + if name == 'irq:softirq_exit': + self.handle_irq_softirq_exit(event) + elif name == 'irq:softirq_entry': + self.handle_irq_softirq_entry(event) + elif name == 'irq:softirq_raise': + self.handle_irq_softirq_raise(event) + elif name == 'irq:irq_handler_entry': + self.handle_irq_handler_entry(event) + elif name == 'irq:irq_handler_exit': + self.handle_irq_handler_exit(event) + elif name == 'napi:napi_poll': + self.handle_napi_poll(event) + elif name == 'net:netif_receive_skb': + self.handle_netif_receive_skb(event) + elif name == 'net:netif_rx': + self.handle_netif_rx(event) + elif name == 'skb:skb_copy_datagram_iovec': + self.handle_skb_copy_datagram_iovec(event) + elif name == 'net:net_dev_queue': + self.handle_net_dev_queue(event) + elif name == 'net:net_dev_xmit': + self.handle_net_dev_xmit(event) + elif name == 'skb:kfree_skb': + self.handle_kfree_skb(event) + elif name == 'skb:consume_skb': + self.handle_consume_skb(event) + + def process_event(self, sample: perf.sample_event) -> None: + """Process events directly on-the-fly.""" + name = str(sample.evsel) + ev_name = name[6:-1] if name.startswith("evsel(") else name + pid = sample.sample_pid + if hasattr(self, 'session') and self.session: + try: + thread = self.session.find_thread(sample.sample_pid, sample.sample_tid) + comm = (thread.comm() if thread else None) or "[unknown]" + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + comm = "[unknown]" + else: + comm = "Unknown" + event_data = { + 'name': ev_name, + 'time': sample.sample_time, + 'cpu': sample.sample_cpu, + 'pid': pid, + 'comm': comm, + } + + # Extract specific fields based on event type + if ev_name.startswith("irq:softirq_"): + event_data['vec'] = getattr(sample, "vec", 0) + # Filter for NET_RX + if event_data['vec'] != 3: # NET_RX_SOFTIRQ is usually 3 + return + elif ev_name == "irq:irq_handler_entry": + event_data['irq'] = getattr(sample, "irq", -1) + event_data['irq_name'] = getattr(sample, "name", "[unknown]") + elif ev_name == "irq:irq_handler_exit": + event_data['irq'] = getattr(sample, "irq", -1) + event_data['ret'] = getattr(sample, "ret", 0) + elif ev_name == "napi:napi_poll": + event_data['napi'] = getattr(sample, "napi", 0) + event_data['dev_name'] = getattr(sample, "dev_name", "[unknown]") + event_data['work'] = getattr(sample, "work", 0) + event_data['budget'] = getattr(sample, "budget", 0) + elif ev_name in ("net:netif_receive_skb", "net:netif_rx", + "net:net_dev_queue"): + event_data['skbaddr'] = getattr(sample, "skbaddr", 0) + event_data['skblen'] = getattr(sample, "len", 0) + event_data['dev_name'] = getattr(sample, "name", "[unknown]") + elif ev_name == "net:net_dev_xmit": + event_data['skbaddr'] = getattr(sample, "skbaddr", 0) + event_data['skblen'] = getattr(sample, "len", 0) + event_data['rc'] = getattr(sample, "rc", 0) + event_data['dev_name'] = getattr(sample, "name", "[unknown]") + elif ev_name == "skb:kfree_skb": + event_data['skbaddr'] = getattr(sample, "skbaddr", 0) + event_data['location'] = getattr(sample, "location", 0) + event_data['protocol'] = getattr(sample, "protocol", 0) + event_data['reason'] = getattr(sample, "reason", 0) + elif ev_name == "skb:consume_skb": + event_data['skbaddr'] = getattr(sample, "skbaddr", 0) + event_data['location'] = getattr(sample, "location", 0) + elif ev_name == "skb:skb_copy_datagram_iovec": + event_data['skbaddr'] = getattr(sample, "skbaddr", 0) + event_data['skblen'] = getattr(sample, "len", 0) + + self.handle_single_event(event_data) + + +NetdevTimes = NetDevTimesAnalyzer + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description="Display a process of packets and processed time.") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("--tx", action="store_true", help="show only tx chart") + ap.add_argument("--rx", action="store_true", help="show only rx chart") + ap.add_argument("--dev", default=None, help="show only specified device") + ap.add_argument("--debug", action="store_true", + help="work with debug mode. It shows buffer status.") + ap.add_argument("positionals", nargs="*", + help="optional positional arguments (tx, rx, dev=, debug)") + args, unknown_args = ap.parse_known_args() + + parsed_args = argparse.Namespace( + tx=args.tx, rx=args.rx, dev=args.dev, debug=args.debug, input=args.input + ) + + for arg in list(args.positionals) + unknown_args: + if not arg or not isinstance(arg, str): + continue + if arg in ('tx', '--tx'): + parsed_args.tx = True + elif arg in ('rx', '--rx'): + parsed_args.rx = True + elif arg.startswith('dev='): + parsed_args.dev = arg[4:] + elif arg.startswith('--dev='): + parsed_args.dev = arg[6:] + elif arg in ('debug', '--debug'): + parsed_args.debug = True + + analyzer = NetDevTimesAnalyzer(parsed_args) + + try: + try: + analyzer.session = perf.session( + perf.data(parsed_args.input), sample=analyzer.process_event + ) + try: + analyzer.session.process_events() + except KeyboardInterrupt: + pass + finally: + # Break the reference cycle between analyzer.session and the bound + # analyzer.process_event callback so the C perf.session object is freed. + analyzer.session = None + analyzer.print_summary() + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError) as e: + print(f"Error processing events: {e}") + sys.exit(1) diff --git a/tools/perf/python/parallel-perf.py b/tools/perf/python/parallel-perf.py new file mode 100755 index 000000000000..a43b003634fd --- /dev/null +++ b/tools/perf/python/parallel-perf.py @@ -0,0 +1,1250 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +"""Parallel perf script.""" +# +# run a perf script command multiple times in parallel, using perf script +# options --cpu and --time so that each job processes a different chunk +# of the data. +# +# Copyright (c) 2024, Intel Corporation. + +import subprocess +import argparse +import pathlib +import shlex +import time +from typing import Any +import copy +import sys +import os +import re + +glb_prog_name = "parallel-perf.py" +glb_min_interval = 10.0 +glb_min_samples = 64 + + +class Verbosity(): + + def __init__(self, quiet: bool = False, verbose: bool = False, + debug: bool = False) -> None: + """__init__.""" + + self.normal = True + self.verbose = verbose + self.debug = debug + self.self_test = True + if self.debug: + self.verbose = True + if self.verbose: + quiet = False + if quiet: + self.normal = False + +# Manage work (start/wait/kill), as represented by a subprocess.Popen command + + +class Work(): + + def __init__(self, cmd: list[str], pipe_to: str, + output_dir: str = ".") -> None: + """__init__.""" + + self.popen: Any = None + self.consumer: Any = None + self.cmd = cmd + self.pipe_to = pipe_to + self.output_dir = output_dir + self.cmdout_name = f"{output_dir}/cmd.txt" + self.stdout_name = f"{output_dir}/out.txt" + self.stderr_name = f"{output_dir}/err.txt" + + def command(self): + """command.""" + + return " ".join(shlex.quote(x) for x in self.cmd) + + def stdout(self): + """stdout.""" + + return open(self.stdout_name, "w", encoding="utf-8") + + def stderr(self): + """stderr.""" + + return open(self.stderr_name, "w", encoding="utf-8") + + def create_output_dir(self): + """create_output_dir.""" + + pathlib.Path(self.output_dir).mkdir(parents=True, exist_ok=True) + + def start(self): + """start.""" + + if self.popen: + return + self.create_output_dir() + with open(self.cmdout_name, "w", encoding="utf-8") as f: + f.write(self.command()) + f.write("\n") + stdout = self.stdout() + stderr = self.stderr() + if self.pipe_to: + self.popen = subprocess.Popen( + self.cmd, stdout=subprocess.PIPE, stderr=stderr) + args = shlex.split(self.pipe_to) + self.consumer = subprocess.Popen( + args, stdin=self.popen.stdout, stdout=stdout, stderr=stderr) + # The consumer now owns the read end of the pipe. Close this + # process's copy so that the producer receives SIGPIPE if the + # consumer exits early, and so that the descriptor isn't leaked + # for the lifetime of this Work. + self.popen.stdout.close() + self.popen.stdout = None + else: + self.popen = subprocess.Popen( + self.cmd, stdout=stdout, stderr=stderr) + + def remove_empty_err_file(self): + """remove_empty_err_file.""" + + if os.path.exists(self.stderr_name): + if os.path.getsize(self.stderr_name) == 0: + os.unlink(self.stderr_name) + + def errors(self): + """errors.""" + + if os.path.exists(self.stderr_name): + if os.path.getsize(self.stderr_name) != 0: + return [f"Non-empty error file {self.stderr_name}"] + return [] + + def tidy_up(self): + """tidy_up.""" + + self.remove_empty_err_file() + + def raw_poll_wait(self, p, wait): + """raw_poll_wait.""" + + if wait: + return p.wait() + return p.poll() + + def poll(self, wait=False): + """poll.""" + + if not self.popen: + return None + result = self.raw_poll_wait(self.popen, wait) + if self.consumer: + res = result + result = self.raw_poll_wait(self.consumer, wait) + if result is not None and res is None: + self.popen.kill() + result = None + elif result == 0 and res is not None and res != 0: + result = res + if result is not None: + self.tidy_up() + return result + + def wait(self): + """wait.""" + + return self.poll(wait=True) + + def kill(self): + """kill.""" + + if not self.popen: + return + self.popen.kill() + if self.consumer: + self.consumer.kill() + + +def kill_work(worklist, _verbosity): + """kill_work.""" + + for w in worklist: + w.kill() + for w in worklist: + w.wait() + + +def number_of_cp_us(): + """number_of_cp_us.""" + + return os.sysconf("SC_NPROCESSORS_ONLN") + + +def nano_secs_to_secs_str(x): + """nano_secs_to_secs_str.""" + + if x is None: + return "" + x = str(x) + if len(x) < 10: + x = "0" * (10 - len(x)) + x + return x[:len(x) - 9] + "." + x[-9:] + + +def insert_option_after(cmd, option, after): + """insert_option_after.""" + + try: + pos = cmd.index(after) + cmd.insert(pos + 1, option) + except (OSError, ValueError, RuntimeError): + cmd.append(option) + + +def create_work_list(cmd, pipe_to, output_dir, cpus, time_ranges_by_cpu): + """create_work_list.""" + + max_len = len(str(cpus[-1])) + cpu_dir_fmt = f"cpu-%.{max_len}u" + worklist = [] + pos = 0 + for cpu in cpus: + if cpu >= 0: + cpu_dir = os.path.join(output_dir, cpu_dir_fmt % cpu) + cpu_option = f"--cpu={cpu}" + else: + cpu_dir = output_dir + cpu_option = None + + tr_dir_fmt = "time-range" + + if len(time_ranges_by_cpu) > 1: + time_ranges = time_ranges_by_cpu[pos] + tr_dir_fmt += f"-{pos}" + pos += 1 + else: + time_ranges = time_ranges_by_cpu[0] + + max_len = len(str(len(time_ranges))) + tr_dir_fmt += f"-%.{max_len}u" + + i = 0 + for r in time_ranges: + if r == [None, None]: + time_option = None + work_output_dir = cpu_dir + else: + time_option = "--time=" + \ + nano_secs_to_secs_str(r[0]) + "," + \ + nano_secs_to_secs_str(r[1]) + work_output_dir = os.path.join(cpu_dir, tr_dir_fmt % i) + i += 1 + work_cmd = list(cmd) + if time_option is not None: + insert_option_after(work_cmd, time_option, "script") + if cpu_option is not None: + insert_option_after(work_cmd, cpu_option, "script") + w = Work(work_cmd, pipe_to, work_output_dir) + worklist.append(w) + return worklist + + +def do_run_work(worklist: list[Work], nr_jobs: int, + _verbosity: Verbosity) -> bool: + """do_run_work.""" + + nr_to_do = len(worklist) + not_started = list(worklist) + running: list[Work] = [] + done: list[Work] = [] + chg = False + while True: + nr_done = len(done) + if chg and _verbosity.normal: + nr_run = len(running) + print( + f"\rThere are {nr_to_do} jobs: {nr_done} completed, {nr_run} running", + flush=True, end=" ") + if _verbosity.verbose: + print() + chg = False + if nr_done == nr_to_do: + break + while len(running) < nr_jobs and len(not_started): + w = not_started.pop(0) + running.append(w) + if _verbosity.verbose: + print("Starting:", w.command()) + w.start() + chg = True + if len(running): + time.sleep(0.1) + finished: list[Work] = [] + not_finished: list[Work] = [] + while len(running): + w = running.pop(0) + r = w.poll() + if r is None: + not_finished.append(w) + continue + if r == 0: + if _verbosity.verbose: + print("Finished:", w.command()) + finished.append(w) + chg = True + continue + if _verbosity.normal and not _verbosity.verbose: + print() + print("Job failed!\n return code:", r, + "\n command: ", w.command()) + if w.pipe_to: + print(" piped to: ", w.pipe_to) + print("Killing outstanding jobs") + kill_work(not_finished, _verbosity) + kill_work(running, _verbosity) + return False + running = not_finished + done += finished + errorlist: list[str] = [] + for w in worklist: + errorlist += w.errors() + if len(errorlist): + print("errors:") + for e in errorlist: + print(e) + elif _verbosity.normal: + print("\r", " "*50, "\rAll jobs finished successfully", flush=True) + return True + + +def run_work(worklist: list[Work], nr_jobs: int = number_of_cp_us(), + _verbosity: Verbosity = Verbosity()) -> bool: + """run_work.""" + try: + return do_run_work(worklist, nr_jobs, _verbosity) + except BaseException: + for w in worklist: + w.kill() + raise + + +def read_header(perf, file_name): + """read_header.""" + + cmd = [perf, "script", "--header-only", "--input", file_name] + with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: + out = proc.stdout.read() if proc.stdout else b"" + return out.decode("utf-8") + + +def parse_header(hdr): + """parse_header.""" + + result = {} + lines = hdr.split("\n") + for line in lines: + if ":" in line and line[0] == "#": + pos = line.index(":") + name = line[1:pos-1].strip() + value = line[pos+1:].strip() + if name in result: + orig_name = name + nr = 2 + while True: + name = f"{orig_name} {nr}" + if name not in result: + break + nr += 1 + result[name] = value + return result + + +def header_field(hdr_dict, hdr_fld): + """header_field.""" + + if hdr_fld not in hdr_dict: + raise RuntimeError(f"'{hdr_fld}' missing from header information") + return hdr_dict[hdr_fld] + +# Represent the position of an option within a command string +# and provide the option value and/or remove the option + + +class OptPos(): + + def init(self, opt_element=-1, value_element=-1, opt_pos=-1, value_pos=-1, error=None): + """init.""" + + self.opt_element = opt_element # list element that contains option + self.value_element = value_element # list element that contains option value + self.opt_pos = opt_pos # string position of option + self.value_pos = value_pos # string position of value + self.error = error # error message string + + def __init__(self, args, short_name, long_name, default=None): + """__init__.""" + + self.args = list(args) + self.default = default + n = 2 + len(long_name) + m = len(short_name) + pos = -1 + for opt in args: + pos += 1 + if m and opt[:2] == f"-{short_name}": + if len(opt) == 2: + if pos + 1 < len(args): + self.init(pos, pos + 1, 0, 0) + else: + self.init(error=f"-{short_name} option missing value") + else: + self.init(pos, pos, 0, 2) + return + if opt[:n] == f"--{long_name}": + if len(opt) == n: + if pos + 1 < len(args): + self.init(pos, pos + 1, 0, 0) + else: + self.init(error=f"--{long_name} option missing value") + elif opt[n] == "=": + self.init(pos, pos, 0, n + 1) + else: + self.init(error=f"--{long_name} option expected '='") + return + if m and opt[:1] == "-" and opt[:2] != "--" and short_name in opt: + ipos = opt.index(short_name) + if "-" in opt[1:]: + hpos = opt[1:].index("-") + if hpos < ipos: + continue + if ipos + 1 == len(opt): + if pos + 1 < len(args): + self.init(pos, pos + 1, ipos, 0) + else: + self.init(error=f"-{short_name} option missing value") + else: + self.init(pos, pos, ipos, ipos + 1) + return + self.init() + + def value(self): + """value.""" + + if self.opt_element >= 0: + if self.opt_element != self.value_element: + return self.args[self.value_element] + else: + return self.args[self.value_element][self.value_pos:] + return self.default + + def remove(self, args): + """remove.""" + + if self.opt_element == -1: + return + if self.opt_element != self.value_element: + del args[self.value_element] + if self.opt_pos: + args[self.opt_element] = args[self.opt_element][:self.opt_pos] + else: + del args[self.opt_element] + + +def determine_input_file_name(cmd): + """determine_input_file_name.""" + + p = OptPos(cmd, "i", "input", "perf.data") + if p.error: + raise RuntimeError(f"perf command {p.error}") + file_name = p.value() + if not os.path.exists(file_name): + raise RuntimeError(f"perf command input file '{file_name}' not found") + return file_name + + +def read_option(args, short_name, long_name, err_prefix, remove=False): + """read_option.""" + + p = OptPos(args, short_name, long_name) + if p.error: + raise RuntimeError(f"{err_prefix}{p.error}") + value = p.value() + if remove: + p.remove(args) + return value + + +def extract_option(args, short_name, long_name, err_prefix): + """extract_option.""" + + return read_option(args, short_name, long_name, err_prefix, True) + + +def read_perf_option(args, short_name, long_name): + """read_perf_option.""" + + return read_option(args, short_name, long_name, "perf command ") + + +def extract_perf_option(args, short_name, long_name): + """extract_perf_option.""" + + return extract_option(args, short_name, long_name, "perf command ") + + +def perf_double_quick_commands(cmd, file_name): + """perf_double_quick_commands.""" + + cpu_str = read_perf_option(cmd, "C", "cpu") + time_str = read_perf_option(cmd, "", "time") + # Use double-quick sampling to determine trace data density + times_cmd = ["perf", "script", "--ns", + "--input", file_name, "--itrace=qqi"] + if cpu_str is not None and cpu_str != "": + times_cmd.append(f"--cpu={cpu_str}") + if time_str is not None and time_str != "": + times_cmd.append(f"--time={time_str}") + cnts_cmd = list(times_cmd) + cnts_cmd.append("-Fcpu") + times_cmd.append("-Fcpu,time") + return cnts_cmd, times_cmd + + +class CPUTimeRange(): + def __init__(self, cpu): + """__init__.""" + + self.cpu = cpu + self.sample_cnt = 0 + self.time_ranges = None + self.interval = 0 + self.interval_remaining = 0 + self.remaining = 0 + self.tr_pos = 0 + + +def calc_time_ranges_by_cpu(line, cpu, cpu_time_ranges, max_time): + """calc_time_ranges_by_cpu.""" + + cpu_time_range = cpu_time_ranges[cpu] + cpu_time_range.remaining -= 1 + cpu_time_range.interval_remaining -= 1 + if cpu_time_range.remaining == 0: + cpu_time_range.time_ranges[cpu_time_range.tr_pos][1] = max_time + return + if cpu_time_range.interval_remaining == 0: + ts = time_val(line[1][:-1], 0) + time_ranges = cpu_time_range.time_ranges + time_ranges[cpu_time_range.tr_pos][1] = ts - 1 + time_ranges.append([ts, max_time]) + cpu_time_range.tr_pos += 1 + cpu_time_range.interval_remaining = cpu_time_range.interval + + +def count_samples_by_cpu(_line: list[str], cpu: int, + cpu_time_ranges: list[Any]) -> None: + """count_samples_by_cpu.""" + + try: + cpu_time_ranges[cpu].sample_cnt += 1 + except (OSError, ValueError, RuntimeError, IndexError): + print("exception") + print("cpu", cpu) + print("len(cpu_time_ranges)", len(cpu_time_ranges)) + raise + + +def process_command_output_lines(cmd, per_cpu, fn, *x): + """process_command_output_lines.""" + + # Assume CPU number is at beginning of line and enclosed by [] + pat = re.compile(r"\s*\[[0-9]+\]") + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while True: + line = p.stdout.readline() + if line: + line = line.decode("utf-8") + if pat.match(line): + line = line.split() + if per_cpu: + # Assumes CPU number is enclosed by [] + cpu = int(line[0][1:-1]) + else: + cpu = 0 + fn(line, cpu, *x) + else: + break + p.wait() + + +def intersect_time_ranges(new_time_ranges, time_ranges): + """intersect_time_ranges.""" + + pos = 0 + new_pos = 0 + # Can assume len(time_ranges) != 0 and len(new_time_ranges) != 0 + # Note also, there *must* be at least one intersection. + while pos < len(time_ranges) and new_pos < len(new_time_ranges): + # new end < old start => no intersection, remove new + if new_time_ranges[new_pos][1] < time_ranges[pos][0]: + del new_time_ranges[new_pos] + continue + # new start > old end => no intersection, check next + if new_time_ranges[new_pos][0] > time_ranges[pos][1]: + pos += 1 + if pos < len(time_ranges): + continue + # no next, so remove remaining + while new_pos < len(new_time_ranges): + del new_time_ranges[new_pos] + return + # Found an intersection + # new start < old start => adjust new start = old start + if new_time_ranges[new_pos][0] < time_ranges[pos][0]: + new_time_ranges[new_pos][0] = time_ranges[pos][0] + # new end > old end => keep the overlap, insert the remainder + if new_time_ranges[new_pos][1] > time_ranges[pos][1]: + r = [time_ranges[pos][1] + 1, new_time_ranges[new_pos][1]] + new_time_ranges[new_pos][1] = time_ranges[pos][1] + new_pos += 1 + new_time_ranges.insert(new_pos, r) + continue + # new [start, end] is within old [start, end] + new_pos += 1 + + +def split_time_ranges_by_trace_data_density( + time_ranges, cpus, nr, cmd, file_name, per_cpu, min_size, min_interval, _verbosity +): + """split_time_ranges_by_trace_data_density.""" + + if _verbosity.normal: + print("\rAnalyzing...", flush=True, end=" ") + if _verbosity.verbose: + print() + cnts_cmd, times_cmd = perf_double_quick_commands(cmd, file_name) + + nr_cpus = cpus[-1] + 1 if per_cpu else 1 + if per_cpu: + nr_cpus = cpus[-1] + 1 + cpu_time_ranges = [CPUTimeRange(cpu) for cpu in range(nr_cpus)] + else: + nr_cpus = 1 + cpu_time_ranges = [CPUTimeRange(-1)] + + if _verbosity.debug: + print("nr_cpus", nr_cpus) + print("cnts_cmd", cnts_cmd) + print("times_cmd", times_cmd) + + # Count the number of "double quick" samples per CPU + process_command_output_lines( + cnts_cmd, per_cpu, count_samples_by_cpu, cpu_time_ranges) + + tot = 0 + mx = 0 + for cpu_time_range in cpu_time_ranges: + cnt = cpu_time_range.sample_cnt + tot += cnt + if cnt > mx: + mx = cnt + if _verbosity.debug: + print("cpu:", cpu_time_range.cpu, "sample_cnt", cnt) + + if min_size < 1: + min_size = 1 + + if mx < min_size: + # Too little data to be worth splitting + if _verbosity.debug: + print("Too little data to split by time") + if nr == 0: + nr = 1 + return [split_time_ranges_into_n(time_ranges, nr, min_interval)] + + if nr: + divisor = nr + min_size = 1 + else: + divisor = number_of_cp_us() + + interval = int(round(tot / divisor, 0)) + if interval < min_size: + interval = min_size + + if _verbosity.debug: + print("divisor", divisor) + print("min_size", min_size) + print("interval", interval) + + min_time = time_ranges[0][0] + max_time = time_ranges[-1][1] + + for cpu_time_range in cpu_time_ranges: + cnt = cpu_time_range.sample_cnt + if cnt == 0: + cpu_time_range.time_ranges = copy.deepcopy(time_ranges) + continue + # Adjust target interval for CPU to give approximately equal interval sizes + # Determine number of intervals, rounding to nearest integer + n = int(round(cnt / interval, 0)) + if n < 1: + n = 1 + # Determine interval size, rounding up + d, m = divmod(cnt, n) + if m: + d += 1 + cpu_time_range.interval = d + cpu_time_range.interval_remaining = d + cpu_time_range.remaining = cnt + # init. time ranges for each CPU with the start time + cpu_time_range.time_ranges = [[min_time, max_time]] + + # Set time ranges so that the same number of "double quick" samples + # will fall into each time range. + process_command_output_lines( + times_cmd, per_cpu, calc_time_ranges_by_cpu, cpu_time_ranges, max_time) + + for cpu_time_range in cpu_time_ranges: + if cpu_time_range.sample_cnt: + intersect_time_ranges(cpu_time_range.time_ranges, time_ranges) + + return [cpu_time_ranges[cpu].time_ranges for cpu in cpus] + + +def split_single_time_range_into_n(time_range, n): + """split_single_time_range_into_n.""" + + if n <= 1: + return [time_range] + start = time_range[0] + end = time_range[1] + duration = int((end - start + 1) / n) + if duration < 1: + return [time_range] + time_ranges = [] + for _i in range(n): + time_ranges.append([start, start + duration - 1]) + start += duration + time_ranges[-1][1] = end + return time_ranges + + +def time_range_duration(r): + """time_range_duration.""" + + return r[1] - r[0] + 1 + + +def total_duration(time_ranges): + """total_duration.""" + + duration = 0 + for r in time_ranges: + duration += time_range_duration(r) + return duration + + +def split_time_ranges_by_interval(time_ranges, interval): + """split_time_ranges_by_interval.""" + + new_ranges = [] + for r in time_ranges: + duration = time_range_duration(r) + n = duration / interval + n = int(round(n, 0)) + new_ranges += split_single_time_range_into_n(r, n) + return new_ranges + + +def split_time_ranges_into_n(time_ranges, n, min_interval): + """split_time_ranges_into_n.""" + + if n <= len(time_ranges): + return time_ranges + duration = total_duration(time_ranges) + interval = duration / n + if interval < min_interval: + interval = min_interval + return split_time_ranges_by_interval(time_ranges, interval) + + +def recombine_time_ranges(tr): + """recombine_time_ranges.""" + + new_tr = copy.deepcopy(tr) + i = 1 + while i < len(new_tr): + # if prev end + 1 == cur start, combine them + if new_tr[i - 1][1] + 1 == new_tr[i][0]: + new_tr[i][0] = new_tr[i - 1][0] + del new_tr[i - 1] + else: + i += 1 + return new_tr + + +def open_time_range_ends(time_ranges, min_time, max_time): + """open_time_range_ends.""" + + if time_ranges[0][0] <= min_time: + time_ranges[0][0] = None + if time_ranges[-1][1] >= max_time: + time_ranges[-1][1] = None + + +def bad_time_str(time_str): + """bad_time_str.""" + + raise RuntimeError( + f"perf command bad time option: '{time_str}'\n" + "Check also 'time of first sample' and 'time of last sample' " + "in perf script --header-only" + ) + + +def validate_time_ranges(time_ranges, time_str): + """validate_time_ranges.""" + + n = len(time_ranges) + for i in range(n): + start = time_ranges[i][0] + end = time_ranges[i][1] + if i != 0 and start <= time_ranges[i - 1][1]: + bad_time_str(time_str) + if start > end: + bad_time_str(time_str) + + +def time_val(s, dflt): + """time_val.""" + + s = s.strip() + if s == "": + return dflt + a = s.split(".") + if len(a) > 2: + raise RuntimeError(f"Bad time value'{s}'") + x = int(a[0]) + if x < 0: + raise RuntimeError("Negative time not allowed") + x *= 1000000000 + if len(a) > 1: + x += int((a[1] + "000000000")[:9]) + return x + + +def bad_cpu_str(cpu_str): + """bad_cpu_str.""" + + raise RuntimeError( + f"perf command bad cpu option: '{cpu_str}'\n" + "Check also 'nrcpus avail' in perf script --header-only" + ) + + +def parse_time_str(time_str, min_time, max_time): + """parse_time_str.""" + + if time_str is None or time_str == "": + return [[min_time, max_time]] + time_ranges = [] + for r in time_str.split(): + a = r.split(",") + if len(a) != 2: + bad_time_str(time_str) + try: + start = time_val(a[0], min_time) + end = time_val(a[1], max_time) + except (OSError, ValueError, RuntimeError): + bad_time_str(time_str) + time_ranges.append([start, end]) + validate_time_ranges(time_ranges, time_str) + return time_ranges + + +def parse_cpu_str(cpu_str, nr_cpus): + """parse_cpu_str.""" + + if cpu_str is None or cpu_str == "": + return [-1] + cpus = [] + for r in cpu_str.split(","): + a = r.split("-") + if len(a) < 1 or len(a) > 2: + bad_cpu_str(cpu_str) + try: + start = int(a[0].strip()) + if len(a) > 1: + end = int(a[1].strip()) + else: + end = start + except (OSError, ValueError, RuntimeError): + bad_cpu_str(cpu_str) + if start < 0 or end < 0 or end < start or end >= nr_cpus: + bad_cpu_str(cpu_str) + cpus.extend(range(start, end + 1)) + cpus = list(set(cpus)) # remove duplicates + cpus.sort() + return cpus + + +class ParallelPerf(): + + def __init__(self, a): + """init.""" + self.nr = 0 + self.jobs = 0 + self.file_name = None + self.hdr = None + self.hdr_dict = None + self.cmd_line = None + self.min_time = None + self.max_time = None + self.time_str = None + self.time_ranges = None + self.cpu_str = None + self.cpus = None + self.split_time_ranges_for_each_cpu = None + self.worklist = None + self.per_cpu = None + self.interval = None + self.min_size = None + self.min_interval = None + self.pipe_to = None + self.output_dir = None + self.cmd = getattr(a, 'cmd', None) + self.no_per_cpu = getattr(a, 'no_per_cpu', False) + self.dry_run = getattr(a, 'dry_run', False) + self._verbosity = getattr(a, "_verbosity", Verbosity()) + + for arg_name in vars(a): + setattr(self, arg_name, getattr(a, arg_name)) + self.orig_nr = self.nr + self.orig_cmd = list(self.cmd) + self.perf = self.cmd[0] + if os.path.exists(self.output_dir): + raise RuntimeError(f"Output '{self.output_dir}' already exists") + if self.jobs < 0 or self.nr < 0 or self.interval < 0: + raise RuntimeError( + "Bad options (negative values): try -h option for help") + if self.nr != 0 and self.interval != 0: + raise RuntimeError( + "Cannot specify number of time subdivisions and time interval") + if self.jobs == 0: + self.jobs = number_of_cp_us() + if self.nr == 0 and self.interval == 0: + if self.per_cpu: + self.nr = 1 + else: + self.nr = self.jobs + + def init(self): + """init.""" + + if self._verbosity.debug: + print("cmd", self.cmd) + self.file_name = determine_input_file_name(self.cmd) + self.hdr = read_header(self.perf, self.file_name) + self.hdr_dict = parse_header(self.hdr) + self.cmd_line = header_field(self.hdr_dict, "cmdline") + + def extract_time_info(self): + """extract_time_info.""" + + self.min_time = time_val(header_field( + self.hdr_dict, "time of first sample"), 0) + self.max_time = time_val(header_field( + self.hdr_dict, "time of last sample"), 0) + self.time_str = extract_perf_option(self.cmd, "", "time") + self.time_ranges = parse_time_str( + self.time_str, self.min_time, self.max_time) + if self._verbosity.debug: + print("time_ranges", self.time_ranges) + + def extract_cpu_info(self): + """extract_cpu_info.""" + + if self.per_cpu: + nr_cpus = int(header_field(self.hdr_dict, "nrcpus avail")) + self.cpu_str = extract_perf_option(self.cmd, "C", "cpu") + if self.cpu_str is None or self.cpu_str == "": + self.cpus = [x for x in range(nr_cpus)] + else: + self.cpus = parse_cpu_str(self.cpu_str, nr_cpus) + else: + self.cpu_str = None + self.cpus = [-1] + if self._verbosity.debug: + print("cpus", self.cpus) + + def is_intel_pt(self): + """is_intel_pt.""" + + return self.cmd_line.find("intel_pt") >= 0 + + def split_time_ranges(self): + """split_time_ranges.""" + + if self.is_intel_pt() and self.interval == 0: + self.split_time_ranges_for_each_cpu = ( + split_time_ranges_by_trace_data_density( + self.time_ranges, self.cpus, self.orig_nr, + self.orig_cmd, self.file_name, self.per_cpu, + self.min_size, self.min_interval, self._verbosity + ) + ) + elif self.nr: + self.split_time_ranges_for_each_cpu = [split_time_ranges_into_n( + self.time_ranges, self.nr, self.min_interval)] + else: + self.split_time_ranges_for_each_cpu = [ + split_time_ranges_by_interval(self.time_ranges, self.interval)] + + def check_time_ranges(self): + """check_time_ranges.""" + + for tr in self.split_time_ranges_for_each_cpu: + # Re-combined time ranges should be the same + new_tr = recombine_time_ranges(tr) + if new_tr != self.time_ranges: + if self._verbosity.debug: + print("tr", tr) + print("new_tr", new_tr) + raise RuntimeError("Self test failed!") + + def open_time_range_ends(self): + """open_time_range_ends.""" + + for time_ranges in self.split_time_ranges_for_each_cpu: + open_time_range_ends(time_ranges, self.min_time, self.max_time) + + def create_work_list(self): + """create_work_list.""" + + self.worklist = create_work_list( + self.cmd, self.pipe_to, self.output_dir, self.cpus, self.split_time_ranges_for_each_cpu) + + def perf_data_recorded_per_cpu(self): + """perf_data_recorded_per_cpu.""" + + if "--per-thread" in self.cmd_line.split(): + return False + return True + + def default_to_per_cpu(self): + """default_to_per_cpu.""" + + # --no-per-cpu option takes precedence + if self.no_per_cpu: + return False + if not self.perf_data_recorded_per_cpu(): + return False + # Default to per-cpu for Intel PT data that was recorded per-cpu, + # because decoding can be done for each CPU separately. + if self.is_intel_pt(): + return True + return False + + def config(self): + """config.""" + + self.init() + self.extract_time_info() + if not self.per_cpu: + self.per_cpu = self.default_to_per_cpu() + if self._verbosity.debug: + print("per_cpu", self.per_cpu) + self.extract_cpu_info() + self.split_time_ranges() + if self._verbosity.self_test: + self.check_time_ranges() + # Prefer open-ended time range to starting / ending with min_time / max_time resp. + self.open_time_range_ends() + self.create_work_list() + + def run(self): + """run.""" + + if self.dry_run: + print(len(self.worklist), "jobs:") + for w in self.worklist: + print(w.command()) + return True + result = run_work(self.worklist, self.jobs, _verbosity=self._verbosity) + if self._verbosity.verbose: + print(glb_prog_name, "done") + return result + + +def run_parallel_perf(a): + """run_parallel_perf.""" + + pp = ParallelPerf(a) + pp.config() + return pp.run() + + +def main(args): + """main.""" + + ap = argparse.ArgumentParser( + prog=glb_prog_name, formatter_class=argparse.RawDescriptionHelpFormatter, + description=""" +run a perf script command multiple times in parallel, using perf script options +--cpu and --time so that each job processes a different chunk of the data. +""", + epilog=""" +Follow the options by '--' and then the perf script command e.g. + + $ perf record -a -- sleep 10 + $ parallel-perf.py --nr=4 -- perf script --ns + All jobs finished successfully + $ tree parallel-perf-output/ + parallel-perf-output/ + ├── time-range-0 + │   ├── cmd.txt + │   └── out.txt + ├── time-range-1 + │   ├── cmd.txt + │   └── out.txt + ├── time-range-2 + │   ├── cmd.txt + │   └── out.txt + └── time-range-3 + ├── cmd.txt + └── out.txt + $ find parallel-perf-output -name cmd.txt | sort | xargs grep -H . + parallel-perf-output/time-range-0/cmd.txt:perf script --time=,9466.504461499 --ns + parallel-perf-output/time-range-1/cmd.txt:perf script --time=9466.504461500,9469.005396999 --ns + parallel-perf-output/time-range-2/cmd.txt:perf script --time=9469.005397000,9471.506332499 --ns + parallel-perf-output/time-range-3/cmd.txt:perf script --time=9471.506332500, --ns + +Any perf script command can be used, including the use of perf script options +--dlfilter and --script, so that the benefit of running parallel jobs +naturally extends to them also. + +If option --pipe-to is used, standard output is first piped through that +command. Beware, if the command fails (e.g. grep with no matches), it will be +considered a fatal error. + +Final standard output is redirected to files named out.txt in separate +subdirectories under the output directory. Similarly, standard error is +written to files named err.txt. In addition, files named cmd.txt contain the +corresponding perf script command. After processing, err.txt files are removed +if they are empty. + +If any job exits with a non-zero exit code, then all jobs are killed and no +more are started. A message is printed if any job results in a non-empty +err.txt file. + +There is a separate output subdirectory for each time range. If the --per-cpu +option is used, these are further grouped under cpu-n subdirectories, e.g. + + $ parallel-perf.py --per-cpu --nr=2 -- perf script --ns --cpu=0,1 + All jobs finished successfully + $ tree parallel-perf-output + parallel-perf-output/ + ├── cpu-0 + │   ├── time-range-0 + │   │   ├── cmd.txt + │   │   └── out.txt + │   └── time-range-1 + │   ├── cmd.txt + │   └── out.txt + └── cpu-1 + ├── time-range-0 + │   ├── cmd.txt + │   └── out.txt + └── time-range-1 + ├── cmd.txt + └── out.txt + $ find parallel-perf-output -name cmd.txt | sort | xargs grep -H . + parallel-perf-output/cpu-0/time-range-0/cmd.txt:perf script --cpu=0 --time=,9469.005396999 --ns + parallel-perf-output/cpu-0/time-range-1/cmd.txt:perf script --cpu=0 --time=9469.005397000, --ns + parallel-perf-output/cpu-1/time-range-0/cmd.txt:perf script --cpu=1 --time=,9469.005396999 --ns + parallel-perf-output/cpu-1/time-range-1/cmd.txt:perf script --cpu=1 --time=9469.005397000, --ns + +Subdivisions of time range, and cpus if the --per-cpu option is used, are +expressed by the --time and --cpu perf script options respectively. If the +supplied perf script command has a --time option, then that time range is +subdivided, otherwise the time range given by 'time of first sample' to +'time of last sample' is used (refer perf script --header-only). Similarly, the +supplied perf script command may provide a --cpu option, and only those CPUs +will be processed. + +To prevent time intervals becoming too small, the --min-interval option can +be used. + +Note there is special handling for processing Intel PT traces. If an interval is +not specified and the perf record command contained the intel_pt event, then the +time range will be subdivided in order to produce subdivisions that contain +approximately the same amount of trace data. That is accomplished by counting +double-quick (--itrace=qqi) samples, and choosing time ranges that encompass +approximately the same number of samples. In that case, time ranges may not be +the same for each CPU processed. For Intel PT, --per-cpu is the default, but +that can be overridden by --no-per-cpu. Note, for Intel PT, double-quick +decoding produces 1 sample for each PSB synchronization packet, which in turn +come after a certain number of bytes output, determined by psb_period (refer +perf Intel PT documentation). The minimum number of double-quick samples that +will define a time range can be set by the --min_size option, which defaults to +64. +""") + ap.add_argument("-o", "--output-dir", default="parallel-perf-output", + help="output directory (default 'parallel-perf-output')") + ap.add_argument("-j", "--jobs", type=int, default=0, + help="maximum number of jobs to run in parallel at one time " + "(default is the number of CPUs)") + ap.add_argument("-n", "--nr", type=int, default=0, + help="number of time subdivisions (default is the number of jobs)") + ap.add_argument("-i", "--interval", type=float, default=0, + help="subdivide the time range using this time interval " + "(in seconds e.g. 0.1 for a tenth of a second)") + ap.add_argument("-c", "--per-cpu", action="store_true", + help="process data for each CPU in parallel") + ap.add_argument("-m", "--min-interval", type=float, default=glb_min_interval, + help=f"minimum interval (default {glb_min_interval} seconds)") + ap.add_argument("-p", "--pipe-to", + help="command to pipe output to (optional)") + ap.add_argument("-N", "--no-per-cpu", action="store_true", + help="do not process data for each CPU in parallel") + ap.add_argument("-b", "--min_size", type=int, default=glb_min_samples, + help="minimum data size (for Intel PT in PSBs)") + ap.add_argument("-D", "--dry-run", action="store_true", + help="do not run any jobs, just show the perf script commands") + ap.add_argument("-q", "--quiet", action="store_true", + help="do not print any messages except errors") + ap.add_argument("-v", "--verbose", action="store_true", + help="print more messages") + ap.add_argument("-d", "--debug", action="store_true", + help="print debugging messages") + cmd_line = list(args) + try: + split_pos = cmd_line.index("--") + cmd = cmd_line[split_pos + 1:] + args = cmd_line[:split_pos] + except (OSError, ValueError, RuntimeError): + cmd = None + args = cmd_line + a = ap.parse_args(args=args[1:]) + a.cmd = cmd + setattr(a, "_verbosity", Verbosity(a.quiet, a.verbose, a.debug)) + try: + if not a.cmd: + if a.cmd is None and len(args) <= 1: + ap.print_help() + return True + raise RuntimeError( + "command line must contain '--' before perf command") + return run_parallel_perf(a) + except (OSError, ValueError, RuntimeError, KeyboardInterrupt) as e: + print("Fatal error: ", str(e)) + if a.debug: + raise + return False + + +if __name__ == "__main__": + if not main(sys.argv): + sys.exit(1) diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi index 58b0b3ed819d..087da00728fd 100644 --- a/tools/perf/python/perf.pyi +++ b/tools/perf/python/perf.pyi @@ -20,7 +20,7 @@ def metrics() -> List[Dict[str, Union[str, List[str]]]]: """ ... -def syscall_name(id: int, *, elf_machine: Optional[int] = None) -> str: +def syscall_name(id: int, elf_machine: Optional[int] = None) -> Optional[str]: """Convert a syscall number to its name. Args: @@ -32,7 +32,7 @@ def syscall_name(id: int, *, elf_machine: Optional[int] = None) -> str: """ ... -def syscall_id(name: str, *, elf_machine: Optional[int] = None) -> int: +def syscall_id(name: str, elf_machine: Optional[int] = None) -> int: """Convert a syscall name to its number. Args: @@ -44,6 +44,18 @@ def syscall_id(name: str, *, elf_machine: Optional[int] = None) -> int: """ ... +def arch_strerrno(err: int, elf_machine: Optional[int] = None) -> Optional[str]: + """Convert an errno number to its symbolic name. + + Args: + err: The errno number (positive or negative). + elf_machine: Optional ELF machine type. + + Returns: + The symbolic name of the errno, or None if unknown. + """ + ... + def parse_events( event_string: str, cpus: Optional[cpu_map] = None, @@ -84,7 +96,7 @@ class data: class thread: """Represents a thread in the system.""" - def comm(self) -> str: + def comm(self) -> Optional[str]: """Get the command name of the thread.""" ... pid: int @@ -153,7 +165,13 @@ class evsel: def __str__(self) -> str: """Return string representation of the event.""" ... - def open(self) -> None: + def open( + self, + cpus: Optional['cpu_map'] = None, + threads: Optional[thread_map] = None, + group: bool = False, + inherit: bool = False, + ) -> None: """Open the event selector file descriptor table.""" ... def read(self, cpu: int, thread: int) -> counts_values: @@ -182,6 +200,8 @@ class _sample_members: sample_time: int sample_id: int sample_stream_id: int + machine_pid: int + vcpu: int sample_period: int sample_cpu: int @@ -197,19 +217,27 @@ class sample_event(_sample_members): sample_cyc_count: int type: int raw_buf: bytes - dso: str - dso_long_name: str - dso_bid: Optional[bytes] - map_start: int - map_end: int - map_pgoff: int - symbol: str - sym_start: int - sym_end: int + dso: Optional[str] + dso_long_name: Optional[str] + dso_bid: Optional[str] + map_start: Optional[int] + map_end: Optional[int] + map_pgoff: Optional[int] + symbol: Optional[str] + sym_start: Optional[int] + sym_end: Optional[int] + sym_offset: Optional[int] + addr_dso: Optional[str] + addr_symbol: Optional[str] + addr_sym_offset: Optional[int] + branch_type: int + in_tx: int + flags: int + transaction: int brstack: Optional['branch_stack'] callchain: Optional['callchain'] - def srccode(self) -> str: ... - def insn(self) -> str: ... + def srccode(self, addr: int = ..., /) -> Optional[tuple[Optional[str], int, Optional[str]]]: ... + def insn(self) -> Optional[bytes]: ... def __getattr__(self, name: str) -> Any: ... class mmap_event(_sample_members): @@ -286,8 +314,9 @@ class read_event(_sample_members): class switch_event(_sample_members): """Represents a SWITCH or SWITCH_CPU_WIDE record.""" type: int - next_prev_pid: int - next_prev_tid: int + misc: int + next_prev_pid: Optional[int] + next_prev_tid: Optional[int] evsel: Optional['evsel'] class branch_entry: @@ -316,17 +345,20 @@ class branch_stack: """Sequence of branch entries in the branch stack.""" def __len__(self) -> int: ... def __getitem__(self, index: int) -> branch_entry: ... + def __iter__(self) -> Iterator[branch_entry]: ... class callchain_node: """Represents a frame in the callchain.""" ip: int - symbol: Optional[str] - dso: Optional[str] + symbol: str + dso: str + sym_offset: Optional[int] class callchain: """Sequence of callchain frames.""" def __len__(self) -> int: ... def __getitem__(self, index: int) -> callchain_node: ... + def __iter__(self) -> Iterator[callchain_node]: ... class stat_event(_sample_members): """Represents a stat event from perf.""" @@ -362,10 +394,10 @@ class evlist: def close(self) -> None: """Close the events in the list.""" ... - def mmap(self) -> None: + def mmap(self, pages: int = 128, overwrite: bool = False) -> None: """Memory map the event buffers.""" ... - def poll(self, timeout: int) -> int: + def poll(self, timeout: int = -1) -> int: """Poll for events. Args: @@ -375,7 +407,7 @@ class evlist: Number of events ready. """ ... - def read_on_cpu(self, cpu: int) -> Optional[Any]: + def read_on_cpu(self, cpu: int, sample_id_all: bool = True) -> Optional[Any]: """Read a sample event from a specific CPU. Args: @@ -412,23 +444,50 @@ class evlist: def enable(self) -> None: """Enable all events in the list.""" ... - def get_pollfd(self) -> List[int]: + def get_pollfd(self) -> List[Any]: """Get a list of file descriptors for polling.""" ... - def add(self, evsel: evsel) -> int: + def add(self, evsel: evsel, /) -> int: """Add an event to the list.""" ... + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> evsel: ... def __iter__(self) -> Iterator[evsel]: """Iterate over the events (evsel) in the list.""" ... +class call_return: + """Represents a call/return event from instruction trace decoding.""" + db_id: int + parent_id: int + insn_count: int + cyc_count: int + comm: Optional[str] + machine_pid: int + pid: Optional[int] + tid: Optional[int] + call_time: int + return_time: int + branch_count: int + call_ref: int + return_ref: int + flags: int + call_path: Optional[callchain] + + class session: def __init__( self, data: data, sample: Optional[Callable[[sample_event], None]] = None, - stat: Optional[Callable[[Any, Optional[str]], None]] = None + stat: Optional[Callable[[Any, Optional[str]], None]] = None, + context_switch: Optional[Callable[[switch_event], None]] = None, + call_return: Optional[Callable[[call_return], None]] = None, + itrace: Optional[str] = None, + vmlinux: Optional[str] = None, + kallsyms: Optional[str] = None, + symfs: Optional[str] = None ) -> None: """Initialize a perf session. @@ -438,10 +497,12 @@ class session: stat: Callback for stat events. """ ... + e_machine: int + is_64_bit: bool def process_events(self) -> None: """Process all events in the session.""" ... - def find_thread(self, pid: int) -> thread: + def find_thread(self, pid: int, tid: int = -1, /) -> Optional[thread]: """Returns the thread associated with a pid.""" ... @@ -670,3 +731,12 @@ RECORD_STAT_ROUND: int RECORD_MISC_SWITCH_OUT: int """MISC_SWITCH_OUT record.""" + +CALL_RETURN_NO_CALL: int +"""'return' but no matching 'call'.""" + +CALL_RETURN_NO_RETURN: int +"""'call' but no matching 'return'.""" + +CALL_RETURN_NON_CALL: int +"""A branch but not a 'call' to the start of a different symbol.""" diff --git a/tools/perf/python/perf_live.py b/tools/perf/python/perf_live.py index 616b5f657463..99e73d58a96f 100755 --- a/tools/perf/python/perf_live.py +++ b/tools/perf/python/perf_live.py @@ -33,10 +33,16 @@ class LiveSession: # Poll for events with 100ms timeout try: self.evlist.poll(100) + # CPython's PyErr_SetFromErrno() invokes PyErr_CheckSignals() on + # EINTR, converting SIGINT into KeyboardInterrupt, which inherits + # from BaseException rather than InterruptedError/OSError, so + # Ctrl-C exits the loop and triggers finally: self.evlist.close() + # while non-fatal signals retry poll(). except InterruptedError: continue for cpu in self.cpus: - for _ in range(1000): # Limit to 1000 events per CPU per poll to prevent starvation + # Limit to 1000 events per CPU per poll to prevent starvation + for _ in range(1000): try: event = self.evlist.read_on_cpu(cpu) except TypeError as e: @@ -53,7 +59,5 @@ class LiveSession: if event.type == perf.RECORD_SAMPLE: self.sample_callback(event) - except KeyboardInterrupt: - pass finally: self.evlist.close() diff --git a/tools/perf/scripts/python/powerpc-hcalls.py b/tools/perf/python/powerpc-hcalls.py old mode 100644 new mode 100755 similarity index 54% rename from tools/perf/scripts/python/powerpc-hcalls.py rename to tools/perf/python/powerpc-hcalls.py index fedce1b68cad..ab5867c05d3e --- a/tools/perf/scripts/python/powerpc-hcalls.py +++ b/tools/perf/python/powerpc-hcalls.py @@ -1,40 +1,20 @@ +#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0+ -# -# Copyright (C) 2018 Ravi Bangoria, IBM Corporation -# -# Hypervisor call statisics +""" +Hypervisor call statistics -from __future__ import print_function +Copyright (C) 2018 Ravi Bangoria, IBM Corporation +Ported from tools/perf/scripts/python/powerpc-hcalls.py +""" +from __future__ import annotations -import os +import argparse +from collections import defaultdict import sys +import perf -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import * - -# output: { -# opcode: { -# 'min': minimum time nsec -# 'max': maximum time nsec -# 'time': average time nsec -# 'cnt': counter -# } ... -# } -output = {} -sort_key = 'count' - -# d_enter: { -# cpu: { -# opcode: nsec -# } ... -# } -d_enter = {} - -hcall_table = { +# Hypervisor call table +HCALL_TABLE = { 4: 'H_REMOVE', 8: 'H_ENTER', 12: 'H_READ', @@ -240,96 +220,115 @@ hcall_table = { 63500: 'H_COPY_TOFROM_GUEST', } -def hcall_table_lookup(opcode): - if (opcode in hcall_table): - return hcall_table[opcode] - else: - return opcode -print_ptrn = '%-28s%10s%10s%10s%10s' +class HCallAnalyzer: + """Analyzes hypervisor calls and aggregates statistics.""" -def sort_output(opcode): - stats = output[opcode] + def __init__(self, cmd_args) -> None: + self.args = cmd_args + # output: {opcode: {'min': min, 'max': max, 'time': time, 'cnt': cnt}} + self.output: dict[int, dict[str, 'int | float']] = \ + defaultdict(lambda: {'time': 0, 'cnt': 0, 'min': float('inf'), 'max': 0}) + # d_enter: {cpu: {opcode: nsec}} + self.d_enter: dict[int, dict[int, int]] = {} + self.print_ptrn = '%-28s%10s%10s%10s%10s' - if sort_key == 'min': - return stats['min'] - if sort_key == 'max': - return stats['max'] - if sort_key == 'avg': - return stats['time'] // stats['cnt'] + def hcall_table_lookup(self, opcode: int) -> str: + """Lookup hcall name by opcode.""" + return HCALL_TABLE.get(opcode, str(opcode)) - return stats['cnt'] + def process_event(self, sample: perf.sample_event) -> None: + """Process a single sample event.""" + name = str(sample.evsel) + if name not in ("evsel(powerpc:hcall_entry)", "evsel(powerpc:hcall_exit)"): + return -def trace_begin(): - global sort_key + try: + cpu = sample.sample_cpu + opcode = sample.opcode + except AttributeError: + print("ERROR: tracepoint fields missed (is libtraceevent enabled?)", + file=sys.stderr) + sys.exit(1) - valid_sort_keys = ['count', 'min', 'max', 'avg'] + # sample.sample_time represents the nsecs + time = sample.sample_time - i = 1 - while i < len(sys.argv): - arg = sys.argv[i] + if opcode < 0 or cpu < 0: + return - if arg == '-s' or arg == '--sort': - if i + 1 >= len(sys.argv): - print("Error: -s/--sort requires a sort key argument") - sys.exit(1) - sort_key = sys.argv[i + 1] - i += 2 - continue + if name == "evsel(powerpc:hcall_entry)": + if cpu not in self.d_enter: + self.d_enter[cpu] = {} + self.d_enter[cpu][opcode] = time + elif name == "evsel(powerpc:hcall_exit)": + if cpu in self.d_enter and opcode in self.d_enter[cpu]: + time_entry = self.d_enter[cpu][opcode] + diff = time - time_entry + del self.d_enter[cpu][opcode] - if arg.startswith('--sort='): - sort_key = arg.split('=', 1)[1] - i += 1 - continue + stats = self.output[opcode] + stats['time'] += diff + stats['cnt'] += 1 + if diff < stats['min']: + stats['min'] = diff + if diff > stats['max']: + stats['max'] = diff - i += 1 + def print_summary(self) -> None: + """Print aggregated statistics.""" + print(self.print_ptrn % ('hcall', 'count', 'min(ns)', 'max(ns)', 'avg(ns)')) + print('-' * 68) - if sort_key not in valid_sort_keys: - print(f"Error: Invalid sort key '{sort_key}'. Valid options are: {', '.join(valid_sort_keys)}") - sys.exit(1) + def sort_output(opcode): + stats = self.output[opcode] + sort_by = getattr(self.args, 'sort', None) + if sort_by == 'min': + return stats['min'] + if sort_by == 'max': + return stats['max'] + if sort_by == 'avg': + return stats['time'] // stats['cnt'] + return stats['cnt'] - print("SORT KEY =", sort_key) + for opcode in sorted(self.output.keys(), key=sort_output, reverse=True): + h_name = self.hcall_table_lookup(opcode) + stats = self.output[opcode] + time = stats['time'] + cnt = stats['cnt'] + min_t = stats['min'] + max_t = stats['max'] -def trace_end(): - print(print_ptrn % ('hcall', 'count', 'min(ns)', 'max(ns)', 'avg(ns)')) - print('-' * 68) - for opcode in sorted(output, key = sort_output, - reverse=True): - h_name = hcall_table_lookup(opcode) - time = output[opcode]['time'] - cnt = output[opcode]['cnt'] - min_t = output[opcode]['min'] - max_t = output[opcode]['max'] + # Avoid float representation for large integers if possible, + # or use formatted strings. Legacy used time//cnt. + avg_t = time // cnt if cnt > 0 else 0 - print(print_ptrn % (h_name, cnt, min_t, max_t, time//cnt)) + # If min was not updated, it remains inf, but cnt should be > 0 if in output + if min_t == float('inf'): + min_t = 0 -def powerpc__hcall_exit(name, context, cpu, sec, nsec, pid, comm, callchain, - opcode, retval): - if (cpu in d_enter and opcode in d_enter[cpu]): - diff = nsecs(sec, nsec) - d_enter[cpu][opcode] + print(self.print_ptrn % (h_name, cnt, int(min_t), int(max_t), avg_t)) - if (opcode in output): - output[opcode]['time'] += diff - output[opcode]['cnt'] += 1 - if (output[opcode]['min'] > diff): - output[opcode]['min'] = diff - if (output[opcode]['max'] < diff): - output[opcode]['max'] = diff - else: - output[opcode] = { - 'time': diff, - 'cnt': 1, - 'min': diff, - 'max': diff, - } - del d_enter[cpu][opcode] -# else: -# print("Can't find matching hcall_enter event. Ignoring sample") +if __name__ == "__main__": + ap = argparse.ArgumentParser(description="Hypervisor call statistics") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("-s", "--sort", default="count", + choices=["count", "min", "max", "avg"], help="Sort key") + args = ap.parse_args() -def powerpc__hcall_entry(event_name, context, cpu, sec, nsec, pid, comm, - callchain, opcode): - if (cpu in d_enter): - d_enter[cpu][opcode] = nsecs(sec, nsec) - else: - d_enter[cpu] = {opcode: nsecs(sec, nsec)} + analyzer = HCallAnalyzer(args) + + session = None + try: + session = perf.session(perf.data(args.input), sample=analyzer.process_event) + session.process_events() + analyzer.print_summary() + except KeyboardInterrupt: + analyzer.print_summary() + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError) as e: + print(f"Error processing events: {e}") + sys.exit(1) + finally: + # Break the reference cycle between perf.session and analyzer.process_event. + session = None diff --git a/tools/perf/python/rw-by-file.py b/tools/perf/python/rw-by-file.py new file mode 100755 index 000000000000..b33330261606 --- /dev/null +++ b/tools/perf/python/rw-by-file.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Display r/w activity for files read/written to for a given program.""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import sys +from typing import Optional, Dict +import perf + +class RwByFile: + """Tracks and displays read/write activity by file descriptor.""" + def __init__(self, comm: str) -> None: + self.for_comm = comm + self.reads: Dict[int, Dict[str, int]] = defaultdict( + lambda: {"bytes_requested": 0, "total_reads": 0} + ) + self.writes: Dict[int, Dict[str, int]] = defaultdict( + lambda: {"bytes_written": 0, "total_writes": 0} + ) + self.unhandled: Dict[str, int] = defaultdict(int) + self.session: Optional[perf.session] = None + + def process_event(self, sample: perf.sample_event) -> None: + """Process events.""" + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + event_name = "".join(c if c.isprintable() else "?" for c in event_name) + + pid = sample.sample_pid + assert self.session is not None + try: + thread = self.session.find_thread(pid, sample.sample_tid) + comm = (thread.comm() if thread else None) or "unknown" + except (TypeError, AttributeError): + comm = "unknown" + + if comm != self.for_comm: + return + + if event_name == "syscalls:sys_enter_read": + try: + fd = sample.fd + count = sample.count + self.reads[fd]["bytes_requested"] += count + self.reads[fd]["total_reads"] += 1 + except AttributeError: + self.unhandled[event_name] += 1 + elif event_name == "syscalls:sys_enter_write": + try: + fd = sample.fd + count = sample.count + self.writes[fd]["bytes_written"] += count + self.writes[fd]["total_writes"] += 1 + except AttributeError: + self.unhandled[event_name] += 1 + else: + self.unhandled[event_name] += 1 + + def print_totals(self) -> None: + """Print summary tables.""" + print(f"file read counts for {self.for_comm}:\n") + print(f"{'fd':>6s} {'# reads':>10s} {'bytes_requested':>15s}") + print(f"{'-'*6} {'-'*10} {'-'*15}") + + for fd, data in sorted(self.reads.items(), + key=lambda kv: kv[1]["bytes_requested"], reverse=True): + print(f"{fd:6d} {data['total_reads']:10d} {data['bytes_requested']:15d}") + + print(f"\nfile write counts for {self.for_comm}:\n") + print(f"{'fd':>6s} {'# writes':>10s} {'bytes_written':>15s}") + print(f"{'-'*6} {'-'*10} {'-'*15}") + + for fd, data in sorted(self.writes.items(), + key=lambda kv: kv[1]["bytes_written"], reverse=True): + print(f"{fd:6d} {data['total_writes']:10d} {data['bytes_written']:15d}") + + if self.unhandled: + print("\nunhandled events:\n") + print(f"{'event':<40s} {'count':>10s}") + print(f"{'-'*40} {'-'*10}") + for event_name, count in self.unhandled.items(): + print(f"{event_name:<40s} {count:10d}") + + def run(self, input_file: str) -> None: + """Run the session.""" + self.session = perf.session(perf.data(input_file), sample=self.process_event) + try: + self.session.process_events() + finally: + # Break the reference cycle between perf.session and self.process_event + # because perf.session lacks cyclic GC support (tp_traverse). + self.session = None + self.print_totals() + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Trace r/w activity by file") + parser.add_argument("comm", help="Filter by command name") + parser.add_argument("-i", "--input", default="perf.data", help="Input file") + args = parser.parse_args() + + analyzer = RwByFile(args.comm) + try: + analyzer.run(args.input) + except IOError as e: + print(e, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/rw-by-pid.py b/tools/perf/python/rw-by-pid.py new file mode 100755 index 000000000000..8e2d1dbd9a08 --- /dev/null +++ b/tools/perf/python/rw-by-pid.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Display r/w activity for all processes.""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import sys +from typing import Optional, Dict, List, Tuple, Any +import perf + +class RwByPid: + """Tracks and displays read/write activity by PID.""" + def __init__(self) -> None: + self.reads: Dict[int, Dict[str, Any]] = defaultdict( + lambda: { + "bytes_requested": 0, + "bytes_read": 0, + "total_reads": 0, + "comm": "", + "errors": defaultdict(int), + } + ) + self.writes: Dict[int, Dict[str, Any]] = defaultdict( + lambda: { + "bytes_requested": 0, + "bytes_written": 0, + "total_writes": 0, + "comm": "", + "errors": defaultdict(int), + } + ) + self.unhandled: Dict[str, int] = defaultdict(int) + self.session: Optional[perf.session] = None + + def _handle_sys_enter_read(self, pid: int, comm: str, sample: perf.sample_event) -> None: + try: + count = sample.count + self.reads[pid]["bytes_requested"] += count + self.reads[pid]["total_reads"] += 1 + self.reads[pid]["comm"] = comm + except AttributeError: + self.unhandled["sys_enter_read_attr_err"] += 1 + + def _handle_sys_exit_read(self, pid: int, sample: perf.sample_event, comm: str = "") -> None: + try: + ret = sample.ret + # Convert unsigned 64-bit or 32-bit tracepoint return values to signed integers. + # Because kernel read/write transfers are bounded by MAX_RW_COUNT (0x7ffff000 < 2 GiB), + # 0xfffff000..0xffffffff is always a 32-bit negative errno even on 64-bit hosts. + if ret >= 0x8000000000000000: + ret -= 0x10000000000000000 + elif 0xfffff000 <= ret <= 0xffffffff: + ret -= 0x100000000 + if not self.reads[pid]["comm"] and comm: + self.reads[pid]["comm"] = comm + if ret >= 0: + self.reads[pid]["bytes_read"] += ret + else: + self.reads[pid]["errors"][ret] += 1 + except AttributeError: + self.unhandled["sys_exit_read_attr_err"] += 1 + + def _handle_sys_enter_write(self, pid: int, comm: str, sample: perf.sample_event) -> None: + try: + count = sample.count + self.writes[pid]["bytes_requested"] += count + self.writes[pid]["total_writes"] += 1 + self.writes[pid]["comm"] = comm + except AttributeError: + self.unhandled["sys_enter_write_attr_err"] += 1 + + def _handle_sys_exit_write(self, pid: int, sample: perf.sample_event, comm: str = "") -> None: + try: + ret = sample.ret + # Convert unsigned 64-bit or 32-bit tracepoint return values to signed integers. + # Because kernel read/write transfers are bounded by MAX_RW_COUNT (0x7ffff000 < 2 GiB), + # 0xfffff000..0xffffffff is always a 32-bit negative errno even on 64-bit hosts. + if ret >= 0x8000000000000000: + ret -= 0x10000000000000000 + elif 0xfffff000 <= ret <= 0xffffffff: + ret -= 0x100000000 + if not self.writes[pid]["comm"] and comm: + self.writes[pid]["comm"] = comm + if ret >= 0: + self.writes[pid]["bytes_written"] += ret + else: + self.writes[pid]["errors"][ret] += 1 + except AttributeError: + self.unhandled["sys_exit_write_attr_err"] += 1 + + def process_event(self, sample: perf.sample_event) -> None: + """Process events.""" + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + event_name = "".join(c if c.isprintable() else "?" for c in event_name) + pid = sample.sample_pid + + assert self.session is not None + try: + thread = self.session.find_thread(pid, sample.sample_tid) + comm = (thread.comm() if thread else None) or "unknown" + except (TypeError, AttributeError): + comm = "unknown" + comm = "".join(c if c.isprintable() else "?" for c in comm) + + if event_name == "syscalls:sys_enter_read": + self._handle_sys_enter_read(pid, comm, sample) + elif event_name == "syscalls:sys_exit_read": + self._handle_sys_exit_read(pid, sample, comm) + elif event_name == "syscalls:sys_enter_write": + self._handle_sys_enter_write(pid, comm, sample) + elif event_name == "syscalls:sys_exit_write": + self._handle_sys_exit_write(pid, sample, comm) + else: + self.unhandled[event_name] += 1 + + def print_totals(self) -> None: + """Print summary tables.""" + print("read counts by pid:\n") + print( + f"{'pid':>6s} {'comm':<20s} {'# reads':>10s} " + f"{'bytes_requested':>15s} {'bytes_read':>10s}" + ) + print(f"{'-'*6} {'-'*20} {'-'*10} {'-'*15} {'-'*10}") + + for pid, data in sorted(self.reads.items(), + key=lambda kv: kv[1]["bytes_read"], reverse=True): + print( + f"{pid:6d} {data['comm']:<20s} {data['total_reads']:10d} " + f"{data['bytes_requested']:15d} {data['bytes_read']:10d}" + ) + + print("\nfailed reads by pid:\n") + print(f"{'pid':>6s} {'comm':<20s} {'error #':>7s} {'# errors':>10s}") + print(f"{'-'*6} {'-'*20} {'-'*7} {'-'*10}") + + errcounts: List[Tuple[int, str, int, int]] = [] + for pid, data in self.reads.items(): + for error, count in data["errors"].items(): + errcounts.append((pid, data["comm"], error, count)) + + for pid, comm, error, count in sorted(errcounts, key=lambda x: x[3], reverse=True): + print(f"{pid:6d} {comm:<20s} {error:7d} {count:10d}") + + print("\nwrite counts by pid:\n") + print( + f"{'pid':>6s} {'comm':<20s} {'# writes':>10s} " + f"{'bytes_requested':>15s} {'bytes_written':>15s}" + ) + print(f"{'-'*6} {'-'*20} {'-'*10} {'-'*15} {'-'*15}") + + for pid, data in sorted(self.writes.items(), + key=lambda kv: kv[1]["bytes_written"], reverse=True): + print( + f"{pid:6d} {data['comm']:<20s} {data['total_writes']:10d} " + f"{data['bytes_requested']:15d} {data['bytes_written']:15d}" + ) + + print("\nfailed writes by pid:\n") + print(f"{'pid':>6s} {'comm':<20s} {'error #':>7s} {'# errors':>10s}") + print(f"{'-'*6} {'-'*20} {'-'*7} {'-'*10}") + + errcounts = [] + for pid, data in self.writes.items(): + for error, count in data["errors"].items(): + errcounts.append((pid, data["comm"], error, count)) + + for pid, comm, error, count in sorted(errcounts, key=lambda x: x[3], reverse=True): + print(f"{pid:6d} {comm:<20s} {error:7d} {count:10d}") + + if self.unhandled: + print("\nunhandled events:\n") + print(f"{'event':<40s} {'count':>10s}") + print(f"{'-'*40} {'-'*10}") + for event_name, count in self.unhandled.items(): + print(f"{event_name:<40s} {count:10d}") + + def run(self, input_file: str) -> None: + """Run the session.""" + self.session = perf.session(perf.data(input_file), sample=self.process_event) + try: + self.session.process_events() + finally: + # Break the reference cycle between perf.session and self.process_event + # because perf.session lacks cyclic GC support (tp_traverse). + self.session = None + self.print_totals() + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Trace r/w activity by PID") + parser.add_argument("-i", "--input", default="perf.data", help="Input file") + args = parser.parse_args() + + analyzer = RwByPid() + try: + analyzer.run(args.input) + except IOError as e: + print(e, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/rwtop.py b/tools/perf/python/rwtop.py new file mode 100755 index 000000000000..86674d7c8f6f --- /dev/null +++ b/tools/perf/python/rwtop.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Periodically displays system-wide r/w call activity, broken down by pid.""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import os +import sys +from typing import Optional, Dict, Any +import perf +from perf_live import LiveSession + +class RwTop: + """Periodically displays system-wide r/w call activity.""" + def __init__(self, interval: int = 3, nlines: int = 20) -> None: + self.offline = False + self.interval_ns = interval * 1000000000 + self.nlines = nlines + self.reads: Dict[int, Dict[str, Any]] = defaultdict( + lambda: { + "bytes_requested": 0, + "bytes_read": 0, + "total_reads": 0, + "comm": "", + "errors": defaultdict(int), + } + ) + self.writes: Dict[int, Dict[str, Any]] = defaultdict( + lambda: { + "bytes_requested": 0, + "bytes_written": 0, + "total_writes": 0, + "comm": "", + "errors": defaultdict(int), + } + ) + self.unhandled: Dict[str, int] = defaultdict(int) + self.comm_cache: Dict[int, str] = {} + self.session: Optional[perf.session] = None + self.last_print_time: int = 0 + + def get_comm(self, pid: int, tid: Optional[int] = None) -> str: + """Resolve and cache the comm(and) of a pid.""" + comm = None + if self.session: + # In offline mode, query session.find_thread() directly so PERF_RECORD_COMM + # updates after execve are reflected immediately instead of returning a + # stale pre-exec comm from comm_cache. + try: + thread = (self.session.find_thread(pid, tid) + if tid is not None else self.session.find_thread(pid)) + comm = thread.comm() if thread else None + except (TypeError, AttributeError): + pass + if not comm: + comm = self.comm_cache.get(pid) + else: + comm = self.comm_cache.get(pid) + if comm: + return comm + try: + with open(f"/proc/{pid}/comm", "r", encoding="utf-8", errors="replace") as f: + comm = f.read().strip() + except OSError: + # The thread may have exited before /proc could be read. + pass + if not comm: + comm = f"PID({pid})" + comm = ''.join(c if c.isprintable() else '?' for c in comm) + self.comm_cache[pid] = comm + return comm + + def process_event(self, sample: perf.sample_event) -> None: + """Process events.""" + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + event_name = "".join(c if c.isprintable() else "?" for c in event_name) + pid = sample.sample_pid + sample_time = getattr(sample, "sample_time", 0) or 0 + + if sample_time > 0: + if self.last_print_time == 0: + self.last_print_time = sample_time + elif (sample_time > self.last_print_time and + sample_time - self.last_print_time >= self.interval_ns): + self.print_totals() + self.last_print_time = sample_time + + # Map each event onto the totals it updates. "enter" events count the + # requested bytes, "exit" events the transferred bytes or the error. + handlers = { + "syscalls:sys_enter_read": (self.reads, "total_reads", None), + "syscalls:sys_exit_read": (self.reads, None, "bytes_read"), + "syscalls:sys_enter_write": (self.writes, "total_writes", None), + "syscalls:sys_exit_write": (self.writes, None, "bytes_written"), + } + handler = handlers.get(event_name) + if not handler: + self.unhandled[event_name] += 1 + return + + totals, count_key, bytes_key = handler + try: + value = sample.count if count_key else sample.ret + except AttributeError: + self.unhandled[event_name] += 1 + return + + data = totals[pid] + data["comm"] = self.get_comm(pid, getattr(sample, "sample_tid", None)) + if count_key: + data["bytes_requested"] += value + data[count_key] += 1 + elif bytes_key: + # Convert unsigned 32-bit or 64-bit kernel error return values to signed integers. + # Because kernel read/write transfers are bounded by MAX_RW_COUNT (0x7ffff000 < 2 GiB), + # 0xfffff000..0xffffffff is always a 32-bit negative errno even on 64-bit hosts. + if value > 0: + if 0xfffff000 <= value <= 0xffffffff: + value -= 0x100000000 + elif value >= 0x8000000000000000: + value -= 0x10000000000000000 + if value >= 0: + data[bytes_key] += value + else: + data["errors"][value] += 1 + + def print_totals(self) -> None: + """Print summary tables.""" + if not self.offline: + print('\x1b[H\x1b[2J', end='') + print("read counts by pid:\n") + print( + f"{'pid':>6s} {'comm':<20s} {'# reads':>10s} " + f"{'bytes_req':>10s} {'bytes_read':>10s}" + ) + print(f"{'-'*6} {'-'*20} {'-'*10} {'-'*10} {'-'*10}") + + count = 0 + for pid, data in sorted(self.reads.items(), + key=lambda kv: kv[1]["bytes_read"], reverse=True): + print( + f"{pid:6d} {data['comm']:<20s} {data['total_reads']:10d} " + f"{data['bytes_requested']:10d} {data['bytes_read']:10d}" + ) + count += 1 + if count >= self.nlines: + break + + print("\nfailed reads by pid:\n") + print(f"{'pid':>6s} {'comm':<20s} {'error #':>7s} {'# errors':>10s}") + print(f"{'-'*6} {'-'*20} {'-'*7} {'-'*10}") + + errcounts = [] + for pid, data in self.reads.items(): + for error, cnt in data["errors"].items(): + errcounts.append((pid, data["comm"], error, cnt)) + + sorted_errcounts = sorted(errcounts, key=lambda x: x[3], reverse=True) + for pid, comm, error, cnt in sorted_errcounts[:self.nlines]: + print(f"{pid:6d} {comm:<20s} {error:7d} {cnt:10d}") + + print("\nwrite counts by pid:\n") + print( + f"{'pid':>6s} {'comm':<20s} {'# writes':>10s} " + f"{'bytes_req':>10s} {'bytes_written':>13s}" + ) + print(f"{'-'*6} {'-'*20} {'-'*10} {'-'*10} {'-'*13}") + + count = 0 + for pid, data in sorted(self.writes.items(), + key=lambda kv: kv[1]["bytes_written"], reverse=True): + print( + f"{pid:6d} {data['comm']:<20s} {data['total_writes']:10d} " + f"{data['bytes_requested']:10d} {data['bytes_written']:13d}" + ) + count += 1 + if count >= self.nlines: + break + + print("\nfailed writes by pid:\n") + print(f"{'pid':>6s} {'comm':<20s} {'error #':>7s} {'# errors':>10s}") + print(f"{'-'*6} {'-'*20} {'-'*7} {'-'*10}") + + errcounts = [] + for pid, data in self.writes.items(): + for error, cnt in data["errors"].items(): + errcounts.append((pid, data["comm"], error, cnt)) + + sorted_errcounts = sorted(errcounts, key=lambda x: x[3], reverse=True) + for pid, comm, error, cnt in sorted_errcounts[:self.nlines]: + print(f"{pid:6d} {comm:<20s} {error:7d} {cnt:10d}") + + # Reset counts + self.reads.clear() + self.writes.clear() + self.comm_cache.clear() + + def run(self, input_file: str) -> None: + """Run the session.""" + self.session = perf.session(perf.data(input_file), sample=self.process_event) + try: + self.session.process_events() + finally: + # Break the reference cycle between perf.session and self.process_event + # because perf.session lacks cyclic GC support (tp_traverse). + self.session = None + + # Print final totals if there are any left + if self.reads or self.writes: + self.print_totals() + + if self.unhandled: + print("\nunhandled events:\n") + print(f"{'event':<40s} {'count':>10s}") + print(f"{'-'*40} {'-'*10}") + for event_name, count in self.unhandled.items(): + print(f"{event_name:<40s} {count:10d}") + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Trace r/w activity by PID") + parser.add_argument( + "interval", type=int, nargs="?", default=3, help="Refresh interval in seconds" + ) + parser.add_argument("-i", "--input", default="perf.data", help="Input file") + parser.add_argument("-l", "--live", action="store_true", help="Run in live mode") + args = parser.parse_args() + + analyzer = RwTop(args.interval) + try: + if args.live or (not os.path.exists(args.input) and args.input == "perf.data"): + # Live mode + events = ( + "syscalls:sys_enter_read,syscalls:sys_exit_read," + "syscalls:sys_enter_write,syscalls:sys_exit_write" + ) + live_session = LiveSession(events, sample_callback=analyzer.process_event) + print("Live mode started. Press Ctrl+C to stop.", file=sys.stderr) + live_session.run() + else: + analyzer.offline = True + analyzer.run(args.input) + except IOError as e: + print(e, file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + if not analyzer.offline: + print("\nStopping live mode...", file=sys.stderr) + if analyzer.reads or analyzer.writes: + analyzer.print_totals() + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/sched-migration.py b/tools/perf/python/sched-migration.py new file mode 100755 index 000000000000..d92c1a80d42f --- /dev/null +++ b/tools/perf/python/sched-migration.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Cpu task migration overview toy + +Copyright (C) 2010 Frederic Weisbecker +Ported to modern directory structure and refactored to use class. +""" +from __future__ import annotations + +import argparse +from collections import defaultdict, UserList +import importlib +import sys +from typing import Any +import perf + + +# Global threads dictionary +threads = defaultdict(lambda: "unknown") +threads[0] = "idle" + + +def thread_name(pid: int) -> str: + """Return thread name formatted with pid.""" + return f"{threads[pid]}:{pid}" + + +def task_state(state: int) -> str: + """Map task state integer to string.""" + states = { + 0: "R", + 1: "S", + 2: "D", + 64: "DEAD" + } + return states.get(state, "Unknown") + + +class RunqueueEventUnknown: + """Unknown runqueue event.""" + @staticmethod + def color(): + """Return color for event.""" + return None + + def __repr__(self): + return "unknown" + + +class RunqueueEventSleep: + """Sleep runqueue event.""" + @staticmethod + def color(): + """Return color for event.""" + return 0, 0, 0xff + + def __init__(self, sleeper: int): + self.sleeper = sleeper + + def __repr__(self): + return f"{thread_name(self.sleeper)} gone to sleep" + + +class RunqueueEventWakeup: + """Wakeup runqueue event.""" + @staticmethod + def color(): + """Return color for event.""" + return 0xff, 0xff, 0 + + def __init__(self, wakee: int): + self.wakee = wakee + + def __repr__(self): + return f"{thread_name(self.wakee)} woke up" + + +class RunqueueEventFork: + """Fork runqueue event.""" + @staticmethod + def color(): + """Return color for event.""" + return 0, 0xff, 0 + + def __init__(self, child: int): + self.child = child + + def __repr__(self): + return f"new forked task {thread_name(self.child)}" + + +class RunqueueMigrateIn: + """Migrate in runqueue event.""" + @staticmethod + def color(): + """Return color for event.""" + return 0, 0xf0, 0xff + + def __init__(self, new: int): + self.new = new + + def __repr__(self): + return f"task migrated in {thread_name(self.new)}" + + +class RunqueueMigrateOut: + """Migrate out runqueue event.""" + @staticmethod + def color(): + """Return color for event.""" + return 0xff, 0, 0xff + + def __init__(self, old: int): + self.old = old + + def __repr__(self): + return f"task migrated out {thread_name(self.old)}" + + +class RunqueueSnapshot: + """Snapshot of runqueue state.""" + + def __init__(self, tasks=None, event=None): + if tasks is None: + tasks = (0,) + if event is None: + event = RunqueueEventUnknown() + self.tasks = tuple(tasks) + self.event = event + + def sched_switch(self, prev: int, prev_state: int, next_pid: int): + """Handle sched switch in snapshot.""" + if task_state(prev_state) == "R" and next_pid in self.tasks \ + and prev in self.tasks: + return self + + event = ( + RunqueueEventSleep(prev) + if task_state(prev_state) != "R" + else RunqueueEventUnknown() + ) + + next_tasks = list(self.tasks[:]) + if prev in self.tasks: + if task_state(prev_state) != "R": + next_tasks.remove(prev) + elif task_state(prev_state) == "R": + next_tasks.append(prev) + + if next_pid not in next_tasks: + next_tasks.append(next_pid) + + return RunqueueSnapshot(next_tasks, event) + + def migrate_out(self, old: int): + """Handle task migrate out in snapshot.""" + if old not in self.tasks: + return self + next_tasks = [task for task in self.tasks if task != old] + + return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old)) + + def __migrate_in(self, new: int, event): + if new in self.tasks: + return RunqueueSnapshot(self.tasks, event) + next_tasks = self.tasks + tuple([new]) + + return RunqueueSnapshot(next_tasks, event) + + def migrate_in(self, new: int): + """Handle task migrate in snapshot.""" + return self.__migrate_in(new, RunqueueMigrateIn(new)) + + def wake_up(self, new: int): + """Handle task wakeup in snapshot.""" + return self.__migrate_in(new, RunqueueEventWakeup(new)) + + def wake_up_new(self, new: int): + """Handle task fork in snapshot.""" + return self.__migrate_in(new, RunqueueEventFork(new)) + + def load(self) -> int: + """Provide the number of tasks on the runqueue. Don't count idle""" + return len(self.tasks) - 1 + + def __repr__(self): + return self.tasks.__repr__() + + +class TimeSlice: + """Represents a time slice of execution.""" + + def __init__(self, start: int, prev): + self.start = start + self.prev = prev + self.end = start + # cpus that triggered the event + self.event_cpus: list[int] = [] + if prev is not None: + self.total_load = prev.total_load + self.rqs = prev.rqs.copy() + else: + self.rqs = defaultdict(RunqueueSnapshot) + self.total_load = 0 + + def __update_total_load(self, old_rq: RunqueueSnapshot, new_rq: RunqueueSnapshot): + diff = new_rq.load() - old_rq.load() + self.total_load += diff + + def sched_switch(self, ts_list, prev: int, prev_state: int, next_pid: int, cpu: int): + """Process sched_switch in time slice.""" + old_rq = self.prev.rqs[cpu] + new_rq = old_rq.sched_switch(prev, prev_state, next_pid) + + if old_rq is new_rq: + return + + self.rqs[cpu] = new_rq + self.__update_total_load(old_rq, new_rq) + ts_list.append(self) + self.event_cpus = [cpu] + + def migrate(self, ts_list, new: int, old_cpu: int, new_cpu: int): + """Process task migration in time slice.""" + if old_cpu == new_cpu: + return + old_rq = self.prev.rqs[old_cpu] + out_rq = old_rq.migrate_out(new) + self.rqs[old_cpu] = out_rq + self.__update_total_load(old_rq, out_rq) + + new_rq = self.prev.rqs[new_cpu] + in_rq = new_rq.migrate_in(new) + self.rqs[new_cpu] = in_rq + self.__update_total_load(new_rq, in_rq) + + ts_list.append(self) + + if old_rq is not out_rq: + self.event_cpus.append(old_cpu) + self.event_cpus.append(new_cpu) + + def wake_up(self, ts_list, pid: int, cpu: int, fork: bool): + """Process wakeup in time slice.""" + old_rq = self.prev.rqs[cpu] + if fork: + new_rq = old_rq.wake_up_new(pid) + else: + new_rq = old_rq.wake_up(pid) + + if new_rq is old_rq: + return + self.rqs[cpu] = new_rq + self.__update_total_load(old_rq, new_rq) + ts_list.append(self) + self.event_cpus = [cpu] + + def next(self, t: int): + """Create next time slice.""" + self.end = t + return TimeSlice(t, self) + + +class TimeSliceList(UserList): + """List of time slices with search capabilities.""" + + def __init__(self, arg=None): + super().__init__(arg if arg is not None else []) + self.root_win = None + + def get_time_slice(self, ts: int) -> TimeSlice: + """Get or create time slice for timestamp.""" + if len(self.data) == 0: + ts_slice = TimeSlice(ts, TimeSlice(-1, None)) + else: + ts_slice = self.data[-1].next(ts) + return ts_slice + + def find_time_slice(self, ts: int) -> int: + """Binary search for time slice containing timestamp.""" + if not self.data: + return -1 + start = 0 + end = len(self.data) + found = -1 + searching = True + while searching: + if start in (end, end - 1): + searching = False + + i = (end + start) // 2 + if self.data[i].start <= ts <= self.data[i].end: + found = i + break + + if self.data[i].end < ts: + start = i + elif self.data[i].start > ts: + end = i + + return found + + def set_root_win(self, win): + """Set root window for GUI.""" + self.root_win = win + + def mouse_down(self, cpu: int, t: int): + """Handle mouse down event from GUI.""" + idx = self.find_time_slice(t) + if idx == -1: + return + + ts = self[idx] + rq = ts.rqs[cpu] + raw = f"CPU: {cpu}\n" + raw += f"Last event : {repr(rq.event)}\n" + raw += f"Timestamp : {ts.start // (10 ** 9)}.{ts.start % (10 ** 9) // 1000:06d}\n" + raw += f"Duration : {(ts.end - ts.start) // (10 ** 3):6d} us\n" + raw += f"Load = {rq.load()}\n" + for task in rq.tasks: + raw += f"{thread_name(task)} \n" + + if self.root_win: + self.root_win.update_summary(raw) + + def update_rectangle_cpu(self, slice_obj: TimeSlice, cpu: int): + """Update rectangle for CPU in GUI.""" + rq = slice_obj.rqs[cpu] + + if slice_obj.total_load != 0: + load_rate = rq.load() / float(slice_obj.total_load) + else: + load_rate = 0 + + red_power = int(0xff - (0xff * load_rate)) + color = (0xff, red_power, red_power) + + top_color = None + if cpu in slice_obj.event_cpus: + top_color = rq.event.color() + + if self.root_win: + self.root_win.paint_rectangle_zone(cpu, color, top_color, + slice_obj.start, slice_obj.end) + + def fill_zone(self, start: int, end: int): + """Fill zone in GUI.""" + i = self.find_time_slice(start) + if i == -1: + return + + for idx in range(i, len(self.data)): + timeslice = self.data[idx] + if timeslice.start > end: + return + + for cpu in timeslice.rqs: + self.update_rectangle_cpu(timeslice, cpu) + + def interval(self) -> tuple[int, int]: + """Return start and end timestamps.""" + if len(self.data) == 0: + return 0, 0 + return self.data[0].start, self.data[-1].end + + def nr_rectangles(self) -> int: + """Return maximum CPU number.""" + if not self.data: + return 0 + last_ts = self.data[-1] + max_cpu = 0 + for cpu in last_ts.rqs: + max_cpu = max(max_cpu, cpu) + return max_cpu + + +class SchedMigrationAnalyzer: + """Analyzes task migrations and manages time slices.""" + + def __init__(self): + self.current_tsk = defaultdict(lambda: -1) + self.timeslices = TimeSliceList() + + def sched_switch(self, time: int, cpu: int, prev_comm: str, prev_pid: int, prev_state: int, + next_comm: str, next_pid: int): + """Handle sched_switch event.""" + on_cpu_task = self.current_tsk[cpu] + + if on_cpu_task not in (-1, prev_pid): + print(f"Sched switch event rejected ts: {time} cpu: {cpu} " + f"prev: {prev_comm}({prev_pid}) next: {next_comm}({next_pid})") + + threads[prev_pid] = prev_comm + threads[next_pid] = next_comm + self.current_tsk[cpu] = next_pid + + ts = self.timeslices.get_time_slice(time) + ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, cpu) + + def migrate(self, time: int, pid: int, orig_cpu: int, dest_cpu: int): + """Handle sched_migrate_task event.""" + ts = self.timeslices.get_time_slice(time) + ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu) + + def wake_up(self, time: int, pid: int, success: int, target_cpu: int, fork: bool): + """Handle wakeup event.""" + if success == 0: + return + ts = self.timeslices.get_time_slice(time) + ts.wake_up(self.timeslices, pid, target_cpu, fork) + + def process_event(self, sample: perf.sample_event) -> None: + """Collect events and pass to analyzer.""" + name = str(sample.evsel) + if name.startswith("evsel(") and name.endswith(")"): + name = name[6:-1] + if name.count(":") >= 2: + name = ":".join(name.split(":")[:2]) + time = sample.sample_time + cpu = sample.sample_cpu + _pid = sample.sample_pid + _comm = "Unknown" + + if name.startswith("sched:sched_switch"): + prev_comm = getattr(sample, "prev_comm", "Unknown") + prev_pid = getattr(sample, "prev_pid", -1) + prev_state = getattr(sample, "prev_state", 0) + next_comm = getattr(sample, "next_comm", "Unknown") + next_pid = getattr(sample, "next_pid", -1) + self.sched_switch(time, cpu, prev_comm, prev_pid, prev_state, next_comm, next_pid) + elif name.startswith("sched:sched_migrate_task"): + task_pid = getattr(sample, "pid", -1) + orig_cpu = getattr(sample, "orig_cpu", -1) + dest_cpu = getattr(sample, "dest_cpu", -1) + self.migrate(time, task_pid, orig_cpu, dest_cpu) + elif name.startswith("sched:sched_wakeup_new"): + task_pid = getattr(sample, "pid", -1) + success = getattr(sample, "success", 1) + target_cpu = getattr(sample, "target_cpu", -1) + self.wake_up(time, task_pid, success, target_cpu, True) + elif name.startswith("sched:sched_wakeup"): + task_pid = getattr(sample, "pid", -1) + success = getattr(sample, "success", 1) + target_cpu = getattr(sample, "target_cpu", -1) + self.wake_up(time, task_pid, success, target_cpu, False) + + def run_gui(self): + """Start wxPython GUI.""" + try: + wx_mod: Any = importlib.import_module("wx") + sched_gui: Any = importlib.import_module("SchedGui") + except ImportError: + print("wxPython is not available. Cannot start GUI.") + return + app = wx_mod.App(False) + _frame = sched_gui.RootFrame(self.timeslices, "Migration") + app.MainLoop() + + +if __name__ == "__main__": + ap = argparse.ArgumentParser(description="Cpu task migration overview toy") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("-v", "--verbose", action="store_true", + help="Print parsed migration summary") + ap.add_argument("--gui", action="store_true", default=True, + help="Start the GUI (default)") + ap.add_argument("--no-gui", action="store_true", help="Do not start the GUI") + args = ap.parse_args() + + analyzer = SchedMigrationAnalyzer() + session = None + + try: + try: + session = perf.session(perf.data(args.input), sample=analyzer.process_event) + try: + session.process_events() + except KeyboardInterrupt: + pass + finally: + # Release the perf.session reference before launching the long-running + # wxPython GUI event loop so the underlying C session and callback + # reference cycle are freed immediately. + session = None + if args.verbose: + start_ts, end_ts = analyzer.timeslices.interval() + print(f"Timeslices: {len(analyzer.timeslices.data)} " + f"(interval: {start_ts}..{end_ts})") + if args.gui and not args.no_gui: + analyzer.run_gui() + except KeyboardInterrupt: + pass + except OSError as e: + print(f"Error processing events: {e}") + sys.exit(1) diff --git a/tools/perf/python/sctop.py b/tools/perf/python/sctop.py new file mode 100755 index 000000000000..48320af8b755 --- /dev/null +++ b/tools/perf/python/sctop.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +System call top + +Periodically displays system-wide system call totals, broken down by +syscall. If a [comm] arg is specified, only syscalls called by +[comm] are displayed. If an [interval] arg is specified, the display +will be refreshed every [interval] seconds. The default interval is +3 seconds. + +Ported from tools/perf/scripts/python/sctop.py +""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import os +import sys +import threading +from typing import Optional +import perf +from perf_live import LiveSession + + +class SCTopAnalyzer: + """Periodically displays system-wide system call totals.""" + + def __init__(self, for_comm: Optional[str], interval: int, offline: bool = False): + self.for_comm = for_comm + self.interval = interval + self.syscalls: dict[int, int] = defaultdict(int) + self.comm_cache: dict[int, str] = {} + self.lock = threading.Lock() + self.stop_event = threading.Event() + self.thread = threading.Thread(target=self.print_syscall_totals) + self.offline = offline + self.own_pid = os.getpid() + self.last_print_time: Optional[int] = None + self.printed = False + self.session: Optional[perf.session] = None + self.e_machine: Optional[int] = None + + def syscall_name(self, syscall_id: int) -> str: + """Lookup syscall name by ID.""" + # Mask out the x86_64 x32 ABI bit (__X32_SYSCALL_BIT = 0x40000000) before + # resolving the syscall number in the architecture's syscall table. + raw_sc_id = syscall_id & ~0x40000000 + try: + e_machine = getattr(self.session, "e_machine", self.e_machine) + if e_machine is not None: + name = perf.syscall_name(raw_sc_id, e_machine) + else: + name = perf.syscall_name(raw_sc_id) + if name is not None: + return name + except (TypeError, OverflowError): + pass + return str(syscall_id) + + def process_event(self, sample: perf.sample_event) -> None: + """Collect syscall events.""" + if not self.offline and sample.sample_pid == self.own_pid: + return + + name = str(sample.evsel) + # Per-syscall syscalls:sys_enter_* tracepoints expose '__syscall_nr' (or 'nr') + # and may have an unrelated syscall argument named 'id', whereas + # raw_syscalls:sys_enter (and legacy pre-2.6.35 syscalls:sys_enter) expose 'id'. + if name.startswith("evsel(syscalls:sys_enter_"): + syscall_id = getattr(sample, "__syscall_nr", -1) + if not (0 <= (syscall_id & ~0x40000000) <= 0xffff): + syscall_id = getattr(sample, "nr", -1) + elif name.startswith(("evsel(raw_syscalls:sys_enter", "evsel(syscalls:sys_enter")): + syscall_id = getattr(sample, "id", -1) + if not (0 <= (syscall_id & ~0x40000000) <= 0xffff): + syscall_id = getattr(sample, "__syscall_nr", -1) + if not (0 <= (syscall_id & ~0x40000000) <= 0xffff): + syscall_id = getattr(sample, "nr", -1) + else: + syscall_id = -1 + + skip = False + with self.lock: + if self.for_comm is not None: + is_execve = (0 <= (syscall_id & ~0x40000000) <= 0xffff and + self.syscall_name(syscall_id) in ("execve", "execveat")) + if is_execve: + self.comm_cache.pop(sample.sample_pid, None) + + comm = "Unknown" + if hasattr(self, 'session') and self.session: + # In offline perf.data mode, query session.find_thread() directly + # so PERF_RECORD_COMM updates after execve (e.g. perf -> sleep) + # are reflected immediately rather than returning a stale cached comm. + try: + proc = self.session.find_thread(sample.sample_pid, sample.sample_tid) + if proc: + comm = proc.comm() or "Unknown" + except TypeError: + pass + if comm != "Unknown" and not is_execve: + self.comm_cache[sample.sample_pid] = comm + elif sample.sample_pid in self.comm_cache: + comm = self.comm_cache[sample.sample_pid] + elif sample.sample_pid in self.comm_cache: + comm = self.comm_cache[sample.sample_pid] + else: + try: + with open(f"/proc/{sample.sample_pid}/comm", "r", + encoding="utf-8", errors="replace") as f: + comm = f.read().strip() + except OSError: + comm = "Unknown" + # Cache both matching and non-matching comms (including "Unknown" + # when a PID has exited or is inaccessible) so live system-wide + # tracing does not re-open /proc//comm on every syscall. + # Do not cache during sys_enter(execve/execveat) since /proc//comm + # still holds the pre-exec command name until the syscall completes. + if not is_execve: + self.comm_cache[sample.sample_pid] = comm + + if comm != self.for_comm: + skip = True + + is_enter = (name.startswith("evsel(raw_syscalls:sys_enter") or + name.startswith("evsel(syscalls:sys_enter")) + if not skip and is_enter and 0 <= (syscall_id & ~0x40000000) <= 0xffff: + self.syscalls[syscall_id] += 1 + + if self.offline and hasattr(sample, "sample_time"): + interval_ns = self.interval * (10 ** 9) + if self.last_print_time is None: + self.last_print_time = sample.sample_time + elif sample.sample_time - self.last_print_time >= interval_ns: + self.print_current_totals() + self.last_print_time = sample.sample_time + + def print_current_totals(self): + """Print current syscall totals.""" + self.printed = True + # Clear terminal + if not self.offline: + print("\x1b[2J\x1b[H", end="") + else: + print() + + with self.lock: + for_comm = self.for_comm + if for_comm is not None: + print(f"\nsyscall events for {for_comm}:\n") + else: + print("\nsyscall events:\n") + + print(f"{'event':40s} {'count':10s}") + print(f"{'-' * 40:40s} {'-' * 10:10s}") + + with self.lock: + current_syscalls = list(self.syscalls.items()) + self.syscalls.clear() + self.comm_cache.clear() + + current_syscalls.sort(key=lambda kv: (-kv[1], kv[0])) + + for syscall_id, val in current_syscalls: + print(f"{self.syscall_name(syscall_id):<40s} {val:10d}") + + def print_syscall_totals(self): + """Periodically print syscall totals.""" + while not self.stop_event.is_set(): + self.print_current_totals() + self.stop_event.wait(self.interval) + # Print final batch + self.print_current_totals() + + def start(self): + """Start the background thread.""" + self.thread.start() + + def stop(self): + """Stop the background thread.""" + self.stop_event.set() + self.thread.join() + + +def main(): + """Main function.""" + ap = argparse.ArgumentParser(description="System call top") + ap.add_argument("args", nargs="*", help="[comm] [interval] or [interval]") + ap.add_argument("-i", "--input", help="Input file name") + args = ap.parse_args() + + for_comm = None + default_interval = 3 + interval = default_interval + + if len(args.args) > 2: + print("Usage: python sctop.py [comm] [interval]") + sys.exit(1) + + if len(args.args) > 1: + for_comm = args.args[0] + try: + interval = int(args.args[1]) + except ValueError: + print(f"Invalid interval: {args.args[1]}") + sys.exit(1) + elif len(args.args) > 0: + try: + interval = int(args.args[0]) + except ValueError: + for_comm = args.args[0] + interval = default_interval + + analyzer = SCTopAnalyzer(for_comm, interval, offline=bool(args.input)) + session = None + + try: + if args.input: + session = perf.session(perf.data(args.input), sample=analyzer.process_event) + analyzer.session = session + analyzer.e_machine = getattr(session, "e_machine", None) + session.process_events() + else: + try: + live_session = LiveSession( + "raw_syscalls:sys_enter", sample_callback=analyzer.process_event + ) + except OSError: + live_session = LiveSession( + "syscalls:sys_enter_*", sample_callback=analyzer.process_event + ) + analyzer.start() + live_session.run() + except KeyboardInterrupt: + pass + except (OSError, IOError) as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + finally: + if args.input: + if not analyzer.printed or analyzer.syscalls: + analyzer.print_current_totals() + # Break the reference cycle between perf.session and analyzer.process_event + # because perf.session lacks cyclic GC support (tp_traverse). + analyzer.session = None + session = None + elif analyzer.thread.is_alive(): + analyzer.stop() + + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/stackcollapse.py b/tools/perf/python/stackcollapse.py new file mode 100755 index 000000000000..0e8a65969db3 --- /dev/null +++ b/tools/perf/python/stackcollapse.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +stackcollapse.py - format perf samples with one line per distinct call stack + +This script's output has two space-separated fields. The first is a semicolon +separated stack including the program name (from the "comm" field) and the +function names from the call stack. The second is a count: + + swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 2 + +The file is sorted according to the first field. + +Ported from tools/perf/scripts/python/stackcollapse.py +""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import os +import sys +import perf + + +class StackCollapseAnalyzer: + """Accumulates call stacks and prints them collapsed.""" + + def __init__(self, args: argparse.Namespace) -> None: + self.args = args + self.lines: dict[str, int] = defaultdict(int) + self.session: perf.session | None = None + + def tidy_function_name(self, sym: str, dso: str) -> str: + """Beautify function names based on options.""" + if sym is None: + sym = "[unknown]" + + sym = sym.replace(";", ":") + if self.args.tidy_java: + # Beautify Java signatures + sym = sym.replace("<", "") + sym = sym.replace(">", "") + if sym.startswith("L") and "/" in sym: + sym = sym[1:] + try: + sym = sym[:sym.index("(")] + except ValueError: + pass + + if self.args.annotate_kernel and dso == "[kernel.kallsyms]": + return sym + "_[k]" + return sym + + def process_event(self, sample: perf.sample_event) -> None: + """Collect call stack for each sample.""" + stack = [] + callchain = sample.callchain + if callchain is not None: + for node in callchain: + stack.append(self.tidy_function_name(node.symbol, node.dso)) + else: + # Fallback if no callchain + sym = (sample.symbol or '[unknown]') + dso = (sample.dso or '[unknown]') + stack.append(self.tidy_function_name(sym, dso)) + + if self.args.include_comm: + comm = "Unknown" + if self.session is not None: + try: + proc = self.session.find_thread( + sample.sample_pid, sample.sample_tid + ) + if proc: + proc_comm = proc.comm() + if proc_comm is not None: + comm = proc_comm + except TypeError: + pass + comm = str(comm).replace(" ", "_") + sep = "-" + if self.args.include_pid: + comm = f"{comm}{sep}{(sample.sample_pid or 0)}" + sep = "/" + if self.args.include_tid: + comm = f"{comm}{sep}{(sample.sample_tid or 0)}" + stack.append(comm) + + stack_string = ";".join(reversed(stack)) + self.lines[stack_string] += 1 + + def print_totals(self) -> None: + """Print sorted collapsed stacks.""" + try: + for stack in sorted(self.lines): + print(f"{stack} {self.lines[stack]}") + sys.stdout.flush() + except BrokenPipeError: + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + os.close(devnull) + + +def main(): + """Main function.""" + ap = argparse.ArgumentParser( + description="Format perf samples with one line per distinct call stack" + ) + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + ap.add_argument("--include-tid", action="store_true", help="include thread id in stack") + ap.add_argument("--include-pid", action="store_true", help="include process id in stack") + ap.add_argument("--no-comm", dest="include_comm", action="store_false", default=True, + help="do not separate stacks according to comm") + ap.add_argument("--tidy-java", action="store_true", help="beautify Java signatures") + ap.add_argument("--kernel", dest="annotate_kernel", action="store_true", + help="annotate kernel functions with _[k]") + + args = ap.parse_args() + + if args.include_tid and not args.include_comm: + print("requesting tid but not comm is invalid", file=sys.stderr) + sys.exit(1) + if args.include_pid and not args.include_comm: + print("requesting pid but not comm is invalid", file=sys.stderr) + sys.exit(1) + + analyzer = StackCollapseAnalyzer(args) + + try: + session = perf.session(perf.data(args.input), sample=analyzer.process_event) + analyzer.session = session + session.process_events() + except IOError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + pass + finally: + analyzer.session = None + + analyzer.print_totals() + + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/stat-cpi.py b/tools/perf/python/stat-cpi.py new file mode 100755 index 000000000000..b4ec87b07938 --- /dev/null +++ b/tools/perf/python/stat-cpi.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +"""Calculate CPI from perf stat data or live.""" +from __future__ import annotations + +import argparse +import os +import signal +import sys +import time +from typing import Any, Optional +import perf + +class StatCpiAnalyzer: + """Accumulates cycles and instructions and calculates CPI.""" + + def __init__(self, args: argparse.Namespace) -> None: + self.args = args + self.data: dict[str, float] = {} + self.prev_data: dict[str, tuple[int, int, int]] = {} + self.recorded_pairs: set[tuple[int, int]] = set() + + def get_key(self, event: str, cpu: int, thread: int) -> str: + """Get key for data dictionary.""" + return f"{event}-{cpu}-{thread}" + + def store_key(self, cpu: int, thread: int) -> None: + """Store CPU and thread IDs.""" + self.recorded_pairs.add((cpu, thread)) + + def store(self, event: str, cpu: int, thread: int, + counts: tuple[int, int, int], is_delta: bool = False, + raw_name: Optional[str] = None) -> None: + """Store counter values, computing difference from previous + absolute values if not already deltas.""" + self.store_key(cpu, thread) + key = self.get_key(event, cpu, thread) + prev_key = self.get_key(raw_name or event, cpu, thread) + + val, ena, run = counts + if is_delta: + # counts are already deltas + cur_val = val + cur_ena = ena + cur_run = run + else: + if prev_key in self.prev_data: + prev_val, prev_ena, prev_run = self.prev_data[prev_key] + cur_val = val - prev_val + cur_ena = ena - prev_ena + cur_run = run - prev_run + else: + cur_val = val + cur_ena = ena + cur_run = run + self.prev_data[prev_key] = counts # Store absolute value for next time + + # Scale each raw event's delta by its own multiplexing ratio before + # summing across PMUs (e.g. cpu_core and cpu_atom on hybrid systems) so + # enabled time from an idle PMU does not inflate the multiplier of an + # active PMU. + scaled_val = cur_val * (cur_ena / float(cur_run)) if cur_run > 0 else float(cur_val) + self.data[key] = self.data.get(key, 0.0) + scaled_val + + def get(self, event: str, cpu: int, thread: int) -> float: + """Get scaled counter value.""" + key = self.get_key(event, cpu, thread) + return self.data.get(key, 0.0) + + @staticmethod + def _classify_event(name: str) -> Optional[str]: + """Classify an event name as 'cycles' or 'instructions'.""" + ev = name[6:-1] if name.startswith("evsel(") and name.endswith(")") else name + ev = ev.split(":", 1)[0] + if "/" in ev: + parts = [p for p in ev.split("/") if p] + if len(parts) >= 2: + ev = parts[1] + if ev in ("cycles", "cpu-cycles"): + return "cycles" + if ev == "instructions": + return "instructions" + return None + + def process_stat_event(self, event: Any, name: Optional[str] = None) -> None: + """Process PERF_RECORD_STAT and PERF_RECORD_STAT_ROUND events.""" + if event.type == perf.RECORD_STAT: + if name: + event_name = self._classify_event(name) + if not event_name: + return + self.store(event_name, event.cpu, event.thread, + (event.val, event.ena, event.run), raw_name=name) + elif event.type == perf.RECORD_STAT_ROUND: + timestamp = getattr(event, "time", 0) + self.print_interval(timestamp) + self.data.clear() + self.recorded_pairs.clear() + + def print_interval(self, timestamp: int) -> None: + """Print CPI for the current interval.""" + for cpu, thread in sorted(self.recorded_pairs): + cyc = self.get("cycles", cpu, thread) + ins = self.get("instructions", cpu, thread) + cpi = 0.0 + if ins != 0: + cpi = cyc / float(ins) + t_sec = timestamp / 1000000000.0 + print(f"{t_sec:15f}: cpu {cpu}, thread {thread} -> cpi {cpi:f} ({cyc:.0f}/{ins:.0f})", + flush=True) + + def read_counters(self, evlist: Any) -> None: + """Read counters live.""" + for evsel in evlist: + name = str(evsel) + event_name = self._classify_event(name) + if not event_name: + continue + + for cpu in evsel.cpus(): + for thread in evsel.threads(): + try: + counts = evsel.read(cpu, thread) + self.store(event_name, cpu, thread, + (counts.val, counts.ena, counts.run), + is_delta=True, raw_name=name) + except OSError: + pass + + def run_file(self) -> None: + """Process events from file.""" + session: Optional[perf.session] = perf.session( + perf.data(self.args.input), stat=self.process_stat_event + ) + try: + assert session is not None + session.process_events() + finally: + session = None + + def _open_live_evlist(self) -> Any: + """Open evlist for live mode, falling back to user-space or process scope on EACCES.""" + threads = perf.thread_map(self.args.pid) if self.args.pid else None + candidates = [ + ("cycles,instructions", threads), + ("cycles:u,instructions:u", threads), + ] + if threads is None: + self_threads = perf.thread_map(os.getpid()) + candidates.append(("cycles,instructions", self_threads)) + candidates.append(("cycles:u,instructions:u", self_threads)) + + last_err: Optional[OSError] = None + for events, tmap in candidates: + evlist = None + try: + evlist = perf.parse_events(events, None, tmap) + for evsel in evlist: + evsel.read_format |= ( + perf.FORMAT_TOTAL_TIME_ENABLED | perf.FORMAT_TOTAL_TIME_RUNNING + ) + evlist.open() + evlist.enable() + return evlist + except PermissionError as e: + if evlist is not None: + evlist.close() + last_err = e + except OSError as e: + if evlist is not None: + evlist.close() + if e.errno == 13: + last_err = e + else: + raise + except BaseException: + if evlist is not None: + evlist.close() + raise + if last_err is not None: + raise last_err + raise RuntimeError("Failed to open events") + + def run_live(self) -> None: + """Read counters live.""" + def handle_signal(_signum: int, _frame: Any) -> None: + raise KeyboardInterrupt + + signal.signal(signal.SIGINT, signal.default_int_handler) + signal.signal(signal.SIGTERM, handle_signal) + + evlist = None + try: + try: + evlist = self._open_live_evlist() + except OSError as e: + print(f"Failed to open events: {e}", file=sys.stderr) + sys.exit(1) + + print("Live mode started. Press Ctrl+C to stop.", flush=True) + while True: + time.sleep(self.args.interval) + timestamp = time.time_ns() + self.read_counters(evlist) + self.print_interval(timestamp) + self.data.clear() + self.recorded_pairs.clear() + except KeyboardInterrupt: + print("\nStopped.", flush=True) + finally: + if evlist is not None: + evlist.close() + +def main() -> None: + """Main function.""" + ap = argparse.ArgumentParser(description="Calculate CPI from perf stat data or live") + ap.add_argument("-i", "--input", help="Input file name (enables file mode)") + ap.add_argument("-I", "--interval", type=float, default=1.0, + help="Interval in seconds for live mode") + ap.add_argument("-p", "--pid", type=int, + help="Monitor specific process ID in live mode") + args = ap.parse_args() + + analyzer = StatCpiAnalyzer(args) + if args.input: + analyzer.run_file() + else: + analyzer.run_live() + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/syscall-counts-by-pid.py b/tools/perf/python/syscall-counts-by-pid.py new file mode 100755 index 000000000000..fc0ec1bf7ca1 --- /dev/null +++ b/tools/perf/python/syscall-counts-by-pid.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Displays system-wide system call totals, broken down by syscall. +If a [comm] arg is specified, only syscalls called by [comm] are displayed. +""" +from __future__ import annotations + +import argparse +from collections import defaultdict +from typing import (Dict, Tuple) +import perf + +syscalls: Dict[Tuple[str, int, int], int] = defaultdict(int) +for_comm = None +for_pid = None +session = None + + +def print_syscall_totals(): + """Print aggregated statistics.""" + if for_comm is not None: + print(f"\nsyscall events for {for_comm}:\n") + elif for_pid is not None: + print(f"\nsyscall events for PID {for_pid}:\n") + else: + print("\nsyscall events:\n") + + print(f"{'comm [pid]/syscalls':<40} {'count':>10}") + print("---------------------------------------- -----------") + + sorted_keys = sorted(syscalls.keys(), key=lambda k: (k[0], k[1], -syscalls[k], k[2])) + current_comm_pid = None + for comm, pid, sc_id in sorted_keys: + if current_comm_pid != (comm, pid): + print(f"\n{comm} [{pid}]") + current_comm_pid = (comm, pid) + e_machine = getattr(session, "e_machine", 0) or 0 + # Mask out the x86_64 x32 ABI bit (__X32_SYSCALL_BIT = 0x40000000) before + # resolving the syscall number in the architecture's syscall table. + raw_sc_id = sc_id & ~0x40000000 + if e_machine: + name = perf.syscall_name(raw_sc_id, e_machine) or str(sc_id) + else: + name = perf.syscall_name(raw_sc_id) or str(sc_id) + print(f" {name:<38} {syscalls[(comm, pid, sc_id)]:>10}") + + +def process_event(sample): + """Process a single sample event.""" + event_name = str(sample.evsel) + # Per-syscall syscalls:sys_enter_* tracepoints expose '__syscall_nr' (or 'nr') + # and may have an unrelated syscall argument named 'id', whereas + # raw_syscalls:sys_enter (and legacy pre-2.6.35 syscalls:sys_enter) expose 'id'. + if event_name.startswith("evsel(syscalls:sys_enter_"): + sc_id = getattr(sample, "__syscall_nr", None) + if sc_id is not None and not (0 <= (sc_id & ~0x40000000) <= 0xffff): + sc_id = None + if sc_id is None: + sc_id = getattr(sample, "nr", -1) + elif event_name.startswith(("evsel(raw_syscalls:sys_enter", "evsel(syscalls:sys_enter")): + sc_id = getattr(sample, "id", -1) + if not (0 <= (sc_id & ~0x40000000) <= 0xffff): + sc_id = getattr(sample, "__syscall_nr", -1) + if not (0 <= (sc_id & ~0x40000000) <= 0xffff): + sc_id = getattr(sample, "nr", -1) + else: + return + + # Mask out __X32_SYSCALL_BIT (0x40000000) when validating the syscall ID range. + if not (0 <= (sc_id & ~0x40000000) <= 0xffff): + return + + pid = sample.sample_pid + + if for_pid is not None and pid != for_pid: + return + + comm = "unknown" + try: + if session: + proc = session.find_thread(sample.sample_pid, sample.sample_tid) + if proc: + comm = proc.comm() or "unknown" + except (TypeError, AttributeError): + pass + + if for_comm and comm != for_comm: + return + syscalls[(comm, pid, sc_id)] += 1 + + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("filter", nargs="?", help="COMM or PID to filter by") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + args = ap.parse_args() + + if args.filter: + try: + for_pid = int(args.filter) + except ValueError: + for_comm = args.filter + + try: + session = perf.session(perf.data(args.input), sample=process_event) + session.process_events() + print_syscall_totals() + finally: + # Break the reference cycle between session and process_event (whose module + # globals reference session) since perf.session lacks cyclic GC (tp_traverse). + session = None diff --git a/tools/perf/python/syscall-counts.py b/tools/perf/python/syscall-counts.py new file mode 100755 index 000000000000..3ddb1bcc27e0 --- /dev/null +++ b/tools/perf/python/syscall-counts.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +Displays system-wide system call totals, broken down by syscall. + +If a [comm] arg is specified, only syscalls called by [comm] are displayed. +""" +from __future__ import annotations + +import argparse +from collections import defaultdict +from typing import DefaultDict +import perf + +syscalls: DefaultDict[int, int] = defaultdict(int) +for_comm = None +session = None + + +def print_syscall_totals(): + """Print aggregated statistics.""" + if for_comm is not None: + print(f"\nsyscall events for {for_comm}:\n") + else: + print("\nsyscall events:\n") + + print(f"{'event':<40} {'count':>10}") + print("---------------------------------------- -----------") + + for sc_id, val in sorted(syscalls.items(), + key=lambda kv: (-kv[1], kv[0])): + e_machine = getattr(session, "e_machine", 0) or 0 + # Mask out the x86_64 x32 ABI bit (__X32_SYSCALL_BIT = 0x40000000) before + # resolving the syscall number in the architecture's syscall table. + raw_sc_id = sc_id & ~0x40000000 + if e_machine: + name = perf.syscall_name(raw_sc_id, e_machine) or str(sc_id) + else: + name = perf.syscall_name(raw_sc_id) or str(sc_id) + print(f"{name:<40} {val:>10}") + + +def process_event(sample): + """Process a single sample event.""" + event_name = str(sample.evsel) + # Per-syscall syscalls:sys_enter_* tracepoints expose '__syscall_nr' (or 'nr') + # and may have an unrelated syscall argument named 'id' (e.g. sys_enter_clock_gettime), + # whereas raw_syscalls:sys_enter (and legacy pre-2.6.35 syscalls:sys_enter) expose 'id'. + if event_name.startswith("evsel(syscalls:sys_enter_"): + sc_id = getattr(sample, "__syscall_nr", None) + if sc_id is not None and not (0 <= (sc_id & ~0x40000000) <= 0xffff): + sc_id = None + if sc_id is None: + sc_id = getattr(sample, "nr", -1) + elif event_name.startswith(("evsel(raw_syscalls:sys_enter", "evsel(syscalls:sys_enter")): + sc_id = getattr(sample, "id", -1) + if not (0 <= (sc_id & ~0x40000000) <= 0xffff): + sc_id = getattr(sample, "__syscall_nr", -1) + if not (0 <= (sc_id & ~0x40000000) <= 0xffff): + sc_id = getattr(sample, "nr", -1) + else: + return + + # Mask out __X32_SYSCALL_BIT (0x40000000) when validating the syscall ID range. + if not (0 <= (sc_id & ~0x40000000) <= 0xffff): + return + + comm = "unknown" + try: + if session: + proc = session.find_thread(sample.sample_pid, sample.sample_tid) + if proc: + comm = proc.comm() or "unknown" + except (TypeError, AttributeError): + pass + + if for_comm and comm != for_comm: + return + syscalls[sc_id] += 1 + + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("comm", nargs="?", help="Only report syscalls for comm") + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + args = ap.parse_args() + for_comm = args.comm + try: + session = perf.session(perf.data(args.input), sample=process_event) + session.process_events() + print_syscall_totals() + finally: + # Break the reference cycle between session and process_event (whose module + # globals reference session) since perf.session lacks cyclic GC (tp_traverse). + session = None diff --git a/tools/perf/python/task-analyzer.py b/tools/perf/python/task-analyzer.py new file mode 100755 index 000000000000..fedba36b5ced --- /dev/null +++ b/tools/perf/python/task-analyzer.py @@ -0,0 +1,908 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# task-analyzer.py - comprehensive perf tasks analysis +# Copyright (c) 2022, Hagen Paul Pfeifer +# Licensed under the terms of the GNU GPL License version 2 +# +# Usage: +# +# perf record -e sched:sched_switch -a -- sleep 10 +# ./task-analyzer.py +# +"""Comprehensive perf tasks analysis.""" +from __future__ import annotations + +import argparse +from contextlib import contextmanager +import decimal + +from typing import List, Dict, Union + +def _median(numbers: List[decimal.Decimal]) -> decimal.Decimal: + """phython3 hat statistics module - we have nothing""" + n = len(numbers) + index = n // 2 + if n % 2: + return sorted(numbers)[index] + return sum(sorted(numbers)[index - 1 : index + 1]) / decimal.Decimal(2) + +def _mean(numbers: List[decimal.Decimal]) -> decimal.Decimal: + return sum(numbers) / decimal.Decimal(len(numbers)) + +import os +import string +import sys +from typing import Any, Optional +import perf + + +# Columns will have a static size to align everything properly +# Support of 116 days of active update with nano precision +LEN_SWITCHED_IN = len("9999999.999999999") +LEN_SWITCHED_OUT = len("9999999.999999999") +LEN_CPU = len("000") +LEN_PID = len("maxvalue") +LEN_TID = len("maxvalue") +LEN_COMM = len("max-comms-length") +LEN_RUNTIME = len("999999.999") +# Support of 3.45 hours of timespans +LEN_OUT_IN = len("99999999999.999") +LEN_OUT_OUT = len("99999999999.999") +LEN_IN_IN = len("99999999999.999") +LEN_IN_OUT = len("99999999999.999") + +class Timespans: + """Tracks elapsed time between occurrences of the same task.""" + def __init__(self, args: argparse.Namespace, time_unit: str) -> None: + self.args = args + self.time_unit = time_unit + self._last_start: Optional[decimal.Decimal] = None + self._last_finish: Optional[decimal.Decimal] = None + self.current = { + 'out_out': decimal.Decimal(-1), + 'in_out': decimal.Decimal(-1), + 'out_in': decimal.Decimal(-1), + 'in_in': decimal.Decimal(-1) + } + if args.summary_extended: + self._time_in: decimal.Decimal = decimal.Decimal(-1) + self.max_vals = { + 'out_in': decimal.Decimal(-1), + 'at': decimal.Decimal(-1), + 'in_out': decimal.Decimal(-1), + 'in_in': decimal.Decimal(-1), + 'out_out': decimal.Decimal(-1) + } + + def feed(self, task: 'Task') -> None: + """Calculate timespans from chronological task occurrences.""" + if not self._last_finish: + self._last_start = task.time_in(self.time_unit) + self._last_finish = task.time_out(self.time_unit) + return + assert self._last_start is not None + assert self._last_finish is not None + self._time_in = task.time_in() + time_in = task.time_in(self.time_unit) + time_out = task.time_out(self.time_unit) + self.current['in_in'] = time_in - self._last_start + self.current['out_in'] = time_in - self._last_finish + self.current['in_out'] = time_out - self._last_start + self.current['out_out'] = time_out - self._last_finish + if self.args.summary_extended: + self.update_max_entries() + self._last_finish = task.time_out(self.time_unit) + self._last_start = task.time_in(self.time_unit) + + def update_max_entries(self) -> None: + """Update maximum timespans.""" + self.max_vals['in_in'] = max(self.max_vals['in_in'], self.current['in_in']) + self.max_vals['out_out'] = max(self.max_vals['out_out'], self.current['out_out']) + self.max_vals['in_out'] = max(self.max_vals['in_out'], self.current['in_out']) + if self.current['out_in'] > self.max_vals['out_in']: + self.max_vals['out_in'] = self.current['out_in'] + self.max_vals['at'] = self._time_in + +class Task: + """Handles information of a given task.""" + def __init__(self, task_id: str, tid: int, cpu: int, comm: str) -> None: + self.id = task_id + self.tid = tid + self.cpu = cpu + self.comm = comm + self.pid: Optional[int] = None + self._time_in: Optional[decimal.Decimal] = None + self._time_out: Optional[decimal.Decimal] = None + + def schedule_in_at(self, time_ns: int) -> None: + """Set schedule in time.""" + self._time_in = decimal.Decimal(time_ns) / decimal.Decimal(1e9) + + def schedule_out_at(self, time_ns: int) -> None: + """Set schedule out time.""" + self._time_out = decimal.Decimal(time_ns) / decimal.Decimal(1e9) + + def time_out(self, unit: str = "s") -> decimal.Decimal: + """Return schedule out time.""" + factor = TaskAnalyzer.time_uniter(unit) + return self._time_out * decimal.Decimal(factor) if self._time_out else decimal.Decimal(0) + + def time_in(self, unit: str = "s") -> decimal.Decimal: + """Return schedule in time.""" + factor = TaskAnalyzer.time_uniter(unit) + return self._time_in * decimal.Decimal(factor) if self._time_in else decimal.Decimal(0) + + def runtime(self, unit: str = "us") -> decimal.Decimal: + """Return runtime.""" + factor = TaskAnalyzer.time_uniter(unit) + if self._time_out is not None and self._time_in is not None: + return (self._time_out - self._time_in) * decimal.Decimal(factor) + return decimal.Decimal(0) + + def update_pid(self, pid: int) -> None: + """Update PID.""" + self.pid = pid + +class Summary: + """ + Primary instance for calculating the summary output. Processes the whole trace to + find and memorize relevant data such as mean, max et cetera. This instance handles + dynamic alignment aspects for summary output. + """ + + def __init__(self, analyzer): + self.analyzer = analyzer + self.args = analyzer.args + self.db = analyzer.db + self.time_unit = analyzer.time_unit + self.fd_sum = analyzer.fd_sum + self._body = [] + + class AlignmentHelper: + """ + Used to calculated the alignment for the output of the summary. + """ + def __init__(self, pid, tid, comm, runs, acc, mean, + median, min_val, max_val, max_at): + self.pid = pid + self.tid = tid + self.comm = comm + self.runs = runs + self.acc = acc + self.mean = mean + self.median = median + self.min = min_val + self.max = max_val + self.max_at = max_at + self.out_in = None + self.inter_at = None + self.out_out = None + self.in_in = None + self.in_out = None + + def _print_header(self): + ''' + Output is aligned using the formatted column widths in self.db. + ''' + len_tasks = max( + len("Task Information"), + sum(self.db["task_info"].values()) + len(self.db["task_info"]) - 1, + ) + fmt = "{{:^{}}}".format(len_tasks) + fmt += " {{:^{}}}".format( + sum(self.db["runtime_info"].values()) + len(self.db["runtime_info"]) - 1 + ) + _header = ("Task Information", "Runtime Information") + + if self.args.summary_extended: + fmt += " {{:^{}}}".format( + sum(self.db["inter_times"].values()) + len(self.db["inter_times"]) - 1 + ) + _header += ("Max Inter Task Times",) + self.fd_sum.write(fmt.format(*_header) + "\n") + + def _column_titles(self): + """ + Cells are being processed and displayed in different way so an alignment adjust + is implemented depeding on the choice of the timeunit. The positions of the max + values are being displayed in grey. Thus in their format two additional {}, + are placed for color set and reset. + """ + separator, fix_csv_align = self.analyzer.prepare_fmt_sep(is_summary=True) + fmt = "{{:>{}}}".format(self.db["task_info"]["pid"] * fix_csv_align) + fmt += "{}{{:>{}}}".format(separator, self.db["task_info"]["tid"] * fix_csv_align) + fmt += "{}{{:>{}}}".format(separator, self.db["task_info"]["comm"] * fix_csv_align) + fmt += "{}{{:>{}}}".format(separator, self.db["runtime_info"]["runs"] * fix_csv_align) + fmt += "{}{{:>{}}}".format(separator, self.db["runtime_info"]["acc"] * fix_csv_align) + fmt += "{}{{:>{}}}".format(separator, self.db["runtime_info"]["mean"] * fix_csv_align) + fmt += "{}{{:>{}}}".format( + separator, self.db["runtime_info"]["median"] * fix_csv_align + ) + fmt += "{}{{:>{}}}".format( + separator, self.db["runtime_info"]["min"] * fix_csv_align + ) + fmt += "{}{{:>{}}}".format( + separator, self.db["runtime_info"]["max"] * fix_csv_align + ) + fmt += "{}{{}}{{:>{}}}{{}}".format( + separator, self.db["runtime_info"]["max_at"] * fix_csv_align + ) + + grey = "" if self.args.csv_summary else TaskAnalyzer.COLORS["grey"] + reset = "" if self.args.csv_summary else TaskAnalyzer.COLORS["reset"] + column_titles = ("PID", "TID", "Comm") + column_titles += ("Runs", "Accumulated", "Mean", "Median", "Min", "Max") + column_titles += (grey, "Max At", reset) + + if self.args.summary_extended: + fmt += "{}{{:>{}}}".format( + separator, + self.db["inter_times"]["out_in"] * fix_csv_align + ) + fmt += "{}{{}}{{:>{}}}{{}}".format( + separator, + self.db["inter_times"]["inter_at"] * fix_csv_align + ) + fmt += "{}{{:>{}}}".format( + separator, + self.db["inter_times"]["out_out"] * fix_csv_align + ) + fmt += "{}{{:>{}}}".format( + separator, + self.db["inter_times"]["in_in"] * fix_csv_align + ) + fmt += "{}{{:>{}}}".format( + separator, + self.db["inter_times"]["in_out"] * fix_csv_align + ) + + column_titles += ( + "Out-In", grey, "Max At", + reset, "Out-Out", "In-In", "In-Out" + ) + + self.fd_sum.write(fmt.format(*column_titles) + "\n") + + def _task_stats(self): + """calculates the stats of every task and constructs the printable summary""" + grey = "" if self.args.csv_summary else TaskAnalyzer.COLORS["grey"] + reset = "" if self.args.csv_summary else TaskAnalyzer.COLORS["reset"] + for tid in sorted(self.db["tid"]): + color_one_sample = grey + color_reset = reset + no_executed = 0 + runtimes = [] + time_in = [] + timespans = Timespans(self.args, self.time_unit) + for task in self.db["tid"][tid]: + pid = task.pid + comm = task.comm + no_executed += 1 + runtimes.append(task.runtime(self.time_unit)) + time_in.append(task.time_in()) + timespans.feed(task) + if len(runtimes) > 1: + color_one_sample = "" + color_reset = "" + time_max = max(runtimes) + time_min = min(runtimes) + max_at = time_in[runtimes.index(max(runtimes))] + + # The size of the decimal after sum,mean and median varies, thus we cut + # the decimal number, by rounding it. It has no impact on the output, + # because we have a precision of the decimal points at the output. + time_sum = round(sum(runtimes), 3) + time_mean = round(_mean(runtimes), 3) + time_median = round(_median(runtimes), 3) + + align_helper = self.AlignmentHelper(pid, tid, comm, no_executed, time_sum, + time_mean, time_median, time_min, time_max, max_at) + self._body.append([ + pid, tid, comm, no_executed, time_sum, color_one_sample, + time_mean, time_median, time_min, time_max, + grey, max_at, + reset, color_reset + ]) + if self.args.summary_extended: + self._body[-1].extend([timespans.max_vals['out_in'], + grey, timespans.max_vals['at'], + reset, timespans.max_vals['out_out'], + timespans.max_vals['in_in'], + timespans.max_vals['in_out']]) + align_helper.out_in = timespans.max_vals['out_in'] + align_helper.inter_at = timespans.max_vals['at'] + align_helper.out_out = timespans.max_vals['out_out'] + align_helper.in_in = timespans.max_vals['in_in'] + align_helper.in_out = timespans.max_vals['in_out'] + self._calc_alignments_summary(align_helper) + + def _format_stats(self): + separator, fix_csv_align = self.analyzer.prepare_fmt_sep(is_summary=True) + decimal_precision, time_precision = self.analyzer.prepare_fmt_precision() + len_pid = self.db["task_info"]["pid"] * fix_csv_align + len_tid = self.db["task_info"]["tid"] * fix_csv_align + len_comm = self.db["task_info"]["comm"] * fix_csv_align + len_runs = self.db["runtime_info"]["runs"] * fix_csv_align + len_acc = self.db["runtime_info"]["acc"] * fix_csv_align + len_mean = self.db["runtime_info"]["mean"] * fix_csv_align + len_median = self.db["runtime_info"]["median"] * fix_csv_align + len_min = self.db["runtime_info"]["min"] * fix_csv_align + len_max = self.db["runtime_info"]["max"] * fix_csv_align + len_max_at = self.db["runtime_info"]["max_at"] * fix_csv_align + if self.args.summary_extended: + len_out_in = self.db["inter_times"]["out_in"] * fix_csv_align + len_inter_at = self.db["inter_times"]["inter_at"] * fix_csv_align + len_out_out = self.db["inter_times"]["out_out"] * fix_csv_align + len_in_in = self.db["inter_times"]["in_in"] * fix_csv_align + len_in_out = self.db["inter_times"]["in_out"] * fix_csv_align + + fmt = "{{:{}d}}".format(len_pid) + fmt += "{}{{:{}d}}".format(separator, len_tid) + fmt += "{}{{:>{}}}".format(separator, len_comm) + fmt += "{}{{:{}d}}".format(separator, len_runs) + fmt += "{}{{:{}.{}f}}".format(separator, len_acc, time_precision) + fmt += "{}{{}}{{:{}.{}f}}".format(separator, len_mean, time_precision) + fmt += "{}{{:{}.{}f}}".format(separator, len_median, time_precision) + fmt += "{}{{:{}.{}f}}".format(separator, len_min, time_precision) + fmt += "{}{{:{}.{}f}}".format(separator, len_max, time_precision) + fmt += "{}{{}}{{:{}.{}f}}{{}}{{}}".format( + separator, len_max_at, decimal_precision + ) + if self.args.summary_extended: + fmt += "{}{{:{}.{}f}}".format(separator, len_out_in, time_precision) + fmt += "{}{{}}{{:{}.{}f}}{{}}".format( + separator, len_inter_at, decimal_precision + ) + fmt += "{}{{:{}.{}f}}".format(separator, len_out_out, time_precision) + fmt += "{}{{:{}.{}f}}".format(separator, len_in_in, time_precision) + fmt += "{}{{:{}.{}f}}".format(separator, len_in_out, time_precision) + return fmt + + def _calc_alignments_summary(self, align_helper): + # Measure the formatted string widths (e.g. f"{val:.{decimal_precision}f}" + # and f"{val:.{time_precision}f}") rather than str(val), because Decimal's + # default string representation may include unrounded fractional digits or + # omit trailing zeros that _format_stats() renders with fixed precision. + decimal_precision, time_precision = self.analyzer.prepare_fmt_precision() + for key in self.db["task_info"]: + val_len = len(str(getattr(align_helper, key))) + if val_len > self.db["task_info"][key]: + self.db["task_info"][key] = val_len + for key in self.db["runtime_info"]: + val = getattr(align_helper, key) + if key == "runs": + val_len = len(str(val)) + elif key == "max_at": + val_len = len(f"{val:.{decimal_precision}f}") + else: + val_len = len(f"{val:.{time_precision}f}") + if val_len > self.db["runtime_info"][key]: + self.db["runtime_info"][key] = val_len + if self.args.summary_extended: + for key in self.db["inter_times"]: + val = getattr(align_helper, key) + if key == "inter_at": + val_len = len(f"{val:.{decimal_precision}f}") + else: + val_len = len(f"{val:.{time_precision}f}") + if val_len > self.db["inter_times"][key]: + self.db["inter_times"][key] = val_len + + def print(self): + self._task_stats() + fmt = self._format_stats() + + if not self.args.csv_summary: + print("\nSummary", file=self.fd_sum) + self._print_header() + self._column_titles() + for i in range(len(self._body)): + self.fd_sum.write(fmt.format(*tuple(self._body[i])) + "\n") + + +class TaskAnalyzer: + + """Main class for task analysis.""" + + COLORS = { + "grey": "\033[90m", + "red": "\033[91m", + "green": "\033[92m", + "yellow": "\033[93m", + "blue": "\033[94m", + "violet": "\033[95m", + "reset": "\033[0m", + } + + def __init__(self, args: argparse.Namespace) -> None: + self.args = args + self.db: Dict[str, Any] = {} + self.session: Optional[perf.session] = None + self._tgid_cache: Dict[int, int] = {} + self.time_unit = "us" + if args.ns: + self.time_unit = "ns" + elif args.ms: + self.time_unit = "ms" + self._init_db() + self._check_color() + self.fd_task = sys.stdout + self.fd_sum = sys.stdout + + @contextmanager + def open_output(self, filename: str, default: Any): + """Context manager for file or stdout.""" + if filename: + with open(filename, "w", encoding="utf-8") as f: + yield f + else: + yield default + + def _init_db(self) -> None: + self.db["running"] = {} + self.db["tid"] = {} + self.db["global"] = [] + if (self.args.summary or self.args.summary_extended or + self.args.summary_only or self.args.csv_summary): + self.db["task_info"] = {} + self.db["runtime_info"] = {} + self.db["task_info"]["pid"] = len("PID") + self.db["task_info"]["tid"] = len("TID") + self.db["task_info"]["comm"] = len("Comm") + self.db["runtime_info"]["runs"] = len("Runs") + self.db["runtime_info"]["acc"] = len("Accumulated") + self.db["runtime_info"]["max"] = len("Max") + self.db["runtime_info"]["max_at"] = len("Max At") + self.db["runtime_info"]["min"] = len("Min") + self.db["runtime_info"]["mean"] = len("Mean") + self.db["runtime_info"]["median"] = len("Median") + if self.args.summary_extended: + self.db["inter_times"] = {} + self.db["inter_times"]["out_in"] = len("Out-In") + self.db["inter_times"]["inter_at"] = len("Max At") + self.db["inter_times"]["out_out"] = len("Out-Out") + self.db["inter_times"]["in_in"] = len("In-In") + self.db["inter_times"]["in_out"] = len("In-Out") + + def _check_color(self) -> None: + """Check if color should be enabled.""" + if self.args.csv: + TaskAnalyzer.COLORS = {k: "" for k in TaskAnalyzer.COLORS} + return + if sys.stdout.isatty() and self.args.stdio_color != "never": + return + if self.args.stdio_color == "always": + return + TaskAnalyzer.COLORS = {k: "" for k in TaskAnalyzer.COLORS} + + @staticmethod + def time_uniter(unit: str) -> float: + """Return time unit factor.""" + picker = {"s": 1, "ms": 1e3, "us": 1e6, "ns": 1e9} + return picker[unit] + + def _task_id(self, pid: int, cpu: int) -> str: + return f"{pid}-{cpu}" + + def _filter_non_printable(self, unfiltered: Union[str, bytearray]) -> str: + # Strip non-printable characters, whitespace control characters + # (\r\n\t\x0b\x0c), quotes (", '), and ';' because --csv and + # --csv-summary emit unquoted ';'-delimited columns. Also replace a + # leading '=', '+', '-', or '@' with '_' to mitigate CSV formula / DDE + # injection attacks if an attacker-controlled process comm is opened in + # a spreadsheet program. + if isinstance(unfiltered, (bytearray, bytes)): + unfiltered = unfiltered.decode('utf-8', 'ignore') + filtered = "" + for char in unfiltered: + if char in string.printable and char not in "\r\n\t\x0b\x0c;\"'": + filtered += char + stripped = filtered.lstrip() + if stripped and stripped[0] in "=+-@": + filtered = "_" + stripped[1:] + return filtered + + def prepare_fmt_precision(self) -> tuple[int, int]: + if self.args.ns: + return 9, 0 + return 6, 3 + + def prepare_fmt_sep(self, is_summary: bool = False) -> tuple[str, int]: + # fix_csv_align multiplies format field widths: 0 in CSV mode collapses + # {:>N} padding to {:>0} (no extra spaces around ';'), while 1 in + # standard text mode preserves fixed-width column alignment. + if (is_summary and self.args.csv_summary) or (not is_summary and self.args.csv): + return ";", 0 + return " ", 1 + + def _fmt_header(self) -> str: + separator, fix_csv_align = self.prepare_fmt_sep() + fmt = f"{{:>{LEN_SWITCHED_IN*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_SWITCHED_OUT*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_CPU*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_PID*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_TID*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_COMM*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_RUNTIME*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_OUT_IN*fix_csv_align}}}" + if self.args.extended_times: + fmt += f"{separator}{{:>{LEN_OUT_OUT*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_IN_IN*fix_csv_align}}}" + fmt += f"{separator}{{:>{LEN_IN_OUT*fix_csv_align}}}" + return fmt + + def _fmt_body(self) -> str: + separator, fix_csv_align = self.prepare_fmt_sep() + decimal_precision, time_precision = self.prepare_fmt_precision() + fmt = f"{{}}{{:{LEN_SWITCHED_IN*fix_csv_align}.{decimal_precision}f}}" + fmt += f"{separator}{{:{LEN_SWITCHED_OUT*fix_csv_align}.{decimal_precision}f}}" + fmt += f"{separator}{{:{LEN_CPU*fix_csv_align}d}}" + fmt += f"{separator}{{:{LEN_PID*fix_csv_align}d}}" + fmt += f"{separator}{{}}{{:{LEN_TID*fix_csv_align}d}}{{}}" + fmt += f"{separator}{{}}{{:>{LEN_COMM*fix_csv_align}}}" + fmt += f"{separator}{{:{LEN_RUNTIME*fix_csv_align}.{time_precision}f}}" + if self.args.extended_times: + fmt += f"{separator}{{:{LEN_OUT_IN*fix_csv_align}.{time_precision}f}}" + fmt += f"{separator}{{:{LEN_OUT_OUT*fix_csv_align}.{time_precision}f}}" + fmt += f"{separator}{{:{LEN_IN_IN*fix_csv_align}.{time_precision}f}}" + fmt += f"{separator}{{:{LEN_IN_OUT*fix_csv_align}.{time_precision}f}}{{}}" + else: + fmt += f"{separator}{{:{LEN_OUT_IN*fix_csv_align}.{time_precision}f}}{{}}" + return fmt + + def _print_header(self) -> None: + fmt = self._fmt_header() + header = ["Switched-In", "Switched-Out", "CPU", "PID", "TID", "Comm", + "Runtime", "Time Out-In"] + if self.args.extended_times: + header += ["Time Out-Out", "Time In-In", "Time In-Out"] + self.fd_task.write(fmt.format(*header) + "\n") + + def _print_task_finish(self, task: Task) -> None: + c_row_set = "" + c_row_reset = "" + out_in: Any = -1 + out_out: Any = -1 + in_in: Any = -1 + in_out: Any = -1 + fmt = self._fmt_body() + + if str(task.tid) in self.args.highlight_tasks_map: + c_row_set = TaskAnalyzer.COLORS.get(self.args.highlight_tasks_map[str(task.tid)], '') + c_row_reset = TaskAnalyzer.COLORS["reset"] + if task.comm in self.args.highlight_tasks_map: + c_row_set = TaskAnalyzer.COLORS.get(self.args.highlight_tasks_map[task.comm], '') + c_row_reset = TaskAnalyzer.COLORS["reset"] + + c_tid_set = "" + c_tid_reset = "" + if task.pid == task.tid: + c_tid_set = TaskAnalyzer.COLORS["grey"] + c_tid_reset = TaskAnalyzer.COLORS["reset"] + + if task.tid in self.db["tid"]: + last_tid_task = self.db["tid"][task.tid][-1] + timespan_gap_tid = Timespans(self.args, self.time_unit) + timespan_gap_tid.feed(last_tid_task) + timespan_gap_tid.feed(task) + out_in = timespan_gap_tid.current['out_in'] + out_out = timespan_gap_tid.current['out_out'] + in_in = timespan_gap_tid.current['in_in'] + in_out = timespan_gap_tid.current['in_out'] + + if self.args.extended_times: + line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu, + task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm, + task.runtime(self.time_unit), out_in, out_out, in_in, in_out, + c_row_reset) + "\n" + else: + line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu, + task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm, + task.runtime(self.time_unit), out_in, c_row_reset) + "\n" + self.fd_task.write(line_out) + + def _record_cleanup(self, _list: list[Any]) -> list[Any]: + need_summary = (self.args.summary or self.args.summary_extended or + self.args.summary_only or self.args.csv_summary) + if not need_summary and len(_list) > 1: + return _list[len(_list) - 1:] + return _list + + def _record_by_tid(self, task: Task) -> None: + tid = task.tid + if tid not in self.db["tid"]: + self.db["tid"][tid] = [] + self.db["tid"][tid].append(task) + self.db["tid"][tid] = self._record_cleanup(self.db["tid"][tid]) + + def _record_global(self, task: Task) -> None: + self.db["global"].append(task) + self.db["global"] = self._record_cleanup(self.db["global"]) + + def _handle_task_finish(self, tid: int, cpu: int, time_ns: int, pid: int) -> None: + if tid == 0 and (not self.args.tid_renames or 0 not in self.args.tid_renames): + return + _id = self._task_id(tid, cpu) + if _id not in self.db["running"]: + return + task = self.db["running"][_id] + task.schedule_out_at(time_ns) + task.update_pid(pid) + del self.db["running"][_id] + + if not self._limit_filtered(tid, pid, task.comm): + if not self.args.summary_only: + self._print_task_finish(task) + self._record_by_tid(task) + self._record_global(task) + + def _handle_task_start(self, tid: int, cpu: int, comm: str, time_ns: int) -> None: + if tid == 0 and (not self.args.tid_renames or 0 not in self.args.tid_renames): + return + if tid in self.args.tid_renames: + comm = self._filter_non_printable(self.args.tid_renames[tid]) + _id = self._task_id(tid, cpu) + if _id in self.db["running"]: + return + task = Task(_id, tid, cpu, comm) + task.schedule_in_at(time_ns) + self.db["running"][_id] = task + + def _limit_filtered(self, tid: int, pid: int, comm: str) -> bool: + """Filter tasks based on CLI arguments.""" + match_filter = False + if self.args.filter_tasks: + if (str(tid) in self.args.filter_tasks or + str(pid) in self.args.filter_tasks or + comm in self.args.filter_tasks): + match_filter = True + + match_limit = False + if self.args.limit_to_tasks: + if (str(tid) in self.args.limit_to_tasks or + str(pid) in self.args.limit_to_tasks or + comm in self.args.limit_to_tasks): + match_limit = True + + if self.args.filter_tasks and match_filter: + return True + if self.args.limit_to_tasks and not match_limit: + return True + return False + + def _is_within_timelimit(self, time_ns: int) -> bool: + if not self.args.time_limit: + return True + time_s = decimal.Decimal(time_ns) / decimal.Decimal(1e9) + bounds = self.args.time_limit.split(":") + lower_bound = bounds[0] if len(bounds) > 0 else "" + upper_bound = bounds[1] if len(bounds) > 1 else "" + if lower_bound and time_s < decimal.Decimal(lower_bound): + return False + if upper_bound and time_s > decimal.Decimal(upper_bound): + return False + return True + + def process_event(self, sample: perf.sample_event) -> None: + """Process sched:sched_switch events.""" + if "sched:sched_switch" not in str(sample.evsel): + return + + time_ns = sample.sample_time + if not self._is_within_timelimit(time_ns): + return + + # Access tracepoint fields directly from sample object + try: + prev_pid = sample.prev_pid + next_pid = sample.next_pid + next_comm = sample.next_comm + common_cpu = sample.sample_cpu + except AttributeError: + if not self.db.get("warned_missing_fields"): + self.db["warned_missing_fields"] = True + print("Warning: sched:sched_switch sample missing tracepoint fields " + "(is libtraceevent support enabled?).", file=sys.stderr) + return + + next_comm = self._filter_non_printable(next_comm) + + # Task finish for previous task. Resolve TGID (process ID) from TID: + # in file mode we query perf.session.find_thread() (checking thread.pid > 0 + # so an unresolved -1 does not overwrite the prev_pid fallback), whereas in + # live mode (self.session is None) we read /proc//status and cache + # the lookup in _tgid_cache. + prev_tgid = prev_pid # Fallback + if prev_pid != 0: + if self.session: + try: + thread = self.session.find_thread(-1, prev_pid) + if thread and thread.pid > 0: + prev_tgid = thread.pid + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + pass + elif prev_pid in self._tgid_cache: + prev_tgid = self._tgid_cache[prev_pid] + else: + if len(self._tgid_cache) >= 4096: + self._tgid_cache.pop(next(iter(self._tgid_cache))) + self._tgid_cache[prev_pid] = prev_tgid + try: + with open(f"/proc/{prev_pid}/status", encoding="utf-8") as f: + for line in f: + if line.startswith("Tgid:"): + prev_tgid = int(line.split()[1]) + self._tgid_cache[prev_pid] = prev_tgid + break + except (OSError, ValueError, KeyError, RuntimeError): + pass + self._handle_task_finish(prev_pid, common_cpu, time_ns, prev_tgid) + # Task start for next task + self._handle_task_start(next_pid, common_cpu, next_comm, time_ns) + + def print_summary(self) -> None: + """Calculate and print summary.""" + need_summary = (self.args.summary or self.args.summary_extended or + self.args.summary_only or self.args.csv_summary) + if not need_summary: + return + + Summary(self).print() + + def _run_file(self) -> None: + if not self.args.summary_only: + self._print_header() + + session = perf.session(perf.data(self.args.input), sample=self.process_event) + self.session = session + try: + session.process_events() + except KeyboardInterrupt: + pass + finally: + # Break the reference cycle between self.session and the bound + # self.process_event callback so the C perf.session object is freed. + self.session = None + + if not self.db["global"]: + print(f"Warning: No sched:sched_switch trace events found in '{self.args.input}'.", + file=sys.stderr) + + self.print_summary() + + def _run_live(self) -> None: + if not self.args.summary_only: + self._print_header() + + cpus = perf.cpu_map() + threads = perf.thread_map(-1) + evlist = perf.parse_events("sched:sched_switch", cpus, threads) + evlist.config() + + evlist.open() + evlist.mmap() + evlist.enable() + + # Per-CPU perf ring buffers are drained sequentially in a loop, so events + # read from CPU N may have timestamps slightly earlier than events just + # read from CPU 0. Buffer events in pending_events and only dispatch those + # older than a 50 ms watermark behind max_ts so cross-CPU migrations and + # switches are processed in global timestamp order. + pending_events: list[perf.sample_event] = [] + print("Live mode started. Press Ctrl+C to stop.", file=sys.stderr) + try: + while True: + try: + evlist.poll(timeout=100) + except InterruptedError: + continue + for cpu in cpus: + while True: + event = evlist.read_on_cpu(cpu) + if not event: + break + if not isinstance(event, perf.sample_event): + continue + pending_events.append(event) + if pending_events: + pending_events.sort( + key=lambda e: getattr(e, 'sample_time', getattr(e, 'time', 0)) + ) + max_ts = getattr( + pending_events[-1], 'sample_time', getattr(pending_events[-1], 'time', 0) + ) + cutoff_ts = max_ts - 50_000_000 + ready_idx = 0 + for event in pending_events: + ev_ts = getattr(event, 'sample_time', getattr(event, 'time', 0)) + if ev_ts <= cutoff_ts: + self.process_event(event) + ready_idx += 1 + else: + break + del pending_events[:ready_idx] + except KeyboardInterrupt: + print("\nStopping live mode...", file=sys.stderr) + finally: + pending_events.sort(key=lambda e: getattr(e, 'sample_time', getattr(e, 'time', 0))) + for event in pending_events: + self.process_event(event) + evlist.close() + self.print_summary() + + def run(self) -> None: + """Run the session.""" + is_live = (self.args.live or + (not os.path.exists(self.args.input) and self.args.input == "perf.data")) + with self.open_output(self.args.csv, sys.stdout) as fd_task: + if self.args.csv and self.args.csv == self.args.csv_summary: + self.fd_task = fd_task + self.fd_sum = fd_task + if is_live: + self._run_live() + else: + self._run_file() + else: + with self.open_output(self.args.csv_summary, sys.stdout) as fd_sum: + self.fd_task = fd_task + self.fd_sum = fd_sum + if is_live: + self._run_live() + else: + self._run_file() + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Analyze tasks behavior") + parser.add_argument("-i", "--input", default="perf.data", help="Input file name") + parser.add_argument("--time-limit", default="", help="print tasks only in time window") + parser.add_argument("--summary", action="store_true", + help="print additional runtime information") + parser.add_argument("--summary-only", action="store_true", + help="print only summary without traces") + parser.add_argument("--summary-extended", action="store_true", + help="print extended summary") + parser.add_argument("--ns", action="store_true", help="show timestamps in nanoseconds") + parser.add_argument("--ms", action="store_true", help="show timestamps in milliseconds") + parser.add_argument("--live", action="store_true", + help="force live mode (ignores -i/perf.data)") + parser.add_argument("--extended-times", action="store_true", + help="Show elapsed times between schedule in/out") + parser.add_argument("--filter-tasks", default="", help="filter tasks by tid, pid or comm") + parser.add_argument("--limit-to-tasks", default="", help="limit output to selected tasks") + parser.add_argument("--highlight-tasks", default="", help="colorize special tasks") + parser.add_argument("--rename-comms-by-tids", default="", help="rename task names by using tid") + parser.add_argument("--stdio-color", default="auto", choices=["always", "never", "auto"], + help="configure color output") + parser.add_argument("--csv", default="", help="Write trace to file") + parser.add_argument("--csv-summary", default="", help="Write summary to file") + + args = parser.parse_args() + args.tid_renames = {} + args.highlight_tasks_map = {} + args.filter_tasks = args.filter_tasks.split(",") if args.filter_tasks else [] + args.limit_to_tasks = args.limit_to_tasks.split(",") if args.limit_to_tasks else [] + + if args.rename_comms_by_tids: + for item in args.rename_comms_by_tids.split(","): + try: + tid, name = item.split(":", 1) + args.tid_renames[int(tid)] = name + except ValueError: + print(f"Error: Invalid format for --rename-comms-by-tids '{item}', " + "expected tid:name", file=sys.stderr) + sys.exit(1) + + if args.highlight_tasks: + for item in args.highlight_tasks.split(","): + parts = item.split(":") + if len(parts) == 1: + parts.append("red") + key, color = parts[0], parts[1] + args.highlight_tasks_map[key] = color + + analyzer = TaskAnalyzer(args) + analyzer.run() + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/tracepoint.py b/tools/perf/python/tracepoint.py index 15b0c8268996..faa5311b8418 100755 --- a/tools/perf/python/tracepoint.py +++ b/tools/perf/python/tracepoint.py @@ -1,5 +1,6 @@ #! /usr/bin/env python # SPDX-License-Identifier: GPL-2.0 +"""Example showing how to enable a tracepoint and access its fields.""" # -*- python -*- # -*- coding: utf-8 -*- @@ -9,7 +10,7 @@ def change_proctitle(): try: import setproctitle setproctitle.setproctitle("tracepoint.py") - except: + except ImportError: print("Install the setproctitle python package to help with top and friends") def main(): @@ -29,7 +30,7 @@ def main(): evlist.open() evlist.mmap() - evlist.enable(); + evlist.enable() while True: evlist.poll(timeout = -1) diff --git a/tools/perf/python/treport.py b/tools/perf/python/treport.py new file mode 100755 index 000000000000..fb583786b17a --- /dev/null +++ b/tools/perf/python/treport.py @@ -0,0 +1,807 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +"""treport.py - perf report like tool written using textual.""" +from abc import ABC, abstractmethod +from typing import Callable, Dict, List, Optional, Set +import argparse +import os +import sys +import threading +import time +import perf +from rich.markup import escape +from rich.segment import Segment +from rich.style import Style +from textual import events, work +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.color import Color +from textual.scroll_view import ScrollView +from textual.strip import Strip +from textual.widgets import Footer, Header, TabbedContent, TabPane, Tree +from textual.widgets.tree import TreeNode + +def make_fixed_length_string(s: str, length: int, pad_char=' '): + """Make the string s a fixed length. + + Increases or decreases the length of s to be length. If the length is + increased then pad_char is inserted on the right. + """ + return s[:length] if len(s) > length else s.ljust(length, pad_char) + + +class FlameVisitor(ABC): + """Parent for visitor used by ProfileNode.flame_walk""" + @abstractmethod + def visit(self, node: Optional["ProfileNode"], width: int) -> None: + """Visit a profile node width the specified flame graph width. + + Args: + node: The `ProfileNode` for the current segment. This may be `None` + to represent a gap or an unknown portion of the stack. + width: The calculated width of the flame graph rectangle for this + node, which is proportional to its sample count. + """ + + +class ProfileNode: + """Represents a single node in a call stack tree. + + Generally a ProfileNode corresponds to a symbol in a call stack. + The root is special, its children are events and the events + children are process names. After the process name come the + samples. + + Attributes: + name (str): The name of the function, process or event. + value (int): The sample count for this node including counts from its + children. + parent (ProfileNode): The parent of this node, this node belongs to its + children. + children (Dict[str, ProfileNode]): A dictionary of child nodes, keyed by + their names. + """ + def __init__(self, name: str, parent: Optional["ProfileNode"]): + """Initializes a ProfileNode.""" + self.name = name + self.value: int = 0 + self.parent = parent if parent else self + self.children: Dict[str, ProfileNode] = {} + + def find_or_create_node(self, name: str) -> "ProfileNode": + """Finds a child node by name or creates it if it doesn't exist.""" + if name in self.children: + return self.children[name] + child = ProfileNode(name, self) + self.children[name] = child + return child + + def depth(self) -> int: + """The maximum depth of the call stack tree from this node down.""" + if not self.children: + return 1 + return max(child.depth() for child in self.children.values()) + 1 + + def process_event(self, sample, comm: str) -> None: + """Processes a single profiling event to update the call stack tree. + + Args: + sample: a single profiling sample. + comm: the command name of the sampled thread. + """ + period = sample.sample_period + self.value += period + + node = self.find_or_create_node(comm) + node.value += period + + if sample.callchain: + for entry in reversed(sample.callchain): + name = entry.symbol + if not name or name == "[unknown]": + name = entry.dso or "unknown" + if entry.ip: + name += f" 0x{entry.ip:x}" + node = node.find_or_create_node(name) + node.value += period + else: + name = sample.symbol + if not name or name == "[unknown]": + name = sample.dso or "unknown" + if sample.sample_ip: + name += f" 0x{sample.sample_ip:x}" + node = node.find_or_create_node(name) + node.value += period + + def sorted_children(self) -> List["ProfileNode"]: + """The children, largest value first. + + The root's children are events whose values aren't comparable, each is + 100% of itself, so they are kept in the order they were first seen + rather than reordering as they load. + """ + if self.parent is self: + return list(self.children.values()) + return sorted(self.children.values(), key=lambda pnode: pnode.value, reverse=True) + + def tree_label(self, root_value: int) -> str: + """Label for the node in a Tree, with a percentage of root_value. + + The percentage is highlighted with reversed colors. + """ + if root_value == 0: + return escape(self.name) + return f"{escape(self.name)} [r]{self.value / root_value * 100:.3g}%[/]" + + def largest_child(self) -> "ProfileNode": + """Finds the child with the highest value (sample count).""" + if self.children: + return self.sorted_children()[0] + return self + + def child_after(self, sought: "ProfileNode") -> "ProfileNode": + """Finds the next sibling after the given node, sorted by value.""" + found = False + for child in self.sorted_children(): + if child == sought: + found = True + elif found: + return child + return sought + + def child_before(self, sought: "ProfileNode") -> "ProfileNode": + """Finds the previous sibling before the given node, sorted by value.""" + last = None + for child in self.sorted_children(): + if child == sought: + return last if last else sought + last = child + return sought + + def has_parent(self, parent: "ProfileNode") -> bool: + """Checks if the parent node is an ancestor of this node.""" + p = self.parent + while True: + if p == parent: + return True + new_p = p.parent + if new_p == p: + break + p = new_p + return False + + def has_child(self, sought: "ProfileNode") -> bool: + """Checks if the sought node is a descendant of this node.""" + return sought.has_parent(self) + + def flame_walk(self, wanted_strip: int, cur_strip: int, parent_width: int, + selected: "ProfileNode", visitor: FlameVisitor) -> None: + """Recursively walks the tree to visit a single flame graph row. + + This method calculates the proportional width for each child + based on its value (sample count) relative to its parent. It + then invokes a `visitor` to process each segment of the flame + graph row. + + Args: + wanted_strip (int): The target depth (Y-axis) of the flame graph row + to generate. + cur_strip (int): The current depth of the traversal. + parent_width (int): The width of the parent of this node. + selected (ProfileNode): The currently selected node in the UI, used + to adjust rendering to highlight the + selected path. + visitor (FlameVisitor): A visitor object whose `visit` method is + called for each segment of the flame graph + row. + """ + if parent_width == 0: + return + + parent_selected = selected == self or self.has_parent(selected) + child_selected = not parent_selected and self.has_child(selected) + if not parent_selected and not child_selected: + # Branches of the tree with no node selected aren't drawn. + return + + # left_over is used to check for a gap after the children due + # to samples being in the parent. + left_over = parent_width + for child in self.sorted_children(): + if parent_selected: + if self.value: + desired_width = int((parent_width * child.value) / self.value) + else: + desired_width = parent_width // len(self.children) + if desired_width == 0: + # Nothing can be drawn for this node or later smaller children. + break + elif child == selected or child.has_child(selected): + desired_width = parent_width + else: + # A sibling or its child are selected, but not this branch. + continue + + # Either visit the wanted_strip or recurse to the next level. + if wanted_strip == cur_strip: + visitor.visit(child, desired_width) + else: + child.flame_walk(wanted_strip, cur_strip + 1, desired_width, + selected, visitor) + left_over -= desired_width + if left_over == 0: + # No space left to draw in. + break + + # Always visit the left_over regardless of the wanted_strip as there + # may be additional gap added to a line by a parent. + if left_over: + visitor.visit(None, left_over) + + def make_flame_strip(self, wanted_strip: int, parent_width: int, + cursor: "ProfileNode", selected: "ProfileNode", + theme_variables: Dict[str, str]) -> Strip: + """Creates a renderable 'Strip' for a single row of a flame graph. + + This method orchestrates the `flame_walk` traversal with a specialized + visitor to generate a list of segments. The segments are used by a`Strip` + object for rendering in the terminal. + + Args: + wanted_strip (int): The target depth (Y-axis) of the flame graph row. + parent_width (int): The total width (in characters) of the display + area. + cursor (ProfileNode): The node currently under the cursor, for + highlighting. + selected (ProfileNode): The node that is actively selected. + theme_variables(Dict): Values of colors for the textual theme. + + Returns: + Strip: A renderable strip of segments for the specified row. + """ + primary = Color.parse(theme_variables["primary"]) + secondary = Color.parse(theme_variables["secondary"]) + surface = Color.parse(theme_variables["surface"]) + def luminance(color: Color) -> float: + """Computes the luminance of a color from the rgb""" + return color.r * 0.299 + color.g * 0.587 + color.b * 0.114 + + # Set of styles for different flamegraph segments, the styles are + # cycled through to provide contrast. + normal_styles = [] + for x in range(0, 125, 25): + fgcolor = secondary.blend(primary, x/100) + if luminance(fgcolor) > luminance(surface): + bgcolor = surface.lighten(0.05+x/500) + else: + bgcolor = surface.darken(0.05+x/500) + normal_styles.append(Style(color=fgcolor.rich_color, + bgcolor=bgcolor.rich_color)) + + # Style for the selected flame graph node. + accent = Color.parse(theme_variables["accent"]) + accent_muted = Color.parse(theme_variables["accent-muted"]) + cursor_style = Style(color=accent.rich_color, bgcolor=accent_muted.rich_color) + + class StripVisitor(FlameVisitor): + """Visitor creating textual flame graph segments. + + Attributes: + segments (list): The textual segments that will be placed in a + `Strip`. + gap_width (int): The width of any outstanding gap between the + last and next node. + ctr (int): Used to adjust the flame graph segment's color. + """ + def __init__(self): + self.segments = [] + self.gap_width = 0 + self.ctr = wanted_strip + + def visit(self, node: Optional[ProfileNode], width: int) -> None: + if node: + if self.gap_width > 0: + self.segments.append(Segment( + make_fixed_length_string(" ", self.gap_width))) + self.gap_width = 0 + style = cursor_style + if node != cursor: + style = normal_styles[self.ctr % len(normal_styles)] + self.segments.append(Segment( + make_fixed_length_string(node.name, width), style)) + else: + self.gap_width += width + self.ctr += 1 + + visitor = StripVisitor() + self.flame_walk(wanted_strip, 0, parent_width, selected, visitor) + return Strip(visitor.segments) if visitor.segments else Strip.blank(parent_width) + + def find_node(self, sought_x: int, sought_y: int, parent_width: int, + selected: "ProfileNode") -> "ProfileNode": + """Finds the ProfileNode corresponding to specific X, Y coordinates. + + This translates a mouse click on a flame graph back to the + `ProfileNode` that it represents. + + Args: + sought_x (int): The X coordinate (character column). + sought_y (int): The Y coordinate (row or depth). + parent_width (int): The total width of the display area. + selected (ProfileNode): The currently selected node, which affects + layout. + + Returns: + Optional[ProfileNode]: The node found at the coordinates, or None. + + """ + class FindVisitor(FlameVisitor): + """Visitor locating a `ProfileNode`. + + Attributes: + x (int): offset within line. + found (Optional[ProfileNode]): located node + gap_width (int): The width of any outstanding gap between the + last and next node. + ctr (int): Used to adjust the flame graph segment's color. + """ + def __init__(self): + self.x = 0 + self.found = None + + def visit(self, node: Optional[ProfileNode], width: int) -> None: + if self.x <= sought_x < self.x + width: + self.found = node + self.x += width + + visitor = FindVisitor() + self.flame_walk(sought_y, 0, parent_width, selected, visitor) + return visitor.found + + +class FlameGraph(ScrollView): + """A scrollable widget to display a flame graph from a profile. + + Attributes: + root (ProfileNode): Root of the profile tree. + cursor (ProfileNode): Currently highlighted cursor node. + selected (ProfileNode): The currently selected node for zooming. + """ + + # Define key bindings for navigating the flame graph. + # Allows movement with vim-style keys (h,j,k,l) and arrow keys. + BINDINGS = [ + Binding("j,down", "move_down", "Down", key_display="↓", + tooltip="Move cursor down to largest child"), + Binding("k,up", "move_up", "Up", key_display="↑", + tooltip="Move cursor up to parent"), + Binding("l,right", "move_right", "Right", key_display="→", + tooltip="Move cursor to the right sibling"), + Binding("h,left", "move_left", "Left", key_display="←", + tooltip="Move cursor to the left sibling"), + Binding("enter", "zoom_in", "Zoom In", + tooltip="Expand the cursor's node to be screen width"), + Binding("escape", "zoom_out", "Zoom Out", + tooltip="Zoom out to initial view."), + ] + + # Default CSS for the widget to ensure it fills its container's width. + DEFAULT_CSS = """ + FlameGraph { + width: 100%; + } + """ + + def __init__(self, root: ProfileNode, lock: threading.Lock, *pos_args, **kwargs): + """Initialize the FlameGraph widget. + + The lock must be held when reading the profile as it may be being built + in another thread. + """ + super().__init__(*pos_args, **kwargs) + self.root = root + self.profile_lock = lock + self.cursor = root + self.selected = root + + def action_move_down(self) -> None: + """Handle key press down.""" + with self.profile_lock: + self.cursor = self.cursor.largest_child() + self.refresh() + + def action_move_up(self) -> None: + """Handle key press up.""" + if self.cursor.parent != self.cursor.parent.parent: + self.cursor = self.cursor.parent + self.refresh() + + def action_move_right(self) -> None: + """Handle key press right.""" + with self.profile_lock: + self.cursor = self.cursor.parent.child_after(self.cursor) + self.refresh() + + def action_move_left(self) -> None: + """Handle key press left.""" + with self.profile_lock: + self.cursor = self.cursor.parent.child_before(self.cursor) + self.refresh() + + def action_zoom_in(self) -> None: + """Handle key press zoom in.""" + self.selected = self.cursor + self.refresh() + + def action_zoom_out(self) -> None: + """Handle key press zoom out.""" + self.selected = self.root + self.refresh() + + def render_line(self, y: int) -> Strip: + """Render a single line (row) of the flame graph.""" + _, scroll_y = self.scroll_offset + y += scroll_y + with self.profile_lock: + return self.root.make_flame_strip(y, self.size.width, self.cursor, + self.selected, self.app.theme_variables) + + def profile_changed(self) -> None: + """Resize and redraw after the profile changed.""" + with self.profile_lock: + self.styles.height = self.root.depth() + self.refresh() + + def on_mount(self) -> None: + """Set the height of the widget when it is displayed.""" + self.profile_changed() + + def on_click(self, click: events.Click) -> None: + """Handles a mouse click and update the cursor position.""" + _, scroll_y = self.scroll_offset + y = scroll_y + click.y + with self.profile_lock: + clicked_node = self.root.find_node(click.x, y, self.size.width, + self.selected) + if clicked_node: + self.cursor = clicked_node + self.refresh() + + +class ProfileTree(Tree): + """A tree view of the profile that can be updated while it is built. + + Tree nodes are created lazily when expanded, the data of each tree node is + its ProfileNode. The lock must be held when reading the profile. + """ + + def __init__(self, root: ProfileNode, lock: threading.Lock, *pos_args, **kwargs): + super().__init__("Profile", *pos_args, **kwargs) + self.profile = root + self.profile_lock = lock + # Events whose initial expansion has been done. + self.seen_events: Set[str] = set() + + def pnode_of(self, tnode: TreeNode) -> ProfileNode: + """The ProfileNode shown by tnode, the tree's root shows the profile's root.""" + return tnode.data if tnode.data is not None else self.profile + + def event_value(self, pnode: ProfileNode) -> int: + """Value of the event containing pnode, percentages are relative to it.""" + while pnode.parent is not self.profile and pnode.parent is not pnode: + pnode = pnode.parent + return pnode.value + + @staticmethod + def expanded_descendants(tnode: TreeNode, expanded: Set[ProfileNode]) -> None: + """Add the profile nodes of the expanded descendants of tnode to expanded.""" + for tchild in tnode.children: + if tchild.is_expanded and tchild.data is not None: + expanded.add(tchild.data) + if tchild.children: + ProfileTree.expanded_descendants(tchild, expanded) + + def sync_node(self, tnode: TreeNode, expanded: Optional[Set[ProfileNode]] = None) -> None: + """Update the children of tnode, and expanded descendants, from the profile. + + expanded holds the profile nodes of descendants that were expanded + before an ancestor's children were recreated. The lock must be held. + """ + children = self.pnode_of(tnode).sorted_children() + if expanded is not None or [tchild.data for tchild in tnode.children] != children: + # Recreate the children in the new order keeping the expanded + # ones, and their expanded descendants. + if expanded is None: + expanded = set() + self.expanded_descendants(tnode, expanded) + tnode.remove_children() + ancestors: Set[ProfileNode] = set() + for pnode in expanded: + parent = pnode.parent + while parent is not None and parent not in ancestors: + ancestors.add(parent) + parent = parent.parent + for child in children: + label = child.tree_label(self.event_value(child)) + new = tnode.add(label, child, allow_expand=bool(child.children)) + if child in expanded: + new.expand() + if child in expanded or child in ancestors: + self.sync_node(new, expanded) + return + for tchild in tnode.children: + child = self.pnode_of(tchild) + tchild.set_label(child.tree_label(self.event_value(child))) + tchild.allow_expand = bool(child.children) + if tchild.is_expanded or tchild.children: + self.sync_node(tchild) + + def expand_largest(self, tnode: TreeNode) -> None: + """Expand the chain of largest children below tnode. + + The lock must be held. + """ + while self.pnode_of(tnode).children: + self.sync_node(tnode) + tnode = tnode.children[0] + tnode.expand() + + def find(self, tnode: TreeNode, pnode: ProfileNode) -> Optional[TreeNode]: + """Find the visible tree node for pnode.""" + for tchild in tnode.children: + if tchild.data is pnode: + return tchild + if tchild.is_expanded: + found = self.find(tchild, pnode) + if found: + return found + return None + + def sync(self) -> None: + """Update the tree from the profile keeping the expanded nodes and cursor.""" + cursor = self.cursor_node.data if self.cursor_node else None + with self.profile_lock: + self.root.expand() + self.sync_node(self.root) + for tnode in self.root.children: + name = self.pnode_of(tnode).name + if name in self.seen_events: + continue + self.seen_events.add(name) + self.expand_largest(tnode) + # If there is only one event, expand it also. + if len(self.root.children) == 1: + tnode.expand() + if cursor is not None: + found = self.find(self.root, cursor) + if found: + self.move_cursor(found) + + def on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None: + """Create the children of a node when it is expanded.""" + if event.node is not self.root: + with self.profile_lock: + self.sync_node(event.node) + + +class ReportApp(App): + """A Textual application to display profiling data.""" + + # The ^q binding is implied but having it here adds it in the Footer. + BINDINGS = [ + Binding(key="^q", action="quit", description="Quit", + tooltip="Quit the app"), + ] + + def __init__(self, input_file: str, root: Optional[ProfileNode] = None): + """Initialize the application. + + If root isn't given the profile is loaded from input_file in the + background after the application starts, the views show the profile + as it is built. + """ + super().__init__() + self.input_file = input_file + self.root = root if root else ProfileNode("root", parent=None) + self.loaded = root is not None + # Held when reading or changing the profile, which is built in a + # background thread. + self.profile_lock = threading.Lock() + # The profile being built in a background thread. + self.loading: Optional[ProfileBuilder] = None + + def compose(self) -> ComposeResult: + """Composes the user interface of the application.""" + yield Header() + with TabbedContent(initial="report"): + with TabPane("Report", id="report"): + yield ProfileTree(self.root, self.profile_lock) + with TabPane("Flame Graph", id="flame"): + yield FlameGraph(self.root, self.profile_lock) + yield Footer() + + def on_mount(self) -> None: + """Start loading the profile unless it was given.""" + self.sub_title = self.input_file + if self.loaded: + self.update_views() + else: + self.sub_title = f"Loading {self.input_file}" + self.loading = ProfileBuilder(self.root, self.profile_lock) + self.load_profile() + + def update_views(self) -> None: + """Show the latest state of the profile.""" + self.query_one(ProfileTree).sync() + self.query_one(FlameGraph).profile_changed() + + @work(thread=True, exclusive=True) + def load_profile(self) -> None: + """Build the profile in a thread so the UI stays responsive.""" + loading = self.loading + assert loading is not None + profile = loading + + def progress() -> None: + try: + self.call_from_thread(self.update_progress, profile) + except RuntimeError: + # The app is no longer running. + profile.cancelled = True + + profile.progress = progress + try: + profile.read(self.input_file) + except LoadCancelled: + return + except (OSError, ValueError, RuntimeError) as e: + self.call_from_thread(self.exit, None, 1, + f"Error processing {self.input_file}: {e}") + return + finally: + profile.progress = None + self.loading = None + if not profile.cancelled: + self.call_from_thread(self.profile_loaded, profile) + + def update_progress(self, profile: "ProfileBuilder") -> None: + """Show how much of the file has been processed and the profile so far.""" + self.sub_title = (f"Loading {self.input_file}: {profile.nr_samples:,} samples" + f"{profile.duration_str()}") + self.update_views() + + def profile_loaded(self, profile: "ProfileBuilder") -> None: + """Called on the UI thread when loading completes.""" + self.loaded = True + self.sub_title = (f"{self.input_file}: {profile.nr_samples:,} samples" + f"{profile.duration_str()}") + self.update_views() + + def cancel_loading(self) -> None: + """Stop a background load, noticed at the next progress interval.""" + loading = self.loading + if loading is not None: + loading.cancelled = True + + async def action_quit(self) -> None: + """Quit, stopping any background load.""" + self.cancel_loading() + await super().action_quit() + + def on_unmount(self) -> None: + """Stop any background load when the app exits.""" + self.cancel_loading() + + +class LoadCancelled(Exception): + """Raised from the sample callback to stop processing events early.""" + + +class ProfileBuilder: + """Constructs a profile tree from a stream of events.""" + # Number of samples between checks for cancellation and progress. + PROGRESS_INTERVAL = 1000 + # Minimum and maximum time between calls to the progress callback. + PROGRESS_SECONDS = 1.0 + PROGRESS_MAX_SECONDS = 10.0 + # Updating the views costs more as the profile grows, so the time between + # progress calls grows as this fraction of the time spent loading. This + # bounds the fraction of the load time spent updating the views. + PROGRESS_FRACTION = 0.25 + + def __init__(self, root: Optional[ProfileNode] = None, + lock: Optional[threading.Lock] = None) -> None: + """Build into root, holding lock when changing it.""" + self.root = root if root else ProfileNode("root", parent=None) + self.profile_lock = lock if lock else threading.Lock() + self.session: Optional[perf.session] = None + self.nr_samples = 0 + self.first_time = 0 + self.last_time = 0 + # Set, possibly from another thread, to stop processing events. + self.cancelled = False + # Called periodically, see PROGRESS_FRACTION, while processing events. + self.progress: Optional[Callable[[], None]] = None + self.start_progress = time.monotonic() + self.last_progress = self.start_progress + + def duration_str(self) -> str: + """Description of the time covered by the processed samples.""" + if not self.last_time: + return "" + return f", {(self.last_time - self.first_time) / 1e9:.3f}s of trace" + + def comm(self, sample) -> str: + """The command name of the thread of the sample.""" + pid = sample.sample_pid + try: + assert self.session + thread = self.session.find_thread(pid, sample.sample_tid) + return (thread.comm() if thread else None) or f"unknown ({pid})" + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError, + AssertionError): + return f"unknown ({pid})" + + def process_event(self, sample) -> None: + """Called by session.process_events to update the profile tree.""" + self.nr_samples += 1 + if self.nr_samples % self.PROGRESS_INTERVAL == 0: + if self.cancelled: + raise LoadCancelled() + now = time.monotonic() + interval = min(max(self.PROGRESS_SECONDS, + (now - self.start_progress) * self.PROGRESS_FRACTION), + self.PROGRESS_MAX_SECONDS) + if self.progress and now - self.last_progress >= interval: + # Must not hold the lock as the callback may read the profile. + self.progress() + # Time from when the callback, that may block, returns. + self.last_progress = time.monotonic() + sample_time = sample.sample_time + if sample_time: + if not self.first_time or sample_time < self.first_time: + self.first_time = sample_time + self.last_time = max(self.last_time, sample_time) + ev_name = str(sample.evsel)[6:-1] + comm = self.comm(sample) + with self.profile_lock: + ev_root = self.root.find_or_create_node(ev_name) + ev_root.process_event(sample, comm) + + def read(self, input_file: str) -> None: + """Process the events in input_file, raising on errors.""" + if self.cancelled: + raise LoadCancelled() + self.start_progress = self.last_progress = time.monotonic() + try: + self.session = perf.session(perf.data(input_file), sample=self.process_event) + self.session.process_events() + finally: + # Break the reference cycle between the session and the callback. + self.session = None + + +def main() -> None: + """Parse arguments and run the app.""" + parser = argparse.ArgumentParser( + description="TUI report and flame graph using perf python module.") + parser.add_argument("-i", "--input", help="input perf.data file") + args = parser.parse_args() + + input_file = args.input or "perf.data" + if input_file == "-": + # The interactive UI reads the keyboard from stdin. + print("Error: reading perf.data from stdin isn't supported.", file=sys.stderr) + sys.exit(1) + if not os.path.exists(input_file): + print(f"Error: {input_file} not found. (try 'perf record' first)", file=sys.stderr) + sys.exit(1) + + # The app starts immediately and builds the profile in the background. + app = ReportApp(input_file) + app.run() + sys.exit(app.return_code or 0) + + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/ttimechart.py b/tools/perf/python/ttimechart.py new file mode 100755 index 000000000000..d7ba0276a9fb --- /dev/null +++ b/tools/perf/python/ttimechart.py @@ -0,0 +1,1806 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +"""ttimechart.py - interactive perf timechart written using textual. + +Reads a perf.data file, typically created with 'perf timechart record', and +displays per-CPU and per-task timelines in the terminal. Scheduler +(sched:sched_switch, sched:sched_wakeup), power (power:cpu_idle, +power:cpu_frequency) and I/O syscall (perf timechart record -I) tracepoints +are understood. Unlike 'perf timechart', which writes an SVG file, the +timeline can be zoomed, panned and queried interactively. + +Usage: + perf timechart record -- + perf timechart --tui +or: + perf script ttimechart [-i perf.data] +""" +from __future__ import annotations + +from abc import ABC, abstractmethod +import argparse +import bisect +from collections import defaultdict +from dataclasses import dataclass, replace +import math +import os +import sys +import threading +from time import monotonic +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple + +import perf +from rich.segment import Segment +from rich.style import Style +from textual import events, on, work +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.color import Color +from textual.geometry import Size +from textual.message import Message +from textual.reactive import reactive +from textual.scroll_view import ScrollView +from textual.strip import Strip +from textual.widgets import DataTable, Footer, Header, Static, TabbedContent, TabPane +from textual.widgets.data_table import CellDoesNotExist, RowDoesNotExist + +# Task states, SLEEPING and UNKNOWN aren't drawn. +STATE_UNKNOWN = -1 +STATE_SLEEPING = 0 +STATE_RUNNING = 1 +STATE_WAITING = 2 +STATE_BLOCKED = 3 +NUM_STATES = 4 +STATE_NAMES = { + STATE_UNKNOWN: "unknown", + STATE_SLEEPING: "sleeping", + STATE_RUNNING: "running", + STATE_WAITING: "runnable (waiting for a CPU)", + STATE_BLOCKED: "blocked (uninterruptible)", +} + +# I/O types, matching builtin-timechart.c. +IOTYPE_READ = 0 +IOTYPE_WRITE = 1 +IOTYPE_SYNC = 2 +IOTYPE_TX = 3 +IOTYPE_RX = 4 +IOTYPE_POLL = 5 +NUM_IOTYPES = 6 +IOTYPE_NAMES = ["read", "write", "sync", "tx", "rx", "poll"] + +IO_SYSCALLS = { + "read": IOTYPE_READ, "pread64": IOTYPE_READ, "readv": IOTYPE_READ, + "preadv": IOTYPE_READ, + "write": IOTYPE_WRITE, "pwrite64": IOTYPE_WRITE, "writev": IOTYPE_WRITE, + "pwritev": IOTYPE_WRITE, + "sync": IOTYPE_SYNC, "sync_file_range": IOTYPE_SYNC, "fsync": IOTYPE_SYNC, + "msync": IOTYPE_SYNC, + "recvfrom": IOTYPE_RX, "recvmmsg": IOTYPE_RX, "recvmsg": IOTYPE_RX, + "sendto": IOTYPE_TX, "sendmsg": IOTYPE_TX, "sendmmsg": IOTYPE_TX, + "epoll_pwait": IOTYPE_POLL, "epoll_wait": IOTYPE_POLL, "poll": IOTYPE_POLL, + "ppoll": IOTYPE_POLL, "pselect6": IOTYPE_POLL, "select": IOTYPE_POLL, +} + +# Trace flags for interrupt context in the common_flags tracepoint field. +TRACE_FLAG_HARDIRQ = 0x08 +TRACE_FLAG_SOFTIRQ = 0x10 +# Value of state in power:cpu_idle when leaving idle. +PWR_EVENT_EXIT = 0xffffffff +# Width of the row label column. +LABEL_WIDTH = 28 +# Characters for drawing fractional bars. +BARS = " ▁▂▃▄▅▆▇█" +NSEC_PER_SEC = 1_000_000_000 + + +def fmt_duration(nsecs: float) -> str: + """Format a duration in nanoseconds with an appropriate unit.""" + if nsecs >= NSEC_PER_SEC: + return f"{nsecs / NSEC_PER_SEC:.3f}s" + if nsecs >= 1_000_000: + return f"{nsecs / 1_000_000:.3f}ms" + if nsecs >= 1_000: + return f"{nsecs / 1_000:.3f}us" + return f"{nsecs:.0f}ns" + + +def escape(text: str) -> str: + """Escape text, such as a task name, for use in textual markup. + + rich.markup.escape doesn't escape tags like "[1]", the name given to tasks + with an unknown command, but textual fails to parse them. + """ + return text.replace("[", "\\[") + + +def make_fixed_length_string(s: str, length: int, pad_char: str = ' ') -> str: + """Truncate or right pad s so that it is length characters long.""" + return s[:length] if len(s) > length else s.ljust(length, pad_char) + + +def bar_char(frac: float) -> str: + """A block character whose height represents frac, in the range [0, 1].""" + if frac <= 0: + return BARS[0] + return BARS[max(1, min(8, round(frac * 8)))] + + +class SegmentList: + """A time ordered list of non-overlapping [start, end) segments. + + Each segment has a small integer key, used for computing coverage, and + arbitrary associated data. + """ + def __init__(self) -> None: + self.starts: List[int] = [] + self.ends: List[int] = [] + self.keys: List[int] = [] + self.data: List[Any] = [] + + def __len__(self) -> int: + return len(self.starts) + + def add(self, start: int, end: int, key: int, data: Any = None) -> None: + """Append a segment, segments must be added in time order.""" + if end <= start: + return + if self.ends and start < self.ends[-1]: + # Clip overlaps caused by inconsistent data. + start = self.ends[-1] + if end <= start: + return + self.starts.append(start) + self.ends.append(end) + self.keys.append(key) + self.data.append(data) + + def find(self, time: float) -> int: + """Index of the segment containing time or -1.""" + i = bisect.bisect_right(self.starts, time) - 1 + if i >= 0 and time < self.ends[i]: + return i + return -1 + + def last_before(self, time: float) -> int: + """Index of the last segment starting at or before time or -1.""" + return bisect.bisect_right(self.starts, time) - 1 + + def next_change(self, time: float) -> Optional[int]: + """The first segment start or end after time.""" + candidates = [] + i = bisect.bisect_right(self.starts, time) + if i < len(self.starts): + candidates.append(self.starts[i]) + i = bisect.bisect_right(self.ends, time) + if i < len(self.ends): + candidates.append(self.ends[i]) + return min(candidates) if candidates else None + + def prev_change(self, time: float) -> Optional[int]: + """The last segment start or end before time.""" + candidates = [] + i = bisect.bisect_left(self.starts, time) - 1 + if i >= 0: + candidates.append(self.starts[i]) + i = bisect.bisect_left(self.ends, time) - 1 + if i >= 0: + candidates.append(self.ends[i]) + return max(candidates) if candidates else None + + def coverage(self, t0: float, dt: float, width: int, nkeys: int, + weight_fn=None) -> List[List[float]]: + """Time covered by each key in each of width columns of size dt. + + If weight_fn is given then, rather than the time covered, the time + covered multiplied by weight_fn(data) is accumulated. + """ + cols = [[0.0] * nkeys for _ in range(width)] + t1 = t0 + dt * width + i = bisect.bisect_right(self.ends, t0) + num = len(self.starts) + while i < num and self.starts[i] < t1: + start = max(self.starts[i], t0) + end = min(self.ends[i], t1) + key = self.keys[i] + weight = weight_fn(self.data[i]) if weight_fn else 1.0 + x0 = min(int((start - t0) / dt), width - 1) + x1 = min(int((end - t0) / dt), width - 1) + if x0 == x1: + cols[x0][key] += (end - start) * weight + else: + cols[x0][key] += (t0 + (x0 + 1) * dt - start) * weight + for x in range(x0 + 1, x1): + cols[x][key] += dt * weight + cols[x1][key] += (end - (t0 + x1 * dt)) * weight + i += 1 + return cols + + +class Task: + """Scheduling and I/O history of a single thread.""" + def __init__(self, tid: int, comm: str) -> None: + self.tid = tid + self.comm = comm + self.comms: List[str] = [comm] + self.state = STATE_UNKNOWN + self.since = 0 + self.cpu = -1 + # Segments with a state key and the CPU as data. + self.segs = SegmentList() + # Segments with an I/O type key and (fd, ret) as data. + self.io = SegmentList() + self.io_pending: Optional[Tuple[int, int, int]] = None + # Wakeups of this task as (time, waker tid) pairs. + self.wakeups: List[Tuple[int, int]] = [] + self.totals = [0] * NUM_STATES + self.switches = 0 + self.io_bytes = 0 + + def name(self) -> str: + """Name for the task used in labels.""" + return f"{self.comm} ({self.tid})" + + def set_comm(self, comm: Optional[str]) -> None: + """Update the task's command name.""" + if not comm or comm == self.comm: + return + self.comm = comm + if comm not in self.comms: + self.comms.append(comm) + + def change_state(self, time: int, state: int, cpu: int = -1) -> None: + """Record the current state as a segment and switch to a new state.""" + if self.state in (STATE_RUNNING, STATE_WAITING, STATE_BLOCKED) and time > self.since: + self.segs.add(self.since, time, self.state, self.cpu) + self.totals[self.state] += time - self.since + self.state = state + self.since = time + if cpu >= 0: + self.cpu = cpu + + def passes_filter(self, filters: Sequence[str]) -> bool: + """Does the task match one of the process filters (pid or name)?""" + if not filters: + return True + return any(f == str(self.tid) or f in self.comms for f in filters) + + +class Cpu: + """Activity on a single CPU.""" + def __init__(self, cpu: int) -> None: + self.cpu = cpu + self.cur_tid = -1 + self.since = 0 + # Busy segments, key 1, with the running tid as data. + self.run = SegmentList() + # Idle state segments, key 1, with the C-state as data. + self.cstate = SegmentList() + self.cstate_cur: Optional[Tuple[int, int]] = None + # Frequency segments, key 1, with the frequency in kHz as data. + self.pstate = SegmentList() + self.pstate_cur: Optional[Tuple[int, int]] = None + + +class LoadCancelled(Exception): + """Raised from the sample callback to stop processing events early.""" + + +class TimechartData: + """Builds per-task and per-CPU timelines from perf events. + + The data may be displayed while it is loaded in another thread, lock must + be held when modifying it or when reading it from another thread. + """ + # Number of samples between checks for cancellation and progress. + PROGRESS_INTERVAL = 1000 + # Minimum and maximum time between calls to the progress callback. + PROGRESS_SECONDS = 1.0 + PROGRESS_MAX_SECONDS = 10.0 + # Updating the views costs more as more data is loaded, so the time between + # progress calls grows as this fraction of the time spent loading. This + # bounds the fraction of the load time spent updating the views. + PROGRESS_FRACTION = 0.25 + + def __init__(self) -> None: + self.tasks: Dict[int, Task] = {} + self.cpus: Dict[int, Cpu] = {} + self.first_time = 0 + self.last_time = 0 + self.min_freq = 0 + self.max_freq = 0 + self.max_cstate = 0 + self.sched_events = 0 + self.power_events = 0 + self.io_events = 0 + self.nr_samples = 0 + self.unhandled: Dict[str, int] = defaultdict(int) + self.session: Optional[perf.session] = None + # Evsel name and event handler, keyed by sample ID. + self._handlers: Dict[int, Tuple[str, Optional[Callable[[int, perf.sample_event], + None]]]] = {} + self.lock = threading.Lock() + # Set, possibly from another thread, to stop processing events. + self.cancelled = False + # Called periodically, see PROGRESS_FRACTION, while processing events. + self.progress: Optional[Callable[[], None]] = None + self.start_progress = monotonic() + self.last_progress = self.start_progress + + def has_events(self) -> bool: + """Were any events that can be displayed processed?""" + return bool(self.sched_events or self.power_events or self.io_events) + + def task(self, tid: int, comm: Optional[str] = None) -> Task: + """Find or create a task.""" + task = self.tasks.get(tid) + if task is None: + if not comm and self.session: + try: + thread = self.session.find_thread(tid, tid) + comm = thread.comm() if thread else None + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + comm = None + task = Task(tid, comm or f"[{tid}]") + self.tasks[tid] = task + else: + task.set_comm(comm) + return task + + def cpu(self, cpu: int) -> Cpu: + """Find or create a CPU.""" + c = self.cpus.get(cpu) + if c is None: + c = Cpu(cpu) + self.cpus[cpu] = c + return c + + def sched_switch(self, time: int, cpu: int, prev_tid: int, prev_comm: Optional[str], + prev_state: int, next_tid: int, next_comm: Optional[str]) -> None: + """Process a sched:sched_switch event.""" + self.sched_events += 1 + c = self.cpu(cpu) + if c.cur_tid == -1 and prev_tid != 0: + # Assume the task was running from the start of the trace. + c.cur_tid = prev_tid + c.since = self.first_time + if c.cur_tid > 0: + c.run.add(c.since, time, 1, c.cur_tid) + c.cur_tid = next_tid + c.since = time + + if prev_tid != 0: + prev = self.task(prev_tid, prev_comm) + if prev.state == STATE_UNKNOWN: + prev.state = STATE_RUNNING + prev.since = self.first_time + prev.cpu = cpu + # Ignore bits like TASK_REPORT_MAX used to report preemption. + state = prev_state & 0xff + if state == 0: + new_state = STATE_WAITING + elif state & 2: + new_state = STATE_BLOCKED + else: + new_state = STATE_SLEEPING + prev.change_state(time, new_state) + prev.switches += 1 + + if next_tid != 0: + nxt = self.task(next_tid, next_comm) + nxt.change_state(time, STATE_RUNNING, cpu) + + def sched_wakeup(self, time: int, wakee: int, comm: Optional[str], waker: int) -> None: + """Process a sched:sched_wakeup or sched:sched_wakeup_new event.""" + self.sched_events += 1 + if wakee == 0: + return + task = self.task(wakee, comm) + task.wakeups.append((time, waker)) + if task.state in (STATE_UNKNOWN, STATE_SLEEPING, STATE_BLOCKED): + task.change_state(time, STATE_WAITING) + + def cstate_start(self, time: int, cpu: int, state: int) -> None: + """Enter an idle state.""" + self.power_events += 1 + c = self.cpu(cpu) + if c.cstate_cur: + c.cstate.add(c.cstate_cur[0], time, 1, c.cstate_cur[1]) + c.cstate_cur = (time, state) + self.max_cstate = max(self.max_cstate, state) + + def cstate_end(self, time: int, cpu: int) -> None: + """Leave an idle state.""" + self.power_events += 1 + c = self.cpu(cpu) + if c.cstate_cur: + c.cstate.add(c.cstate_cur[0], time, 1, c.cstate_cur[1]) + c.cstate_cur = None + + def pstate_change(self, time: int, cpu: int, freq: int) -> None: + """Change of CPU frequency, freq is in kHz.""" + if freq <= 0 or freq > 8000000: + return + self.power_events += 1 + c = self.cpu(cpu) + if c.pstate_cur: + c.pstate.add(c.pstate_cur[0], time, 1, c.pstate_cur[1]) + c.pstate_cur = (time, freq) + self.max_freq = max(self.max_freq, freq) + self.min_freq = freq if not self.min_freq else min(self.min_freq, freq) + + def io_enter(self, time: int, tid: int, iotype: int, fd: int) -> None: + """Entry to an I/O syscall.""" + self.io_events += 1 + self.task(tid).io_pending = (time, iotype, fd) + + def io_exit(self, time: int, tid: int, iotype: int, ret: int) -> None: + """Exit from an I/O syscall.""" + self.io_events += 1 + task = self.task(tid) + pending = task.io_pending + task.io_pending = None + if not pending or pending[1] != iotype: + return + start = pending[0] + task.io.add(start, max(time, start + 1), iotype, (pending[2], ret)) + if ret > 0 and iotype in (IOTYPE_READ, IOTYPE_WRITE, IOTYPE_TX, IOTYPE_RX): + task.io_bytes += ret + + def _on_sched_switch(self, time: int, sample: perf.sample_event) -> None: + self.sched_switch(time, sample.sample_cpu, sample.prev_pid, + getattr(sample, "prev_comm", None), sample.prev_state, + sample.next_pid, getattr(sample, "next_comm", None)) + + def _on_sched_wakeup(self, time: int, sample: perf.sample_event) -> None: + waker = getattr(sample, "common_pid", sample.sample_tid) + flags = getattr(sample, "common_flags", 0) + if flags & (TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ): + waker = -1 + self.sched_wakeup(time, sample.pid, getattr(sample, "comm", None), waker) + + def _cstate(self, time: int, cpu: int, state: int) -> None: + if (state & 0xffffffff) == PWR_EVENT_EXIT: + self.cstate_end(time, cpu) + else: + self.cstate_start(time, cpu, state) + + def _on_cpu_idle(self, time: int, sample: perf.sample_event) -> None: + self._cstate(time, sample.cpu_id, sample.state) + + def _on_power_start(self, time: int, sample: perf.sample_event) -> None: + self._cstate(time, sample.cpu_id, sample.value) + + def _on_power_end(self, time: int, sample: perf.sample_event) -> None: + self.cstate_end(time, sample.sample_cpu) + + def _on_cpu_frequency(self, time: int, sample: perf.sample_event) -> None: + self.pstate_change(time, sample.cpu_id, sample.state) + + def _on_power_frequency(self, time: int, sample: perf.sample_event) -> None: + self.pstate_change(time, sample.cpu_id, sample.value) + + def _handler_for(self, name: str) -> Optional[Callable[[int, perf.sample_event], None]]: + """Find the handler for events with the given evsel name.""" + handlers: Dict[str, Callable[[int, perf.sample_event], None]] = { + "sched:sched_switch": self._on_sched_switch, + "sched:sched_wakeup": self._on_sched_wakeup, + "sched:sched_wakeup_new": self._on_sched_wakeup, + "power:cpu_idle": self._on_cpu_idle, + "power:power_start": self._on_power_start, + "power:power_end": self._on_power_end, + "power:cpu_frequency": self._on_cpu_frequency, + "power:power_frequency": self._on_power_frequency, + } + if name in handlers: + return handlers[name] + if name.startswith("syscalls:sys_enter_") and name[19:] in IO_SYSCALLS: + iotype = IO_SYSCALLS[name[19:]] + return lambda time, sample: self.io_enter(time, sample.sample_tid, iotype, + getattr(sample, "fd", -1)) + if name.startswith("syscalls:sys_exit_") and name[18:] in IO_SYSCALLS: + iotype = IO_SYSCALLS[name[18:]] + return lambda time, sample: self.io_exit(time, sample.sample_tid, iotype, + sample.ret) + return None + + def process_event(self, sample: perf.sample_event) -> None: + """Callback from perf.session for each sample.""" + self.nr_samples += 1 + if self.nr_samples % self.PROGRESS_INTERVAL == 0: + if self.cancelled: + raise LoadCancelled() + now = monotonic() + interval = min(max(self.PROGRESS_SECONDS, + (now - self.start_progress) * self.PROGRESS_FRACTION), + self.PROGRESS_MAX_SECONDS) + if self.progress and now - self.last_progress >= interval: + with self.lock: + self.update_comms() + # Must not hold the lock as the callback may read the data. + self.progress() + # Time from when the callback, that may block, returns. + self.last_progress = monotonic() + with self.lock: + self._process_event(sample) + + def _process_event(self, sample: perf.sample_event) -> None: + """Update the data from a sample, the lock must be held.""" + time = sample.sample_time + if not self.first_time or time < self.first_time: + self.first_time = time + self.last_time = max(self.last_time, time) + + # Computing the evsel name and matching it is relatively expensive, + # so cache the result by sample ID. Each ID belongs to a single evsel. + sample_id = sample.sample_id + cached = self._handlers.get(sample_id) + if cached is None: + name = str(sample.evsel) + if name.startswith("evsel(") and name.endswith(")"): + name = name[6:-1] + cached = (name, self._handler_for(name)) + self._handlers[sample_id] = cached + name, handler = cached + if handler is None: + self.unhandled[name] += 1 + return + try: + handler(time, sample) + except AttributeError: + self.unhandled[name] += 1 + + def update_comms(self) -> None: + """Refresh task command names from the session, the lock must be held.""" + if not self.session: + return + for tid, task in self.tasks.items(): + try: + thread = self.session.find_thread(tid, tid) + if thread: + task.set_comm(thread.comm()) + except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError): + pass + + def finish(self) -> None: + """Close open segments at the end of the trace.""" + with self.lock: + self.update_comms() + end = self.last_time + for task in self.tasks.values(): + task.change_state(end, STATE_UNKNOWN) + for c in self.cpus.values(): + if c.cur_tid > 0: + c.run.add(c.since, end, 1, c.cur_tid) + if c.cstate_cur: + c.cstate.add(c.cstate_cur[0], end, 1, c.cstate_cur[1]) + c.cstate_cur = None + if c.pstate_cur: + c.pstate.add(c.pstate_cur[0], end, 1, c.pstate_cur[1]) + c.pstate_cur = None + + def fmt_time(self, time: float) -> str: + """Format an absolute timestamp relative to the trace start.""" + return f"{(time - self.first_time) / NSEC_PER_SEC:.6f}s" + + def task_name(self, tid: int) -> str: + """Name of a task, or a description for special tids.""" + if tid == 0: + return "idle" + if tid < 0: + return "interrupt" + task = self.tasks.get(tid) + return task.name() if task else f"[{tid}]" + + def sched_tasks(self, filters: Sequence[str]) -> List[Task]: + """Tasks with scheduling history passing the filters, sorted by tid.""" + return sorted((t for t in self.tasks.values() + if len(t.segs) and t.passes_filter(filters)), + key=lambda t: t.tid) + + def io_tasks(self, filters: Sequence[str]) -> List[Task]: + """Tasks with I/O passing the filters, most I/O first.""" + return sorted((t for t in self.tasks.values() + if len(t.io) and t.passes_filter(filters)), + key=lambda t: -len(t.io)) + + def dump(self, power_only: bool, tasks_only: bool, filters: Sequence[str]) -> None: + """Print a plain text summary, for use without a terminal UI.""" + duration = self.last_time - self.first_time + span = max(duration, 1) + tasks = self.sched_tasks(filters) + io_tasks = self.io_tasks(filters) + print(f"Duration: {fmt_duration(duration)}, CPUs: {len(self.cpus)}, " + f"tasks: {len(tasks) or len(io_tasks)}, sched events: {self.sched_events}, " + f"power events: {self.power_events}, I/O events: {self.io_events}") + if not tasks_only: + for c in sorted(self.cpus.values(), key=lambda c: c.cpu): + busy = sum(e - s for s, e in zip(c.run.starts, c.run.ends)) + print(f"CPU {c.cpu}: busy {busy * 100 / span:.1f}%, " + f"{len(c.run)} runs, {len(c.cstate)} idle periods, " + f"{len(c.pstate)} frequency periods") + if power_only: + return + print(f"{'Task':<24} {'TID':>8} {'Running':>12} {'Waiting':>12} {'Blocked':>12} " + f"{'Switches':>9} {'Wakeups':>8}") + for task in sorted(tasks, key=lambda t: -t.totals[STATE_RUNNING]): + print(f"{task.comm[:24]:<24} {task.tid:>8} " + f"{fmt_duration(task.totals[STATE_RUNNING]):>12} " + f"{fmt_duration(task.totals[STATE_WAITING]):>12} " + f"{fmt_duration(task.totals[STATE_BLOCKED]):>12} " + f"{task.switches:>9} {len(task.wakeups):>8}") + for task in io_tasks: + print(f"I/O {task.name()}: {len(task.io)} syscalls, {task.io_bytes} bytes") + + +Cell = Tuple[str, Style] + + +class ThemeColors: + """Colors and Rich styles derived from the active Textual theme.""" + def __init__(self, theme_variables: Mapping[str, str]) -> None: + self.running = theme_variables["primary"] + self.waiting = theme_variables["error"] + self.blocked = theme_variables["warning"] + self.idle_light = theme_variables["secondary-lighten-2"] + self.idle_dark = theme_variables["secondary-darken-2"] + self.freq_low = theme_variables["success"] + self.freq_high = theme_variables["error"] + self.io = [ + theme_variables["success"], + theme_variables["error"], + theme_variables["warning"], + theme_variables["accent"], + theme_variables["primary"], + theme_variables["secondary-lighten-2"], + ] + run_color = Color.parse(self.running).rich_color + wait_color = Color.parse(self.waiting).rich_color + block_color = Color.parse(self.blocked).rich_color + self.run_style = Style(color=run_color) + self.wait_style = Style(color=run_color, bgcolor=wait_color) + self.block_style = Style(color=run_color, bgcolor=block_color) + low = Color.parse(self.freq_low) + high = Color.parse(self.freq_high) + self.freq_styles = [Style(color=low.blend(high, x / 8).rich_color) for x in range(9)] + light = Color.parse(self.idle_light) + dark = Color.parse(self.idle_dark) + self.idle_styles = [Style(color=light.blend(dark, x / 8).rich_color) for x in range(9)] + self.io_styles = [Style(color=Color.parse(c).rich_color) for c in self.io] + self.io_err_styles = [Style(color=Color.parse(c).rich_color, underline=True) + for c in self.io] + accent = Color.parse(theme_variables["accent"]).rich_color + accent_muted = Color.parse(theme_variables["accent-muted"]).rich_color + self.selected_style = Style(color=accent, bgcolor=accent_muted, bold=True) + + +class Row(ABC): + """A row within a timeline view.""" + def __init__(self, label: str) -> None: + self._label = label + + def label(self) -> str: + """Label shown to the left of the row.""" + return self._label + + @abstractmethod + def segments(self) -> SegmentList: + """Segments used for navigating between changes.""" + + @abstractmethod + def cells(self, t0: float, dt: float, width: int, colors: ThemeColors) -> List[Cell]: + """Cells for the columns starting at time t0 and dt wide.""" + + @abstractmethod + def describe(self, time: float, t0: float, t1: float) -> str: + """Rich markup describing the row at time within the window [t0, t1).""" + + +class TaskRow(Row): + """Row showing the running, waiting and blocked states of a task. + + The height of the bar shows the fraction of the time running, the + background shows waiting for a CPU or being blocked. + """ + def __init__(self, data: TimechartData, task: Task) -> None: + super().__init__(task.name()) + self.data = data + self.task = task + + def label(self) -> str: + return self.task.name() + + def segments(self) -> SegmentList: + return self.task.segs + + def cells(self, t0: float, dt: float, width: int, colors: ThemeColors) -> List[Cell]: + result = [] + for col in self.task.segs.coverage(t0, dt, width, NUM_STATES): + run = col[STATE_RUNNING] / dt + wait = col[STATE_WAITING] + block = col[STATE_BLOCKED] + if wait > 0 and wait >= block: + style = colors.wait_style + elif block > 0: + style = colors.block_style + else: + style = colors.run_style + result.append((bar_char(run), style)) + return result + + def describe(self, time: float, t0: float, t1: float) -> str: + task = self.task + i = task.segs.find(time) + if i >= 0: + state = task.segs.keys[i] + desc = STATE_NAMES[state] + if state == STATE_RUNNING: + desc += f" on CPU {task.segs.data[i]}" + elif state == STATE_WAITING and task.segs.data[i] >= 0: + desc += f", last ran on CPU {task.segs.data[i]}" + start = task.segs.starts[i] + end = task.segs.ends[i] + desc += (f" from {self.data.fmt_time(start)} for " + f"{fmt_duration(end - start)}") + else: + desc = "sleeping or not traced" + window = task.segs.coverage(t0, max(t1 - t0, 1), 1, NUM_STATES)[0] + span = max(t1 - t0, 1) + lines = [ + f"[b]{escape(task.name())}[/b]: {desc}", + f"In view: running {window[STATE_RUNNING] * 100 / span:.1f}%, " + f"waiting {window[STATE_WAITING] * 100 / span:.1f}%, " + f"blocked {window[STATE_BLOCKED] * 100 / span:.1f}%", + f"Total: running {fmt_duration(task.totals[STATE_RUNNING])}, " + f"waiting {fmt_duration(task.totals[STATE_WAITING])}, " + f"blocked {fmt_duration(task.totals[STATE_BLOCKED])}, " + f"{task.switches} switches, {len(task.wakeups)} wakeups", + ] + idx = bisect.bisect_right(task.wakeups, (time, sys.maxsize)) - 1 + if idx >= 0: + wake_time, waker = task.wakeups[idx] + lines.append(f"Last woken at {self.data.fmt_time(wake_time)} by " + f"{escape(self.data.task_name(waker))} (press 'w' to go to waker)") + if len(task.comms) > 1: + lines.append(f"Names: {escape(', '.join(task.comms))}") + return "\n".join(lines) + + +class CpuRow(Row): + """Row showing how busy a CPU is, colored by frequency if known.""" + def __init__(self, data: TimechartData, cpu: Cpu) -> None: + super().__init__(f"CPU {cpu.cpu}") + self.data = data + self.cpu = cpu + + def segments(self) -> SegmentList: + return self.cpu.run + + def cells(self, t0: float, dt: float, width: int, colors: ThemeColors) -> List[Cell]: + busy = self.cpu.run.coverage(t0, dt, width, 2) + freqs: Optional[List[List[float]]] = None + weighted: Optional[List[List[float]]] = None + if len(self.cpu.pstate) and self.data.max_freq > self.data.min_freq: + freqs = self.cpu.pstate.coverage(t0, dt, width, 2) + weighted = self.cpu.pstate.coverage(t0, dt, width, 2, weight_fn=float) + result = [] + for x in range(width): + style = colors.run_style + if freqs and weighted and freqs[x][1] > 0: + freq = weighted[x][1] / freqs[x][1] + frac = (freq - self.data.min_freq) / (self.data.max_freq - self.data.min_freq) + style = colors.freq_styles[max(0, min(8, round(frac * 8)))] + result.append((bar_char(busy[x][1] / dt), style)) + return result + + def describe(self, time: float, t0: float, t1: float) -> str: + c = self.cpu + i = c.run.find(time) + if i >= 0: + desc = (f"running {escape(self.data.task_name(c.run.data[i]))} from " + f"{self.data.fmt_time(c.run.starts[i])} for " + f"{fmt_duration(c.run.ends[i] - c.run.starts[i])}") + else: + desc = "idle" + i = c.cstate.find(time) + if i >= 0: + desc += f", C-state C{c.cstate.data[i]}" + i = c.pstate.find(time) + if i >= 0: + desc += f", {c.pstate.data[i] / 1000:.0f} MHz" + span = max(t1 - t0, 1) + window = c.run.coverage(t0, span, 1, 2)[0] + return "\n".join([ + f"[b]CPU {c.cpu}[/b]: {desc}", + f"In view: busy {window[1] * 100 / span:.1f}%", + "Bar height is the fraction of time busy" + + (", color is the frequency from low to high" + if len(c.pstate) else ""), + ]) + + +class CStateRow(Row): + """Row showing the idle states of a CPU.""" + def __init__(self, data: TimechartData, cpu: Cpu) -> None: + super().__init__(f" CPU {cpu.cpu} idle") + self.data = data + self.cpu = cpu + + def segments(self) -> SegmentList: + return self.cpu.cstate + + def cells(self, t0: float, dt: float, width: int, colors: ThemeColors) -> List[Cell]: + cov = self.cpu.cstate.coverage(t0, dt, width, 2) + depth = self.cpu.cstate.coverage(t0, dt, width, 2, weight_fn=float) + max_cstate = max(self.data.max_cstate, 1) + result = [] + for x in range(width): + frac = cov[x][1] / dt + style = colors.idle_styles[0] + if cov[x][1] > 0: + avg = depth[x][1] / cov[x][1] + style = colors.idle_styles[max(0, min(8, round(avg * 8 / max_cstate)))] + result.append((bar_char(frac), style)) + return result + + def describe(self, time: float, t0: float, t1: float) -> str: + c = self.cpu + i = c.cstate.find(time) + if i >= 0: + desc = (f"C{c.cstate.data[i]} from {self.data.fmt_time(c.cstate.starts[i])} for " + f"{fmt_duration(c.cstate.ends[i] - c.cstate.starts[i])}") + else: + desc = "not idle" + span = max(t1 - t0, 1) + window = c.cstate.coverage(t0, span, 1, 2)[0] + return "\n".join([ + f"[b]CPU {c.cpu} idle state[/b]: {desc}", + f"In view: idle {window[1] * 100 / span:.1f}%", + "Bar height is the fraction of time idle, darker colors are deeper C-states", + ]) + + +class FreqRow(Row): + """Row showing the frequency of a CPU.""" + def __init__(self, data: TimechartData, cpu: Cpu) -> None: + super().__init__(f" CPU {cpu.cpu} freq") + self.data = data + self.cpu = cpu + + def segments(self) -> SegmentList: + return self.cpu.pstate + + def cells(self, t0: float, dt: float, width: int, colors: ThemeColors) -> List[Cell]: + cov = self.cpu.pstate.coverage(t0, dt, width, 2) + weighted = self.cpu.pstate.coverage(t0, dt, width, 2, weight_fn=float) + max_freq = max(self.data.max_freq, 1) + min_freq = self.data.min_freq + result = [] + for x in range(width): + if cov[x][1] <= 0: + result.append((" ", colors.freq_styles[0])) + continue + freq = weighted[x][1] / cov[x][1] + frac = (freq - min_freq) / (max_freq - min_freq) if max_freq > min_freq else 1.0 + result.append((bar_char(max(freq / max_freq, 1 / 8)), + colors.freq_styles[max(0, min(8, round(frac * 8)))])) + return result + + def describe(self, time: float, t0: float, t1: float) -> str: + c = self.cpu + i = c.pstate.find(time) + if i >= 0: + desc = (f"{c.pstate.data[i] / 1000:.0f} MHz from " + f"{self.data.fmt_time(c.pstate.starts[i])} for " + f"{fmt_duration(c.pstate.ends[i] - c.pstate.starts[i])}") + else: + desc = "unknown" + return "\n".join([ + f"[b]CPU {c.cpu} frequency[/b]: {desc}", + f"Range: {self.data.min_freq / 1000:.0f} - {self.data.max_freq / 1000:.0f} MHz", + "Bar height is the frequency relative to the maximum", + ]) + + +class IoRow(Row): + """Row showing the I/O syscalls of a task.""" + def __init__(self, data: TimechartData, task: Task) -> None: + super().__init__(task.name()) + self.data = data + self.task = task + + def label(self) -> str: + return self.task.name() + + def segments(self) -> SegmentList: + return self.task.io + + def cells(self, t0: float, dt: float, width: int, colors: ThemeColors) -> List[Cell]: + errs = self.task.io.coverage(t0, dt, width, NUM_IOTYPES, + weight_fn=lambda d: 1.0 if d[1] < 0 else 0.0) + result = [] + for x, col in enumerate(self.task.io.coverage(t0, dt, width, NUM_IOTYPES)): + total = sum(col) + if total <= 0: + result.append((" ", colors.io_styles[0])) + continue + iotype = col.index(max(col)) + styles = colors.io_err_styles if sum(errs[x]) > 0 else colors.io_styles + result.append((bar_char(max(total / dt, 1 / 8)), styles[iotype])) + return result + + def describe(self, time: float, t0: float, t1: float) -> str: + task = self.task + i = task.io.find(time) + if i < 0: + i = task.io.last_before(time) + prefix = "last I/O" + else: + prefix = "in" + if i >= 0: + fd, ret = task.io.data[i] + result = f"returned {ret}" if ret >= 0 else f"failed with error {-ret}" + desc = (f"{prefix} {IOTYPE_NAMES[task.io.keys[i]]} fd={fd} at " + f"{self.data.fmt_time(task.io.starts[i])} for " + f"{fmt_duration(task.io.ends[i] - task.io.starts[i])}, {result}") + else: + desc = "no I/O" + span = max(t1 - t0, 1) + window = task.io.coverage(t0, span, 1, NUM_IOTYPES)[0] + in_view = ", ".join(f"{IOTYPE_NAMES[t]} {window[t] * 100 / span:.1f}%" + for t in range(NUM_IOTYPES) if window[t] > 0) + return "\n".join([ + f"[b]{escape(task.name())}[/b]: {desc}", + f"In view: {in_view or 'no I/O'}", + f"Total: {len(task.io)} I/O syscalls, {task.io_bytes} bytes", + ]) + + +@dataclass(frozen=True) +class TimeWindow: + """The visible time range and the cursor, shared between views. + + Immutable so that it can be a reactive value, the methods return new windows. + """ + first: int + last: int + start: float + end: float + cursor: float + + @staticmethod + def whole(first: int, last: int) -> "TimeWindow": + """A window showing the whole trace with the cursor at the start.""" + last = max(last, first + 1) + return TimeWindow(first, last, first, last, first) + + def span(self) -> float: + """Length of the visible time range.""" + return self.end - self.start + + def reset(self) -> "TimeWindow": + """Show the whole trace.""" + return replace(self, start=self.first, end=self.last) + + def with_range(self, start: float, span: float) -> "TimeWindow": + """Set the visible range clamped to the trace.""" + span = min(max(span, 100.0), self.last - self.first) + start = min(max(start, self.first), self.last - span) + return replace(self, start=start, end=start + span) + + def zoom(self, factor: float) -> "TimeWindow": + """Zoom by factor keeping the cursor at the same position.""" + cursor = self.cursor + if not self.start <= cursor <= self.end: + cursor = (self.start + self.end) / 2 + rel = (cursor - self.start) / self.span() + span = self.span() * factor + return replace(self, cursor=cursor).with_range(cursor - rel * span, span) + + def pan(self, frac: float) -> "TimeWindow": + """Pan the view by the fraction of the visible span.""" + delta = self.span() * frac + win = self.with_range(self.start + delta, self.span()) + return replace(win, cursor=min(max(self.cursor + delta, win.start), win.end)) + + def with_cursor(self, time: float) -> "TimeWindow": + """Move the cursor, scrolling to keep it visible.""" + win = replace(self, cursor=min(max(time, self.first), self.last)) + if win.cursor < win.start: + return win.with_range(win.cursor, win.span()) + if win.cursor >= win.end: + return win.with_range(win.cursor - win.span() * 0.9, win.span()) + return win + + def extend(self, first: int, last: int) -> "TimeWindow": + """Change the bounds of the trace as more of it is loaded. + + If the whole trace was visible then it still is, otherwise the visible + range is kept. + """ + last = max(last, first + 1) + whole = self.start == self.first and self.end == self.last + win = replace(self, first=first, last=last, cursor=min(max(self.cursor, first), last)) + return win.reset() if whole else win.with_range(self.start, self.span()) + + +class TimelineView(ScrollView): + """A scrollable view of rows against a time axis. + + Line 0 is a time ruler that stays at the top, the other lines are rows. + """ + BINDINGS = [ + Binding("up,k", "row_up", "Up", show=False), + Binding("down,j", "row_down", "Down", show=False), + Binding("left,h", "cursor_left", "Cursor ←", key_display="←"), + Binding("right,l", "cursor_right", "Cursor →", key_display="→"), + Binding("shift+left,H", "pan_left", "Pan left", show=False), + Binding("shift+right,L", "pan_right", "Pan right", show=False), + Binding("plus,equals_sign", "zoom_in", "Zoom in", key_display="+"), + Binding("minus", "zoom_out", "Zoom out", key_display="-"), + Binding("0,escape", "zoom_reset", "Reset zoom", key_display="0"), + Binding("n", "next_change", "Next change"), + Binding("p", "prev_change", "Prev change"), + Binding("pageup", "page_up", "Page up", show=False), + Binding("pagedown", "page_down", "Page down", show=False), + ] + + DEFAULT_CSS = """ + TimelineView { + width: 100%; + height: 1fr; + } + """ + + class SelectionChanged(Message): + """Posted when the selected row changes.""" + + # Bound to TimechartApp.window, changing it repaints the view. + window: reactive[TimeWindow] = reactive(TimeWindow.whole(0, 1)) + # Index of the selected row. + selected: reactive[int] = reactive(0) + + def __init__(self, data: TimechartData, rows: List[Row], *pos_args, **kwargs) -> None: + super().__init__(*pos_args, **kwargs) + self.can_focus = True + self.data = data + self.rows = rows + self.cache: Dict[int, List[Cell]] = {} + self.cache_key: Tuple[float, float, int, Optional[ThemeColors]] = (0.0, 0.0, 0, None) + self.label_style = Style() + self.ruler_style = Style(dim=True) + self.cursor_style = Style(reverse=True) + + def selected_row(self) -> Optional[Row]: + """The selected row, if any.""" + if 0 <= self.selected < len(self.rows): + return self.rows[self.selected] + return None + + def timeline_width(self) -> int: + """Number of columns used for the timeline.""" + return max(self.scrollable_content_region.width - LABEL_WIDTH, 1) + + def update_size(self) -> None: + """Update the virtual size after rows change or the widget resizes.""" + self.virtual_size = Size(self.scrollable_content_region.width, len(self.rows) + 1) + + def on_mount(self) -> None: + """Size the view when mounted.""" + self.update_size() + + def on_resize(self) -> None: + """Size the view when resized.""" + self.update_size() + self.refresh() + + def set_rows(self, rows: List[Row]) -> None: + """Replace the rows, after sorting or as more data is loaded. + + The selected row is kept, if it moves watch_selected scrolls to it. + """ + selected = self.selected_row() + self.rows = rows + self.cache.clear() + self.update_size() + self.selected = rows.index(selected) if selected in rows else 0 + self.refresh() + + def validate_selected(self, idx: int) -> int: + """Keep the selection within the rows.""" + return max(0, min(idx, len(self.rows) - 1)) + + def watch_selected(self) -> None: + """Keep the selection visible and tell the app.""" + self.scroll_to_selected() + self.post_message(self.SelectionChanged()) + + def scroll_to_selected(self) -> None: + """Scroll so that the selected row is visible below the ruler.""" + _, scroll_y = self.scroll_offset + visible = max(self.scrollable_content_region.height - 1, 1) + if self.selected < scroll_y: + self.scroll_to(y=self.selected, animate=False) + elif self.selected >= scroll_y + visible: + self.scroll_to(y=self.selected - visible + 1, animate=False) + + def set_window(self, window: TimeWindow) -> None: + """Change the window shared by all views, owned by the app.""" + app = self.app + if isinstance(app, TimechartApp): + app.window = window + + def ruler(self, width: int) -> Strip: + """The time axis, labeled in seconds relative to the trace start.""" + win = self.window + dt = win.span() / width + tick = 14 + decimals = max(0, min(9, 1 - math.floor(math.log10(max(dt * tick, 1) / NSEC_PER_SEC)))) + chars = [" "] * width + x = 0 + while x < width: + label = f"|{(win.start + x * dt - self.data.first_time) / NSEC_PER_SEC:.{decimals}f}" + if x + len(label) > width: + break + chars[x:x + len(label)] = list(label) + x += max(tick, len(label) + 2) + segments = [Segment(make_fixed_length_string("Time (s)", LABEL_WIDTH), self.ruler_style)] + cursor_x = self.cursor_column(width) + if 0 <= cursor_x < width: + segments.append(Segment("".join(chars[:cursor_x]), self.ruler_style)) + segments.append(Segment("▼")) + segments.append(Segment("".join(chars[cursor_x + 1:]), self.ruler_style)) + else: + segments.append(Segment("".join(chars), self.ruler_style)) + return Strip(segments) + + def cursor_column(self, width: int) -> int: + """Column of the cursor or -1 if not visible.""" + win = self.window + if not win.start <= win.cursor < win.end: + return -1 + return min(int((win.cursor - win.start) * width / win.span()), width - 1) + + def render_line(self, y: int) -> Strip: + """Render the ruler or a row.""" + width = self.timeline_width() + if y == 0: + return self.ruler(width) + _, scroll_y = self.scroll_offset + idx = scroll_y + y - 1 + if idx >= len(self.rows): + return Strip.blank(self.scrollable_content_region.width) + + colors = self.app.theme_colors if isinstance(self.app, TimechartApp) else \ + ThemeColors(self.app.theme_variables) + win = self.window + key = (win.start, win.end, width, colors) + if key != self.cache_key: + self.cache.clear() + self.cache_key = key + row = self.rows[idx] + with self.data.lock: + cells = self.cache.get(idx) + if cells is None: + cells = row.cells(win.start, win.span() / width, width, colors) + self.cache[idx] = cells + label = row.label() + + label_style = colors.selected_style if idx == self.selected else self.label_style + segments = [Segment(make_fixed_length_string(label, LABEL_WIDTH - 1) + " ", + label_style)] + cursor_x = self.cursor_column(width) + # Merge runs of cells with the same style into one segment. + text = "" + style: Optional[Style] = None + for x, (char, cell_style) in enumerate(cells): + if x == cursor_x: + cell_style = cell_style + self.cursor_style + if cell_style is not style and text: + segments.append(Segment(text, style)) + text = "" + text += char + style = cell_style + if text: + segments.append(Segment(text, style)) + return Strip(segments) + + def column_time(self, x: int) -> float: + """Time at the center of the column containing screen offset x.""" + width = self.timeline_width() + return self.window.start + (x - LABEL_WIDTH + 0.5) * self.window.span() / width + + def on_click(self, click: events.Click) -> None: + """Select the clicked row and move the cursor to the clicked time.""" + if click.x >= LABEL_WIDTH: + self.set_window(self.window.with_cursor(self.column_time(click.x))) + if click.y > 0: + _, scroll_y = self.scroll_offset + self.selected = scroll_y + click.y - 1 + + def action_row_up(self) -> None: + """Select the previous row.""" + self.selected -= 1 + + def action_row_down(self) -> None: + """Select the next row.""" + self.selected += 1 + + def action_page_up(self) -> None: + """Select a row a page up.""" + self.selected -= max(self.scrollable_content_region.height - 2, 1) + + def action_page_down(self) -> None: + """Select a row a page down.""" + self.selected += max(self.scrollable_content_region.height - 2, 1) + + def action_cursor_left(self) -> None: + """Move the cursor one column left.""" + win = self.window + self.set_window(win.with_cursor(win.cursor - win.span() / self.timeline_width())) + + def action_cursor_right(self) -> None: + """Move the cursor one column right.""" + win = self.window + self.set_window(win.with_cursor(win.cursor + win.span() / self.timeline_width())) + + def action_pan_left(self) -> None: + """Pan a quarter of the view left.""" + self.set_window(self.window.pan(-0.25)) + + def action_pan_right(self) -> None: + """Pan a quarter of the view right.""" + self.set_window(self.window.pan(0.25)) + + def action_zoom_in(self) -> None: + """Halve the visible time range around the cursor.""" + self.set_window(self.window.zoom(0.5)) + + def action_zoom_out(self) -> None: + """Double the visible time range around the cursor.""" + self.set_window(self.window.zoom(2)) + + def action_zoom_reset(self) -> None: + """Show the whole trace.""" + self.set_window(self.window.reset()) + + def action_next_change(self) -> None: + """Move the cursor to the next change in the selected row.""" + row = self.selected_row() + if row: + # Skip changes within the cursor's column. + win = self.window + with self.data.lock: + time = row.segments().next_change( + win.cursor + win.span() / self.timeline_width() / 2) + if time is not None: + self.set_window(win.with_cursor(time)) + + def action_prev_change(self) -> None: + """Move the cursor to the previous change in the selected row.""" + row = self.selected_row() + if row: + win = self.window + with self.data.lock: + time = row.segments().prev_change( + win.cursor - win.span() / self.timeline_width() / 2) + if time is not None: + self.set_window(win.with_cursor(time)) + + +class TimechartApp(App): + """A Textual application to display a timechart.""" + TITLE = "perf timechart" + + BINDINGS = [ + Binding("s", "sort", "Sort tasks", tooltip="Cycle task sort order"), + Binding("w", "goto_waker", "Go to waker", + tooltip="Select the task that last woke the selected task"), + Binding(key="^q", action="quit", description="Quit", tooltip="Quit the app"), + ] + + CSS = """ + TabbedContent, TabbedContent > ContentSwitcher, TabPane { + height: 1fr; + } + TabPane { + padding: 0; + } + .legend { + height: 1; + padding: 0 1; + } + #details { + height: 8; + border: round $primary; + padding: 0 1; + } + """ + + SORT_ORDERS = ["run time", "tid", "name", "first run"] + + # The visible time range and cursor, bound to each TimelineView's window. + window: reactive[TimeWindow] = reactive(TimeWindow.whole(0, 1), init=False) + # Index into SORT_ORDERS for the tasks view. + sort_order: reactive[int] = reactive(0, init=False) + + def __init__(self, input_name: str, power_only: bool, tasks_only: bool, + filters: Sequence[str], data: Optional[TimechartData] = None) -> None: + """Create the app, if data isn't given it is loaded from input_name. + + While the data is loading the views show what has been read so far. + """ + super().__init__() + self.input_name = input_name + self.power_only = power_only + self.tasks_only = tasks_only + self.filters = filters + # The data being displayed, possibly still being loaded. + self.data = data if data else TimechartData() + self.loaded = data is not None + # The data being loaded in a background thread. + self.loading: Optional[TimechartData] = None + self.tasks: List[Task] = [] + self.io_tasks: List[Task] = [] + # Rows keyed by type and CPU or tid. They are reused as the data loads + # so that the views can keep the selected row. + self.row_cache: Dict[Tuple[str, int], Row] = {} + self.io_tab_shown = True + # Does the summary table need updating before it is next shown? + self.summary_stale = True + self.theme_colors = ThemeColors(self.theme_variables) + + def cached_row(self, cls: Callable[[TimechartData, Any], Row], num: int, obj: Any) -> Row: + """Find or create the row of type cls for the CPU or task obj numbered num.""" + key = (getattr(cls, "__name__", ""), num) + row = self.row_cache.get(key) + if row is None: + row = cls(self.data, obj) + self.row_cache[key] = row + return row + + def cpu_rows(self) -> List[Row]: + """Rows for the CPUs tab, the data's lock must be held.""" + rows: List[Row] = [] + for cpu in sorted(self.data.cpus.values(), key=lambda c: c.cpu): + if len(cpu.run) or len(cpu.cstate) or len(cpu.pstate): + rows.append(self.cached_row(CpuRow, cpu.cpu, cpu)) + if len(cpu.cstate): + rows.append(self.cached_row(CStateRow, cpu.cpu, cpu)) + if len(cpu.pstate): + rows.append(self.cached_row(FreqRow, cpu.cpu, cpu)) + return rows + + def task_rows(self) -> List[Row]: + """Rows for the tasks tab in the current sort order, the data's lock must be held.""" + order = self.SORT_ORDERS[self.sort_order] + if order == "run time": + tasks = sorted(self.tasks, key=lambda t: -t.totals[STATE_RUNNING]) + elif order == "name": + tasks = sorted(self.tasks, key=lambda t: (t.comm, t.tid)) + elif order == "first run": + tasks = sorted(self.tasks, key=lambda t: t.segs.starts[0]) + else: + tasks = self.tasks + return [self.cached_row(TaskRow, t.tid, t) for t in tasks] + + def compose(self) -> ComposeResult: + """Composes the user interface of the application.""" + yield Header() + with TabbedContent(): + if not self.tasks_only: + with TabPane("CPUs", id="cpus"): + yield Static(id="cpus_legend", classes="legend") + yield TimelineView(self.data, [], + id="cpus_view").data_bind(TimechartApp.window) + if not self.power_only: + with TabPane("Tasks", id="tasks"): + yield Static(id="tasks_legend", classes="legend") + yield TimelineView(self.data, [], + id="tasks_view").data_bind(TimechartApp.window) + # Hidden until there are tasks doing I/O. + with TabPane("I/O", id="io"): + yield Static(id="io_legend", classes="legend") + yield TimelineView(self.data, [], + id="io_view").data_bind(TimechartApp.window) + with TabPane("Summary", id="summary"): + yield DataTable(id="summary_table", cursor_type="row") + yield Static(id="details") + yield Footer() + + def on_mount(self) -> None: + """Show the data, loading it in the background if it wasn't given.""" + self.theme_changed_signal.subscribe(self, self.on_theme_changed) + self.update_legends() + self.update_views() + view = self.active_view() + if view: + view.focus() + if self.loaded: + self.finish_loading() + else: + self.sub_title = f"Loading {self.input_name}" + self.loading = self.data + self.load_events() + + def on_theme_changed(self, _theme: Any) -> None: + """Update colors, legends and timeline views when the theme changes.""" + self.theme_colors = ThemeColors(self.theme_variables) + self.update_legends() + for view in self.query(TimelineView): + view.refresh() + + def update_legends(self) -> None: + """Update the legend strings using the active theme's colors.""" + c = self.theme_colors + with self.data.lock: + cpus_legend = self.cpu_legend() + for static in self.query("#cpus_legend").results(Static): + static.update(cpus_legend) + tasks_legend = (f"[{c.running}]█[/] running " + f"[on {c.waiting}] [/] runnable " + f"[on {c.blocked}] [/] blocked " + "(bar height is the fraction of time running)") + for static in self.query("#tasks_legend").results(Static): + static.update(tasks_legend) + io_legend = (" ".join(f"[{c.io[t]}]█[/] {IOTYPE_NAMES[t]}" + for t in range(NUM_IOTYPES)) + + " (underline is an error)") + for static in self.query("#io_legend").results(Static): + static.update(io_legend) + + @work(thread=True, exclusive=True) + def load_events(self) -> None: + """Read the perf.data file in a thread so the UI stays responsive.""" + data = self.loading or self.data + self.loading = data + + def progress() -> None: + try: + # Blocks until the UI has shown the data read so far. + self.call_from_thread(self.update_progress, data) + except RuntimeError: + # The app is no longer running. + data.cancelled = True + + data.progress = progress + data.start_progress = data.last_progress = monotonic() + try: + read_events(data, self.input_name) + except LoadCancelled: + return + except (OSError, ValueError, RuntimeError) as e: + self.call_from_thread(self.exit, None, 1, + f"Error processing {self.input_name}: {e}") + return + finally: + data.progress = None + self.loading = None + if data.cancelled: + return + if not data.has_events(): + self.call_from_thread(self.exit, None, 1, + f"Error: no scheduler, power or I/O events found in " + f"{self.input_name}.\n" + "Record them with 'perf timechart record'.") + return + self.call_from_thread(self.data_loaded) + + def data_loaded(self) -> None: + """Called on the UI thread when loading completes.""" + self.loaded = True + self.finish_loading() + + def update_progress(self, data: TimechartData) -> None: + """Show how much of the file has been processed and the data so far.""" + self.sub_title = (f"Loading {self.input_name}: {data.nr_samples:,} samples, " + f"{fmt_duration(data.last_time - data.first_time)} of trace") + self.update_views() + + def finish_loading(self) -> None: + """Show all of the data and a summary of it.""" + self.update_views() + views = self.query("#cpus_view") + if views and not views.first(TimelineView).rows: + self.query_one(TabbedContent).hide_tab("cpus") + task_views = self.query("#tasks_view") + if task_views and not task_views.first(TimelineView).rows and self.io_tasks: + self.query_one(TabbedContent).hide_tab("tasks") + nr_tasks = len(self.tasks) or len(self.io_tasks) + self.sub_title = (f"{self.input_name}: " + f"{fmt_duration(self.data.last_time - self.data.first_time)}, " + f"{len(self.data.cpus)} CPUs, {nr_tasks} tasks") + + def cancel_loading(self) -> None: + """Stop a background load, the worker notices at the next progress interval.""" + loading = self.loading + if loading is not None: + loading.cancelled = True + + async def action_quit(self) -> None: + """Quit, stopping any background load.""" + self.cancel_loading() + await super().action_quit() + + def on_unmount(self) -> None: + """Stop any background load when the app exits.""" + self.cancel_loading() + + def cpu_legend(self) -> str: + """Legend for the CPUs tab, saying which power events are missing. + + The data's lock must be held. + """ + c = self.theme_colors + data = self.data + has_pstate = any(len(cpu.pstate) for cpu in data.cpus.values()) + has_cstate = any(len(cpu.cstate) for cpu in data.cpus.values()) + parts = [f"[{c.running}]█[/] busy"] + if has_pstate and data.max_freq > data.min_freq: + parts.append(f"[{c.freq_low}]█[/]→[{c.freq_high}]█[/] frequency") + elif has_pstate: + parts.append(f"frequency constant at {data.max_freq / 1000:.0f} MHz") + if has_cstate: + parts.append(f"[{c.idle_light}]█[/]→[{c.idle_dark}]█[/] idle state") + missing = [] + if not has_pstate: + missing.append("power:cpu_frequency") + if not has_cstate: + missing.append("power:cpu_idle") + if missing: + parts.append(f"[dim](no {' or '.join(missing)} events)[/dim]") + return " ".join(parts) + + def update_views(self) -> None: + """Update the views from the data, which may still be loading.""" + data = self.data + with data.lock: + first, last = data.first_time, data.last_time + self.tasks = data.sched_tasks(self.filters) + self.io_tasks = data.io_tasks(self.filters) + all_rows = { + "cpus_view": self.cpu_rows(), + "tasks_view": self.task_rows(), + "io_view": [self.cached_row(IoRow, t.tid, t) for t in self.io_tasks], + } + legend = self.cpu_legend() + for view_id, rows in all_rows.items(): + for view in self.query(f"#{view_id}").results(TimelineView): + view.set_rows(rows) + for static in self.query("#cpus_legend").results(Static): + static.update(legend) + if self.query("#io") and bool(self.io_tasks) != self.io_tab_shown: + self.io_tab_shown = bool(self.io_tasks) + tabbed = self.query_one(TabbedContent) + if self.io_tab_shown: + tabbed.show_tab("io") + else: + tabbed.hide_tab("io") + if first or last: + self.window = self.window.extend(first, last) + self.summary_stale = True + tabs = self.query(TabbedContent) + if tabs and tabs.first(TabbedContent).active == "summary": + self.update_summary() + self.update_details() + + def update_summary(self) -> None: + """Fill in the summary table keeping the selected task.""" + tables = self.query("#summary_table") + if not tables: + return + self.summary_stale = False + table = tables.first(DataTable) + if not table.columns: + table.add_columns("Task", "TID", "Running", "Waiting", "Blocked", + "Switches", "Wakeups", "I/O bytes") + selected = None + if table.row_count: + try: + selected = table.coordinate_to_cell_key(table.cursor_coordinate).row_key + except CellDoesNotExist: + pass + summary_tasks = self.tasks if self.tasks else self.io_tasks + with self.data.lock: + rows = [(task.comm, task.tid, + fmt_duration(task.totals[STATE_RUNNING]), + fmt_duration(task.totals[STATE_WAITING]), + fmt_duration(task.totals[STATE_BLOCKED]), + task.switches, len(task.wakeups), task.io_bytes) + for task in sorted(summary_tasks, + key=lambda t: (-t.totals[STATE_RUNNING], -t.io_bytes))] + table.clear() + for row in rows: + table.add_row(*row, key=str(row[1])) + if selected is not None: + try: + table.move_cursor(row=table.get_row_index(selected), animate=False) + except RowDoesNotExist: + pass + + def active_view(self) -> Optional[TimelineView]: + """The timeline view in the active tab, if any.""" + tabs = self.query(TabbedContent) + if not tabs: + return None + active = tabs.first(TabbedContent).active + views = self.query(f"#{active}_view") if active else None + return views.first(TimelineView) if views else None + + def watch_window(self) -> None: + """The bound views repaint themselves, update the details.""" + self.update_details() + + @on(TimelineView.SelectionChanged) + def on_selection_changed(self) -> None: + """Describe the newly selected row.""" + self.update_details() + + def update_details(self) -> None: + """Describe the selected row at the cursor.""" + details = self.query("#details") + if not details: + return + win = self.window + text = (f"Cursor [b]{self.data.fmt_time(win.cursor)}[/b] " + f"View {self.data.fmt_time(win.start)} - {self.data.fmt_time(win.end)} " + f"({fmt_duration(win.span())})\n") + view = self.active_view() + row = view.selected_row() if view else None + if row: + with self.data.lock: + text += row.describe(win.cursor, win.start, win.end) + elif view is None: + text += "Select a task and press enter to show it in the Tasks timeline" + details.first(Static).update(text) + + @on(TabbedContent.TabActivated) + def on_tab_activated(self) -> None: + """Focus the view in the newly active tab.""" + view = self.active_view() + if view: + view.focus() + else: + if self.summary_stale: + self.update_summary() + tables = self.query("#summary_table") + if tables: + tables.first(DataTable).focus() + self.update_details() + + @on(DataTable.RowSelected) + def on_row_selected(self, event: DataTable.RowSelected) -> None: + """Show the selected summary task in the tasks timeline.""" + if event.row_key.value is not None: + self.goto_task(int(event.row_key.value), None) + + def goto_task(self, tid: int, time: Optional[float]) -> None: + """Select the task in the tasks or I/O view, optionally moving the cursor.""" + for tab_id, view_id in (("tasks", "#tasks_view"), ("io", "#io_view")): + views = self.query(view_id) + if not views: + continue + view = views.first(TimelineView) + for idx, row in enumerate(view.rows): + if isinstance(row, (TaskRow, IoRow)) and row.task.tid == tid: + self.query_one(TabbedContent).active = tab_id + if time is not None: + self.window = self.window.with_cursor(time) + view.selected = idx + view.focus() + return + self.notify(f"Task {tid} isn't shown in the tasks view", severity="warning") + + def action_sort(self) -> None: + """Cycle the sort order of the tasks view.""" + self.sort_order = (self.sort_order + 1) % len(self.SORT_ORDERS) + + def watch_sort_order(self) -> None: + """Re-sort the tasks view.""" + views = self.query("#tasks_view") + if not views: + return + with self.data.lock: + rows = self.task_rows() + views.first(TimelineView).set_rows(rows) + self.notify(f"Tasks sorted by {self.SORT_ORDERS[self.sort_order]}") + self.update_details() + + def action_goto_waker(self) -> None: + """Select the task that woke the selected task before the cursor.""" + view = self.active_view() + row = view.selected_row() if view else None + if not isinstance(row, (TaskRow, IoRow)): + return + with self.data.lock: + wakeups = row.task.wakeups + idx = bisect.bisect_right(wakeups, (self.window.cursor, sys.maxsize)) - 1 + wake_time, waker = wakeups[idx] if idx >= 0 else (0, 0) + if idx < 0: + self.notify("No wakeup before the cursor", severity="warning") + return + if waker <= 0: + self.notify(f"Woken by {self.data.task_name(waker)} at " + f"{self.data.fmt_time(wake_time)}") + return + self.goto_task(waker, wake_time) + + +def main() -> None: + """Parse arguments, read the perf.data file and run the app.""" + parser = argparse.ArgumentParser( + description="Interactive timechart of CPU, task and I/O activity.") + parser.add_argument("-i", "--input", default="perf.data", help="input perf.data file") + parser.add_argument("-P", "--power-only", action="store_true", + help="only show CPU power information") + parser.add_argument("-T", "--tasks-only", action="store_true", + help="only show task information") + parser.add_argument("-p", "--process", action="append", default=[], + help="only show processes with the given name or PID, may be repeated") + parser.add_argument("--dump", action="store_true", + help="print a text summary rather than running the interactive UI") + args = parser.parse_args() + + if args.power_only and args.tasks_only: + print("Error: -P and -T options cannot be used at the same time.", file=sys.stderr) + sys.exit(1) + + if args.input == "-": + if not args.dump: + # The interactive UI reads the keyboard from stdin. + print("Error: reading perf.data from stdin requires --dump.", file=sys.stderr) + sys.exit(1) + elif not os.path.exists(args.input): + print(f"Error: {args.input} not found. (try 'perf timechart record' first)", + file=sys.stderr) + sys.exit(1) + + if args.dump: + data = load_data(args.input) + data.dump(args.power_only, args.tasks_only, args.process) + return + + # The app starts immediately and loads the data in the background. + app = TimechartApp(args.input, args.power_only, args.tasks_only, args.process) + app.run() + sys.exit(app.return_code or 0) + + +def read_events(data: TimechartData, input_name: str) -> None: + """Process the events in input_name into data, raising on errors.""" + if data.cancelled: + raise LoadCancelled() + try: + data.session = perf.session(perf.data(input_name), sample=data.process_event) + data.session.process_events() + data.finish() + finally: + # Break the reference cycle between the session and the callback. + data.session = None + + +def load_data(input_name: str) -> TimechartData: + """Read the perf.data file exiting on errors or if there's nothing to show.""" + if input_name != "-" and not os.path.exists(input_name): + print(f"Error: {input_name} not found. (try 'perf timechart record' first)", + file=sys.stderr) + sys.exit(1) + + data = TimechartData() + try: + read_events(data, input_name) + except (OSError, ValueError, RuntimeError) as e: + print(f"Error processing {input_name}: {e}", file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + data.finish() + + if not data.has_events(): + print(f"Error: no scheduler, power or I/O events found in {input_name}.\n" + "Record them with 'perf timechart record'.", file=sys.stderr) + sys.exit(1) + return data + + +if __name__ == "__main__": + main() diff --git a/tools/perf/python/twatch.py b/tools/perf/python/twatch.py index 04f3db29b9bc..b0f1bf113162 100755 --- a/tools/perf/python/twatch.py +++ b/tools/perf/python/twatch.py @@ -1,5 +1,6 @@ #! /usr/bin/env python # SPDX-License-Identifier: GPL-2.0-only +"""Example to show how to enable a software event and track context switches.""" # -*- python -*- # -*- coding: utf-8 -*- # twatch - Experimental use of the perf python interface @@ -9,53 +10,51 @@ import perf def main(context_switch = 0, thread = -1): - cpus = perf.cpu_map() - threads = perf.thread_map(thread) - evsel = perf.evsel(type = perf.TYPE_SOFTWARE, - config = perf.COUNT_SW_DUMMY, - task = 1, comm = 1, mmap = 0, freq = 0, - wakeup_events = 1, watermark = 1, - sample_id_all = 1, context_switch = context_switch, - sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU) + cpus = perf.cpu_map() + threads = perf.thread_map(thread) + evsel = perf.evsel(type = perf.TYPE_SOFTWARE, + config = perf.COUNT_SW_DUMMY, + task = 1, comm = 1, mmap = 0, freq = 0, + wakeup_events = 1, watermark = 1, + sample_id_all = 1, context_switch = context_switch, + sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU) - """What we want are just the PERF_RECORD_ lifetime events for threads, - using the default, PERF_TYPE_HARDWARE + PERF_COUNT_HW_CYCLES & freq=1 - (the default), makes perf reenable irq_vectors:local_timer_entry, when - disabling nohz, not good for some use cases where all we want is to get - threads comes and goes... So use (perf.TYPE_SOFTWARE, perf_COUNT_SW_DUMMY, - freq=0) instead.""" + # What we want are just the PERF_RECORD_ lifetime events for threads, + # using the default, PERF_TYPE_HARDWARE + PERF_COUNT_HW_CYCLES & freq=1 + # (the default), makes perf reenable irq_vectors:local_timer_entry, when + # disabling nohz, not good for some use cases where all we want is to get + # threads comes and goes... So use (perf.TYPE_SOFTWARE, perf_COUNT_SW_DUMMY, + # freq=0) instead. - evsel.open(cpus = cpus, threads = threads); - evlist = perf.evlist(cpus, threads) - evlist.add(evsel) - evlist.mmap() - while True: - evlist.poll(timeout = -1) - for cpu in cpus: - event = evlist.read_on_cpu(cpu) - if not event: - continue - print("cpu: {0}, pid: {1}, tid: {2} {3}".format(event.sample_cpu, + evsel.open(cpus = cpus, threads = threads) + evlist = perf.evlist(cpus, threads) + evlist.add(evsel) + evlist.mmap() + while True: + evlist.poll(timeout = -1) + for cpu in cpus: + event = evlist.read_on_cpu(cpu) + if not event: + continue + print("cpu: {0}, pid: {1}, tid: {2} {3}".format(event.sample_cpu, event.sample_pid, event.sample_tid, event)) if __name__ == '__main__': - """ - To test the PERF_RECORD_SWITCH record, pick a pid and replace - in the following line. + # To test the PERF_RECORD_SWITCH record, pick a pid and replace + # in the following line. - Example output: + # Example output: -cpu: 3, pid: 31463, tid: 31593 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31593, switch_out: 1 } -cpu: 1, pid: 31463, tid: 31489 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31489, switch_out: 1 } -cpu: 2, pid: 31463, tid: 31496 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31496, switch_out: 1 } -cpu: 3, pid: 31463, tid: 31491 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31491, switch_out: 0 } + # cpu: 3, pid: 31463, tid: 31593 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31593, switch_out: 1 } + # cpu: 1, pid: 31463, tid: 31489 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31489, switch_out: 1 } + # cpu: 2, pid: 31463, tid: 31496 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31496, switch_out: 1 } + # cpu: 3, pid: 31463, tid: 31491 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31491, switch_out: 0 } + # + # It is possible as well to use event.misc & perf.PERF_RECORD_MISC_SWITCH_OUT + # to figure out if this is a context switch in or out of the monitored threads. - It is possible as well to use event.misc & perf.PERF_RECORD_MISC_SWITCH_OUT - to figure out if this is a context switch in or out of the monitored threads. - - If bored, please add command line option parsing support for these options :-) - """ - # main(context_switch = 1, thread = 31463) + # If bored, please add command line option parsing support for these options :-) + # # main(context_switch = 1, thread = 31463) main() diff --git a/tools/perf/python/wakeup-latency.py b/tools/perf/python/wakeup-latency.py new file mode 100755 index 000000000000..f77045d02734 --- /dev/null +++ b/tools/perf/python/wakeup-latency.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Display avg/min/max wakeup latency.""" +from __future__ import annotations + +import argparse +from collections import defaultdict +import sys +from typing import Optional, Dict +import perf + +class WakeupLatency: + """Tracks and displays wakeup latency statistics.""" + def __init__(self) -> None: + self.last_wakeup: Dict[int, int] = defaultdict(int) + self.max_wakeup_latency: int = 0 + self.min_wakeup_latency: Optional[int] = None + self.total_wakeup_latency = 0 + self.total_wakeups = 0 + self.unhandled: Dict[str, int] = defaultdict(int) + self.session: Optional[perf.session] = None + + def process_event(self, sample: perf.sample_event) -> None: + """Process events.""" + event_name = str(sample.evsel) + if event_name.startswith("evsel(") and event_name.endswith(")"): + event_name = event_name[6:-1] + sample_time = sample.sample_time + + if "sched:sched_wakeup" in event_name: + try: + pid = sample.pid + self.last_wakeup[pid] = sample_time + except AttributeError: + self.unhandled[event_name] += 1 + elif "sched:sched_switch" in event_name: + try: + next_pid = sample.next_pid + wakeup_ts = self.last_wakeup.get(next_pid, 0) + if wakeup_ts: + latency = sample_time - wakeup_ts + self.max_wakeup_latency = max(self.max_wakeup_latency, latency) + if self.min_wakeup_latency is None: + self.min_wakeup_latency = latency + else: + self.min_wakeup_latency = min(self.min_wakeup_latency, latency) + self.total_wakeup_latency += latency + self.total_wakeups += 1 + del self.last_wakeup[next_pid] + except AttributeError: + self.unhandled[event_name] += 1 + else: + self.unhandled[event_name] += 1 + + def print_totals(self) -> None: + """Print summary statistics.""" + print("wakeup_latency stats:\n") + print(f"total_wakeups: {self.total_wakeups}") + if self.total_wakeups: + avg = self.total_wakeup_latency // self.total_wakeups + print(f"avg_wakeup_latency (ns): {avg}") + print(f"min_wakeup_latency (ns): {self.min_wakeup_latency}") + print(f"max_wakeup_latency (ns): {self.max_wakeup_latency}") + else: + print("avg_wakeup_latency (ns): N/A") + print("min_wakeup_latency (ns): N/A") + print("max_wakeup_latency (ns): N/A") + + if self.unhandled: + print("\nunhandled events:\n") + print(f"{'event':<40s} {'count':>10s}") + print(f"{'-'*40} {'-'*10}") + for event_name, count in self.unhandled.items(): + print(f"{event_name:<40s} {count:10d}") + + def run(self, input_file: str) -> None: + """Run the session.""" + self.session = perf.session(perf.data(input_file), sample=self.process_event) + try: + self.session.process_events() + except KeyboardInterrupt: + pass + finally: + # Break the reference cycle between self.session and the bound + # self.process_event callback so the C perf.session object is freed. + self.session = None + self.print_totals() + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Trace wakeup latency") + parser.add_argument("-i", "--input", default="perf.data", help="Input file") + args = parser.parse_args() + + analyzer = WakeupLatency() + try: + analyzer.run(args.input) + except IOError as e: + print(e, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tools/perf/scripts/Build b/tools/perf/scripts/Build deleted file mode 100644 index 91229a1fe3ff..000000000000 --- a/tools/perf/scripts/Build +++ /dev/null @@ -1,30 +0,0 @@ -ifeq ($(CONFIG_LIBTRACEEVENT),y) - perf-util-$(CONFIG_LIBPERL) += perl/Perf-Trace-Util/ -endif -perf-util-$(CONFIG_LIBPYTHON) += python/Perf-Trace-Util/ - -ifdef MYPY - PY_TESTS := $(shell find python -type f -name '*.py') - MYPY_TEST_LOGS := $(PY_TESTS:python/%=python/%.mypy_log) -else - MYPY_TEST_LOGS := -endif - -$(OUTPUT)%.mypy_log: % - $(call rule_mkdir) - $(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false) - -perf-y += $(MYPY_TEST_LOGS) - -ifdef PYLINT - PY_TESTS := $(shell find python -type f -name '*.py') - PYLINT_TEST_LOGS := $(PY_TESTS:python/%=python/%.pylint_log) -else - PYLINT_TEST_LOGS := -endif - -$(OUTPUT)%.pylint_log: % - $(call rule_mkdir) - $(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false) - -perf-y += $(PYLINT_TEST_LOGS) diff --git a/tools/perf/scripts/install-build-deps.sh b/tools/perf/scripts/install-build-deps.sh index d003e7fab2be..3e53c7bf0f85 100755 --- a/tools/perf/scripts/install-build-deps.sh +++ b/tools/perf/scripts/install-build-deps.sh @@ -155,7 +155,7 @@ fedora_pkg_for() { libcapstone) echo "capstone-devel" ;; - libpython) + python-module) echo "python3-devel" ;; libtraceevent) @@ -199,8 +199,8 @@ fedora_pkg_for() { # opt-in features, which a default build does not enable: the libbfd # disassembler family (libbfd, libbfd-threadsafe, libbfd-liberty, # disassembler-*, cplus-demangle), only linked on BUILD_NONDISTRO - # builds and deprecated in favor of capstone, GTK2, LIBPERL and - # LIBUNWIND support (ifdef GTK2 / ifdef LIBPERL / LIBUNWIND=1), + # builds and deprecated in favor of capstone, GTK4, LIBPERL and + # LIBUNWIND support (ifdef GTK4 / ifdef LIBPERL / LIBUNWIND=1), # and CoreSight (ifdef CORESIGHT), are deliberately not mapped. # libaio is not mapped either: its # test uses the POSIX AIO API (aio.h, aio_*, -lrt), provided by @@ -314,7 +314,7 @@ debian_pkg_for() { libcapstone) echo "libcapstone-dev" ;; - libpython) + python-module) echo "python3-dev" ;; libtraceevent) diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Build b/tools/perf/scripts/perl/Perf-Trace-Util/Build deleted file mode 100644 index 01a1a0ed51ae..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/Build +++ /dev/null @@ -1,9 +0,0 @@ -perf-util-y += Context.o - -CFLAGS_Context.o += $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-bad-function-cast -Wno-declaration-after-statement -Wno-switch-enum -CFLAGS_Context.o += -Wno-unused-parameter -Wno-nested-externs -Wno-undef -CFLAGS_Context.o += -Wno-switch-default -Wno-shadow -Wno-thread-safety-analysis - -ifeq ($(CC_NO_CLANG), 1) - CFLAGS_Context.o += -Wno-unused-command-line-argument -endif diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Context.c b/tools/perf/scripts/perl/Perf-Trace-Util/Context.c deleted file mode 100644 index 25c47d23a130..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/Context.c +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * This file was generated automatically by ExtUtils::ParseXS version 2.18_02 from the - * contents of Context.xs. Do not edit this file, edit Context.xs instead. - * - * ANY CHANGES MADE HERE WILL BE LOST! - */ -#include -#ifndef HAS_BOOL -# define HAS_BOOL 1 -#endif -#line 1 "Context.xs" -/* - * Context.xs. XS interfaces for perf script. - * - * Copyright (C) 2009 Tom Zanussi - */ - -#include "EXTERN.h" -#include "perl.h" -#include "XSUB.h" -#include "../../../util/trace-event.h" - -#ifndef PERL_UNUSED_VAR -# define PERL_UNUSED_VAR(var) if (0) var = var -#endif - -#line 42 "Context.c" - -XS(XS_Perf__Trace__Context_common_pc); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Perf__Trace__Context_common_pc) -{ -#ifdef dVAR - dVAR; dXSARGS; -#else - dXSARGS; -#endif - if (items != 1) - Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_pc", "context"); - PERL_UNUSED_VAR(cv); /* -W */ - { - struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0))); - int RETVAL; - dXSTARG; - - RETVAL = common_pc(context); - XSprePUSH; PUSHi((IV)RETVAL); - } - XSRETURN(1); -} - - -XS(XS_Perf__Trace__Context_common_flags); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Perf__Trace__Context_common_flags) -{ -#ifdef dVAR - dVAR; dXSARGS; -#else - dXSARGS; -#endif - if (items != 1) - Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_flags", "context"); - PERL_UNUSED_VAR(cv); /* -W */ - { - struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0))); - int RETVAL; - dXSTARG; - - RETVAL = common_flags(context); - XSprePUSH; PUSHi((IV)RETVAL); - } - XSRETURN(1); -} - - -XS(XS_Perf__Trace__Context_common_lock_depth); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Perf__Trace__Context_common_lock_depth) -{ -#ifdef dVAR - dVAR; dXSARGS; -#else - dXSARGS; -#endif - if (items != 1) - Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_lock_depth", "context"); - PERL_UNUSED_VAR(cv); /* -W */ - { - struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0))); - int RETVAL; - dXSTARG; - - RETVAL = common_lock_depth(context); - XSprePUSH; PUSHi((IV)RETVAL); - } - XSRETURN(1); -} - -#ifdef __cplusplus -extern "C" -#endif -XS(boot_Perf__Trace__Context); /* prototype to pass -Wmissing-prototypes */ -XS(boot_Perf__Trace__Context) -{ -#ifdef dVAR - dVAR; dXSARGS; -#else - dXSARGS; -#endif - const char* file = __FILE__; - - PERL_UNUSED_VAR(cv); /* -W */ - PERL_UNUSED_VAR(items); /* -W */ - XS_VERSION_BOOTCHECK ; - - newXSproto("Perf::Trace::Context::common_pc", XS_Perf__Trace__Context_common_pc, file, "$"); - newXSproto("Perf::Trace::Context::common_flags", XS_Perf__Trace__Context_common_flags, file, "$"); - newXSproto("Perf::Trace::Context::common_lock_depth", XS_Perf__Trace__Context_common_lock_depth, file, "$"); - if (PL_unitcheckav) - call_list(PL_scopestack_ix, PL_unitcheckav); - XSRETURN_YES; -} - diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs b/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs deleted file mode 100644 index 8c7ea42444d1..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Context.xs. XS interfaces for perf script. - * - * Copyright (C) 2009 Tom Zanussi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "EXTERN.h" -#include "perl.h" -#include "XSUB.h" -#include "../../../perf.h" -#include "../../../util/trace-event.h" - -MODULE = Perf::Trace::Context PACKAGE = Perf::Trace::Context -PROTOTYPES: ENABLE - -int -common_pc(context) - struct scripting_context * context - -int -common_flags(context) - struct scripting_context * context - -int -common_lock_depth(context) - struct scripting_context * context - diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL b/tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL deleted file mode 100644 index e8994332d7dc..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -use 5.010000; -use ExtUtils::MakeMaker; -# See lib/ExtUtils/MakeMaker.pm for details of how to influence -# the contents of the Makefile that is written. -WriteMakefile( - NAME => 'Perf::Trace::Context', - VERSION_FROM => 'lib/Perf/Trace/Context.pm', # finds $VERSION - PREREQ_PM => {}, # e.g., Module::Name => 1.1 - ($] >= 5.005 ? ## Add these new keywords supported since 5.005 - (ABSTRACT_FROM => 'lib/Perf/Trace/Context.pm', # retrieve abstract from module - AUTHOR => 'Tom Zanussi ') : ()), - LIBS => [''], # e.g., '-lm' - DEFINE => '-I ../..', # e.g., '-DHAVE_SOMETHING' - INC => '-I.', # e.g., '-I. -I/usr/include/other' - # Un-comment this if you add C files to link with later: - OBJECT => 'Context.o', # link all the C files too -); diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/README b/tools/perf/scripts/perl/Perf-Trace-Util/README deleted file mode 100644 index 2f0c7f3043ee..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/README +++ /dev/null @@ -1,59 +0,0 @@ -Perf-Trace-Util version 0.01 -============================ - -This module contains utility functions for use with perf script. - -Core.pm and Util.pm are pure Perl modules; Core.pm contains routines -that the core perf support for Perl calls on and should always be -'used', while Util.pm contains useful but optional utility functions -that scripts may want to use. Context.pm contains the Perl->C -interface that allows scripts to access data in the embedding perf -executable; scripts wishing to do that should 'use Context.pm'. - -The Perl->C perf interface is completely driven by Context.xs. If you -want to add new Perl functions that end up accessing C data in the -perf executable, you add desciptions of the new functions here. -scripting_context is a pointer to the perf data in the perf executable -that you want to access - it's passed as the second parameter, -$context, to all handler functions. - -After you do that: - - perl Makefile.PL # to create a Makefile for the next step - make # to create Context.c - - edit Context.c to add const to the char* file = __FILE__ line in - XS(boot_Perf__Trace__Context) to silence a warning/error. - - You can delete the Makefile, object files and anything else that was - generated e.g. blib and shared library, etc, except for of course - Context.c - - You should then be able to run the normal perf make as usual. - -INSTALLATION - -Building perf with perf script Perl scripting should install this -module in the right place. - -You should make sure libperl and ExtUtils/Embed.pm are installed first -e.g. apt-get install libperl-dev or yum install perl-ExtUtils-Embed. - -DEPENDENCIES - -This module requires these other modules and libraries: - - None - -COPYRIGHT AND LICENCE - -Copyright (C) 2009 by Tom Zanussi - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.10.0 or, -at your option, any later version of Perl 5 you may have available. - -Alternatively, this software may be distributed under the terms of the -GNU General Public License ("GPL") version 2 as published by the Free -Software Foundation. - diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm deleted file mode 100644 index 4e2f6039ac92..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm +++ /dev/null @@ -1,55 +0,0 @@ -package Perf::Trace::Context; - -use 5.010000; -use strict; -use warnings; - -require Exporter; - -our @ISA = qw(Exporter); - -our %EXPORT_TAGS = ( 'all' => [ qw( -) ] ); - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw( - common_pc common_flags common_lock_depth -); - -our $VERSION = '0.01'; - -require XSLoader; -XSLoader::load('Perf::Trace::Context', $VERSION); - -1; -__END__ -=head1 NAME - -Perf::Trace::Context - Perl extension for accessing functions in perf. - -=head1 SYNOPSIS - - use Perf::Trace::Context; - -=head1 SEE ALSO - -Perf (script) documentation - -=head1 AUTHOR - -Tom Zanussi, Etzanussi@gmail.com - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2009 by Tom Zanussi - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.10.0 or, -at your option, any later version of Perl 5 you may have available. - -Alternatively, this software may be distributed under the terms of the -GNU General Public License ("GPL") version 2 as published by the Free -Software Foundation. - -=cut diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm deleted file mode 100644 index 9158458d3eeb..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm +++ /dev/null @@ -1,192 +0,0 @@ -package Perf::Trace::Core; - -use 5.010000; -use strict; -use warnings; - -require Exporter; - -our @ISA = qw(Exporter); - -our %EXPORT_TAGS = ( 'all' => [ qw( -) ] ); - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw( -define_flag_field define_flag_value flag_str dump_flag_fields -define_symbolic_field define_symbolic_value symbol_str dump_symbolic_fields -trace_flag_str -); - -our $VERSION = '0.01'; - -my %trace_flags = (0x00 => "NONE", - 0x01 => "IRQS_OFF", - 0x02 => "IRQS_NOSUPPORT", - 0x04 => "NEED_RESCHED", - 0x08 => "HARDIRQ", - 0x10 => "SOFTIRQ"); - -sub trace_flag_str -{ - my ($value) = @_; - - my $string; - - my $print_delim = 0; - - foreach my $idx (sort {$a <=> $b} keys %trace_flags) { - if (!$value && !$idx) { - $string .= "NONE"; - last; - } - - if ($idx && ($value & $idx) == $idx) { - if ($print_delim) { - $string .= " | "; - } - $string .= "$trace_flags{$idx}"; - $print_delim = 1; - $value &= ~$idx; - } - } - - return $string; -} - -my %flag_fields; -my %symbolic_fields; - -sub flag_str -{ - my ($event_name, $field_name, $value) = @_; - - my $string; - - if ($flag_fields{$event_name}{$field_name}) { - my $print_delim = 0; - foreach my $idx (sort {$a <=> $b} keys %{$flag_fields{$event_name}{$field_name}{"values"}}) { - if (!$value && !$idx) { - $string .= "$flag_fields{$event_name}{$field_name}{'values'}{$idx}"; - last; - } - if ($idx && ($value & $idx) == $idx) { - if ($print_delim && $flag_fields{$event_name}{$field_name}{'delim'}) { - $string .= " $flag_fields{$event_name}{$field_name}{'delim'} "; - } - $string .= "$flag_fields{$event_name}{$field_name}{'values'}{$idx}"; - $print_delim = 1; - $value &= ~$idx; - } - } - } - - return $string; -} - -sub define_flag_field -{ - my ($event_name, $field_name, $delim) = @_; - - $flag_fields{$event_name}{$field_name}{"delim"} = $delim; -} - -sub define_flag_value -{ - my ($event_name, $field_name, $value, $field_str) = @_; - - $flag_fields{$event_name}{$field_name}{"values"}{$value} = $field_str; -} - -sub dump_flag_fields -{ - for my $event (keys %flag_fields) { - print "event $event:\n"; - for my $field (keys %{$flag_fields{$event}}) { - print " field: $field:\n"; - print " delim: $flag_fields{$event}{$field}{'delim'}\n"; - foreach my $idx (sort {$a <=> $b} keys %{$flag_fields{$event}{$field}{"values"}}) { - print " value $idx: $flag_fields{$event}{$field}{'values'}{$idx}\n"; - } - } - } -} - -sub symbol_str -{ - my ($event_name, $field_name, $value) = @_; - - if ($symbolic_fields{$event_name}{$field_name}) { - foreach my $idx (sort {$a <=> $b} keys %{$symbolic_fields{$event_name}{$field_name}{"values"}}) { - if (!$value && !$idx) { - return "$symbolic_fields{$event_name}{$field_name}{'values'}{$idx}"; - last; - } - if ($value == $idx) { - return "$symbolic_fields{$event_name}{$field_name}{'values'}{$idx}"; - } - } - } - - return undef; -} - -sub define_symbolic_field -{ - my ($event_name, $field_name) = @_; - - # nothing to do, really -} - -sub define_symbolic_value -{ - my ($event_name, $field_name, $value, $field_str) = @_; - - $symbolic_fields{$event_name}{$field_name}{"values"}{$value} = $field_str; -} - -sub dump_symbolic_fields -{ - for my $event (keys %symbolic_fields) { - print "event $event:\n"; - for my $field (keys %{$symbolic_fields{$event}}) { - print " field: $field:\n"; - foreach my $idx (sort {$a <=> $b} keys %{$symbolic_fields{$event}{$field}{"values"}}) { - print " value $idx: $symbolic_fields{$event}{$field}{'values'}{$idx}\n"; - } - } - } -} - -1; -__END__ -=head1 NAME - -Perf::Trace::Core - Perl extension for perf script - -=head1 SYNOPSIS - - use Perf::Trace::Core - -=head1 SEE ALSO - -Perf (script) documentation - -=head1 AUTHOR - -Tom Zanussi, Etzanussi@gmail.com - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2009 by Tom Zanussi - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.10.0 or, -at your option, any later version of Perl 5 you may have available. - -Alternatively, this software may be distributed under the terms of the -GNU General Public License ("GPL") version 2 as published by the Free -Software Foundation. - -=cut diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm deleted file mode 100644 index 053500114625..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm +++ /dev/null @@ -1,94 +0,0 @@ -package Perf::Trace::Util; - -use 5.010000; -use strict; -use warnings; - -require Exporter; - -our @ISA = qw(Exporter); - -our %EXPORT_TAGS = ( 'all' => [ qw( -) ] ); - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw( -avg nsecs nsecs_secs nsecs_nsecs nsecs_usecs print_nsecs -clear_term -); - -our $VERSION = '0.01'; - -sub avg -{ - my ($total, $n) = @_; - - return $total / $n; -} - -my $NSECS_PER_SEC = 1000000000; - -sub nsecs -{ - my ($secs, $nsecs) = @_; - - return $secs * $NSECS_PER_SEC + $nsecs; -} - -sub nsecs_secs { - my ($nsecs) = @_; - - return $nsecs / $NSECS_PER_SEC; -} - -sub nsecs_nsecs { - my ($nsecs) = @_; - - return $nsecs % $NSECS_PER_SEC; -} - -sub nsecs_str { - my ($nsecs) = @_; - - my $str = sprintf("%5u.%09u", nsecs_secs($nsecs), nsecs_nsecs($nsecs)); - - return $str; -} - -sub clear_term -{ - print "\x1b[H\x1b[2J"; -} - -1; -__END__ -=head1 NAME - -Perf::Trace::Util - Perl extension for perf script - -=head1 SYNOPSIS - - use Perf::Trace::Util; - -=head1 SEE ALSO - -Perf (script) documentation - -=head1 AUTHOR - -Tom Zanussi, Etzanussi@gmail.com - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2009 by Tom Zanussi - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.10.0 or, -at your option, any later version of Perl 5 you may have available. - -Alternatively, this software may be distributed under the terms of the -GNU General Public License ("GPL") version 2 as published by the Free -Software Foundation. - -=cut diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/typemap b/tools/perf/scripts/perl/Perf-Trace-Util/typemap deleted file mode 100644 index 840836804aa7..000000000000 --- a/tools/perf/scripts/perl/Perf-Trace-Util/typemap +++ /dev/null @@ -1 +0,0 @@ -struct scripting_context * T_PTR diff --git a/tools/perf/scripts/perl/bin/check-perf-trace-record b/tools/perf/scripts/perl/bin/check-perf-trace-record deleted file mode 100644 index 423ad6aed056..000000000000 --- a/tools/perf/scripts/perl/bin/check-perf-trace-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -a -e kmem:kmalloc -e irq:softirq_entry -e kmem:kfree diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-record b/tools/perf/scripts/perl/bin/failed-syscalls-record deleted file mode 100644 index 74685f318379..000000000000 --- a/tools/perf/scripts/perl/bin/failed-syscalls-record +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -(perf record -e raw_syscalls:sys_exit $@ || \ - perf record -e syscalls:sys_exit $@) 2> /dev/null diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-report b/tools/perf/scripts/perl/bin/failed-syscalls-report deleted file mode 100644 index 9f83cc1ad8ba..000000000000 --- a/tools/perf/scripts/perl/bin/failed-syscalls-report +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# description: system-wide failed syscalls -# args: [comm] -if [ $# -gt 0 ] ; then - if ! expr match "$1" "-" > /dev/null ; then - comm=$1 - shift - fi -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/failed-syscalls.pl $comm diff --git a/tools/perf/scripts/perl/bin/rw-by-file-record b/tools/perf/scripts/perl/bin/rw-by-file-record deleted file mode 100644 index 33efc8673aae..000000000000 --- a/tools/perf/scripts/perl/bin/rw-by-file-record +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -perf record -e syscalls:sys_enter_read -e syscalls:sys_enter_write $@ - diff --git a/tools/perf/scripts/perl/bin/rw-by-file-report b/tools/perf/scripts/perl/bin/rw-by-file-report deleted file mode 100644 index 77200b3f3100..000000000000 --- a/tools/perf/scripts/perl/bin/rw-by-file-report +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# description: r/w activity for a program, by file -# args: -if [ $# -lt 1 ] ; then - echo "usage: rw-by-file " - exit -fi -comm=$1 -shift -perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-file.pl $comm diff --git a/tools/perf/scripts/perl/bin/rw-by-pid-record b/tools/perf/scripts/perl/bin/rw-by-pid-record deleted file mode 100644 index 7cb9db230448..000000000000 --- a/tools/perf/scripts/perl/bin/rw-by-pid-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write $@ diff --git a/tools/perf/scripts/perl/bin/rw-by-pid-report b/tools/perf/scripts/perl/bin/rw-by-pid-report deleted file mode 100644 index a27b9f311f95..000000000000 --- a/tools/perf/scripts/perl/bin/rw-by-pid-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: system-wide r/w activity -perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-pid.pl diff --git a/tools/perf/scripts/perl/bin/rwtop-record b/tools/perf/scripts/perl/bin/rwtop-record deleted file mode 100644 index 7cb9db230448..000000000000 --- a/tools/perf/scripts/perl/bin/rwtop-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write $@ diff --git a/tools/perf/scripts/perl/bin/rwtop-report b/tools/perf/scripts/perl/bin/rwtop-report deleted file mode 100644 index 83e11ec2e190..000000000000 --- a/tools/perf/scripts/perl/bin/rwtop-report +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# description: system-wide r/w top -# args: [interval] -n_args=0 -for i in "$@" -do - if expr match "$i" "-" > /dev/null ; then - break - fi - n_args=$(( $n_args + 1 )) -done -if [ "$n_args" -gt 1 ] ; then - echo "usage: rwtop-report [interval]" - exit -fi -if [ "$n_args" -gt 0 ] ; then - interval=$1 - shift -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rwtop.pl $interval diff --git a/tools/perf/scripts/perl/bin/wakeup-latency-record b/tools/perf/scripts/perl/bin/wakeup-latency-record deleted file mode 100644 index 464251a1bd7e..000000000000 --- a/tools/perf/scripts/perl/bin/wakeup-latency-record +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -perf record -e sched:sched_switch -e sched:sched_wakeup $@ - - - - diff --git a/tools/perf/scripts/perl/bin/wakeup-latency-report b/tools/perf/scripts/perl/bin/wakeup-latency-report deleted file mode 100644 index 889e8130cca5..000000000000 --- a/tools/perf/scripts/perl/bin/wakeup-latency-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: system-wide min/max/avg wakeup latency -perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/wakeup-latency.pl diff --git a/tools/perf/scripts/perl/check-perf-trace.pl b/tools/perf/scripts/perl/check-perf-trace.pl deleted file mode 100644 index d307ce8fd6ed..000000000000 --- a/tools/perf/scripts/perl/check-perf-trace.pl +++ /dev/null @@ -1,106 +0,0 @@ -# perf script event handlers, generated by perf script -g perl -# (c) 2009, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 - -# This script tests basic functionality such as flag and symbol -# strings, common_xxx() calls back into perf, begin, end, unhandled -# events, etc. Basically, if this script runs successfully and -# displays expected results, perl scripting support should be ok. - -use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; -use lib "./Perf-Trace-Util/lib"; -use Perf::Trace::Core; -use Perf::Trace::Context; -use Perf::Trace::Util; - -sub trace_begin -{ - print "trace_begin\n"; -} - -sub trace_end -{ - print "trace_end\n"; - - print_unhandled(); -} - -sub irq::softirq_entry -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $vec) = @_; - - print_header($event_name, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm); - - print_uncommon($context); - - printf("vec=%s\n", - symbol_str("irq::softirq_entry", "vec", $vec)); -} - -sub kmem::kmalloc -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $call_site, $ptr, $bytes_req, $bytes_alloc, - $gfp_flags) = @_; - - print_header($event_name, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm); - - print_uncommon($context); - - printf("call_site=%p, ptr=%p, bytes_req=%u, bytes_alloc=%u, ". - "gfp_flags=%s\n", - $call_site, $ptr, $bytes_req, $bytes_alloc, - - flag_str("kmem::kmalloc", "gfp_flags", $gfp_flags)); -} - -# print trace fields not included in handler args -sub print_uncommon -{ - my ($context) = @_; - - printf("common_preempt_count=%d, common_flags=%s, common_lock_depth=%d, ", - common_pc($context), trace_flag_str(common_flags($context)), - common_lock_depth($context)); - -} - -my %unhandled; - -sub print_unhandled -{ - if ((scalar keys %unhandled) == 0) { - return; - } - - print "\nunhandled events:\n\n"; - - printf("%-40s %10s\n", "event", "count"); - printf("%-40s %10s\n", "----------------------------------------", - "-----------"); - - foreach my $event_name (keys %unhandled) { - printf("%-40s %10d\n", $event_name, $unhandled{$event_name}); - } -} - -sub trace_unhandled -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain) = @_; - - $unhandled{$event_name}++; -} - -sub print_header -{ - my ($event_name, $cpu, $secs, $nsecs, $pid, $comm) = @_; - - printf("%-20s %5u %05u.%09u %8u %-20s ", - $event_name, $cpu, $secs, $nsecs, $pid, $comm); -} diff --git a/tools/perf/scripts/perl/failed-syscalls.pl b/tools/perf/scripts/perl/failed-syscalls.pl deleted file mode 100644 index 05954a8f363a..000000000000 --- a/tools/perf/scripts/perl/failed-syscalls.pl +++ /dev/null @@ -1,47 +0,0 @@ -# failed system call counts -# (c) 2010, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 -# -# Displays system-wide failed system call totals -# If a [comm] arg is specified, only syscalls called by [comm] are displayed. - -use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; -use lib "./Perf-Trace-Util/lib"; -use Perf::Trace::Core; -use Perf::Trace::Context; -use Perf::Trace::Util; - -my $for_comm = shift; - -my %failed_syscalls; - -sub raw_syscalls::sys_exit -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $id, $ret) = @_; - - if ($ret < 0) { - $failed_syscalls{$common_comm}++; - } -} - -sub syscalls::sys_exit -{ - raw_syscalls::sys_exit(@_) -} - -sub trace_end -{ - printf("\nfailed syscalls by comm:\n\n"); - - printf("%-20s %10s\n", "comm", "# errors"); - printf("%-20s %6s %10s\n", "--------------------", "----------"); - - foreach my $comm (sort {$failed_syscalls{$b} <=> $failed_syscalls{$a}} - keys %failed_syscalls) { - next if ($for_comm && $comm ne $for_comm); - - printf("%-20s %10s\n", $comm, $failed_syscalls{$comm}); - } -} diff --git a/tools/perf/scripts/perl/rw-by-file.pl b/tools/perf/scripts/perl/rw-by-file.pl deleted file mode 100644 index 92a750b8552b..000000000000 --- a/tools/perf/scripts/perl/rw-by-file.pl +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/perl -w -# SPDX-License-Identifier: GPL-2.0-only -# (c) 2009, Tom Zanussi - -# Display r/w activity for files read/written to for a given program - -# The common_* event handler fields are the most useful fields common to -# all events. They don't necessarily correspond to the 'common_*' fields -# in the status files. Those fields not available as handler params can -# be retrieved via script functions of the form get_common_*(). - -use 5.010000; -use strict; -use warnings; - -use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; -use lib "./Perf-Trace-Util/lib"; -use Perf::Trace::Core; -use Perf::Trace::Util; - -my $usage = "perf script -s rw-by-file.pl \n"; - -my $for_comm = shift or die $usage; - -my %reads; -my %writes; - -sub syscalls::sys_enter_read -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, $nr, $fd, $buf, $count) = @_; - - if ($common_comm eq $for_comm) { - $reads{$fd}{bytes_requested} += $count; - $reads{$fd}{total_reads}++; - } -} - -sub syscalls::sys_enter_write -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, $nr, $fd, $buf, $count) = @_; - - if ($common_comm eq $for_comm) { - $writes{$fd}{bytes_written} += $count; - $writes{$fd}{total_writes}++; - } -} - -sub trace_end -{ - printf("file read counts for $for_comm:\n\n"); - - printf("%6s %10s %10s\n", "fd", "# reads", "bytes_requested"); - printf("%6s %10s %10s\n", "------", "----------", "-----------"); - - foreach my $fd (sort {$reads{$b}{bytes_requested} <=> - $reads{$a}{bytes_requested}} keys %reads) { - my $total_reads = $reads{$fd}{total_reads}; - my $bytes_requested = $reads{$fd}{bytes_requested}; - printf("%6u %10u %10u\n", $fd, $total_reads, $bytes_requested); - } - - printf("\nfile write counts for $for_comm:\n\n"); - - printf("%6s %10s %10s\n", "fd", "# writes", "bytes_written"); - printf("%6s %10s %10s\n", "------", "----------", "-----------"); - - foreach my $fd (sort {$writes{$b}{bytes_written} <=> - $writes{$a}{bytes_written}} keys %writes) { - my $total_writes = $writes{$fd}{total_writes}; - my $bytes_written = $writes{$fd}{bytes_written}; - printf("%6u %10u %10u\n", $fd, $total_writes, $bytes_written); - } - - print_unhandled(); -} - -my %unhandled; - -sub print_unhandled -{ - if ((scalar keys %unhandled) == 0) { - return; - } - - print "\nunhandled events:\n\n"; - - printf("%-40s %10s\n", "event", "count"); - printf("%-40s %10s\n", "----------------------------------------", - "-----------"); - - foreach my $event_name (keys %unhandled) { - printf("%-40s %10d\n", $event_name, $unhandled{$event_name}); - } -} - -sub trace_unhandled -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain) = @_; - - $unhandled{$event_name}++; -} - - diff --git a/tools/perf/scripts/perl/rw-by-pid.pl b/tools/perf/scripts/perl/rw-by-pid.pl deleted file mode 100644 index d789fe39caab..000000000000 --- a/tools/perf/scripts/perl/rw-by-pid.pl +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/perl -w -# SPDX-License-Identifier: GPL-2.0-only -# (c) 2009, Tom Zanussi - -# Display r/w activity for all processes - -# The common_* event handler fields are the most useful fields common to -# all events. They don't necessarily correspond to the 'common_*' fields -# in the status files. Those fields not available as handler params can -# be retrieved via script functions of the form get_common_*(). - -use 5.010000; -use strict; -use warnings; - -use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; -use lib "./Perf-Trace-Util/lib"; -use Perf::Trace::Core; -use Perf::Trace::Util; - -my %reads; -my %writes; - -sub syscalls::sys_exit_read -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $ret) = @_; - - if ($ret > 0) { - $reads{$common_pid}{bytes_read} += $ret; - } else { - if (!defined ($reads{$common_pid}{bytes_read})) { - $reads{$common_pid}{bytes_read} = 0; - } - $reads{$common_pid}{errors}{$ret}++; - } -} - -sub syscalls::sys_enter_read -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $fd, $buf, $count) = @_; - - $reads{$common_pid}{bytes_requested} += $count; - $reads{$common_pid}{total_reads}++; - $reads{$common_pid}{comm} = $common_comm; -} - -sub syscalls::sys_exit_write -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $ret) = @_; - - if ($ret <= 0) { - $writes{$common_pid}{errors}{$ret}++; - } -} - -sub syscalls::sys_enter_write -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $fd, $buf, $count) = @_; - - $writes{$common_pid}{bytes_written} += $count; - $writes{$common_pid}{total_writes}++; - $writes{$common_pid}{comm} = $common_comm; -} - -sub trace_end -{ - printf("read counts by pid:\n\n"); - - printf("%6s %20s %10s %10s %10s\n", "pid", "comm", - "# reads", "bytes_requested", "bytes_read"); - printf("%6s %-20s %10s %10s %10s\n", "------", "--------------------", - "-----------", "----------", "----------"); - - foreach my $pid (sort { ($reads{$b}{bytes_read} || 0) <=> - ($reads{$a}{bytes_read} || 0) } keys %reads) { - my $comm = $reads{$pid}{comm} || ""; - my $total_reads = $reads{$pid}{total_reads} || 0; - my $bytes_requested = $reads{$pid}{bytes_requested} || 0; - my $bytes_read = $reads{$pid}{bytes_read} || 0; - - printf("%6s %-20s %10s %10s %10s\n", $pid, $comm, - $total_reads, $bytes_requested, $bytes_read); - } - - printf("\nfailed reads by pid:\n\n"); - - printf("%6s %20s %6s %10s\n", "pid", "comm", "error #", "# errors"); - printf("%6s %20s %6s %10s\n", "------", "--------------------", - "------", "----------"); - - my @errcounts = (); - - foreach my $pid (keys %reads) { - foreach my $error (keys %{$reads{$pid}{errors}}) { - my $comm = $reads{$pid}{comm} || ""; - my $errcount = $reads{$pid}{errors}{$error} || 0; - push @errcounts, [$pid, $comm, $error, $errcount]; - } - } - - @errcounts = sort { $b->[3] <=> $a->[3] } @errcounts; - - for my $i (0 .. $#errcounts) { - printf("%6d %-20s %6d %10s\n", $errcounts[$i][0], - $errcounts[$i][1], $errcounts[$i][2], $errcounts[$i][3]); - } - - printf("\nwrite counts by pid:\n\n"); - - printf("%6s %20s %10s %10s\n", "pid", "comm", - "# writes", "bytes_written"); - printf("%6s %-20s %10s %10s\n", "------", "--------------------", - "-----------", "----------"); - - foreach my $pid (sort { ($writes{$b}{bytes_written} || 0) <=> - ($writes{$a}{bytes_written} || 0)} keys %writes) { - my $comm = $writes{$pid}{comm} || ""; - my $total_writes = $writes{$pid}{total_writes} || 0; - my $bytes_written = $writes{$pid}{bytes_written} || 0; - - printf("%6s %-20s %10s %10s\n", $pid, $comm, - $total_writes, $bytes_written); - } - - printf("\nfailed writes by pid:\n\n"); - - printf("%6s %20s %6s %10s\n", "pid", "comm", "error #", "# errors"); - printf("%6s %20s %6s %10s\n", "------", "--------------------", - "------", "----------"); - - @errcounts = (); - - foreach my $pid (keys %writes) { - foreach my $error (keys %{$writes{$pid}{errors}}) { - my $comm = $writes{$pid}{comm} || ""; - my $errcount = $writes{$pid}{errors}{$error} || 0; - push @errcounts, [$pid, $comm, $error, $errcount]; - } - } - - @errcounts = sort { $b->[3] <=> $a->[3] } @errcounts; - - for my $i (0 .. $#errcounts) { - printf("%6d %-20s %6d %10s\n", $errcounts[$i][0], - $errcounts[$i][1], $errcounts[$i][2], $errcounts[$i][3]); - } - - print_unhandled(); -} - -my %unhandled; - -sub print_unhandled -{ - if ((scalar keys %unhandled) == 0) { - return; - } - - print "\nunhandled events:\n\n"; - - printf("%-40s %10s\n", "event", "count"); - printf("%-40s %10s\n", "----------------------------------------", - "-----------"); - - foreach my $event_name (keys %unhandled) { - printf("%-40s %10d\n", $event_name, $unhandled{$event_name}); - } -} - -sub trace_unhandled -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain) = @_; - - $unhandled{$event_name}++; -} diff --git a/tools/perf/scripts/perl/rwtop.pl b/tools/perf/scripts/perl/rwtop.pl deleted file mode 100644 index eba4df67af6b..000000000000 --- a/tools/perf/scripts/perl/rwtop.pl +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/perl -w -# SPDX-License-Identifier: GPL-2.0-only -# (c) 2010, Tom Zanussi - -# read/write top -# -# Periodically displays system-wide r/w call activity, broken down by -# pid. If an [interval] arg is specified, the display will be -# refreshed every [interval] seconds. The default interval is 3 -# seconds. - -use 5.010000; -use strict; -use warnings; - -use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; -use lib "./Perf-Trace-Util/lib"; -use Perf::Trace::Core; -use Perf::Trace::Util; -use POSIX qw/SIGALRM SA_RESTART/; - -my $default_interval = 3; -my $nlines = 20; -my $print_thread; -my $print_pending = 0; - -my %reads; -my %writes; - -my $interval = shift; -if (!$interval) { - $interval = $default_interval; -} - -sub syscalls::sys_exit_read -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $ret) = @_; - - print_check(); - - if ($ret > 0) { - $reads{$common_pid}{bytes_read} += $ret; - } else { - if (!defined ($reads{$common_pid}{bytes_read})) { - $reads{$common_pid}{bytes_read} = 0; - } - $reads{$common_pid}{errors}{$ret}++; - } -} - -sub syscalls::sys_enter_read -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $fd, $buf, $count) = @_; - - print_check(); - - $reads{$common_pid}{bytes_requested} += $count; - $reads{$common_pid}{total_reads}++; - $reads{$common_pid}{comm} = $common_comm; -} - -sub syscalls::sys_exit_write -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $ret) = @_; - - print_check(); - - if ($ret <= 0) { - $writes{$common_pid}{errors}{$ret}++; - } -} - -sub syscalls::sys_enter_write -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $nr, $fd, $buf, $count) = @_; - - print_check(); - - $writes{$common_pid}{bytes_written} += $count; - $writes{$common_pid}{total_writes}++; - $writes{$common_pid}{comm} = $common_comm; -} - -sub trace_begin -{ - my $sa = POSIX::SigAction->new(\&set_print_pending); - $sa->flags(SA_RESTART); - $sa->safe(1); - POSIX::sigaction(SIGALRM, $sa) or die "Can't set SIGALRM handler: $!\n"; - alarm 1; -} - -sub trace_end -{ - print_unhandled(); - print_totals(); -} - -sub print_check() -{ - if ($print_pending == 1) { - $print_pending = 0; - print_totals(); - } -} - -sub set_print_pending() -{ - $print_pending = 1; - alarm $interval; -} - -sub print_totals -{ - my $count; - - $count = 0; - - clear_term(); - - printf("\nread counts by pid:\n\n"); - - printf("%6s %20s %10s %10s %10s\n", "pid", "comm", - "# reads", "bytes_req", "bytes_read"); - printf("%6s %-20s %10s %10s %10s\n", "------", "--------------------", - "----------", "----------", "----------"); - - foreach my $pid (sort { ($reads{$b}{bytes_read} || 0) <=> - ($reads{$a}{bytes_read} || 0) } keys %reads) { - my $comm = $reads{$pid}{comm} || ""; - my $total_reads = $reads{$pid}{total_reads} || 0; - my $bytes_requested = $reads{$pid}{bytes_requested} || 0; - my $bytes_read = $reads{$pid}{bytes_read} || 0; - - printf("%6s %-20s %10s %10s %10s\n", $pid, $comm, - $total_reads, $bytes_requested, $bytes_read); - - if (++$count == $nlines) { - last; - } - } - - $count = 0; - - printf("\nwrite counts by pid:\n\n"); - - printf("%6s %20s %10s %13s\n", "pid", "comm", - "# writes", "bytes_written"); - printf("%6s %-20s %10s %13s\n", "------", "--------------------", - "----------", "-------------"); - - foreach my $pid (sort { ($writes{$b}{bytes_written} || 0) <=> - ($writes{$a}{bytes_written} || 0)} keys %writes) { - my $comm = $writes{$pid}{comm} || ""; - my $total_writes = $writes{$pid}{total_writes} || 0; - my $bytes_written = $writes{$pid}{bytes_written} || 0; - - printf("%6s %-20s %10s %13s\n", $pid, $comm, - $total_writes, $bytes_written); - - if (++$count == $nlines) { - last; - } - } - - %reads = (); - %writes = (); -} - -my %unhandled; - -sub print_unhandled -{ - if ((scalar keys %unhandled) == 0) { - return; - } - - print "\nunhandled events:\n\n"; - - printf("%-40s %10s\n", "event", "count"); - printf("%-40s %10s\n", "----------------------------------------", - "-----------"); - - foreach my $event_name (keys %unhandled) { - printf("%-40s %10d\n", $event_name, $unhandled{$event_name}); - } -} - -sub trace_unhandled -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain) = @_; - - $unhandled{$event_name}++; -} diff --git a/tools/perf/scripts/perl/wakeup-latency.pl b/tools/perf/scripts/perl/wakeup-latency.pl deleted file mode 100644 index 53444ff4ec7f..000000000000 --- a/tools/perf/scripts/perl/wakeup-latency.pl +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/perl -w -# SPDX-License-Identifier: GPL-2.0-only -# (c) 2009, Tom Zanussi - -# Display avg/min/max wakeup latency - -# The common_* event handler fields are the most useful fields common to -# all events. They don't necessarily correspond to the 'common_*' fields -# in the status files. Those fields not available as handler params can -# be retrieved via script functions of the form get_common_*(). - -use 5.010000; -use strict; -use warnings; - -use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; -use lib "./Perf-Trace-Util/lib"; -use Perf::Trace::Core; -use Perf::Trace::Util; - -my %last_wakeup; - -my $max_wakeup_latency; -my $min_wakeup_latency; -my $total_wakeup_latency = 0; -my $total_wakeups = 0; - -sub sched::sched_switch -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $prev_comm, $prev_pid, $prev_prio, $prev_state, $next_comm, $next_pid, - $next_prio) = @_; - - my $wakeup_ts = $last_wakeup{$common_cpu}{ts}; - if ($wakeup_ts) { - my $switch_ts = nsecs($common_secs, $common_nsecs); - my $wakeup_latency = $switch_ts - $wakeup_ts; - if ($wakeup_latency > $max_wakeup_latency) { - $max_wakeup_latency = $wakeup_latency; - } - if ($wakeup_latency < $min_wakeup_latency) { - $min_wakeup_latency = $wakeup_latency; - } - $total_wakeup_latency += $wakeup_latency; - $total_wakeups++; - } - $last_wakeup{$common_cpu}{ts} = 0; -} - -sub sched::sched_wakeup -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain, - $comm, $pid, $prio, $success, $target_cpu) = @_; - - $last_wakeup{$target_cpu}{ts} = nsecs($common_secs, $common_nsecs); -} - -sub trace_begin -{ - $min_wakeup_latency = 1000000000; - $max_wakeup_latency = 0; -} - -sub trace_end -{ - printf("wakeup_latency stats:\n\n"); - print "total_wakeups: $total_wakeups\n"; - if ($total_wakeups) { - printf("avg_wakeup_latency (ns): %u\n", - avg($total_wakeup_latency, $total_wakeups)); - } else { - printf("avg_wakeup_latency (ns): N/A\n"); - } - printf("min_wakeup_latency (ns): %u\n", $min_wakeup_latency); - printf("max_wakeup_latency (ns): %u\n", $max_wakeup_latency); - - print_unhandled(); -} - -my %unhandled; - -sub print_unhandled -{ - if ((scalar keys %unhandled) == 0) { - return; - } - - print "\nunhandled events:\n\n"; - - printf("%-40s %10s\n", "event", "count"); - printf("%-40s %10s\n", "----------------------------------------", - "-----------"); - - foreach my $event_name (keys %unhandled) { - printf("%-40s %10d\n", $event_name, $unhandled{$event_name}); - } -} - -sub trace_unhandled -{ - my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs, - $common_pid, $common_comm, $common_callchain) = @_; - - $unhandled{$event_name}++; -} diff --git a/tools/perf/scripts/python/Perf-Trace-Util/Build b/tools/perf/scripts/python/Perf-Trace-Util/Build deleted file mode 100644 index be3710c61320..000000000000 --- a/tools/perf/scripts/python/Perf-Trace-Util/Build +++ /dev/null @@ -1,4 +0,0 @@ -perf-util-y += Context.o - -# -Wno-declaration-after-statement: The python headers have mixed code with declarations (decls after asserts, for instance) -CFLAGS_Context.o += $(PYTHON_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-nested-externs -Wno-declaration-after-statement diff --git a/tools/perf/scripts/python/Perf-Trace-Util/Context.c b/tools/perf/scripts/python/Perf-Trace-Util/Context.c deleted file mode 100644 index c19f44610983..000000000000 --- a/tools/perf/scripts/python/Perf-Trace-Util/Context.c +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Context.c. Python interfaces for perf script. - * - * Copyright (C) 2010 Tom Zanussi - */ - -/* - * Use Py_ssize_t for '#' formats to avoid DeprecationWarning: PY_SSIZE_T_CLEAN - * will be required for '#' formats. - */ -#define PY_SSIZE_T_CLEAN - -#include -#include "../../../util/config.h" -#include "../../../util/trace-event.h" -#include "../../../util/event.h" -#include "../../../util/symbol.h" -#include "../../../util/thread.h" -#include "../../../util/map.h" -#include "../../../util/maps.h" -#include "../../../util/auxtrace.h" -#include "../../../util/session.h" -#include "../../../util/srcline.h" -#include "../../../util/srccode.h" - -#define _PyCapsule_GetPointer(arg1, arg2) \ - PyCapsule_GetPointer((arg1), (arg2)) -#define _PyBytes_FromStringAndSize(arg1, arg2) \ - PyBytes_FromStringAndSize((arg1), (arg2)) -#define _PyUnicode_AsUTF8(arg) \ - PyUnicode_AsUTF8(arg) - -PyMODINIT_FUNC PyInit_perf_trace_context(void); - -static struct scripting_context *get_args(PyObject *args, const char *name, PyObject **arg2) -{ - int cnt = 1 + !!arg2; - PyObject *context; - - if (!PyArg_UnpackTuple(args, name, 1, cnt, &context, arg2)) - return NULL; - - return _PyCapsule_GetPointer(context, NULL); -} - -static struct scripting_context *get_scripting_context(PyObject *args) -{ - return get_args(args, "context", NULL); -} - -#ifdef HAVE_LIBTRACEEVENT -static PyObject *perf_trace_context_common_pc(PyObject *obj, PyObject *args) -{ - struct scripting_context *c = get_scripting_context(args); - - if (!c) - return NULL; - - return Py_BuildValue("i", common_pc(c)); -} - -static PyObject *perf_trace_context_common_flags(PyObject *obj, - PyObject *args) -{ - struct scripting_context *c = get_scripting_context(args); - - if (!c) - return NULL; - - return Py_BuildValue("i", common_flags(c)); -} - -static PyObject *perf_trace_context_common_lock_depth(PyObject *obj, - PyObject *args) -{ - struct scripting_context *c = get_scripting_context(args); - - if (!c) - return NULL; - - return Py_BuildValue("i", common_lock_depth(c)); -} -#endif - -static PyObject *perf_sample_insn(PyObject *obj, PyObject *args) -{ - struct scripting_context *c = get_scripting_context(args); - - if (!c) - return NULL; - - if (c->sample->ip && !c->sample->insn_len && thread__maps(c->al->thread)) { - struct machine *machine = maps__machine(thread__maps(c->al->thread)); - - perf_sample__fetch_insn(c->sample, c->al->thread, machine); - } - if (!c->sample->insn_len) - Py_RETURN_NONE; /* N.B. This is a return statement */ - - return _PyBytes_FromStringAndSize(c->sample->insn, c->sample->insn_len); -} - -static PyObject *perf_set_itrace_options(PyObject *obj, PyObject *args) -{ - struct scripting_context *c; - const char *itrace_options; - int retval = -1; - PyObject *str; - - c = get_args(args, "itrace_options", &str); - if (!c) - return NULL; - - if (!c->session || !c->session->itrace_synth_opts) - goto out; - - if (c->session->itrace_synth_opts->set) { - retval = 1; - goto out; - } - - itrace_options = _PyUnicode_AsUTF8(str); - - retval = itrace_do_parse_synth_opts(c->session->itrace_synth_opts, itrace_options, 0); -out: - return Py_BuildValue("i", retval); -} - -static PyObject *perf_sample_src(PyObject *obj, PyObject *args, bool get_srccode) -{ - struct scripting_context *c = get_scripting_context(args); - unsigned int line = 0; - char *srcfile = NULL; - char *srccode = NULL; - PyObject *result; - struct map *map; - struct dso *dso; - int len = 0; - u64 addr; - - if (!c) - return NULL; - - map = c->al->map; - addr = c->al->addr; - dso = map ? map__dso(map) : NULL; - - if (dso) - srcfile = get_srcline_split(dso, map__rip_2objdump(map, addr), &line); - - if (get_srccode) { - if (srcfile) - srccode = find_sourceline(srcfile, line, &len); - result = Py_BuildValue("(sIs#)", srcfile, line, srccode, (Py_ssize_t)len); - } else { - result = Py_BuildValue("(sI)", srcfile, line); - } - - free(srcfile); - - return result; -} - -static PyObject *perf_sample_srcline(PyObject *obj, PyObject *args) -{ - return perf_sample_src(obj, args, false); -} - -static PyObject *perf_sample_srccode(PyObject *obj, PyObject *args) -{ - return perf_sample_src(obj, args, true); -} - -static PyObject *__perf_config_get(PyObject *obj, PyObject *args) -{ - const char *config_name; - - if (!PyArg_ParseTuple(args, "s", &config_name)) - return NULL; - return Py_BuildValue("s", perf_config_get(config_name)); -} - -static PyMethodDef ContextMethods[] = { -#ifdef HAVE_LIBTRACEEVENT - { "common_pc", perf_trace_context_common_pc, METH_VARARGS, - "Get the common preempt count event field value."}, - { "common_flags", perf_trace_context_common_flags, METH_VARARGS, - "Get the common flags event field value."}, - { "common_lock_depth", perf_trace_context_common_lock_depth, - METH_VARARGS, "Get the common lock depth event field value."}, -#endif - { "perf_sample_insn", perf_sample_insn, - METH_VARARGS, "Get the machine code instruction."}, - { "perf_set_itrace_options", perf_set_itrace_options, - METH_VARARGS, "Set --itrace options."}, - { "perf_sample_srcline", perf_sample_srcline, - METH_VARARGS, "Get source file name and line number."}, - { "perf_sample_srccode", perf_sample_srccode, - METH_VARARGS, "Get source file name, line number and line."}, - { "perf_config_get", __perf_config_get, METH_VARARGS, "Get perf config entry"}, - { NULL, NULL, 0, NULL} -}; - -PyMODINIT_FUNC PyInit_perf_trace_context(void) -{ - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "perf_trace_context", /* m_name */ - "", /* m_doc */ - -1, /* m_size */ - ContextMethods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ - }; - PyObject *mod; - - mod = PyModule_Create(&moduledef); - /* Add perf_script_context to the module so it can be imported */ - PyObject_SetAttrString(mod, "perf_script_context", Py_None); - - return mod; -} diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py deleted file mode 100644 index 54ace2f6bc36..000000000000 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py +++ /dev/null @@ -1,116 +0,0 @@ -# Core.py - Python extension for perf script, core functions -# -# Copyright (C) 2010 by Tom Zanussi -# -# This software may be distributed under the terms of the GNU General -# Public License ("GPL") version 2 as published by the Free Software -# Foundation. - -from collections import defaultdict - -def autodict(): - return defaultdict(autodict) - -flag_fields = autodict() -symbolic_fields = autodict() - -def define_flag_field(event_name, field_name, delim): - flag_fields[event_name][field_name]['delim'] = delim - -def define_flag_value(event_name, field_name, value, field_str): - flag_fields[event_name][field_name]['values'][value] = field_str - -def define_symbolic_field(event_name, field_name): - # nothing to do, really - pass - -def define_symbolic_value(event_name, field_name, value, field_str): - symbolic_fields[event_name][field_name]['values'][value] = field_str - -def flag_str(event_name, field_name, value): - string = "" - - if flag_fields[event_name][field_name]: - print_delim = 0 - for idx in sorted(flag_fields[event_name][field_name]['values']): - if not value and not idx: - string += flag_fields[event_name][field_name]['values'][idx] - break - if idx and (value & idx) == idx: - if print_delim and flag_fields[event_name][field_name]['delim']: - string += " " + flag_fields[event_name][field_name]['delim'] + " " - string += flag_fields[event_name][field_name]['values'][idx] - print_delim = 1 - value &= ~idx - - return string - -def symbol_str(event_name, field_name, value): - string = "" - - if symbolic_fields[event_name][field_name]: - for idx in sorted(symbolic_fields[event_name][field_name]['values']): - if not value and not idx: - string = symbolic_fields[event_name][field_name]['values'][idx] - break - if (value == idx): - string = symbolic_fields[event_name][field_name]['values'][idx] - break - - return string - -trace_flags = { 0x00: "NONE", \ - 0x01: "IRQS_OFF", \ - 0x02: "IRQS_NOSUPPORT", \ - 0x04: "NEED_RESCHED", \ - 0x08: "HARDIRQ", \ - 0x10: "SOFTIRQ" } - -def trace_flag_str(value): - string = "" - print_delim = 0 - - for idx in trace_flags: - if not value and not idx: - string += "NONE" - break - - if idx and (value & idx) == idx: - if print_delim: - string += " | "; - string += trace_flags[idx] - print_delim = 1 - value &= ~idx - - return string - - -def taskState(state): - states = { - 0 : "R", - 1 : "S", - 2 : "D", - 64: "DEAD" - } - - if state not in states: - return "Unknown" - - return states[state] - - -class EventHeaders: - def __init__(self, common_cpu, common_secs, common_nsecs, - common_pid, common_comm, common_callchain): - self.cpu = common_cpu - self.secs = common_secs - self.nsecs = common_nsecs - self.pid = common_pid - self.comm = common_comm - self.callchain = common_callchain - - def ts(self): - return (self.secs * (10 ** 9)) + self.nsecs - - def ts_format(self): - return "%d.%d" % (self.secs, int(self.nsecs / 1000)) diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py deleted file mode 100755 index 21a7a1298094..000000000000 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py +++ /dev/null @@ -1,97 +0,0 @@ -# EventClass.py -# SPDX-License-Identifier: GPL-2.0 -# -# This is a library defining some events types classes, which could -# be used by other scripts to analyzing the perf samples. -# -# Currently there are just a few classes defined for examples, -# PerfEvent is the base class for all perf event sample, PebsEvent -# is a HW base Intel x86 PEBS event, and user could add more SW/HW -# event classes based on requirements. -from __future__ import print_function - -import struct - -# Event types, user could add more here -EVTYPE_GENERIC = 0 -EVTYPE_PEBS = 1 # Basic PEBS event -EVTYPE_PEBS_LL = 2 # PEBS event with load latency info -EVTYPE_IBS = 3 - -# -# Currently we don't have good way to tell the event type, but by -# the size of raw buffer, raw PEBS event with load latency data's -# size is 176 bytes, while the pure PEBS event's size is 144 bytes. -# -def create_event(name, comm, dso, symbol, raw_buf): - if (len(raw_buf) == 144): - event = PebsEvent(name, comm, dso, symbol, raw_buf) - elif (len(raw_buf) == 176): - event = PebsNHM(name, comm, dso, symbol, raw_buf) - else: - event = PerfEvent(name, comm, dso, symbol, raw_buf) - - return event - -class PerfEvent(object): - event_num = 0 - def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_GENERIC): - self.name = name - self.comm = comm - self.dso = dso - self.symbol = symbol - self.raw_buf = raw_buf - self.ev_type = ev_type - PerfEvent.event_num += 1 - - def show(self): - print("PMU event: name=%12s, symbol=%24s, comm=%8s, dso=%12s" % - (self.name, self.symbol, self.comm, self.dso)) - -# -# Basic Intel PEBS (Precise Event-based Sampling) event, whose raw buffer -# contains the context info when that event happened: the EFLAGS and -# linear IP info, as well as all the registers. -# -class PebsEvent(PerfEvent): - pebs_num = 0 - def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS): - tmp_buf=raw_buf[0:80] - flags, ip, ax, bx, cx, dx, si, di, bp, sp = struct.unpack('QQQQQQQQQQ', tmp_buf) - self.flags = flags - self.ip = ip - self.ax = ax - self.bx = bx - self.cx = cx - self.dx = dx - self.si = si - self.di = di - self.bp = bp - self.sp = sp - - PerfEvent.__init__(self, name, comm, dso, symbol, raw_buf, ev_type) - PebsEvent.pebs_num += 1 - del tmp_buf - -# -# Intel Nehalem and Westmere support PEBS plus Load Latency info which lie -# in the four 64 bit words write after the PEBS data: -# Status: records the IA32_PERF_GLOBAL_STATUS register value -# DLA: Data Linear Address (EIP) -# DSE: Data Source Encoding, where the latency happens, hit or miss -# in L1/L2/L3 or IO operations -# LAT: the actual latency in cycles -# -class PebsNHM(PebsEvent): - pebs_nhm_num = 0 - def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS_LL): - tmp_buf=raw_buf[144:176] - status, dla, dse, lat = struct.unpack('QQQQ', tmp_buf) - self.status = status - self.dla = dla - self.dse = dse - self.lat = lat - - PebsEvent.__init__(self, name, comm, dso, symbol, raw_buf, ev_type) - PebsNHM.pebs_nhm_num += 1 - del tmp_buf diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py deleted file mode 100644 index cac7b2542ee8..000000000000 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py +++ /dev/null @@ -1,184 +0,0 @@ -# SchedGui.py - Python extension for perf script, basic GUI code for -# traces drawing and overview. -# -# Copyright (C) 2010 by Frederic Weisbecker -# -# This software is distributed under the terms of the GNU General -# Public License ("GPL") version 2 as published by the Free Software -# Foundation. - - -try: - import wx -except ImportError: - raise ImportError("You need to install the wxpython lib for this script") - - -class RootFrame(wx.Frame): - Y_OFFSET = 100 - RECT_HEIGHT = 100 - RECT_SPACE = 50 - EVENT_MARKING_WIDTH = 5 - - def __init__(self, sched_tracer, title, parent = None, id = -1): - wx.Frame.__init__(self, parent, id, title) - - (self.screen_width, self.screen_height) = wx.GetDisplaySize() - self.screen_width -= 10 - self.screen_height -= 10 - self.zoom = 0.5 - self.scroll_scale = 20 - self.sched_tracer = sched_tracer - self.sched_tracer.set_root_win(self) - (self.ts_start, self.ts_end) = sched_tracer.interval() - self.update_width_virtual() - self.nr_rects = sched_tracer.nr_rectangles() + 1 - self.height_virtual = RootFrame.Y_OFFSET + (self.nr_rects * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)) - - # whole window panel - self.panel = wx.Panel(self, size=(self.screen_width, self.screen_height)) - - # scrollable container - self.scroll = wx.ScrolledWindow(self.panel) - self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale) - self.scroll.EnableScrolling(True, True) - self.scroll.SetFocus() - - # scrollable drawing area - self.scroll_panel = wx.Panel(self.scroll, size=(self.screen_width - 15, self.screen_height / 2)) - self.scroll_panel.Bind(wx.EVT_PAINT, self.on_paint) - self.scroll_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_press) - self.scroll_panel.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down) - self.scroll.Bind(wx.EVT_PAINT, self.on_paint) - self.scroll.Bind(wx.EVT_KEY_DOWN, self.on_key_press) - self.scroll.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down) - - self.scroll.Fit() - self.Fit() - - self.scroll_panel.SetDimensions(-1, -1, self.width_virtual, self.height_virtual, wx.SIZE_USE_EXISTING) - - self.txt = None - - self.Show(True) - - def us_to_px(self, val): - return val / (10 ** 3) * self.zoom - - def px_to_us(self, val): - return (val / self.zoom) * (10 ** 3) - - def scroll_start(self): - (x, y) = self.scroll.GetViewStart() - return (x * self.scroll_scale, y * self.scroll_scale) - - def scroll_start_us(self): - (x, y) = self.scroll_start() - return self.px_to_us(x) - - def paint_rectangle_zone(self, nr, color, top_color, start, end): - offset_px = self.us_to_px(start - self.ts_start) - width_px = self.us_to_px(end - self.ts_start) - - offset_py = RootFrame.Y_OFFSET + (nr * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)) - width_py = RootFrame.RECT_HEIGHT - - dc = self.dc - - if top_color is not None: - (r, g, b) = top_color - top_color = wx.Colour(r, g, b) - brush = wx.Brush(top_color, wx.SOLID) - dc.SetBrush(brush) - dc.DrawRectangle(offset_px, offset_py, width_px, RootFrame.EVENT_MARKING_WIDTH) - width_py -= RootFrame.EVENT_MARKING_WIDTH - offset_py += RootFrame.EVENT_MARKING_WIDTH - - (r ,g, b) = color - color = wx.Colour(r, g, b) - brush = wx.Brush(color, wx.SOLID) - dc.SetBrush(brush) - dc.DrawRectangle(offset_px, offset_py, width_px, width_py) - - def update_rectangles(self, dc, start, end): - start += self.ts_start - end += self.ts_start - self.sched_tracer.fill_zone(start, end) - - def on_paint(self, event): - dc = wx.PaintDC(self.scroll_panel) - self.dc = dc - - width = min(self.width_virtual, self.screen_width) - (x, y) = self.scroll_start() - start = self.px_to_us(x) - end = self.px_to_us(x + width) - self.update_rectangles(dc, start, end) - - def rect_from_ypixel(self, y): - y -= RootFrame.Y_OFFSET - rect = y / (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE) - height = y % (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE) - - if rect < 0 or rect > self.nr_rects - 1 or height > RootFrame.RECT_HEIGHT: - return -1 - - return rect - - def update_summary(self, txt): - if self.txt: - self.txt.Destroy() - self.txt = wx.StaticText(self.panel, -1, txt, (0, (self.screen_height / 2) + 50)) - - - def on_mouse_down(self, event): - (x, y) = event.GetPositionTuple() - rect = self.rect_from_ypixel(y) - if rect == -1: - return - - t = self.px_to_us(x) + self.ts_start - - self.sched_tracer.mouse_down(rect, t) - - - def update_width_virtual(self): - self.width_virtual = self.us_to_px(self.ts_end - self.ts_start) - - def __zoom(self, x): - self.update_width_virtual() - (xpos, ypos) = self.scroll.GetViewStart() - xpos = self.us_to_px(x) / self.scroll_scale - self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale, xpos, ypos) - self.Refresh() - - def zoom_in(self): - x = self.scroll_start_us() - self.zoom *= 2 - self.__zoom(x) - - def zoom_out(self): - x = self.scroll_start_us() - self.zoom /= 2 - self.__zoom(x) - - - def on_key_press(self, event): - key = event.GetRawKeyCode() - if key == ord("+"): - self.zoom_in() - return - if key == ord("-"): - self.zoom_out() - return - - key = event.GetKeyCode() - (x, y) = self.scroll.GetViewStart() - if key == wx.WXK_RIGHT: - self.scroll.Scroll(x + 1, y) - elif key == wx.WXK_LEFT: - self.scroll.Scroll(x - 1, y) - elif key == wx.WXK_DOWN: - self.scroll.Scroll(x, y + 1) - elif key == wx.WXK_UP: - self.scroll.Scroll(x, y - 1) diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py deleted file mode 100644 index b75d31858e54..000000000000 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py +++ /dev/null @@ -1,92 +0,0 @@ -# Util.py - Python extension for perf script, miscellaneous utility code -# -# Copyright (C) 2010 by Tom Zanussi -# -# This software may be distributed under the terms of the GNU General -# Public License ("GPL") version 2 as published by the Free Software -# Foundation. -from __future__ import print_function - -import errno, os - -FUTEX_WAIT = 0 -FUTEX_WAKE = 1 -FUTEX_PRIVATE_FLAG = 128 -FUTEX_CLOCK_REALTIME = 256 -FUTEX_CMD_MASK = ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) - -NSECS_PER_SEC = 1000000000 - -def avg(total, n): - return total / n - -def nsecs(secs, nsecs): - return secs * NSECS_PER_SEC + nsecs - -def nsecs_secs(nsecs): - return nsecs / NSECS_PER_SEC - -def nsecs_nsecs(nsecs): - return nsecs % NSECS_PER_SEC - -def nsecs_str(nsecs): - str = "%5u.%09u" % (nsecs_secs(nsecs), nsecs_nsecs(nsecs)), - return str - -def add_stats(dict, key, value): - if key not in dict: - dict[key] = (value, value, value, 1) - else: - min, max, avg, count = dict[key] - if value < min: - min = value - if value > max: - max = value - avg = (avg + value) / 2 - dict[key] = (min, max, avg, count + 1) - -def clear_term(): - print("\x1b[H\x1b[2J") - -audit_package_warned = False - -try: - import audit - machine_to_id = { - 'x86_64': audit.MACH_86_64, - 'aarch64': audit.MACH_AARCH64, - 'alpha' : audit.MACH_ALPHA, - 'ia64' : audit.MACH_IA64, - 'ppc' : audit.MACH_PPC, - 'ppc64' : audit.MACH_PPC64, - 'ppc64le' : audit.MACH_PPC64LE, - 's390' : audit.MACH_S390, - 's390x' : audit.MACH_S390X, - 'i386' : audit.MACH_X86, - 'i586' : audit.MACH_X86, - 'i686' : audit.MACH_X86, - } - try: - machine_to_id['armeb'] = audit.MACH_ARMEB - except: - pass - machine_id = machine_to_id[os.uname()[4]] -except: - if not audit_package_warned: - audit_package_warned = True - print("Install the python-audit package to get syscall names.\n" - "For example:\n # apt-get install python3-audit (Ubuntu)" - "\n # yum install python3-audit (Fedora)" - "\n etc.\n") - -def syscall_name(id): - try: - return audit.audit_syscall_to_name(id, machine_id) - except: - return str(id) - -def strerror(nr): - try: - return errno.errorcode[abs(nr)] - except: - return "Unknown %d errno" % nr diff --git a/tools/perf/scripts/python/arm-cs-trace-disasm.py b/tools/perf/scripts/python/arm-cs-trace-disasm.py deleted file mode 100755 index 42579f858684..000000000000 --- a/tools/perf/scripts/python/arm-cs-trace-disasm.py +++ /dev/null @@ -1,356 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# arm-cs-trace-disasm.py: ARM CoreSight Trace Dump With Disassember -# -# Author: Tor Jeremiassen -# Mathieu Poirier -# Leo Yan -# Al Grant - -from __future__ import print_function -import os -from os import path -import re -from subprocess import * -import argparse -import platform - -from perf_trace_context import perf_sample_srccode, perf_config_get - -# Below are some example commands for using this script. -# Note a --kcore recording is required for accurate decode -# due to the alternatives patching mechanism. In addition to this, -# source line info comes from Perf, and when using kcore there is -# no debug info. The following lists the supported features in each mode: -# -# +-----------+-----------------+------------------+------------------+ -# | Recording | Accurate decode | Source line dump | Disassembly dump | -# +-----------+-----------------+------------------+------------------+ -# | --kcore | yes | no | yes | -# | normal | no | yes (inaccurate) | yes (inaccurate) | -# +-----------+-----------------+------------------+------------------+ -# -# Output disassembly with objdump and auto detect vmlinux -# (when running on same machine.): -# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py \ -# -- -d -# -# Output disassembly with llvm-objdump: -# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py \ -# -- -d llvm-objdump-11 -k path/to/vmlinux -# -# Output accurate disassembly by passing kcore to script: -# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py \ -# -- -d -k perf.data/kcore_dir/kcore -# -# Output only source line and symbols: -# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py - -def default_objdump(): - config = perf_config_get("annotate.objdump") - return config if config else "objdump" - -# Command line parsing. -def int_arg(v): - v = int(v) - if v < 0: - raise argparse.ArgumentTypeError("Argument must be a positive integer") - return v - -args = argparse.ArgumentParser() -args.add_argument("-k", "--vmlinux", - help="Set path to vmlinux or kcore file. Omit to autodetect if running on same machine") -args.add_argument("-d", "--objdump", nargs="?", const=default_objdump(), - help="Show disassembly. Can also be used to change the objdump path"), -args.add_argument("-v", "--verbose", action="store_true", help="Enable debugging log") -args.add_argument("--start-time", type=int_arg, help="Monotonic clock time of sample to start from. " - "See 'time' field on samples in -v mode.") -args.add_argument("--stop-time", type=int_arg, help="Monotonic clock time of sample to stop at. " - "See 'time' field on samples in -v mode.") -args.add_argument("--start-sample", type=int_arg, help="Index of sample to start from. " - "See 'index' field on samples in -v mode.") -args.add_argument("--stop-sample", type=int_arg, help="Index of sample to stop at. " - "See 'index' field on samples in -v mode.") - -options = args.parse_args() -if (options.start_time and options.stop_time and - options.start_time >= options.stop_time): - print("--start-time must less than --stop-time") - exit(2) -if (options.start_sample and options.stop_sample and - options.start_sample >= options.stop_sample): - print("--start-sample must less than --stop-sample") - exit(2) - -# Initialize global dicts and regular expression -disasm_cache = dict() -cpu_data = dict() -disasm_re = re.compile(r"^\s*([0-9a-fA-F]+):") -disasm_func_re = re.compile(r"^\s*([0-9a-fA-F]+)\s.*:") -cache_size = 64*1024 -sample_idx = -1 - -glb_source_file_name = None -glb_line_number = None -glb_dso = None - -kver = platform.release() -vmlinux_paths = [ - f"/usr/lib/debug/boot/vmlinux-{kver}.debug", - f"/usr/lib/debug/lib/modules/{kver}/vmlinux", - f"/lib/modules/{kver}/build/vmlinux", - f"/usr/lib/debug/boot/vmlinux-{kver}", - f"/boot/vmlinux-{kver}", - f"/boot/vmlinux", - f"vmlinux" -] - -def get_optional(perf_dict, field): - if field in perf_dict: - return perf_dict[field] - return "[unknown]" - -def get_offset(perf_dict, field): - if field in perf_dict: - return "+%#x" % perf_dict[field] - return "" - -def find_vmlinux(): - if hasattr(find_vmlinux, "path"): - return find_vmlinux.path - - for v in vmlinux_paths: - if os.access(v, os.R_OK): - find_vmlinux.path = v - break - else: - find_vmlinux.path = None - - return find_vmlinux.path - -def get_dso_file_path(dso_name, dso_build_id): - if (dso_name == "[kernel.kallsyms]" or dso_name == "vmlinux"): - if (options.vmlinux): - return options.vmlinux; - else: - return find_vmlinux() if find_vmlinux() else dso_name - - if (dso_name == "[vdso]") : - append = "/vdso" - else: - append = "/elf" - - dso_path = os.environ['PERF_BUILDID_DIR'] + "/" + dso_name + "/" + dso_build_id + append; - # Replace duplicate slash chars to single slash char - dso_path = dso_path.replace('//', '/', 1) - return dso_path - -def read_disam(dso_fname, dso_start, start_addr, stop_addr): - addr_range = str(start_addr) + ":" + str(stop_addr) + ":" + dso_fname - - # Don't let the cache get too big, clear it when it hits max size - if (len(disasm_cache) > cache_size): - disasm_cache.clear(); - - if addr_range in disasm_cache: - disasm_output = disasm_cache[addr_range]; - else: - start_addr = start_addr - dso_start; - stop_addr = stop_addr - dso_start; - disasm = [ options.objdump, "-d", "-z", - "--start-address="+format(start_addr,"#x"), - "--stop-address="+format(stop_addr,"#x") ] - disasm += [ dso_fname ] - disasm_output = check_output(disasm).decode('utf-8').split('\n') - disasm_cache[addr_range] = disasm_output - - return disasm_output - -def print_disam(dso_fname, dso_start, start_addr, stop_addr): - for line in read_disam(dso_fname, dso_start, start_addr, stop_addr): - m = disasm_func_re.search(line) - if m is None: - m = disasm_re.search(line) - if m is None: - continue - print("\t" + line) - -def print_sample(sample): - print("Sample = { cpu: %04d addr: 0x%016x phys_addr: 0x%016x ip: 0x%016x " \ - "pid: %d tid: %d period: %d time: %d index: %d}" % \ - (sample['cpu'], sample['addr'], sample['phys_addr'], \ - sample['ip'], sample['pid'], sample['tid'], \ - sample['period'], sample['time'], sample_idx)) - -def trace_begin(): - print('ARM CoreSight Trace Data Assembler Dump') - -def trace_end(): - print('End') - -def trace_unhandled(event_name, context, event_fields_dict): - print(' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())])) - -def common_start_str(comm, sample): - sec = int(sample["time"] / 1000000000) - ns = sample["time"] % 1000000000 - cpu = sample["cpu"] - pid = sample["pid"] - tid = sample["tid"] - return "%16s %5u/%-5u [%04u] %9u.%09u " % (comm, pid, tid, cpu, sec, ns) - -# This code is copied from intel-pt-events.py for printing source code -# line and symbols. -def print_srccode(comm, param_dict, sample, symbol, dso): - ip = sample["ip"] - if symbol == "[unknown]": - start_str = common_start_str(comm, sample) + ("%x" % ip).rjust(16).ljust(40) - else: - offs = get_offset(param_dict, "symoff") - start_str = common_start_str(comm, sample) + (symbol + offs).ljust(40) - - global glb_source_file_name - global glb_line_number - global glb_dso - - source_file_name, line_number, source_line = perf_sample_srccode(perf_script_context) - if source_file_name: - if glb_line_number == line_number and glb_source_file_name == source_file_name: - src_str = "" - else: - if len(source_file_name) > 40: - src_file = ("..." + source_file_name[-37:]) + " " - else: - src_file = source_file_name.ljust(41) - - if source_line is None: - src_str = src_file + str(line_number).rjust(4) + " " - else: - src_str = src_file + str(line_number).rjust(4) + " " + source_line - glb_dso = None - elif dso == glb_dso: - src_str = "" - else: - src_str = dso - glb_dso = dso - - glb_line_number = line_number - glb_source_file_name = source_file_name - - print(start_str, src_str) - -def process_event(param_dict): - global cache_size - global options - global sample_idx - - sample = param_dict["sample"] - comm = param_dict["comm"] - - name = param_dict["ev_name"] - dso = get_optional(param_dict, "dso") - dso_bid = get_optional(param_dict, "dso_bid") - dso_start = get_optional(param_dict, "dso_map_start") - dso_end = get_optional(param_dict, "dso_map_end") - symbol = get_optional(param_dict, "symbol") - map_pgoff = get_optional(param_dict, "map_pgoff") - # check for valid map offset - if (str(map_pgoff) == '[unknown]'): - map_pgoff = 0 - - cpu = sample["cpu"] - ip = sample["ip"] - addr = sample["addr"] - - sample_idx += 1 - - if (options.start_time and sample["time"] < options.start_time): - return - if (options.stop_time and sample["time"] > options.stop_time): - exit(0) - if (options.start_sample and sample_idx < options.start_sample): - return - if (options.stop_sample and sample_idx > options.stop_sample): - exit(0) - - if (options.verbose == True): - print("Event type: %s" % name) - print_sample(sample) - - # Initialize CPU data if it's empty, and directly return back - # if this is the first tracing event for this CPU. - if (cpu_data.get(str(cpu) + 'addr') == None): - cpu_data[str(cpu) + 'addr'] = addr - return - - # If cannot find dso so cannot dump assembler, bail out - if (dso == '[unknown]'): - return - - # Validate dso start and end addresses - if ((dso_start == '[unknown]') or (dso_end == '[unknown]')): - print("Failed to find valid dso map for dso %s" % dso) - return - - if (name[0:12] == "instructions"): - print_srccode(comm, param_dict, sample, symbol, dso) - return - - # Don't proceed if this event is not a branch sample, . - if (name[0:8] != "branches"): - return - - # The format for packet is: - # - # +------------+------------+------------+ - # sample_prev: | addr | ip | cpu | - # +------------+------------+------------+ - # sample_next: | addr | ip | cpu | - # +------------+------------+------------+ - # - # We need to combine the two continuous packets to get the instruction - # range for sample_prev::cpu: - # - # [ sample_prev::addr .. sample_next::ip ] - # - # For this purose, sample_prev::addr is stored into cpu_data structure - # and read back for 'start_addr' when the new packet comes, and we need - # to use sample_next::ip to calculate 'stop_addr', plusing extra 4 for - # 'stop_addr' is for the sake of objdump so the final assembler dump can - # include last instruction for sample_next::ip. - start_addr = cpu_data[str(cpu) + 'addr'] - stop_addr = ip + 4 - - # Record for previous sample packet - cpu_data[str(cpu) + 'addr'] = addr - - # Filter out zero start_address. Optionally identify CS_ETM_TRACE_ON packet - if (start_addr == 0): - if ((stop_addr == 4) and (options.verbose == True)): - print("CPU%d: CS_ETM_TRACE_ON packet is inserted" % cpu) - return - - if (start_addr < int(dso_start) or start_addr > int(dso_end)): - print("Start address 0x%x is out of range [ 0x%x .. 0x%x ] for dso %s" % (start_addr, int(dso_start), int(dso_end), dso)) - return - - if (stop_addr < int(dso_start) or stop_addr > int(dso_end)): - print("Stop address 0x%x is out of range [ 0x%x .. 0x%x ] for dso %s" % (stop_addr, int(dso_start), int(dso_end), dso)) - return - - if (options.objdump != None): - # It doesn't need to decrease virtual memory offset for disassembly - # for kernel dso and executable file dso, so in this case we set - # vm_start to zero. - if (dso == "[kernel.kallsyms]" or dso_start == 0x400000): - dso_vm_start = 0 - map_pgoff = 0 - else: - dso_vm_start = int(dso_start) - - dso_fname = get_dso_file_path(dso, dso_bid) - if path.exists(dso_fname): - print_disam(dso_fname, dso_vm_start, start_addr + map_pgoff, stop_addr + map_pgoff) - else: - print("Failed to find dso %s for address range [ 0x%x .. 0x%x ]" % (dso, start_addr + map_pgoff, stop_addr + map_pgoff)) - - print_srccode(comm, param_dict, sample, symbol, dso) diff --git a/tools/perf/scripts/python/bin/compaction-times-record b/tools/perf/scripts/python/bin/compaction-times-record deleted file mode 100644 index 6edcd40e14e8..000000000000 --- a/tools/perf/scripts/python/bin/compaction-times-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e compaction:mm_compaction_begin -e compaction:mm_compaction_end -e compaction:mm_compaction_migratepages -e compaction:mm_compaction_isolate_migratepages -e compaction:mm_compaction_isolate_freepages $@ diff --git a/tools/perf/scripts/python/bin/compaction-times-report b/tools/perf/scripts/python/bin/compaction-times-report deleted file mode 100644 index 3dc13897cfde..000000000000 --- a/tools/perf/scripts/python/bin/compaction-times-report +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -#description: display time taken by mm compaction -#args: [-h] [-u] [-p|-pv] [-t | [-m] [-fs] [-ms]] [pid|pid-range|comm-regex] -perf script -s "$PERF_EXEC_PATH"/scripts/python/compaction-times.py $@ diff --git a/tools/perf/scripts/python/bin/event_analyzing_sample-record b/tools/perf/scripts/python/bin/event_analyzing_sample-record deleted file mode 100644 index 5ce652dabd02..000000000000 --- a/tools/perf/scripts/python/bin/event_analyzing_sample-record +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# -# event_analyzing_sample.py can cover all type of perf samples including -# the tracepoints, so no special record requirements, just record what -# you want to analyze. -# -perf record $@ diff --git a/tools/perf/scripts/python/bin/event_analyzing_sample-report b/tools/perf/scripts/python/bin/event_analyzing_sample-report deleted file mode 100644 index 0941fc94e158..000000000000 --- a/tools/perf/scripts/python/bin/event_analyzing_sample-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: analyze all perf samples -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/event_analyzing_sample.py diff --git a/tools/perf/scripts/python/bin/export-to-postgresql-record b/tools/perf/scripts/python/bin/export-to-postgresql-record deleted file mode 100644 index 221d66e05713..000000000000 --- a/tools/perf/scripts/python/bin/export-to-postgresql-record +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# -# export perf data to a postgresql database. Can cover -# perf ip samples (excluding the tracepoints). No special -# record requirements, just record what you want to export. -# -perf record $@ diff --git a/tools/perf/scripts/python/bin/export-to-postgresql-report b/tools/perf/scripts/python/bin/export-to-postgresql-report deleted file mode 100644 index cd335b6e2a01..000000000000 --- a/tools/perf/scripts/python/bin/export-to-postgresql-report +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# description: export perf data to a postgresql database -# args: [database name] [columns] [calls] -n_args=0 -for i in "$@" -do - if expr match "$i" "-" > /dev/null ; then - break - fi - n_args=$(( $n_args + 1 )) -done -if [ "$n_args" -gt 3 ] ; then - echo "usage: export-to-postgresql-report [database name] [columns] [calls]" - exit -fi -if [ "$n_args" -gt 2 ] ; then - dbname=$1 - columns=$2 - calls=$3 - shift 3 -elif [ "$n_args" -gt 1 ] ; then - dbname=$1 - columns=$2 - shift 2 -elif [ "$n_args" -gt 0 ] ; then - dbname=$1 - shift -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/export-to-postgresql.py $dbname $columns $calls diff --git a/tools/perf/scripts/python/bin/export-to-sqlite-record b/tools/perf/scripts/python/bin/export-to-sqlite-record deleted file mode 100644 index 070204fd6d00..000000000000 --- a/tools/perf/scripts/python/bin/export-to-sqlite-record +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# -# export perf data to a sqlite3 database. Can cover -# perf ip samples (excluding the tracepoints). No special -# record requirements, just record what you want to export. -# -perf record $@ diff --git a/tools/perf/scripts/python/bin/export-to-sqlite-report b/tools/perf/scripts/python/bin/export-to-sqlite-report deleted file mode 100644 index 5ff6033e70ba..000000000000 --- a/tools/perf/scripts/python/bin/export-to-sqlite-report +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# description: export perf data to a sqlite3 database -# args: [database name] [columns] [calls] -n_args=0 -for i in "$@" -do - if expr match "$i" "-" > /dev/null ; then - break - fi - n_args=$(( $n_args + 1 )) -done -if [ "$n_args" -gt 3 ] ; then - echo "usage: export-to-sqlite-report [database name] [columns] [calls]" - exit -fi -if [ "$n_args" -gt 2 ] ; then - dbname=$1 - columns=$2 - calls=$3 - shift 3 -elif [ "$n_args" -gt 1 ] ; then - dbname=$1 - columns=$2 - shift 2 -elif [ "$n_args" -gt 0 ] ; then - dbname=$1 - shift -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/export-to-sqlite.py $dbname $columns $calls diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record deleted file mode 100644 index 74685f318379..000000000000 --- a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -(perf record -e raw_syscalls:sys_exit $@ || \ - perf record -e syscalls:sys_exit $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report deleted file mode 100644 index fda5096d0cbf..000000000000 --- a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# description: system-wide failed syscalls, by pid -# args: [comm] -if [ $# -gt 0 ] ; then - if ! expr match "$1" "-" > /dev/null ; then - comm=$1 - shift - fi -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/failed-syscalls-by-pid.py $comm diff --git a/tools/perf/scripts/python/bin/flamegraph-record b/tools/perf/scripts/python/bin/flamegraph-record deleted file mode 100755 index 7df5a19c0163..000000000000 --- a/tools/perf/scripts/python/bin/flamegraph-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -g "$@" diff --git a/tools/perf/scripts/python/bin/flamegraph-report b/tools/perf/scripts/python/bin/flamegraph-report deleted file mode 100755 index 453a6918afbe..000000000000 --- a/tools/perf/scripts/python/bin/flamegraph-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: create flame graphs -perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py "$@" diff --git a/tools/perf/scripts/python/bin/futex-contention-record b/tools/perf/scripts/python/bin/futex-contention-record deleted file mode 100644 index b1495c9a9b20..000000000000 --- a/tools/perf/scripts/python/bin/futex-contention-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e syscalls:sys_enter_futex -e syscalls:sys_exit_futex $@ diff --git a/tools/perf/scripts/python/bin/futex-contention-report b/tools/perf/scripts/python/bin/futex-contention-report deleted file mode 100644 index 6c44271091ab..000000000000 --- a/tools/perf/scripts/python/bin/futex-contention-report +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -# description: futext contention measurement - -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/futex-contention.py diff --git a/tools/perf/scripts/python/bin/gecko-record b/tools/perf/scripts/python/bin/gecko-record deleted file mode 100644 index f0d1aa55f171..000000000000 --- a/tools/perf/scripts/python/bin/gecko-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -F 99 -g "$@" diff --git a/tools/perf/scripts/python/bin/gecko-report b/tools/perf/scripts/python/bin/gecko-report deleted file mode 100755 index 1867ec8d9757..000000000000 --- a/tools/perf/scripts/python/bin/gecko-report +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# description: create firefox gecko profile json format from perf.data -if [ "$*" = "-i -" ]; then -perf script -s "$PERF_EXEC_PATH"/scripts/python/gecko.py -else -perf script -s "$PERF_EXEC_PATH"/scripts/python/gecko.py -- "$@" -fi diff --git a/tools/perf/scripts/python/bin/intel-pt-events-record b/tools/perf/scripts/python/bin/intel-pt-events-record deleted file mode 100644 index 6b9877cfe23e..000000000000 --- a/tools/perf/scripts/python/bin/intel-pt-events-record +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# -# print Intel PT Events including Power Events and PTWRITE. The intel_pt PMU -# event needs to be specified with appropriate config terms. -# -if ! echo "$@" | grep -q intel_pt ; then - echo "Options must include the Intel PT event e.g. -e intel_pt/pwr_evt,ptw/" - echo "and for power events it probably needs to be system wide i.e. -a option" - echo "For example: -a -e intel_pt/pwr_evt,branch=0/ sleep 1" - exit 1 -fi -perf record $@ diff --git a/tools/perf/scripts/python/bin/intel-pt-events-report b/tools/perf/scripts/python/bin/intel-pt-events-report deleted file mode 100644 index beeac3fde9db..000000000000 --- a/tools/perf/scripts/python/bin/intel-pt-events-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: print Intel PT Events including Power Events and PTWRITE -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/intel-pt-events.py diff --git a/tools/perf/scripts/python/bin/mem-phys-addr-record b/tools/perf/scripts/python/bin/mem-phys-addr-record deleted file mode 100644 index 5a875122a904..000000000000 --- a/tools/perf/scripts/python/bin/mem-phys-addr-record +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# -# Profiling physical memory by all retired load instructions/uops event -# MEM_INST_RETIRED.ALL_LOADS or MEM_UOPS_RETIRED.ALL_LOADS -# - -load=`perf list | grep mem_inst_retired.all_loads` -if [ -z "$load" ]; then - load=`perf list | grep mem_uops_retired.all_loads` -fi -if [ -z "$load" ]; then - echo "There is no event to count all retired load instructions/uops." - exit 1 -fi - -arg=$(echo $load | tr -d ' ') -arg="$arg:P" -perf record --phys-data -e $arg $@ diff --git a/tools/perf/scripts/python/bin/mem-phys-addr-report b/tools/perf/scripts/python/bin/mem-phys-addr-report deleted file mode 100644 index 3f2b847e2eab..000000000000 --- a/tools/perf/scripts/python/bin/mem-phys-addr-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: resolve physical address samples -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/mem-phys-addr.py diff --git a/tools/perf/scripts/python/bin/net_dropmonitor-record b/tools/perf/scripts/python/bin/net_dropmonitor-record deleted file mode 100755 index 423fb81dadae..000000000000 --- a/tools/perf/scripts/python/bin/net_dropmonitor-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e skb:kfree_skb $@ diff --git a/tools/perf/scripts/python/bin/net_dropmonitor-report b/tools/perf/scripts/python/bin/net_dropmonitor-report deleted file mode 100755 index 8d698f5a06aa..000000000000 --- a/tools/perf/scripts/python/bin/net_dropmonitor-report +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -# description: display a table of dropped frames - -perf script -s "$PERF_EXEC_PATH"/scripts/python/net_dropmonitor.py $@ diff --git a/tools/perf/scripts/python/bin/netdev-times-record b/tools/perf/scripts/python/bin/netdev-times-record deleted file mode 100644 index 558754b840a9..000000000000 --- a/tools/perf/scripts/python/bin/netdev-times-record +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -perf record -e net:net_dev_xmit -e net:net_dev_queue \ - -e net:netif_receive_skb -e net:netif_rx \ - -e skb:consume_skb -e skb:kfree_skb \ - -e skb:skb_copy_datagram_iovec -e napi:napi_poll \ - -e irq:irq_handler_entry -e irq:irq_handler_exit \ - -e irq:softirq_entry -e irq:softirq_exit \ - -e irq:softirq_raise $@ diff --git a/tools/perf/scripts/python/bin/netdev-times-report b/tools/perf/scripts/python/bin/netdev-times-report deleted file mode 100644 index 8f759291da86..000000000000 --- a/tools/perf/scripts/python/bin/netdev-times-report +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# description: display a process of packet and processing time -# args: [tx] [rx] [dev=] [debug] - -perf script -s "$PERF_EXEC_PATH"/scripts/python/netdev-times.py $@ diff --git a/tools/perf/scripts/python/bin/powerpc-hcalls-record b/tools/perf/scripts/python/bin/powerpc-hcalls-record deleted file mode 100644 index b7402aa9147d..000000000000 --- a/tools/perf/scripts/python/bin/powerpc-hcalls-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e "{powerpc:hcall_entry,powerpc:hcall_exit}" $@ diff --git a/tools/perf/scripts/python/bin/powerpc-hcalls-report b/tools/perf/scripts/python/bin/powerpc-hcalls-report deleted file mode 100644 index dd32ad7465f6..000000000000 --- a/tools/perf/scripts/python/bin/powerpc-hcalls-report +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/powerpc-hcalls.py diff --git a/tools/perf/scripts/python/bin/sched-migration-record b/tools/perf/scripts/python/bin/sched-migration-record deleted file mode 100644 index 7493fddbe995..000000000000 --- a/tools/perf/scripts/python/bin/sched-migration-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -m 16384 -e sched:sched_wakeup -e sched:sched_wakeup_new -e sched:sched_switch -e sched:sched_migrate_task $@ diff --git a/tools/perf/scripts/python/bin/sched-migration-report b/tools/perf/scripts/python/bin/sched-migration-report deleted file mode 100644 index 68b037a1849b..000000000000 --- a/tools/perf/scripts/python/bin/sched-migration-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: sched migration overview -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sched-migration.py diff --git a/tools/perf/scripts/python/bin/sctop-record b/tools/perf/scripts/python/bin/sctop-record deleted file mode 100644 index d6940841e54f..000000000000 --- a/tools/perf/scripts/python/bin/sctop-record +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -(perf record -e raw_syscalls:sys_enter $@ || \ - perf record -e syscalls:sys_enter $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/sctop-report b/tools/perf/scripts/python/bin/sctop-report deleted file mode 100644 index c32db294124d..000000000000 --- a/tools/perf/scripts/python/bin/sctop-report +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# description: syscall top -# args: [comm] [interval] -n_args=0 -for i in "$@" -do - if expr match "$i" "-" > /dev/null ; then - break - fi - n_args=$(( $n_args + 1 )) -done -if [ "$n_args" -gt 2 ] ; then - echo "usage: sctop-report [comm] [interval]" - exit -fi -if [ "$n_args" -gt 1 ] ; then - comm=$1 - interval=$2 - shift 2 -elif [ "$n_args" -gt 0 ] ; then - interval=$1 - shift -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sctop.py $comm $interval diff --git a/tools/perf/scripts/python/bin/stackcollapse-record b/tools/perf/scripts/python/bin/stackcollapse-record deleted file mode 100755 index 9d8f9f0f3a17..000000000000 --- a/tools/perf/scripts/python/bin/stackcollapse-record +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# -# stackcollapse.py can cover all type of perf samples including -# the tracepoints, so no special record requirements, just record what -# you want to analyze. -# -perf record "$@" diff --git a/tools/perf/scripts/python/bin/stackcollapse-report b/tools/perf/scripts/python/bin/stackcollapse-report deleted file mode 100755 index 21a356bd27f6..000000000000 --- a/tools/perf/scripts/python/bin/stackcollapse-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# description: produce callgraphs in short form for scripting use -perf script -s "$PERF_EXEC_PATH"/scripts/python/stackcollapse.py "$@" diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-record b/tools/perf/scripts/python/bin/syscall-counts-by-pid-record deleted file mode 100644 index d6940841e54f..000000000000 --- a/tools/perf/scripts/python/bin/syscall-counts-by-pid-record +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -(perf record -e raw_syscalls:sys_enter $@ || \ - perf record -e syscalls:sys_enter $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report deleted file mode 100644 index 16eb8d65c543..000000000000 --- a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# description: system-wide syscall counts, by pid -# args: [comm] -if [ $# -gt 0 ] ; then - if ! expr match "$1" "-" > /dev/null ; then - comm=$1 - shift - fi -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts-by-pid.py $comm diff --git a/tools/perf/scripts/python/bin/syscall-counts-record b/tools/perf/scripts/python/bin/syscall-counts-record deleted file mode 100644 index d6940841e54f..000000000000 --- a/tools/perf/scripts/python/bin/syscall-counts-record +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -(perf record -e raw_syscalls:sys_enter $@ || \ - perf record -e syscalls:sys_enter $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/syscall-counts-report b/tools/perf/scripts/python/bin/syscall-counts-report deleted file mode 100644 index 0f0e9d453bb4..000000000000 --- a/tools/perf/scripts/python/bin/syscall-counts-report +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# description: system-wide syscall counts -# args: [comm] -if [ $# -gt 0 ] ; then - if ! expr match "$1" "-" > /dev/null ; then - comm=$1 - shift - fi -fi -perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts.py $comm diff --git a/tools/perf/scripts/python/bin/task-analyzer-record b/tools/perf/scripts/python/bin/task-analyzer-record deleted file mode 100755 index 0f6b51bb2767..000000000000 --- a/tools/perf/scripts/python/bin/task-analyzer-record +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -perf record -e sched:sched_switch -e sched:sched_migrate_task "$@" diff --git a/tools/perf/scripts/python/bin/task-analyzer-report b/tools/perf/scripts/python/bin/task-analyzer-report deleted file mode 100755 index 4b16a8cc40a0..000000000000 --- a/tools/perf/scripts/python/bin/task-analyzer-report +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# description: analyze timings of tasks -perf script -s "$PERF_EXEC_PATH"/scripts/python/task-analyzer.py -- "$@" diff --git a/tools/perf/scripts/python/check-perf-trace.py b/tools/perf/scripts/python/check-perf-trace.py deleted file mode 100644 index d2c22954800d..000000000000 --- a/tools/perf/scripts/python/check-perf-trace.py +++ /dev/null @@ -1,84 +0,0 @@ -# perf script event handlers, generated by perf script -g python -# (c) 2010, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 -# -# This script tests basic functionality such as flag and symbol -# strings, common_xxx() calls back into perf, begin, end, unhandled -# events, etc. Basically, if this script runs successfully and -# displays expected results, Python scripting support should be ok. - -from __future__ import print_function - -import os -import sys - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from Core import * -from perf_trace_context import * - -unhandled = autodict() - -def trace_begin(): - print("trace_begin") - pass - -def trace_end(): - print_unhandled() - -def irq__softirq_entry(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, vec): - print_header(event_name, common_cpu, common_secs, common_nsecs, - common_pid, common_comm) - - print_uncommon(context) - - print("vec=%s" % (symbol_str("irq__softirq_entry", "vec", vec))) - -def kmem__kmalloc(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, call_site, ptr, bytes_req, bytes_alloc, - gfp_flags): - print_header(event_name, common_cpu, common_secs, common_nsecs, - common_pid, common_comm) - - print_uncommon(context) - - print("call_site=%u, ptr=%u, bytes_req=%u, " - "bytes_alloc=%u, gfp_flags=%s" % - (call_site, ptr, bytes_req, bytes_alloc, - flag_str("kmem__kmalloc", "gfp_flags", gfp_flags))) - -def trace_unhandled(event_name, context, event_fields_dict): - try: - unhandled[event_name] += 1 - except TypeError: - unhandled[event_name] = 1 - -def print_header(event_name, cpu, secs, nsecs, pid, comm): - print("%-20s %5u %05u.%09u %8u %-20s " % - (event_name, cpu, secs, nsecs, pid, comm), - end=' ') - -# print trace fields not included in handler args -def print_uncommon(context): - print("common_preempt_count=%d, common_flags=%s, " - "common_lock_depth=%d, " % - (common_pc(context), trace_flag_str(common_flags(context)), - common_lock_depth(context))) - -def print_unhandled(): - keys = unhandled.keys() - if not keys: - return - - print("\nunhandled events:\n") - - print("%-40s %10s" % ("event", "count")) - print("%-40s %10s" % ("----------------------------------------", - "-----------")) - - for event_name in keys: - print("%-40s %10d\n" % (event_name, unhandled[event_name])) diff --git a/tools/perf/scripts/python/compaction-times.py b/tools/perf/scripts/python/compaction-times.py deleted file mode 100644 index 9401f7c14747..000000000000 --- a/tools/perf/scripts/python/compaction-times.py +++ /dev/null @@ -1,311 +0,0 @@ -# report time spent in compaction -# Licensed under the terms of the GNU GPL License version 2 - -# testing: -# 'echo 1 > /proc/sys/vm/compact_memory' to force compaction of all zones - -import os -import sys -import re - -import signal -signal.signal(signal.SIGPIPE, signal.SIG_DFL) - -usage = "usage: perf script report compaction-times.py -- [-h] [-u] [-p|-pv] [-t | [-m] [-fs] [-ms]] [pid|pid-range|comm-regex]\n" - -class popt: - DISP_DFL = 0 - DISP_PROC = 1 - DISP_PROC_VERBOSE=2 - -class topt: - DISP_TIME = 0 - DISP_MIG = 1 - DISP_ISOLFREE = 2 - DISP_ISOLMIG = 4 - DISP_ALL = 7 - -class comm_filter: - def __init__(self, re): - self.re = re - - def filter(self, pid, comm): - m = self.re.search(comm) - return m == None or m.group() == "" - -class pid_filter: - def __init__(self, low, high): - self.low = (0 if low == "" else int(low)) - self.high = (0 if high == "" else int(high)) - - def filter(self, pid, comm): - return not (pid >= self.low and (self.high == 0 or pid <= self.high)) - -def set_type(t): - global opt_disp - opt_disp = (t if opt_disp == topt.DISP_ALL else opt_disp|t) - -def ns(sec, nsec): - return (sec * 1000000000) + nsec - -def time(ns): - return "%dns" % ns if opt_ns else "%dus" % (round(ns, -3) / 1000) - -class pair: - def __init__(self, aval, bval, alabel = None, blabel = None): - self.alabel = alabel - self.blabel = blabel - self.aval = aval - self.bval = bval - - def __add__(self, rhs): - self.aval += rhs.aval - self.bval += rhs.bval - return self - - def __str__(self): - return "%s=%d %s=%d" % (self.alabel, self.aval, self.blabel, self.bval) - -class cnode: - def __init__(self, ns): - self.ns = ns - self.migrated = pair(0, 0, "moved", "failed") - self.fscan = pair(0,0, "scanned", "isolated") - self.mscan = pair(0,0, "scanned", "isolated") - - def __add__(self, rhs): - self.ns += rhs.ns - self.migrated += rhs.migrated - self.fscan += rhs.fscan - self.mscan += rhs.mscan - return self - - def __str__(self): - prev = 0 - s = "%s " % time(self.ns) - if (opt_disp & topt.DISP_MIG): - s += "migration: %s" % self.migrated - prev = 1 - if (opt_disp & topt.DISP_ISOLFREE): - s += "%sfree_scanner: %s" % (" " if prev else "", self.fscan) - prev = 1 - if (opt_disp & topt.DISP_ISOLMIG): - s += "%smigration_scanner: %s" % (" " if prev else "", self.mscan) - return s - - def complete(self, secs, nsecs): - self.ns = ns(secs, nsecs) - self.ns - - def increment(self, migrated, fscan, mscan): - if (migrated != None): - self.migrated += migrated - if (fscan != None): - self.fscan += fscan - if (mscan != None): - self.mscan += mscan - - -class chead: - heads = {} - val = cnode(0); - fobj = None - - @classmethod - def add_filter(cls, filter): - cls.fobj = filter - - @classmethod - def create_pending(cls, pid, comm, start_secs, start_nsecs): - filtered = 0 - try: - head = cls.heads[pid] - filtered = head.is_filtered() - except KeyError: - if cls.fobj != None: - filtered = cls.fobj.filter(pid, comm) - head = cls.heads[pid] = chead(comm, pid, filtered) - - if not filtered: - head.mark_pending(start_secs, start_nsecs) - - @classmethod - def increment_pending(cls, pid, migrated, fscan, mscan): - head = cls.heads[pid] - if not head.is_filtered(): - if head.is_pending(): - head.do_increment(migrated, fscan, mscan) - else: - sys.stderr.write("missing start compaction event for pid %d\n" % pid) - - @classmethod - def complete_pending(cls, pid, secs, nsecs): - head = cls.heads[pid] - if not head.is_filtered(): - if head.is_pending(): - head.make_complete(secs, nsecs) - else: - sys.stderr.write("missing start compaction event for pid %d\n" % pid) - - @classmethod - def gen(cls): - if opt_proc != popt.DISP_DFL: - for i in cls.heads: - yield cls.heads[i] - - @classmethod - def str(cls): - return cls.val - - def __init__(self, comm, pid, filtered): - self.comm = comm - self.pid = pid - self.val = cnode(0) - self.pending = None - self.filtered = filtered - self.list = [] - - def __add__(self, rhs): - self.ns += rhs.ns - self.val += rhs.val - return self - - def mark_pending(self, secs, nsecs): - self.pending = cnode(ns(secs, nsecs)) - - def do_increment(self, migrated, fscan, mscan): - self.pending.increment(migrated, fscan, mscan) - - def make_complete(self, secs, nsecs): - self.pending.complete(secs, nsecs) - chead.val += self.pending - - if opt_proc != popt.DISP_DFL: - self.val += self.pending - - if opt_proc == popt.DISP_PROC_VERBOSE: - self.list.append(self.pending) - self.pending = None - - def enumerate(self): - if opt_proc == popt.DISP_PROC_VERBOSE and not self.is_filtered(): - for i, pelem in enumerate(self.list): - sys.stdout.write("%d[%s].%d: %s\n" % (self.pid, self.comm, i+1, pelem)) - - def is_pending(self): - return self.pending != None - - def is_filtered(self): - return self.filtered - - def display(self): - if not self.is_filtered(): - sys.stdout.write("%d[%s]: %s\n" % (self.pid, self.comm, self.val)) - - -def trace_end(): - sys.stdout.write("total: %s\n" % chead.str()) - for i in chead.gen(): - i.display(), - i.enumerate() - -def compaction__mm_compaction_migratepages(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, nr_migrated, nr_failed): - - chead.increment_pending(common_pid, - pair(nr_migrated, nr_failed), None, None) - -def compaction__mm_compaction_isolate_freepages(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken): - - chead.increment_pending(common_pid, - None, pair(nr_scanned, nr_taken), None) - -def compaction__mm_compaction_isolate_migratepages(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken): - - chead.increment_pending(common_pid, - None, None, pair(nr_scanned, nr_taken)) - -def compaction__mm_compaction_end(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, zone_start, migrate_start, free_start, zone_end, - sync, status): - - chead.complete_pending(common_pid, common_secs, common_nsecs) - -def compaction__mm_compaction_begin(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, zone_start, migrate_start, free_start, zone_end, - sync): - - chead.create_pending(common_pid, common_comm, common_secs, common_nsecs) - -def pr_help(): - global usage - - sys.stdout.write(usage) - sys.stdout.write("\n") - sys.stdout.write("-h display this help\n") - sys.stdout.write("-p display by process\n") - sys.stdout.write("-pv display by process (verbose)\n") - sys.stdout.write("-t display stall times only\n") - sys.stdout.write("-m display stats for migration\n") - sys.stdout.write("-fs display stats for free scanner\n") - sys.stdout.write("-ms display stats for migration scanner\n") - sys.stdout.write("-u display results in microseconds (default nanoseconds)\n") - - -comm_re = None -pid_re = None -pid_regex = r"^(\d*)-(\d*)$|^(\d*)$" - -opt_proc = popt.DISP_DFL -opt_disp = topt.DISP_ALL - -opt_ns = True - -argc = len(sys.argv) - 1 -if argc >= 1: - pid_re = re.compile(pid_regex) - - for i, opt in enumerate(sys.argv[1:]): - if opt[0] == "-": - if opt == "-h": - pr_help() - exit(0); - elif opt == "-p": - opt_proc = popt.DISP_PROC - elif opt == "-pv": - opt_proc = popt.DISP_PROC_VERBOSE - elif opt == '-u': - opt_ns = False - elif opt == "-t": - set_type(topt.DISP_TIME) - elif opt == "-m": - set_type(topt.DISP_MIG) - elif opt == "-fs": - set_type(topt.DISP_ISOLFREE) - elif opt == "-ms": - set_type(topt.DISP_ISOLMIG) - else: - sys.exit(usage) - - elif i == argc - 1: - m = pid_re.search(opt) - if m != None and m.group() != "": - if m.group(3) != None: - f = pid_filter(m.group(3), m.group(3)) - else: - f = pid_filter(m.group(1), m.group(2)) - else: - try: - comm_re=re.compile(opt) - except: - sys.stderr.write("invalid regex '%s'" % opt) - sys.exit(usage) - f = comm_filter(comm_re) - - chead.add_filter(f) diff --git a/tools/perf/scripts/python/event_analyzing_sample.py b/tools/perf/scripts/python/event_analyzing_sample.py deleted file mode 100644 index aa1e2cfa26a6..000000000000 --- a/tools/perf/scripts/python/event_analyzing_sample.py +++ /dev/null @@ -1,192 +0,0 @@ -# event_analyzing_sample.py: general event handler in python -# SPDX-License-Identifier: GPL-2.0 -# -# Current perf report is already very powerful with the annotation integrated, -# and this script is not trying to be as powerful as perf report, but -# providing end user/developer a flexible way to analyze the events other -# than trace points. -# -# The 2 database related functions in this script just show how to gather -# the basic information, and users can modify and write their own functions -# according to their specific requirement. -# -# The first function "show_general_events" just does a basic grouping for all -# generic events with the help of sqlite, and the 2nd one "show_pebs_ll" is -# for a x86 HW PMU event: PEBS with load latency data. -# - -from __future__ import print_function - -import os -import sys -import math -import struct -import sqlite3 - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from EventClass import * - -# -# If the perf.data has a big number of samples, then the insert operation -# will be very time consuming (about 10+ minutes for 10000 samples) if the -# .db database is on disk. Move the .db file to RAM based FS to speedup -# the handling, which will cut the time down to several seconds. -# -con = sqlite3.connect("/dev/shm/perf.db") -con.isolation_level = None - -def trace_begin(): - print("In trace_begin:\n") - - # - # Will create several tables at the start, pebs_ll is for PEBS data with - # load latency info, while gen_events is for general event. - # - con.execute(""" - create table if not exists gen_events ( - name text, - symbol text, - comm text, - dso text - );""") - con.execute(""" - create table if not exists pebs_ll ( - name text, - symbol text, - comm text, - dso text, - flags integer, - ip integer, - status integer, - dse integer, - dla integer, - lat integer - );""") - -# -# Create and insert event object to a database so that user could -# do more analysis with simple database commands. -# -def process_event(param_dict): - event_attr = param_dict["attr"] - sample = param_dict["sample"] - raw_buf = param_dict["raw_buf"] - comm = param_dict["comm"] - name = param_dict["ev_name"] - - # Symbol and dso info are not always resolved - if ("dso" in param_dict): - dso = param_dict["dso"] - else: - dso = "Unknown_dso" - - if ("symbol" in param_dict): - symbol = param_dict["symbol"] - else: - symbol = "Unknown_symbol" - - # Create the event object and insert it to the right table in database - event = create_event(name, comm, dso, symbol, raw_buf) - insert_db(event) - -def insert_db(event): - if event.ev_type == EVTYPE_GENERIC: - con.execute("insert into gen_events values(?, ?, ?, ?)", - (event.name, event.symbol, event.comm, event.dso)) - elif event.ev_type == EVTYPE_PEBS_LL: - event.ip &= 0x7fffffffffffffff - event.dla &= 0x7fffffffffffffff - con.execute("insert into pebs_ll values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - (event.name, event.symbol, event.comm, event.dso, event.flags, - event.ip, event.status, event.dse, event.dla, event.lat)) - -def trace_end(): - print("In trace_end:\n") - # We show the basic info for the 2 type of event classes - show_general_events() - show_pebs_ll() - con.close() - -# -# As the event number may be very big, so we can't use linear way -# to show the histogram in real number, but use a log2 algorithm. -# - -def num2sym(num): - # Each number will have at least one '#' - snum = '#' * (int)(math.log(num, 2) + 1) - return snum - -def show_general_events(): - - # Check the total record number in the table - count = con.execute("select count(*) from gen_events") - for t in count: - print("There is %d records in gen_events table" % t[0]) - if t[0] == 0: - return - - print("Statistics about the general events grouped by thread/symbol/dso: \n") - - # Group by thread - commq = con.execute("select comm, count(comm) from gen_events group by comm order by -count(comm)") - print("\n%16s %8s %16s\n%s" % ("comm", "number", "histogram", "="*42)) - for row in commq: - print("%16s %8d %s" % (row[0], row[1], num2sym(row[1]))) - - # Group by symbol - print("\n%32s %8s %16s\n%s" % ("symbol", "number", "histogram", "="*58)) - symbolq = con.execute("select symbol, count(symbol) from gen_events group by symbol order by -count(symbol)") - for row in symbolq: - print("%32s %8d %s" % (row[0], row[1], num2sym(row[1]))) - - # Group by dso - print("\n%40s %8s %16s\n%s" % ("dso", "number", "histogram", "="*74)) - dsoq = con.execute("select dso, count(dso) from gen_events group by dso order by -count(dso)") - for row in dsoq: - print("%40s %8d %s" % (row[0], row[1], num2sym(row[1]))) - -# -# This function just shows the basic info, and we could do more with the -# data in the tables, like checking the function parameters when some -# big latency events happen. -# -def show_pebs_ll(): - - count = con.execute("select count(*) from pebs_ll") - for t in count: - print("There is %d records in pebs_ll table" % t[0]) - if t[0] == 0: - return - - print("Statistics about the PEBS Load Latency events grouped by thread/symbol/dse/latency: \n") - - # Group by thread - commq = con.execute("select comm, count(comm) from pebs_ll group by comm order by -count(comm)") - print("\n%16s %8s %16s\n%s" % ("comm", "number", "histogram", "="*42)) - for row in commq: - print("%16s %8d %s" % (row[0], row[1], num2sym(row[1]))) - - # Group by symbol - print("\n%32s %8s %16s\n%s" % ("symbol", "number", "histogram", "="*58)) - symbolq = con.execute("select symbol, count(symbol) from pebs_ll group by symbol order by -count(symbol)") - for row in symbolq: - print("%32s %8d %s" % (row[0], row[1], num2sym(row[1]))) - - # Group by dse - dseq = con.execute("select dse, count(dse) from pebs_ll group by dse order by -count(dse)") - print("\n%32s %8s %16s\n%s" % ("dse", "number", "histogram", "="*58)) - for row in dseq: - print("%32s %8d %s" % (row[0], row[1], num2sym(row[1]))) - - # Group by latency - latq = con.execute("select lat, count(lat) from pebs_ll group by lat order by lat") - print("\n%32s %8s %16s\n%s" % ("latency", "number", "histogram", "="*58)) - for row in latq: - print("%32s %8d %s" % (row[0], row[1], num2sym(row[1]))) - -def trace_unhandled(event_name, context, event_fields_dict): - print (' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())])) diff --git a/tools/perf/scripts/python/export-to-postgresql.py b/tools/perf/scripts/python/export-to-postgresql.py deleted file mode 100644 index 3a6bdcd74e60..000000000000 --- a/tools/perf/scripts/python/export-to-postgresql.py +++ /dev/null @@ -1,1114 +0,0 @@ -# export-to-postgresql.py: export perf data to a postgresql database -# Copyright (c) 2014, Intel Corporation. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms and conditions of the GNU General Public License, -# version 2, as published by the Free Software Foundation. -# -# This program is distributed in the hope it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. - -from __future__ import print_function - -import os -import sys -import struct -import datetime - -# To use this script you will need to have installed package python-pyside which -# provides LGPL-licensed Python bindings for Qt. You will also need the package -# libqt4-sql-psql for Qt postgresql support. -# -# The script assumes postgresql is running on the local machine and that the -# user has postgresql permissions to create databases. Examples of installing -# postgresql and adding such a user are: -# -# fedora: -# -# $ sudo yum install postgresql postgresql-server qt-postgresql -# $ sudo su - postgres -c initdb -# $ sudo service postgresql start -# $ sudo su - postgres -# $ createuser -s # Older versions may not support -s, in which case answer the prompt below: -# Shall the new role be a superuser? (y/n) y -# $ sudo yum install python-pyside -# -# Alternately, to use Python3 and/or pyside 2, one of the following: -# $ sudo yum install python3-pyside -# $ pip install --user PySide2 -# $ pip3 install --user PySide2 -# -# ubuntu: -# -# $ sudo apt-get install postgresql -# $ sudo su - postgres -# $ createuser -s -# $ sudo apt-get install python-pyside.qtsql libqt4-sql-psql -# -# Alternately, to use Python3 and/or pyside 2, one of the following: -# -# $ sudo apt-get install python3-pyside.qtsql libqt4-sql-psql -# $ sudo apt-get install python-pyside2.qtsql libqt5sql5-psql -# $ sudo apt-get install python3-pyside2.qtsql libqt5sql5-psql -# -# An example of using this script with Intel PT: -# -# $ perf record -e intel_pt//u ls -# $ perf script -s ~/libexec/perf-core/scripts/python/export-to-postgresql.py pt_example branches calls -# 2015-05-29 12:49:23.464364 Creating database... -# 2015-05-29 12:49:26.281717 Writing to intermediate files... -# 2015-05-29 12:49:27.190383 Copying to database... -# 2015-05-29 12:49:28.140451 Removing intermediate files... -# 2015-05-29 12:49:28.147451 Adding primary keys -# 2015-05-29 12:49:28.655683 Adding foreign keys -# 2015-05-29 12:49:29.365350 Done -# -# To browse the database, psql can be used e.g. -# -# $ psql pt_example -# pt_example=# select * from samples_view where id < 100; -# pt_example=# \d+ -# pt_example=# \d+ samples_view -# pt_example=# \q -# -# An example of using the database is provided by the script -# exported-sql-viewer.py. Refer to that script for details. -# -# Tables: -# -# The tables largely correspond to perf tools' data structures. They are largely self-explanatory. -# -# samples -# -# 'samples' is the main table. It represents what instruction was executing at a point in time -# when something (a selected event) happened. The memory address is the instruction pointer or 'ip'. -# -# calls -# -# 'calls' represents function calls and is related to 'samples' by 'call_id' and 'return_id'. -# 'calls' is only created when the 'calls' option to this script is specified. -# -# call_paths -# -# 'call_paths' represents all the call stacks. Each 'call' has an associated record in 'call_paths'. -# 'calls_paths' is only created when the 'calls' option to this script is specified. -# -# branch_types -# -# 'branch_types' provides descriptions for each type of branch. -# -# comm_threads -# -# 'comm_threads' shows how 'comms' relates to 'threads'. -# -# comms -# -# 'comms' contains a record for each 'comm' - the name given to the executable that is running. -# -# dsos -# -# 'dsos' contains a record for each executable file or library. -# -# machines -# -# 'machines' can be used to distinguish virtual machines if virtualization is supported. -# -# selected_events -# -# 'selected_events' contains a record for each kind of event that has been sampled. -# -# symbols -# -# 'symbols' contains a record for each symbol. Only symbols that have samples are present. -# -# threads -# -# 'threads' contains a record for each thread. -# -# Views: -# -# Most of the tables have views for more friendly display. The views are: -# -# calls_view -# call_paths_view -# comm_threads_view -# dsos_view -# machines_view -# samples_view -# symbols_view -# threads_view -# -# More examples of browsing the database with psql: -# Note that some of the examples are not the most optimal SQL query. -# Note that call information is only available if the script's 'calls' option has been used. -# -# Top 10 function calls (not aggregated by symbol): -# -# SELECT * FROM calls_view ORDER BY elapsed_time DESC LIMIT 10; -# -# Top 10 function calls (aggregated by symbol): -# -# SELECT symbol_id,(SELECT name FROM symbols WHERE id = symbol_id) AS symbol, -# SUM(elapsed_time) AS tot_elapsed_time,SUM(branch_count) AS tot_branch_count -# FROM calls_view GROUP BY symbol_id ORDER BY tot_elapsed_time DESC LIMIT 10; -# -# Note that the branch count gives a rough estimation of cpu usage, so functions -# that took a long time but have a relatively low branch count must have spent time -# waiting. -# -# Find symbols by pattern matching on part of the name (e.g. names containing 'alloc'): -# -# SELECT * FROM symbols_view WHERE name LIKE '%alloc%'; -# -# Top 10 function calls for a specific symbol (e.g. whose symbol_id is 187): -# -# SELECT * FROM calls_view WHERE symbol_id = 187 ORDER BY elapsed_time DESC LIMIT 10; -# -# Show function calls made by function in the same context (i.e. same call path) (e.g. one with call_path_id 254): -# -# SELECT * FROM calls_view WHERE parent_call_path_id = 254; -# -# Show branches made during a function call (e.g. where call_id is 29357 and return_id is 29370 and tid is 29670) -# -# SELECT * FROM samples_view WHERE id >= 29357 AND id <= 29370 AND tid = 29670 AND event LIKE 'branches%'; -# -# Show transactions: -# -# SELECT * FROM samples_view WHERE event = 'transactions'; -# -# Note transaction start has 'in_tx' true whereas, transaction end has 'in_tx' false. -# Transaction aborts have branch_type_name 'transaction abort' -# -# Show transaction aborts: -# -# SELECT * FROM samples_view WHERE event = 'transactions' AND branch_type_name = 'transaction abort'; -# -# To print a call stack requires walking the call_paths table. For example this python script: -# #!/usr/bin/python2 -# -# import sys -# from PySide.QtSql import * -# -# if __name__ == '__main__': -# if (len(sys.argv) < 3): -# print >> sys.stderr, "Usage is: printcallstack.py " -# raise Exception("Too few arguments") -# dbname = sys.argv[1] -# call_path_id = sys.argv[2] -# db = QSqlDatabase.addDatabase('QPSQL') -# db.setDatabaseName(dbname) -# if not db.open(): -# raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text()) -# query = QSqlQuery(db) -# print " id ip symbol_id symbol dso_id dso_short_name" -# while call_path_id != 0 and call_path_id != 1: -# ret = query.exec_('SELECT * FROM call_paths_view WHERE id = ' + str(call_path_id)) -# if not ret: -# raise Exception("Query failed: " + query.lastError().text()) -# if not query.next(): -# raise Exception("Query failed") -# print "{0:>6} {1:>10} {2:>9} {3:<30} {4:>6} {5:<30}".format(query.value(0), query.value(1), query.value(2), query.value(3), query.value(4), query.value(5)) -# call_path_id = query.value(6) - -pyside_version_1 = True -if not "pyside-version-1" in sys.argv: - try: - from PySide2.QtSql import * - pyside_version_1 = False - except: - pass - -if pyside_version_1: - from PySide.QtSql import * - -if sys.version_info < (3, 0): - def toserverstr(str): - return str - def toclientstr(str): - return str -else: - # Assume UTF-8 server_encoding and client_encoding - def toserverstr(str): - return bytes(str, "UTF_8") - def toclientstr(str): - return bytes(str, "UTF_8") - -# Need to access PostgreSQL C library directly to use COPY FROM STDIN -from ctypes import * -libpq = CDLL("libpq.so.5") -PQconnectdb = libpq.PQconnectdb -PQconnectdb.restype = c_void_p -PQconnectdb.argtypes = [ c_char_p ] -PQfinish = libpq.PQfinish -PQfinish.argtypes = [ c_void_p ] -PQstatus = libpq.PQstatus -PQstatus.restype = c_int -PQstatus.argtypes = [ c_void_p ] -PQexec = libpq.PQexec -PQexec.restype = c_void_p -PQexec.argtypes = [ c_void_p, c_char_p ] -PQresultStatus = libpq.PQresultStatus -PQresultStatus.restype = c_int -PQresultStatus.argtypes = [ c_void_p ] -PQputCopyData = libpq.PQputCopyData -PQputCopyData.restype = c_int -PQputCopyData.argtypes = [ c_void_p, c_void_p, c_int ] -PQputCopyEnd = libpq.PQputCopyEnd -PQputCopyEnd.restype = c_int -PQputCopyEnd.argtypes = [ c_void_p, c_void_p ] - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -# These perf imports are not used at present -#from perf_trace_context import * -#from Core import * - -perf_db_export_mode = True -perf_db_export_calls = False -perf_db_export_callchains = False - -def printerr(*args, **kw_args): - print(*args, file=sys.stderr, **kw_args) - -def printdate(*args, **kw_args): - print(datetime.datetime.today(), *args, sep=' ', **kw_args) - -def usage(): - printerr("Usage is: export-to-postgresql.py [] [] [] []"); - printerr("where: columns 'all' or 'branches'"); - printerr(" calls 'calls' => create calls and call_paths table"); - printerr(" callchains 'callchains' => create call_paths table"); - printerr(" pyside-version-1 'pyside-version-1' => use pyside version 1"); - raise Exception("Too few or bad arguments") - -if (len(sys.argv) < 2): - usage() - -dbname = sys.argv[1] - -if (len(sys.argv) >= 3): - columns = sys.argv[2] -else: - columns = "all" - -if columns not in ("all", "branches"): - usage() - -branches = (columns == "branches") - -for i in range(3,len(sys.argv)): - if (sys.argv[i] == "calls"): - perf_db_export_calls = True - elif (sys.argv[i] == "callchains"): - perf_db_export_callchains = True - elif (sys.argv[i] == "pyside-version-1"): - pass - else: - usage() - -output_dir_name = os.getcwd() + "/" + dbname + "-perf-data" -os.mkdir(output_dir_name) - -def do_query(q, s): - if (q.exec_(s)): - return - raise Exception("Query failed: " + q.lastError().text()) - -printdate("Creating database...") - -db = QSqlDatabase.addDatabase('QPSQL') -query = QSqlQuery(db) -db.setDatabaseName('postgres') -db.open() -try: - do_query(query, 'CREATE DATABASE ' + dbname) -except: - os.rmdir(output_dir_name) - raise -query.finish() -query.clear() -db.close() - -db.setDatabaseName(dbname) -db.open() - -query = QSqlQuery(db) -do_query(query, 'SET client_min_messages TO WARNING') - -do_query(query, 'CREATE TABLE selected_events (' - 'id bigint NOT NULL,' - 'name varchar(80))') -do_query(query, 'CREATE TABLE machines (' - 'id bigint NOT NULL,' - 'pid integer,' - 'root_dir varchar(4096))') -do_query(query, 'CREATE TABLE threads (' - 'id bigint NOT NULL,' - 'machine_id bigint,' - 'process_id bigint,' - 'pid integer,' - 'tid integer)') -do_query(query, 'CREATE TABLE comms (' - 'id bigint NOT NULL,' - 'comm varchar(16),' - 'c_thread_id bigint,' - 'c_time bigint,' - 'exec_flag boolean)') -do_query(query, 'CREATE TABLE comm_threads (' - 'id bigint NOT NULL,' - 'comm_id bigint,' - 'thread_id bigint)') -do_query(query, 'CREATE TABLE dsos (' - 'id bigint NOT NULL,' - 'machine_id bigint,' - 'short_name varchar(256),' - 'long_name varchar(4096),' - 'build_id varchar(64))') -do_query(query, 'CREATE TABLE symbols (' - 'id bigint NOT NULL,' - 'dso_id bigint,' - 'sym_start bigint,' - 'sym_end bigint,' - 'binding integer,' - 'name varchar(2048))') -do_query(query, 'CREATE TABLE branch_types (' - 'id integer NOT NULL,' - 'name varchar(80))') - -if branches: - do_query(query, 'CREATE TABLE samples (' - 'id bigint NOT NULL,' - 'evsel_id bigint,' - 'machine_id bigint,' - 'thread_id bigint,' - 'comm_id bigint,' - 'dso_id bigint,' - 'symbol_id bigint,' - 'sym_offset bigint,' - 'ip bigint,' - 'time bigint,' - 'cpu integer,' - 'to_dso_id bigint,' - 'to_symbol_id bigint,' - 'to_sym_offset bigint,' - 'to_ip bigint,' - 'branch_type integer,' - 'in_tx boolean,' - 'call_path_id bigint,' - 'insn_count bigint,' - 'cyc_count bigint,' - 'flags integer)') -else: - do_query(query, 'CREATE TABLE samples (' - 'id bigint NOT NULL,' - 'evsel_id bigint,' - 'machine_id bigint,' - 'thread_id bigint,' - 'comm_id bigint,' - 'dso_id bigint,' - 'symbol_id bigint,' - 'sym_offset bigint,' - 'ip bigint,' - 'time bigint,' - 'cpu integer,' - 'to_dso_id bigint,' - 'to_symbol_id bigint,' - 'to_sym_offset bigint,' - 'to_ip bigint,' - 'period bigint,' - 'weight bigint,' - 'transaction bigint,' - 'data_src bigint,' - 'branch_type integer,' - 'in_tx boolean,' - 'call_path_id bigint,' - 'insn_count bigint,' - 'cyc_count bigint,' - 'flags integer)') - -if perf_db_export_calls or perf_db_export_callchains: - do_query(query, 'CREATE TABLE call_paths (' - 'id bigint NOT NULL,' - 'parent_id bigint,' - 'symbol_id bigint,' - 'ip bigint)') -if perf_db_export_calls: - do_query(query, 'CREATE TABLE calls (' - 'id bigint NOT NULL,' - 'thread_id bigint,' - 'comm_id bigint,' - 'call_path_id bigint,' - 'call_time bigint,' - 'return_time bigint,' - 'branch_count bigint,' - 'call_id bigint,' - 'return_id bigint,' - 'parent_call_path_id bigint,' - 'flags integer,' - 'parent_id bigint,' - 'insn_count bigint,' - 'cyc_count bigint)') - -do_query(query, 'CREATE TABLE ptwrite (' - 'id bigint NOT NULL,' - 'payload bigint,' - 'exact_ip boolean)') - -do_query(query, 'CREATE TABLE cbr (' - 'id bigint NOT NULL,' - 'cbr integer,' - 'mhz integer,' - 'percent integer)') - -do_query(query, 'CREATE TABLE mwait (' - 'id bigint NOT NULL,' - 'hints integer,' - 'extensions integer)') - -do_query(query, 'CREATE TABLE pwre (' - 'id bigint NOT NULL,' - 'cstate integer,' - 'subcstate integer,' - 'hw boolean)') - -do_query(query, 'CREATE TABLE exstop (' - 'id bigint NOT NULL,' - 'exact_ip boolean)') - -do_query(query, 'CREATE TABLE pwrx (' - 'id bigint NOT NULL,' - 'deepest_cstate integer,' - 'last_cstate integer,' - 'wake_reason integer)') - -do_query(query, 'CREATE TABLE context_switches (' - 'id bigint NOT NULL,' - 'machine_id bigint,' - 'time bigint,' - 'cpu integer,' - 'thread_out_id bigint,' - 'comm_out_id bigint,' - 'thread_in_id bigint,' - 'comm_in_id bigint,' - 'flags integer)') - -do_query(query, 'CREATE VIEW machines_view AS ' - 'SELECT ' - 'id,' - 'pid,' - 'root_dir,' - 'CASE WHEN id=0 THEN \'unknown\' WHEN pid=-1 THEN \'host\' ELSE \'guest\' END AS host_or_guest' - ' FROM machines') - -do_query(query, 'CREATE VIEW dsos_view AS ' - 'SELECT ' - 'id,' - 'machine_id,' - '(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,' - 'short_name,' - 'long_name,' - 'build_id' - ' FROM dsos') - -do_query(query, 'CREATE VIEW symbols_view AS ' - 'SELECT ' - 'id,' - 'name,' - '(SELECT short_name FROM dsos WHERE id=dso_id) AS dso,' - 'dso_id,' - 'sym_start,' - 'sym_end,' - 'CASE WHEN binding=0 THEN \'local\' WHEN binding=1 THEN \'global\' ELSE \'weak\' END AS binding' - ' FROM symbols') - -do_query(query, 'CREATE VIEW threads_view AS ' - 'SELECT ' - 'id,' - 'machine_id,' - '(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,' - 'process_id,' - 'pid,' - 'tid' - ' FROM threads') - -do_query(query, 'CREATE VIEW comm_threads_view AS ' - 'SELECT ' - 'comm_id,' - '(SELECT comm FROM comms WHERE id = comm_id) AS command,' - 'thread_id,' - '(SELECT pid FROM threads WHERE id = thread_id) AS pid,' - '(SELECT tid FROM threads WHERE id = thread_id) AS tid' - ' FROM comm_threads') - -if perf_db_export_calls or perf_db_export_callchains: - do_query(query, 'CREATE VIEW call_paths_view AS ' - 'SELECT ' - 'c.id,' - 'to_hex(c.ip) AS ip,' - 'c.symbol_id,' - '(SELECT name FROM symbols WHERE id = c.symbol_id) AS symbol,' - '(SELECT dso_id FROM symbols WHERE id = c.symbol_id) AS dso_id,' - '(SELECT dso FROM symbols_view WHERE id = c.symbol_id) AS dso_short_name,' - 'c.parent_id,' - 'to_hex(p.ip) AS parent_ip,' - 'p.symbol_id AS parent_symbol_id,' - '(SELECT name FROM symbols WHERE id = p.symbol_id) AS parent_symbol,' - '(SELECT dso_id FROM symbols WHERE id = p.symbol_id) AS parent_dso_id,' - '(SELECT dso FROM symbols_view WHERE id = p.symbol_id) AS parent_dso_short_name' - ' FROM call_paths c INNER JOIN call_paths p ON p.id = c.parent_id') -if perf_db_export_calls: - do_query(query, 'CREATE VIEW calls_view AS ' - 'SELECT ' - 'calls.id,' - 'thread_id,' - '(SELECT pid FROM threads WHERE id = thread_id) AS pid,' - '(SELECT tid FROM threads WHERE id = thread_id) AS tid,' - '(SELECT comm FROM comms WHERE id = comm_id) AS command,' - 'call_path_id,' - 'to_hex(ip) AS ip,' - 'symbol_id,' - '(SELECT name FROM symbols WHERE id = symbol_id) AS symbol,' - 'call_time,' - 'return_time,' - 'return_time - call_time AS elapsed_time,' - 'branch_count,' - 'insn_count,' - 'cyc_count,' - 'CASE WHEN cyc_count=0 THEN CAST(0 AS NUMERIC(20, 2)) ELSE CAST((CAST(insn_count AS FLOAT) / cyc_count) AS NUMERIC(20, 2)) END AS IPC,' - 'call_id,' - 'return_id,' - 'CASE WHEN flags=0 THEN \'\' WHEN flags=1 THEN \'no call\' WHEN flags=2 THEN \'no return\' WHEN flags=3 THEN \'no call/return\' WHEN flags=6 THEN \'jump\' ELSE CAST ( flags AS VARCHAR(6) ) END AS flags,' - 'parent_call_path_id,' - 'calls.parent_id' - ' FROM calls INNER JOIN call_paths ON call_paths.id = call_path_id') - -do_query(query, 'CREATE VIEW samples_view AS ' - 'SELECT ' - 'id,' - 'time,' - 'cpu,' - '(SELECT pid FROM threads WHERE id = thread_id) AS pid,' - '(SELECT tid FROM threads WHERE id = thread_id) AS tid,' - '(SELECT comm FROM comms WHERE id = comm_id) AS command,' - '(SELECT name FROM selected_events WHERE id = evsel_id) AS event,' - 'to_hex(ip) AS ip_hex,' - '(SELECT name FROM symbols WHERE id = symbol_id) AS symbol,' - 'sym_offset,' - '(SELECT short_name FROM dsos WHERE id = dso_id) AS dso_short_name,' - 'to_hex(to_ip) AS to_ip_hex,' - '(SELECT name FROM symbols WHERE id = to_symbol_id) AS to_symbol,' - 'to_sym_offset,' - '(SELECT short_name FROM dsos WHERE id = to_dso_id) AS to_dso_short_name,' - '(SELECT name FROM branch_types WHERE id = branch_type) AS branch_type_name,' - 'in_tx,' - 'insn_count,' - 'cyc_count,' - 'CASE WHEN cyc_count=0 THEN CAST(0 AS NUMERIC(20, 2)) ELSE CAST((CAST(insn_count AS FLOAT) / cyc_count) AS NUMERIC(20, 2)) END AS IPC,' - 'flags' - ' FROM samples') - -do_query(query, 'CREATE VIEW ptwrite_view AS ' - 'SELECT ' - 'ptwrite.id,' - 'time,' - 'cpu,' - 'to_hex(payload) AS payload_hex,' - 'CASE WHEN exact_ip=FALSE THEN \'False\' ELSE \'True\' END AS exact_ip' - ' FROM ptwrite' - ' INNER JOIN samples ON samples.id = ptwrite.id') - -do_query(query, 'CREATE VIEW cbr_view AS ' - 'SELECT ' - 'cbr.id,' - 'time,' - 'cpu,' - 'cbr,' - 'mhz,' - 'percent' - ' FROM cbr' - ' INNER JOIN samples ON samples.id = cbr.id') - -do_query(query, 'CREATE VIEW mwait_view AS ' - 'SELECT ' - 'mwait.id,' - 'time,' - 'cpu,' - 'to_hex(hints) AS hints_hex,' - 'to_hex(extensions) AS extensions_hex' - ' FROM mwait' - ' INNER JOIN samples ON samples.id = mwait.id') - -do_query(query, 'CREATE VIEW pwre_view AS ' - 'SELECT ' - 'pwre.id,' - 'time,' - 'cpu,' - 'cstate,' - 'subcstate,' - 'CASE WHEN hw=FALSE THEN \'False\' ELSE \'True\' END AS hw' - ' FROM pwre' - ' INNER JOIN samples ON samples.id = pwre.id') - -do_query(query, 'CREATE VIEW exstop_view AS ' - 'SELECT ' - 'exstop.id,' - 'time,' - 'cpu,' - 'CASE WHEN exact_ip=FALSE THEN \'False\' ELSE \'True\' END AS exact_ip' - ' FROM exstop' - ' INNER JOIN samples ON samples.id = exstop.id') - -do_query(query, 'CREATE VIEW pwrx_view AS ' - 'SELECT ' - 'pwrx.id,' - 'time,' - 'cpu,' - 'deepest_cstate,' - 'last_cstate,' - 'CASE WHEN wake_reason=1 THEN \'Interrupt\'' - ' WHEN wake_reason=2 THEN \'Timer Deadline\'' - ' WHEN wake_reason=4 THEN \'Monitored Address\'' - ' WHEN wake_reason=8 THEN \'HW\'' - ' ELSE CAST ( wake_reason AS VARCHAR(2) )' - 'END AS wake_reason' - ' FROM pwrx' - ' INNER JOIN samples ON samples.id = pwrx.id') - -do_query(query, 'CREATE VIEW power_events_view AS ' - 'SELECT ' - 'samples.id,' - 'samples.time,' - 'samples.cpu,' - 'selected_events.name AS event,' - 'FORMAT(\'%6s\', cbr.cbr) AS cbr,' - 'FORMAT(\'%6s\', cbr.mhz) AS MHz,' - 'FORMAT(\'%5s\', cbr.percent) AS percent,' - 'to_hex(mwait.hints) AS hints_hex,' - 'to_hex(mwait.extensions) AS extensions_hex,' - 'FORMAT(\'%3s\', pwre.cstate) AS cstate,' - 'FORMAT(\'%3s\', pwre.subcstate) AS subcstate,' - 'CASE WHEN pwre.hw=FALSE THEN \'False\' WHEN pwre.hw=TRUE THEN \'True\' ELSE NULL END AS hw,' - 'CASE WHEN exstop.exact_ip=FALSE THEN \'False\' WHEN exstop.exact_ip=TRUE THEN \'True\' ELSE NULL END AS exact_ip,' - 'FORMAT(\'%3s\', pwrx.deepest_cstate) AS deepest_cstate,' - 'FORMAT(\'%3s\', pwrx.last_cstate) AS last_cstate,' - 'CASE WHEN pwrx.wake_reason=1 THEN \'Interrupt\'' - ' WHEN pwrx.wake_reason=2 THEN \'Timer Deadline\'' - ' WHEN pwrx.wake_reason=4 THEN \'Monitored Address\'' - ' WHEN pwrx.wake_reason=8 THEN \'HW\'' - ' ELSE FORMAT(\'%2s\', pwrx.wake_reason)' - 'END AS wake_reason' - ' FROM cbr' - ' FULL JOIN mwait ON mwait.id = cbr.id' - ' FULL JOIN pwre ON pwre.id = cbr.id' - ' FULL JOIN exstop ON exstop.id = cbr.id' - ' FULL JOIN pwrx ON pwrx.id = cbr.id' - ' INNER JOIN samples ON samples.id = coalesce(cbr.id, mwait.id, pwre.id, exstop.id, pwrx.id)' - ' INNER JOIN selected_events ON selected_events.id = samples.evsel_id' - ' ORDER BY samples.id') - -do_query(query, 'CREATE VIEW context_switches_view AS ' - 'SELECT ' - 'context_switches.id,' - 'context_switches.machine_id,' - 'context_switches.time,' - 'context_switches.cpu,' - 'th_out.pid AS pid_out,' - 'th_out.tid AS tid_out,' - 'comm_out.comm AS comm_out,' - 'th_in.pid AS pid_in,' - 'th_in.tid AS tid_in,' - 'comm_in.comm AS comm_in,' - 'CASE WHEN context_switches.flags = 0 THEN \'in\'' - ' WHEN context_switches.flags = 1 THEN \'out\'' - ' WHEN context_switches.flags = 3 THEN \'out preempt\'' - ' ELSE CAST ( context_switches.flags AS VARCHAR(11) )' - 'END AS flags' - ' FROM context_switches' - ' INNER JOIN threads AS th_out ON th_out.id = context_switches.thread_out_id' - ' INNER JOIN threads AS th_in ON th_in.id = context_switches.thread_in_id' - ' INNER JOIN comms AS comm_out ON comm_out.id = context_switches.comm_out_id' - ' INNER JOIN comms AS comm_in ON comm_in.id = context_switches.comm_in_id') - -file_header = struct.pack("!11sii", b"PGCOPY\n\377\r\n\0", 0, 0) -file_trailer = b"\377\377" - -def open_output_file(file_name): - path_name = output_dir_name + "/" + file_name - file = open(path_name, "wb+") - file.write(file_header) - return file - -def close_output_file(file): - file.write(file_trailer) - file.close() - -def copy_output_file_direct(file, table_name): - close_output_file(file) - sql = "COPY " + table_name + " FROM '" + file.name + "' (FORMAT 'binary')" - do_query(query, sql) - -# Use COPY FROM STDIN because security may prevent postgres from accessing the files directly -def copy_output_file(file, table_name): - conn = PQconnectdb(toclientstr("dbname = " + dbname)) - if (PQstatus(conn)): - raise Exception("COPY FROM STDIN PQconnectdb failed") - file.write(file_trailer) - file.seek(0) - sql = "COPY " + table_name + " FROM STDIN (FORMAT 'binary')" - res = PQexec(conn, toclientstr(sql)) - if (PQresultStatus(res) != 4): - raise Exception("COPY FROM STDIN PQexec failed") - data = file.read(65536) - while (len(data)): - ret = PQputCopyData(conn, data, len(data)) - if (ret != 1): - raise Exception("COPY FROM STDIN PQputCopyData failed, error " + str(ret)) - data = file.read(65536) - ret = PQputCopyEnd(conn, None) - if (ret != 1): - raise Exception("COPY FROM STDIN PQputCopyEnd failed, error " + str(ret)) - PQfinish(conn) - -def remove_output_file(file): - name = file.name - file.close() - os.unlink(name) - -evsel_file = open_output_file("evsel_table.bin") -machine_file = open_output_file("machine_table.bin") -thread_file = open_output_file("thread_table.bin") -comm_file = open_output_file("comm_table.bin") -comm_thread_file = open_output_file("comm_thread_table.bin") -dso_file = open_output_file("dso_table.bin") -symbol_file = open_output_file("symbol_table.bin") -branch_type_file = open_output_file("branch_type_table.bin") -sample_file = open_output_file("sample_table.bin") -if perf_db_export_calls or perf_db_export_callchains: - call_path_file = open_output_file("call_path_table.bin") -if perf_db_export_calls: - call_file = open_output_file("call_table.bin") -ptwrite_file = open_output_file("ptwrite_table.bin") -cbr_file = open_output_file("cbr_table.bin") -mwait_file = open_output_file("mwait_table.bin") -pwre_file = open_output_file("pwre_table.bin") -exstop_file = open_output_file("exstop_table.bin") -pwrx_file = open_output_file("pwrx_table.bin") -context_switches_file = open_output_file("context_switches_table.bin") - -def trace_begin(): - printdate("Writing to intermediate files...") - # id == 0 means unknown. It is easier to create records for them than replace the zeroes with NULLs - evsel_table(0, "unknown") - machine_table(0, 0, "unknown") - thread_table(0, 0, 0, -1, -1) - comm_table(0, "unknown", 0, 0, 0) - dso_table(0, 0, "unknown", "unknown", "") - symbol_table(0, 0, 0, 0, 0, "unknown") - sample_table(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - if perf_db_export_calls or perf_db_export_callchains: - call_path_table(0, 0, 0, 0) - call_return_table(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - -unhandled_count = 0 - -def is_table_empty(table_name): - do_query(query, 'SELECT * FROM ' + table_name + ' LIMIT 1'); - if query.next(): - return False - return True - -def drop(table_name): - do_query(query, 'DROP VIEW ' + table_name + '_view'); - do_query(query, 'DROP TABLE ' + table_name); - -def trace_end(): - printdate("Copying to database...") - copy_output_file(evsel_file, "selected_events") - copy_output_file(machine_file, "machines") - copy_output_file(thread_file, "threads") - copy_output_file(comm_file, "comms") - copy_output_file(comm_thread_file, "comm_threads") - copy_output_file(dso_file, "dsos") - copy_output_file(symbol_file, "symbols") - copy_output_file(branch_type_file, "branch_types") - copy_output_file(sample_file, "samples") - if perf_db_export_calls or perf_db_export_callchains: - copy_output_file(call_path_file, "call_paths") - if perf_db_export_calls: - copy_output_file(call_file, "calls") - copy_output_file(ptwrite_file, "ptwrite") - copy_output_file(cbr_file, "cbr") - copy_output_file(mwait_file, "mwait") - copy_output_file(pwre_file, "pwre") - copy_output_file(exstop_file, "exstop") - copy_output_file(pwrx_file, "pwrx") - copy_output_file(context_switches_file, "context_switches") - - printdate("Removing intermediate files...") - remove_output_file(evsel_file) - remove_output_file(machine_file) - remove_output_file(thread_file) - remove_output_file(comm_file) - remove_output_file(comm_thread_file) - remove_output_file(dso_file) - remove_output_file(symbol_file) - remove_output_file(branch_type_file) - remove_output_file(sample_file) - if perf_db_export_calls or perf_db_export_callchains: - remove_output_file(call_path_file) - if perf_db_export_calls: - remove_output_file(call_file) - remove_output_file(ptwrite_file) - remove_output_file(cbr_file) - remove_output_file(mwait_file) - remove_output_file(pwre_file) - remove_output_file(exstop_file) - remove_output_file(pwrx_file) - remove_output_file(context_switches_file) - os.rmdir(output_dir_name) - printdate("Adding primary keys") - do_query(query, 'ALTER TABLE selected_events ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE machines ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE threads ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE comms ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE comm_threads ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE dsos ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE symbols ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE branch_types ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE samples ADD PRIMARY KEY (id)') - if perf_db_export_calls or perf_db_export_callchains: - do_query(query, 'ALTER TABLE call_paths ADD PRIMARY KEY (id)') - if perf_db_export_calls: - do_query(query, 'ALTER TABLE calls ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE ptwrite ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE cbr ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE mwait ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE pwre ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE exstop ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE pwrx ADD PRIMARY KEY (id)') - do_query(query, 'ALTER TABLE context_switches ADD PRIMARY KEY (id)') - - printdate("Adding foreign keys") - do_query(query, 'ALTER TABLE threads ' - 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id),' - 'ADD CONSTRAINT processfk FOREIGN KEY (process_id) REFERENCES threads (id)') - do_query(query, 'ALTER TABLE comms ' - 'ADD CONSTRAINT threadfk FOREIGN KEY (c_thread_id) REFERENCES threads (id)') - do_query(query, 'ALTER TABLE comm_threads ' - 'ADD CONSTRAINT commfk FOREIGN KEY (comm_id) REFERENCES comms (id),' - 'ADD CONSTRAINT threadfk FOREIGN KEY (thread_id) REFERENCES threads (id)') - do_query(query, 'ALTER TABLE dsos ' - 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id)') - do_query(query, 'ALTER TABLE symbols ' - 'ADD CONSTRAINT dsofk FOREIGN KEY (dso_id) REFERENCES dsos (id)') - do_query(query, 'ALTER TABLE samples ' - 'ADD CONSTRAINT evselfk FOREIGN KEY (evsel_id) REFERENCES selected_events (id),' - 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id),' - 'ADD CONSTRAINT threadfk FOREIGN KEY (thread_id) REFERENCES threads (id),' - 'ADD CONSTRAINT commfk FOREIGN KEY (comm_id) REFERENCES comms (id),' - 'ADD CONSTRAINT dsofk FOREIGN KEY (dso_id) REFERENCES dsos (id),' - 'ADD CONSTRAINT symbolfk FOREIGN KEY (symbol_id) REFERENCES symbols (id),' - 'ADD CONSTRAINT todsofk FOREIGN KEY (to_dso_id) REFERENCES dsos (id),' - 'ADD CONSTRAINT tosymbolfk FOREIGN KEY (to_symbol_id) REFERENCES symbols (id)') - if perf_db_export_calls or perf_db_export_callchains: - do_query(query, 'ALTER TABLE call_paths ' - 'ADD CONSTRAINT parentfk FOREIGN KEY (parent_id) REFERENCES call_paths (id),' - 'ADD CONSTRAINT symbolfk FOREIGN KEY (symbol_id) REFERENCES symbols (id)') - if perf_db_export_calls: - do_query(query, 'ALTER TABLE calls ' - 'ADD CONSTRAINT threadfk FOREIGN KEY (thread_id) REFERENCES threads (id),' - 'ADD CONSTRAINT commfk FOREIGN KEY (comm_id) REFERENCES comms (id),' - 'ADD CONSTRAINT call_pathfk FOREIGN KEY (call_path_id) REFERENCES call_paths (id),' - 'ADD CONSTRAINT callfk FOREIGN KEY (call_id) REFERENCES samples (id),' - 'ADD CONSTRAINT returnfk FOREIGN KEY (return_id) REFERENCES samples (id),' - 'ADD CONSTRAINT parent_call_pathfk FOREIGN KEY (parent_call_path_id) REFERENCES call_paths (id)') - do_query(query, 'CREATE INDEX pcpid_idx ON calls (parent_call_path_id)') - do_query(query, 'CREATE INDEX pid_idx ON calls (parent_id)') - do_query(query, 'ALTER TABLE comms ADD has_calls boolean') - do_query(query, 'UPDATE comms SET has_calls = TRUE WHERE comms.id IN (SELECT DISTINCT comm_id FROM calls)') - do_query(query, 'ALTER TABLE ptwrite ' - 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') - do_query(query, 'ALTER TABLE cbr ' - 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') - do_query(query, 'ALTER TABLE mwait ' - 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') - do_query(query, 'ALTER TABLE pwre ' - 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') - do_query(query, 'ALTER TABLE exstop ' - 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') - do_query(query, 'ALTER TABLE pwrx ' - 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') - do_query(query, 'ALTER TABLE context_switches ' - 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id),' - 'ADD CONSTRAINT toutfk FOREIGN KEY (thread_out_id) REFERENCES threads (id),' - 'ADD CONSTRAINT tinfk FOREIGN KEY (thread_in_id) REFERENCES threads (id),' - 'ADD CONSTRAINT coutfk FOREIGN KEY (comm_out_id) REFERENCES comms (id),' - 'ADD CONSTRAINT cinfk FOREIGN KEY (comm_in_id) REFERENCES comms (id)') - - printdate("Dropping unused tables") - if is_table_empty("ptwrite"): - drop("ptwrite") - if is_table_empty("mwait") and is_table_empty("pwre") and is_table_empty("exstop") and is_table_empty("pwrx"): - do_query(query, 'DROP VIEW power_events_view'); - drop("mwait") - drop("pwre") - drop("exstop") - drop("pwrx") - if is_table_empty("cbr"): - drop("cbr") - if is_table_empty("context_switches"): - drop("context_switches") - - if (unhandled_count): - printdate("Warning: ", unhandled_count, " unhandled events") - printdate("Done") - -def trace_unhandled(event_name, context, event_fields_dict): - global unhandled_count - unhandled_count += 1 - -def sched__sched_switch(*x): - pass - -def evsel_table(evsel_id, evsel_name, *x): - evsel_name = toserverstr(evsel_name) - n = len(evsel_name) - fmt = "!hiqi" + str(n) + "s" - value = struct.pack(fmt, 2, 8, evsel_id, n, evsel_name) - evsel_file.write(value) - -def machine_table(machine_id, pid, root_dir, *x): - root_dir = toserverstr(root_dir) - n = len(root_dir) - fmt = "!hiqiii" + str(n) + "s" - value = struct.pack(fmt, 3, 8, machine_id, 4, pid, n, root_dir) - machine_file.write(value) - -def thread_table(thread_id, machine_id, process_id, pid, tid, *x): - value = struct.pack("!hiqiqiqiiii", 5, 8, thread_id, 8, machine_id, 8, process_id, 4, pid, 4, tid) - thread_file.write(value) - -def comm_table(comm_id, comm_str, thread_id, time, exec_flag, *x): - comm_str = toserverstr(comm_str) - n = len(comm_str) - fmt = "!hiqi" + str(n) + "s" + "iqiqiB" - value = struct.pack(fmt, 5, 8, comm_id, n, comm_str, 8, thread_id, 8, time, 1, exec_flag) - comm_file.write(value) - -def comm_thread_table(comm_thread_id, comm_id, thread_id, *x): - fmt = "!hiqiqiq" - value = struct.pack(fmt, 3, 8, comm_thread_id, 8, comm_id, 8, thread_id) - comm_thread_file.write(value) - -def dso_table(dso_id, machine_id, short_name, long_name, build_id, *x): - short_name = toserverstr(short_name) - long_name = toserverstr(long_name) - build_id = toserverstr(build_id) - n1 = len(short_name) - n2 = len(long_name) - n3 = len(build_id) - fmt = "!hiqiqi" + str(n1) + "si" + str(n2) + "si" + str(n3) + "s" - value = struct.pack(fmt, 5, 8, dso_id, 8, machine_id, n1, short_name, n2, long_name, n3, build_id) - dso_file.write(value) - -def symbol_table(symbol_id, dso_id, sym_start, sym_end, binding, symbol_name, *x): - symbol_name = toserverstr(symbol_name) - n = len(symbol_name) - fmt = "!hiqiqiqiqiii" + str(n) + "s" - value = struct.pack(fmt, 6, 8, symbol_id, 8, dso_id, 8, sym_start, 8, sym_end, 4, binding, n, symbol_name) - symbol_file.write(value) - -def branch_type_table(branch_type, name, *x): - name = toserverstr(name) - n = len(name) - fmt = "!hiii" + str(n) + "s" - value = struct.pack(fmt, 2, 4, branch_type, n, name) - branch_type_file.write(value) - -def sample_table(sample_id, evsel_id, machine_id, thread_id, comm_id, dso_id, symbol_id, sym_offset, ip, time, cpu, to_dso_id, to_symbol_id, to_sym_offset, to_ip, period, weight, transaction, data_src, branch_type, in_tx, call_path_id, insn_cnt, cyc_cnt, flags, *x): - if branches: - value = struct.pack("!hiqiqiqiqiqiqiqiqiqiqiiiqiqiqiqiiiBiqiqiqii", 21, 8, sample_id, 8, evsel_id, 8, machine_id, 8, thread_id, 8, comm_id, 8, dso_id, 8, symbol_id, 8, sym_offset, 8, ip, 8, time, 4, cpu, 8, to_dso_id, 8, to_symbol_id, 8, to_sym_offset, 8, to_ip, 4, branch_type, 1, in_tx, 8, call_path_id, 8, insn_cnt, 8, cyc_cnt, 4, flags) - else: - value = struct.pack("!hiqiqiqiqiqiqiqiqiqiqiiiqiqiqiqiqiqiqiqiiiBiqiqiqii", 25, 8, sample_id, 8, evsel_id, 8, machine_id, 8, thread_id, 8, comm_id, 8, dso_id, 8, symbol_id, 8, sym_offset, 8, ip, 8, time, 4, cpu, 8, to_dso_id, 8, to_symbol_id, 8, to_sym_offset, 8, to_ip, 8, period, 8, weight, 8, transaction, 8, data_src, 4, branch_type, 1, in_tx, 8, call_path_id, 8, insn_cnt, 8, cyc_cnt, 4, flags) - sample_file.write(value) - -def call_path_table(cp_id, parent_id, symbol_id, ip, *x): - fmt = "!hiqiqiqiq" - value = struct.pack(fmt, 4, 8, cp_id, 8, parent_id, 8, symbol_id, 8, ip) - call_path_file.write(value) - -def call_return_table(cr_id, thread_id, comm_id, call_path_id, call_time, return_time, branch_count, call_id, return_id, parent_call_path_id, flags, parent_id, insn_cnt, cyc_cnt, *x): - fmt = "!hiqiqiqiqiqiqiqiqiqiqiiiqiqiq" - value = struct.pack(fmt, 14, 8, cr_id, 8, thread_id, 8, comm_id, 8, call_path_id, 8, call_time, 8, return_time, 8, branch_count, 8, call_id, 8, return_id, 8, parent_call_path_id, 4, flags, 8, parent_id, 8, insn_cnt, 8, cyc_cnt) - call_file.write(value) - -def ptwrite(id, raw_buf): - data = struct.unpack_from("> 32) & 0x3 - value = struct.pack("!hiqiiii", 3, 8, id, 4, hints, 4, extensions) - mwait_file.write(value) - -def pwre(id, raw_buf): - data = struct.unpack_from("> 7) & 1 - cstate = (payload >> 12) & 0xf - subcstate = (payload >> 8) & 0xf - value = struct.pack("!hiqiiiiiB", 4, 8, id, 4, cstate, 4, subcstate, 1, hw) - pwre_file.write(value) - -def exstop(id, raw_buf): - data = struct.unpack_from("> 4) & 0xf - wake_reason = (payload >> 8) & 0xf - value = struct.pack("!hiqiiiiii", 4, 8, id, 4, deepest_cstate, 4, last_cstate, 4, wake_reason) - pwrx_file.write(value) - -def synth_data(id, config, raw_buf, *x): - if config == 0: - ptwrite(id, raw_buf) - elif config == 1: - mwait(id, raw_buf) - elif config == 2: - pwre(id, raw_buf) - elif config == 3: - exstop(id, raw_buf) - elif config == 4: - pwrx(id, raw_buf) - elif config == 5: - cbr(id, raw_buf) - -def context_switch_table(id, machine_id, time, cpu, thread_out_id, comm_out_id, thread_in_id, comm_in_id, flags, *x): - fmt = "!hiqiqiqiiiqiqiqiqii" - value = struct.pack(fmt, 9, 8, id, 8, machine_id, 8, time, 4, cpu, 8, thread_out_id, 8, comm_out_id, 8, thread_in_id, 8, comm_in_id, 4, flags) - context_switches_file.write(value) diff --git a/tools/perf/scripts/python/export-to-sqlite.py b/tools/perf/scripts/python/export-to-sqlite.py deleted file mode 100644 index 73c992feb1b9..000000000000 --- a/tools/perf/scripts/python/export-to-sqlite.py +++ /dev/null @@ -1,799 +0,0 @@ -# export-to-sqlite.py: export perf data to a sqlite3 database -# Copyright (c) 2017, Intel Corporation. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms and conditions of the GNU General Public License, -# version 2, as published by the Free Software Foundation. -# -# This program is distributed in the hope it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. - -from __future__ import print_function - -import os -import sys -import struct -import datetime - -# To use this script you will need to have installed package python-pyside which -# provides LGPL-licensed Python bindings for Qt. You will also need the package -# libqt4-sql-sqlite for Qt sqlite3 support. -# -# Examples of installing pyside: -# -# ubuntu: -# -# $ sudo apt-get install python-pyside.qtsql libqt4-sql-psql -# -# Alternately, to use Python3 and/or pyside 2, one of the following: -# -# $ sudo apt-get install python3-pyside.qtsql libqt4-sql-psql -# $ sudo apt-get install python-pyside2.qtsql libqt5sql5-psql -# $ sudo apt-get install python3-pyside2.qtsql libqt5sql5-psql -# fedora: -# -# $ sudo yum install python-pyside -# -# Alternately, to use Python3 and/or pyside 2, one of the following: -# $ sudo yum install python3-pyside -# $ pip install --user PySide2 -# $ pip3 install --user PySide2 -# -# An example of using this script with Intel PT: -# -# $ perf record -e intel_pt//u ls -# $ perf script -s ~/libexec/perf-core/scripts/python/export-to-sqlite.py pt_example branches calls -# 2017-07-31 14:26:07.326913 Creating database... -# 2017-07-31 14:26:07.538097 Writing records... -# 2017-07-31 14:26:09.889292 Adding indexes -# 2017-07-31 14:26:09.958746 Done -# -# To browse the database, sqlite3 can be used e.g. -# -# $ sqlite3 pt_example -# sqlite> .header on -# sqlite> select * from samples_view where id < 10; -# sqlite> .mode column -# sqlite> select * from samples_view where id < 10; -# sqlite> .tables -# sqlite> .schema samples_view -# sqlite> .quit -# -# An example of using the database is provided by the script -# exported-sql-viewer.py. Refer to that script for details. -# -# The database structure is practically the same as created by the script -# export-to-postgresql.py. Refer to that script for details. A notable -# difference is the 'transaction' column of the 'samples' table which is -# renamed 'transaction_' in sqlite because 'transaction' is a reserved word. - -pyside_version_1 = True -if not "pyside-version-1" in sys.argv: - try: - from PySide2.QtSql import * - pyside_version_1 = False - except: - pass - -if pyside_version_1: - from PySide.QtSql import * - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -# These perf imports are not used at present -#from perf_trace_context import * -#from Core import * - -perf_db_export_mode = True -perf_db_export_calls = False -perf_db_export_callchains = False - -def printerr(*args, **keyword_args): - print(*args, file=sys.stderr, **keyword_args) - -def printdate(*args, **kw_args): - print(datetime.datetime.today(), *args, sep=' ', **kw_args) - -def usage(): - printerr("Usage is: export-to-sqlite.py [] [] [] []"); - printerr("where: columns 'all' or 'branches'"); - printerr(" calls 'calls' => create calls and call_paths table"); - printerr(" callchains 'callchains' => create call_paths table"); - printerr(" pyside-version-1 'pyside-version-1' => use pyside version 1"); - raise Exception("Too few or bad arguments") - -if (len(sys.argv) < 2): - usage() - -dbname = sys.argv[1] - -if (len(sys.argv) >= 3): - columns = sys.argv[2] -else: - columns = "all" - -if columns not in ("all", "branches"): - usage() - -branches = (columns == "branches") - -for i in range(3,len(sys.argv)): - if (sys.argv[i] == "calls"): - perf_db_export_calls = True - elif (sys.argv[i] == "callchains"): - perf_db_export_callchains = True - elif (sys.argv[i] == "pyside-version-1"): - pass - else: - usage() - -def do_query(q, s): - if (q.exec_(s)): - return - raise Exception("Query failed: " + q.lastError().text()) - -def do_query_(q): - if (q.exec_()): - return - raise Exception("Query failed: " + q.lastError().text()) - -printdate("Creating database ...") - -db_exists = False -try: - f = open(dbname) - f.close() - db_exists = True -except: - pass - -if db_exists: - raise Exception(dbname + " already exists") - -db = QSqlDatabase.addDatabase('QSQLITE') -db.setDatabaseName(dbname) -db.open() - -query = QSqlQuery(db) - -do_query(query, 'PRAGMA journal_mode = OFF') -do_query(query, 'BEGIN TRANSACTION') - -do_query(query, 'CREATE TABLE selected_events (' - 'id integer NOT NULL PRIMARY KEY,' - 'name varchar(80))') -do_query(query, 'CREATE TABLE machines (' - 'id integer NOT NULL PRIMARY KEY,' - 'pid integer,' - 'root_dir varchar(4096))') -do_query(query, 'CREATE TABLE threads (' - 'id integer NOT NULL PRIMARY KEY,' - 'machine_id bigint,' - 'process_id bigint,' - 'pid integer,' - 'tid integer)') -do_query(query, 'CREATE TABLE comms (' - 'id integer NOT NULL PRIMARY KEY,' - 'comm varchar(16),' - 'c_thread_id bigint,' - 'c_time bigint,' - 'exec_flag boolean)') -do_query(query, 'CREATE TABLE comm_threads (' - 'id integer NOT NULL PRIMARY KEY,' - 'comm_id bigint,' - 'thread_id bigint)') -do_query(query, 'CREATE TABLE dsos (' - 'id integer NOT NULL PRIMARY KEY,' - 'machine_id bigint,' - 'short_name varchar(256),' - 'long_name varchar(4096),' - 'build_id varchar(64))') -do_query(query, 'CREATE TABLE symbols (' - 'id integer NOT NULL PRIMARY KEY,' - 'dso_id bigint,' - 'sym_start bigint,' - 'sym_end bigint,' - 'binding integer,' - 'name varchar(2048))') -do_query(query, 'CREATE TABLE branch_types (' - 'id integer NOT NULL PRIMARY KEY,' - 'name varchar(80))') - -if branches: - do_query(query, 'CREATE TABLE samples (' - 'id integer NOT NULL PRIMARY KEY,' - 'evsel_id bigint,' - 'machine_id bigint,' - 'thread_id bigint,' - 'comm_id bigint,' - 'dso_id bigint,' - 'symbol_id bigint,' - 'sym_offset bigint,' - 'ip bigint,' - 'time bigint,' - 'cpu integer,' - 'to_dso_id bigint,' - 'to_symbol_id bigint,' - 'to_sym_offset bigint,' - 'to_ip bigint,' - 'branch_type integer,' - 'in_tx boolean,' - 'call_path_id bigint,' - 'insn_count bigint,' - 'cyc_count bigint,' - 'flags integer)') -else: - do_query(query, 'CREATE TABLE samples (' - 'id integer NOT NULL PRIMARY KEY,' - 'evsel_id bigint,' - 'machine_id bigint,' - 'thread_id bigint,' - 'comm_id bigint,' - 'dso_id bigint,' - 'symbol_id bigint,' - 'sym_offset bigint,' - 'ip bigint,' - 'time bigint,' - 'cpu integer,' - 'to_dso_id bigint,' - 'to_symbol_id bigint,' - 'to_sym_offset bigint,' - 'to_ip bigint,' - 'period bigint,' - 'weight bigint,' - 'transaction_ bigint,' - 'data_src bigint,' - 'branch_type integer,' - 'in_tx boolean,' - 'call_path_id bigint,' - 'insn_count bigint,' - 'cyc_count bigint,' - 'flags integer)') - -if perf_db_export_calls or perf_db_export_callchains: - do_query(query, 'CREATE TABLE call_paths (' - 'id integer NOT NULL PRIMARY KEY,' - 'parent_id bigint,' - 'symbol_id bigint,' - 'ip bigint)') -if perf_db_export_calls: - do_query(query, 'CREATE TABLE calls (' - 'id integer NOT NULL PRIMARY KEY,' - 'thread_id bigint,' - 'comm_id bigint,' - 'call_path_id bigint,' - 'call_time bigint,' - 'return_time bigint,' - 'branch_count bigint,' - 'call_id bigint,' - 'return_id bigint,' - 'parent_call_path_id bigint,' - 'flags integer,' - 'parent_id bigint,' - 'insn_count bigint,' - 'cyc_count bigint)') - -do_query(query, 'CREATE TABLE ptwrite (' - 'id integer NOT NULL PRIMARY KEY,' - 'payload bigint,' - 'exact_ip integer)') - -do_query(query, 'CREATE TABLE cbr (' - 'id integer NOT NULL PRIMARY KEY,' - 'cbr integer,' - 'mhz integer,' - 'percent integer)') - -do_query(query, 'CREATE TABLE mwait (' - 'id integer NOT NULL PRIMARY KEY,' - 'hints integer,' - 'extensions integer)') - -do_query(query, 'CREATE TABLE pwre (' - 'id integer NOT NULL PRIMARY KEY,' - 'cstate integer,' - 'subcstate integer,' - 'hw integer)') - -do_query(query, 'CREATE TABLE exstop (' - 'id integer NOT NULL PRIMARY KEY,' - 'exact_ip integer)') - -do_query(query, 'CREATE TABLE pwrx (' - 'id integer NOT NULL PRIMARY KEY,' - 'deepest_cstate integer,' - 'last_cstate integer,' - 'wake_reason integer)') - -do_query(query, 'CREATE TABLE context_switches (' - 'id integer NOT NULL PRIMARY KEY,' - 'machine_id bigint,' - 'time bigint,' - 'cpu integer,' - 'thread_out_id bigint,' - 'comm_out_id bigint,' - 'thread_in_id bigint,' - 'comm_in_id bigint,' - 'flags integer)') - -# printf was added to sqlite in version 3.8.3 -sqlite_has_printf = False -try: - do_query(query, 'SELECT printf("") FROM machines') - sqlite_has_printf = True -except: - pass - -def emit_to_hex(x): - if sqlite_has_printf: - return 'printf("%x", ' + x + ')' - else: - return x - -do_query(query, 'CREATE VIEW machines_view AS ' - 'SELECT ' - 'id,' - 'pid,' - 'root_dir,' - 'CASE WHEN id=0 THEN \'unknown\' WHEN pid=-1 THEN \'host\' ELSE \'guest\' END AS host_or_guest' - ' FROM machines') - -do_query(query, 'CREATE VIEW dsos_view AS ' - 'SELECT ' - 'id,' - 'machine_id,' - '(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,' - 'short_name,' - 'long_name,' - 'build_id' - ' FROM dsos') - -do_query(query, 'CREATE VIEW symbols_view AS ' - 'SELECT ' - 'id,' - 'name,' - '(SELECT short_name FROM dsos WHERE id=dso_id) AS dso,' - 'dso_id,' - 'sym_start,' - 'sym_end,' - 'CASE WHEN binding=0 THEN \'local\' WHEN binding=1 THEN \'global\' ELSE \'weak\' END AS binding' - ' FROM symbols') - -do_query(query, 'CREATE VIEW threads_view AS ' - 'SELECT ' - 'id,' - 'machine_id,' - '(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,' - 'process_id,' - 'pid,' - 'tid' - ' FROM threads') - -do_query(query, 'CREATE VIEW comm_threads_view AS ' - 'SELECT ' - 'comm_id,' - '(SELECT comm FROM comms WHERE id = comm_id) AS command,' - 'thread_id,' - '(SELECT pid FROM threads WHERE id = thread_id) AS pid,' - '(SELECT tid FROM threads WHERE id = thread_id) AS tid' - ' FROM comm_threads') - -if perf_db_export_calls or perf_db_export_callchains: - do_query(query, 'CREATE VIEW call_paths_view AS ' - 'SELECT ' - 'c.id,' - + emit_to_hex('c.ip') + ' AS ip,' - 'c.symbol_id,' - '(SELECT name FROM symbols WHERE id = c.symbol_id) AS symbol,' - '(SELECT dso_id FROM symbols WHERE id = c.symbol_id) AS dso_id,' - '(SELECT dso FROM symbols_view WHERE id = c.symbol_id) AS dso_short_name,' - 'c.parent_id,' - + emit_to_hex('p.ip') + ' AS parent_ip,' - 'p.symbol_id AS parent_symbol_id,' - '(SELECT name FROM symbols WHERE id = p.symbol_id) AS parent_symbol,' - '(SELECT dso_id FROM symbols WHERE id = p.symbol_id) AS parent_dso_id,' - '(SELECT dso FROM symbols_view WHERE id = p.symbol_id) AS parent_dso_short_name' - ' FROM call_paths c INNER JOIN call_paths p ON p.id = c.parent_id') -if perf_db_export_calls: - do_query(query, 'CREATE VIEW calls_view AS ' - 'SELECT ' - 'calls.id,' - 'thread_id,' - '(SELECT pid FROM threads WHERE id = thread_id) AS pid,' - '(SELECT tid FROM threads WHERE id = thread_id) AS tid,' - '(SELECT comm FROM comms WHERE id = comm_id) AS command,' - 'call_path_id,' - + emit_to_hex('ip') + ' AS ip,' - 'symbol_id,' - '(SELECT name FROM symbols WHERE id = symbol_id) AS symbol,' - 'call_time,' - 'return_time,' - 'return_time - call_time AS elapsed_time,' - 'branch_count,' - 'insn_count,' - 'cyc_count,' - 'CASE WHEN cyc_count=0 THEN CAST(0 AS FLOAT) ELSE ROUND(CAST(insn_count AS FLOAT) / cyc_count, 2) END AS IPC,' - 'call_id,' - 'return_id,' - 'CASE WHEN flags=0 THEN \'\' WHEN flags=1 THEN \'no call\' WHEN flags=2 THEN \'no return\' WHEN flags=3 THEN \'no call/return\' WHEN flags=6 THEN \'jump\' ELSE flags END AS flags,' - 'parent_call_path_id,' - 'calls.parent_id' - ' FROM calls INNER JOIN call_paths ON call_paths.id = call_path_id') - -do_query(query, 'CREATE VIEW samples_view AS ' - 'SELECT ' - 'id,' - 'time,' - 'cpu,' - '(SELECT pid FROM threads WHERE id = thread_id) AS pid,' - '(SELECT tid FROM threads WHERE id = thread_id) AS tid,' - '(SELECT comm FROM comms WHERE id = comm_id) AS command,' - '(SELECT name FROM selected_events WHERE id = evsel_id) AS event,' - + emit_to_hex('ip') + ' AS ip_hex,' - '(SELECT name FROM symbols WHERE id = symbol_id) AS symbol,' - 'sym_offset,' - '(SELECT short_name FROM dsos WHERE id = dso_id) AS dso_short_name,' - + emit_to_hex('to_ip') + ' AS to_ip_hex,' - '(SELECT name FROM symbols WHERE id = to_symbol_id) AS to_symbol,' - 'to_sym_offset,' - '(SELECT short_name FROM dsos WHERE id = to_dso_id) AS to_dso_short_name,' - '(SELECT name FROM branch_types WHERE id = branch_type) AS branch_type_name,' - 'in_tx,' - 'insn_count,' - 'cyc_count,' - 'CASE WHEN cyc_count=0 THEN CAST(0 AS FLOAT) ELSE ROUND(CAST(insn_count AS FLOAT) / cyc_count, 2) END AS IPC,' - 'flags' - ' FROM samples') - -do_query(query, 'CREATE VIEW ptwrite_view AS ' - 'SELECT ' - 'ptwrite.id,' - 'time,' - 'cpu,' - + emit_to_hex('payload') + ' AS payload_hex,' - 'CASE WHEN exact_ip=0 THEN \'False\' ELSE \'True\' END AS exact_ip' - ' FROM ptwrite' - ' INNER JOIN samples ON samples.id = ptwrite.id') - -do_query(query, 'CREATE VIEW cbr_view AS ' - 'SELECT ' - 'cbr.id,' - 'time,' - 'cpu,' - 'cbr,' - 'mhz,' - 'percent' - ' FROM cbr' - ' INNER JOIN samples ON samples.id = cbr.id') - -do_query(query, 'CREATE VIEW mwait_view AS ' - 'SELECT ' - 'mwait.id,' - 'time,' - 'cpu,' - + emit_to_hex('hints') + ' AS hints_hex,' - + emit_to_hex('extensions') + ' AS extensions_hex' - ' FROM mwait' - ' INNER JOIN samples ON samples.id = mwait.id') - -do_query(query, 'CREATE VIEW pwre_view AS ' - 'SELECT ' - 'pwre.id,' - 'time,' - 'cpu,' - 'cstate,' - 'subcstate,' - 'CASE WHEN hw=0 THEN \'False\' ELSE \'True\' END AS hw' - ' FROM pwre' - ' INNER JOIN samples ON samples.id = pwre.id') - -do_query(query, 'CREATE VIEW exstop_view AS ' - 'SELECT ' - 'exstop.id,' - 'time,' - 'cpu,' - 'CASE WHEN exact_ip=0 THEN \'False\' ELSE \'True\' END AS exact_ip' - ' FROM exstop' - ' INNER JOIN samples ON samples.id = exstop.id') - -do_query(query, 'CREATE VIEW pwrx_view AS ' - 'SELECT ' - 'pwrx.id,' - 'time,' - 'cpu,' - 'deepest_cstate,' - 'last_cstate,' - 'CASE WHEN wake_reason=1 THEN \'Interrupt\'' - ' WHEN wake_reason=2 THEN \'Timer Deadline\'' - ' WHEN wake_reason=4 THEN \'Monitored Address\'' - ' WHEN wake_reason=8 THEN \'HW\'' - ' ELSE wake_reason ' - 'END AS wake_reason' - ' FROM pwrx' - ' INNER JOIN samples ON samples.id = pwrx.id') - -do_query(query, 'CREATE VIEW power_events_view AS ' - 'SELECT ' - 'samples.id,' - 'time,' - 'cpu,' - 'selected_events.name AS event,' - 'CASE WHEN selected_events.name=\'cbr\' THEN (SELECT cbr FROM cbr WHERE cbr.id = samples.id) ELSE "" END AS cbr,' - 'CASE WHEN selected_events.name=\'cbr\' THEN (SELECT mhz FROM cbr WHERE cbr.id = samples.id) ELSE "" END AS mhz,' - 'CASE WHEN selected_events.name=\'cbr\' THEN (SELECT percent FROM cbr WHERE cbr.id = samples.id) ELSE "" END AS percent,' - 'CASE WHEN selected_events.name=\'mwait\' THEN (SELECT ' + emit_to_hex('hints') + ' FROM mwait WHERE mwait.id = samples.id) ELSE "" END AS hints_hex,' - 'CASE WHEN selected_events.name=\'mwait\' THEN (SELECT ' + emit_to_hex('extensions') + ' FROM mwait WHERE mwait.id = samples.id) ELSE "" END AS extensions_hex,' - 'CASE WHEN selected_events.name=\'pwre\' THEN (SELECT cstate FROM pwre WHERE pwre.id = samples.id) ELSE "" END AS cstate,' - 'CASE WHEN selected_events.name=\'pwre\' THEN (SELECT subcstate FROM pwre WHERE pwre.id = samples.id) ELSE "" END AS subcstate,' - 'CASE WHEN selected_events.name=\'pwre\' THEN (SELECT hw FROM pwre WHERE pwre.id = samples.id) ELSE "" END AS hw,' - 'CASE WHEN selected_events.name=\'exstop\' THEN (SELECT exact_ip FROM exstop WHERE exstop.id = samples.id) ELSE "" END AS exact_ip,' - 'CASE WHEN selected_events.name=\'pwrx\' THEN (SELECT deepest_cstate FROM pwrx WHERE pwrx.id = samples.id) ELSE "" END AS deepest_cstate,' - 'CASE WHEN selected_events.name=\'pwrx\' THEN (SELECT last_cstate FROM pwrx WHERE pwrx.id = samples.id) ELSE "" END AS last_cstate,' - 'CASE WHEN selected_events.name=\'pwrx\' THEN (SELECT ' - 'CASE WHEN wake_reason=1 THEN \'Interrupt\'' - ' WHEN wake_reason=2 THEN \'Timer Deadline\'' - ' WHEN wake_reason=4 THEN \'Monitored Address\'' - ' WHEN wake_reason=8 THEN \'HW\'' - ' ELSE wake_reason ' - 'END' - ' FROM pwrx WHERE pwrx.id = samples.id) ELSE "" END AS wake_reason' - ' FROM samples' - ' INNER JOIN selected_events ON selected_events.id = evsel_id' - ' WHERE selected_events.name IN (\'cbr\',\'mwait\',\'exstop\',\'pwre\',\'pwrx\')') - -do_query(query, 'CREATE VIEW context_switches_view AS ' - 'SELECT ' - 'context_switches.id,' - 'context_switches.machine_id,' - 'context_switches.time,' - 'context_switches.cpu,' - 'th_out.pid AS pid_out,' - 'th_out.tid AS tid_out,' - 'comm_out.comm AS comm_out,' - 'th_in.pid AS pid_in,' - 'th_in.tid AS tid_in,' - 'comm_in.comm AS comm_in,' - 'CASE WHEN context_switches.flags = 0 THEN \'in\'' - ' WHEN context_switches.flags = 1 THEN \'out\'' - ' WHEN context_switches.flags = 3 THEN \'out preempt\'' - ' ELSE context_switches.flags ' - 'END AS flags' - ' FROM context_switches' - ' INNER JOIN threads AS th_out ON th_out.id = context_switches.thread_out_id' - ' INNER JOIN threads AS th_in ON th_in.id = context_switches.thread_in_id' - ' INNER JOIN comms AS comm_out ON comm_out.id = context_switches.comm_out_id' - ' INNER JOIN comms AS comm_in ON comm_in.id = context_switches.comm_in_id') - -do_query(query, 'END TRANSACTION') - -evsel_query = QSqlQuery(db) -evsel_query.prepare("INSERT INTO selected_events VALUES (?, ?)") -machine_query = QSqlQuery(db) -machine_query.prepare("INSERT INTO machines VALUES (?, ?, ?)") -thread_query = QSqlQuery(db) -thread_query.prepare("INSERT INTO threads VALUES (?, ?, ?, ?, ?)") -comm_query = QSqlQuery(db) -comm_query.prepare("INSERT INTO comms VALUES (?, ?, ?, ?, ?)") -comm_thread_query = QSqlQuery(db) -comm_thread_query.prepare("INSERT INTO comm_threads VALUES (?, ?, ?)") -dso_query = QSqlQuery(db) -dso_query.prepare("INSERT INTO dsos VALUES (?, ?, ?, ?, ?)") -symbol_query = QSqlQuery(db) -symbol_query.prepare("INSERT INTO symbols VALUES (?, ?, ?, ?, ?, ?)") -branch_type_query = QSqlQuery(db) -branch_type_query.prepare("INSERT INTO branch_types VALUES (?, ?)") -sample_query = QSqlQuery(db) -if branches: - sample_query.prepare("INSERT INTO samples VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") -else: - sample_query.prepare("INSERT INTO samples VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") -if perf_db_export_calls or perf_db_export_callchains: - call_path_query = QSqlQuery(db) - call_path_query.prepare("INSERT INTO call_paths VALUES (?, ?, ?, ?)") -if perf_db_export_calls: - call_query = QSqlQuery(db) - call_query.prepare("INSERT INTO calls VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") -ptwrite_query = QSqlQuery(db) -ptwrite_query.prepare("INSERT INTO ptwrite VALUES (?, ?, ?)") -cbr_query = QSqlQuery(db) -cbr_query.prepare("INSERT INTO cbr VALUES (?, ?, ?, ?)") -mwait_query = QSqlQuery(db) -mwait_query.prepare("INSERT INTO mwait VALUES (?, ?, ?)") -pwre_query = QSqlQuery(db) -pwre_query.prepare("INSERT INTO pwre VALUES (?, ?, ?, ?)") -exstop_query = QSqlQuery(db) -exstop_query.prepare("INSERT INTO exstop VALUES (?, ?)") -pwrx_query = QSqlQuery(db) -pwrx_query.prepare("INSERT INTO pwrx VALUES (?, ?, ?, ?)") -context_switch_query = QSqlQuery(db) -context_switch_query.prepare("INSERT INTO context_switches VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") - -def trace_begin(): - printdate("Writing records...") - do_query(query, 'BEGIN TRANSACTION') - # id == 0 means unknown. It is easier to create records for them than replace the zeroes with NULLs - evsel_table(0, "unknown") - machine_table(0, 0, "unknown") - thread_table(0, 0, 0, -1, -1) - comm_table(0, "unknown", 0, 0, 0) - dso_table(0, 0, "unknown", "unknown", "") - symbol_table(0, 0, 0, 0, 0, "unknown") - sample_table(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - if perf_db_export_calls or perf_db_export_callchains: - call_path_table(0, 0, 0, 0) - call_return_table(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - -unhandled_count = 0 - -def is_table_empty(table_name): - do_query(query, 'SELECT * FROM ' + table_name + ' LIMIT 1'); - if query.next(): - return False - return True - -def drop(table_name): - do_query(query, 'DROP VIEW ' + table_name + '_view'); - do_query(query, 'DROP TABLE ' + table_name); - -def trace_end(): - do_query(query, 'END TRANSACTION') - - printdate("Adding indexes") - if perf_db_export_calls: - do_query(query, 'CREATE INDEX pcpid_idx ON calls (parent_call_path_id)') - do_query(query, 'CREATE INDEX pid_idx ON calls (parent_id)') - do_query(query, 'ALTER TABLE comms ADD has_calls boolean') - do_query(query, 'UPDATE comms SET has_calls = 1 WHERE comms.id IN (SELECT DISTINCT comm_id FROM calls)') - - printdate("Dropping unused tables") - if is_table_empty("ptwrite"): - drop("ptwrite") - if is_table_empty("mwait") and is_table_empty("pwre") and is_table_empty("exstop") and is_table_empty("pwrx"): - do_query(query, 'DROP VIEW power_events_view'); - drop("mwait") - drop("pwre") - drop("exstop") - drop("pwrx") - if is_table_empty("cbr"): - drop("cbr") - if is_table_empty("context_switches"): - drop("context_switches") - - if (unhandled_count): - printdate("Warning: ", unhandled_count, " unhandled events") - printdate("Done") - -def trace_unhandled(event_name, context, event_fields_dict): - global unhandled_count - unhandled_count += 1 - -def sched__sched_switch(*x): - pass - -def bind_exec(q, n, x): - for xx in x[0:n]: - q.addBindValue(str(xx)) - do_query_(q) - -def evsel_table(*x): - bind_exec(evsel_query, 2, x) - -def machine_table(*x): - bind_exec(machine_query, 3, x) - -def thread_table(*x): - bind_exec(thread_query, 5, x) - -def comm_table(*x): - bind_exec(comm_query, 5, x) - -def comm_thread_table(*x): - bind_exec(comm_thread_query, 3, x) - -def dso_table(*x): - bind_exec(dso_query, 5, x) - -def symbol_table(*x): - bind_exec(symbol_query, 6, x) - -def branch_type_table(*x): - bind_exec(branch_type_query, 2, x) - -def sample_table(*x): - if branches: - for xx in x[0:15]: - sample_query.addBindValue(str(xx)) - for xx in x[19:25]: - sample_query.addBindValue(str(xx)) - do_query_(sample_query) - else: - bind_exec(sample_query, 25, x) - -def call_path_table(*x): - bind_exec(call_path_query, 4, x) - -def call_return_table(*x): - bind_exec(call_query, 14, x) - -def ptwrite(id, raw_buf): - data = struct.unpack_from("> 32) & 0x3 - mwait_query.addBindValue(str(id)) - mwait_query.addBindValue(str(hints)) - mwait_query.addBindValue(str(extensions)) - do_query_(mwait_query) - -def pwre(id, raw_buf): - data = struct.unpack_from("> 7) & 1 - cstate = (payload >> 12) & 0xf - subcstate = (payload >> 8) & 0xf - pwre_query.addBindValue(str(id)) - pwre_query.addBindValue(str(cstate)) - pwre_query.addBindValue(str(subcstate)) - pwre_query.addBindValue(str(hw)) - do_query_(pwre_query) - -def exstop(id, raw_buf): - data = struct.unpack_from("> 4) & 0xf - wake_reason = (payload >> 8) & 0xf - pwrx_query.addBindValue(str(id)) - pwrx_query.addBindValue(str(deepest_cstate)) - pwrx_query.addBindValue(str(last_cstate)) - pwrx_query.addBindValue(str(wake_reason)) - do_query_(pwrx_query) - -def synth_data(id, config, raw_buf, *x): - if config == 0: - ptwrite(id, raw_buf) - elif config == 1: - mwait(id, raw_buf) - elif config == 2: - pwre(id, raw_buf) - elif config == 3: - exstop(id, raw_buf) - elif config == 4: - pwrx(id, raw_buf) - elif config == 5: - cbr(id, raw_buf) - -def context_switch_table(*x): - bind_exec(context_switch_query, 9, x) diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py deleted file mode 100755 index e0b2e7268ef6..000000000000 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ /dev/null @@ -1,5030 +0,0 @@ -#!/usr/bin/env python -# SPDX-License-Identifier: GPL-2.0 -# exported-sql-viewer.py: view data from sql database -# Copyright (c) 2014-2018, Intel Corporation. - -# To use this script you will need to have exported data using either the -# export-to-sqlite.py or the export-to-postgresql.py script. Refer to those -# scripts for details. -# -# Following on from the example in the export scripts, a -# call-graph can be displayed for the pt_example database like this: -# -# python tools/perf/scripts/python/exported-sql-viewer.py pt_example -# -# Note that for PostgreSQL, this script supports connecting to remote databases -# by setting hostname, port, username, password, and dbname e.g. -# -# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example" -# -# The result is a GUI window with a tree representing a context-sensitive -# call-graph. Expanding a couple of levels of the tree and adjusting column -# widths to suit will display something like: -# -# Call Graph: pt_example -# Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%) -# v- ls -# v- 2638:2638 -# v- _start ld-2.19.so 1 10074071 100.0 211135 100.0 -# |- unknown unknown 1 13198 0.1 1 0.0 -# >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3 -# >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3 -# v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4 -# >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1 -# >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0 -# >- __libc_csu_init ls 1 10354 0.1 10 0.0 -# |- _setjmp libc-2.19.so 1 0 0.0 4 0.0 -# v- main ls 1 8182043 99.6 180254 99.9 -# -# Points to note: -# The top level is a command name (comm) -# The next level is a thread (pid:tid) -# Subsequent levels are functions -# 'Count' is the number of calls -# 'Time' is the elapsed time until the function returns -# Percentages are relative to the level above -# 'Branch Count' is the total number of branches for that function and all -# functions that it calls - -# There is also a "All branches" report, which displays branches and -# possibly disassembly. However, presently, the only supported disassembler is -# Intel XED, and additionally the object code must be present in perf build ID -# cache. To use Intel XED, libxed.so must be present. To build and install -# libxed.so: -# git clone https://github.com/intelxed/mbuild.git mbuild -# git clone https://github.com/intelxed/xed -# cd xed -# ./mfile.py --share -# sudo ./mfile.py --prefix=/usr/local install -# sudo ldconfig -# -# Example report: -# -# Time CPU Command PID TID Branch Type In Tx Branch -# 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so) -# 7fab593ea260 48 89 e7 mov %rsp, %rdi -# 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel]) -# 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so) -# 7fab593ea260 48 89 e7 mov %rsp, %rdi -# 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930 -# 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so) -# 7fab593ea930 55 pushq %rbp -# 7fab593ea931 48 89 e5 mov %rsp, %rbp -# 7fab593ea934 41 57 pushq %r15 -# 7fab593ea936 41 56 pushq %r14 -# 7fab593ea938 41 55 pushq %r13 -# 7fab593ea93a 41 54 pushq %r12 -# 7fab593ea93c 53 pushq %rbx -# 7fab593ea93d 48 89 fb mov %rdi, %rbx -# 7fab593ea940 48 83 ec 68 sub $0x68, %rsp -# 7fab593ea944 0f 31 rdtsc -# 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx -# 7fab593ea94a 89 c0 mov %eax, %eax -# 7fab593ea94c 48 09 c2 or %rax, %rdx -# 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax -# 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel]) -# 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -# 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax -# 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip) -# 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel]) - -from __future__ import print_function - -import sys -# Only change warnings if the python -W option was not used -if not sys.warnoptions: - import warnings - # PySide2 causes deprecation warnings, ignore them. - warnings.filterwarnings("ignore", category=DeprecationWarning) -import argparse -import weakref -import threading -import string -try: - # Python2 - import cPickle as pickle - # size of pickled integer big enough for record size - glb_nsz = 8 -except ImportError: - import pickle - glb_nsz = 16 -import re -import os -import random -import copy -import math -from libxed import LibXED - -pyside_version_1 = True -if not "--pyside-version-1" in sys.argv: - try: - from PySide2.QtCore import * - from PySide2.QtGui import * - from PySide2.QtSql import * - from PySide2.QtWidgets import * - pyside_version_1 = False - except: - pass - -if pyside_version_1: - from PySide.QtCore import * - from PySide.QtGui import * - from PySide.QtSql import * - -from decimal import Decimal, ROUND_HALF_UP -from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \ - c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong -from multiprocessing import Process, Array, Value, Event - -# xrange is range in Python3 -try: - xrange -except NameError: - xrange = range - -def printerr(*args, **keyword_args): - print(*args, file=sys.stderr, **keyword_args) - -# Data formatting helpers - -def tohex(ip): - if ip < 0: - ip += 1 << 64 - return "%x" % ip - -def offstr(offset): - if offset: - return "+0x%x" % offset - return "" - -def dsoname(name): - if name == "[kernel.kallsyms]": - return "[kernel]" - return name - -def findnth(s, sub, n, offs=0): - pos = s.find(sub) - if pos < 0: - return pos - if n <= 1: - return offs + pos - return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1) - -# Percent to one decimal place - -def PercentToOneDP(n, d): - if not d: - return "0.0" - x = (n * Decimal(100)) / d - return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP)) - -# Helper for queries that must not fail - -def QueryExec(query, stmt): - ret = query.exec_(stmt) - if not ret: - raise Exception("Query failed: " + query.lastError().text()) - -# Background thread - -class Thread(QThread): - - done = Signal(object) - - def __init__(self, task, param=None, parent=None): - super(Thread, self).__init__(parent) - self.task = task - self.param = param - - def run(self): - while True: - if self.param is None: - done, result = self.task() - else: - done, result = self.task(self.param) - self.done.emit(result) - if done: - break - -# Tree data model - -class TreeModel(QAbstractItemModel): - - def __init__(self, glb, params, parent=None): - super(TreeModel, self).__init__(parent) - self.glb = glb - self.params = params - self.root = self.GetRoot() - self.last_row_read = 0 - - def Item(self, parent): - if parent.isValid(): - return parent.internalPointer() - else: - return self.root - - def rowCount(self, parent): - result = self.Item(parent).childCount() - if result < 0: - result = 0 - self.dataChanged.emit(parent, parent) - return result - - def hasChildren(self, parent): - return self.Item(parent).hasChildren() - - def headerData(self, section, orientation, role): - if role == Qt.TextAlignmentRole: - return self.columnAlignment(section) - if role != Qt.DisplayRole: - return None - if orientation != Qt.Horizontal: - return None - return self.columnHeader(section) - - def parent(self, child): - child_item = child.internalPointer() - if child_item is self.root: - return QModelIndex() - parent_item = child_item.getParentItem() - return self.createIndex(parent_item.getRow(), 0, parent_item) - - def index(self, row, column, parent): - child_item = self.Item(parent).getChildItem(row) - return self.createIndex(row, column, child_item) - - def DisplayData(self, item, index): - return item.getData(index.column()) - - def FetchIfNeeded(self, row): - if row > self.last_row_read: - self.last_row_read = row - if row + 10 >= self.root.child_count: - self.fetcher.Fetch(glb_chunk_sz) - - def columnAlignment(self, column): - return Qt.AlignLeft - - def columnFont(self, column): - return None - - def data(self, index, role): - if role == Qt.TextAlignmentRole: - return self.columnAlignment(index.column()) - if role == Qt.FontRole: - return self.columnFont(index.column()) - if role != Qt.DisplayRole: - return None - item = index.internalPointer() - return self.DisplayData(item, index) - -# Table data model - -class TableModel(QAbstractTableModel): - - def __init__(self, parent=None): - super(TableModel, self).__init__(parent) - self.child_count = 0 - self.child_items = [] - self.last_row_read = 0 - - def Item(self, parent): - if parent.isValid(): - return parent.internalPointer() - else: - return self - - def rowCount(self, parent): - return self.child_count - - def headerData(self, section, orientation, role): - if role == Qt.TextAlignmentRole: - return self.columnAlignment(section) - if role != Qt.DisplayRole: - return None - if orientation != Qt.Horizontal: - return None - return self.columnHeader(section) - - def index(self, row, column, parent): - return self.createIndex(row, column, self.child_items[row]) - - def DisplayData(self, item, index): - return item.getData(index.column()) - - def FetchIfNeeded(self, row): - if row > self.last_row_read: - self.last_row_read = row - if row + 10 >= self.child_count: - self.fetcher.Fetch(glb_chunk_sz) - - def columnAlignment(self, column): - return Qt.AlignLeft - - def columnFont(self, column): - return None - - def data(self, index, role): - if role == Qt.TextAlignmentRole: - return self.columnAlignment(index.column()) - if role == Qt.FontRole: - return self.columnFont(index.column()) - if role != Qt.DisplayRole: - return None - item = index.internalPointer() - return self.DisplayData(item, index) - -# Model cache - -model_cache = weakref.WeakValueDictionary() -model_cache_lock = threading.Lock() - -def LookupCreateModel(model_name, create_fn): - model_cache_lock.acquire() - try: - model = model_cache[model_name] - except: - model = None - if model is None: - model = create_fn() - model_cache[model_name] = model - model_cache_lock.release() - return model - -def LookupModel(model_name): - model_cache_lock.acquire() - try: - model = model_cache[model_name] - except: - model = None - model_cache_lock.release() - return model - -# Find bar - -class FindBar(): - - def __init__(self, parent, finder, is_reg_expr=False): - self.finder = finder - self.context = [] - self.last_value = None - self.last_pattern = None - - label = QLabel("Find:") - label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - - self.textbox = QComboBox() - self.textbox.setEditable(True) - self.textbox.currentIndexChanged.connect(self.ValueChanged) - - self.progress = QProgressBar() - self.progress.setRange(0, 0) - self.progress.hide() - - if is_reg_expr: - self.pattern = QCheckBox("Regular Expression") - else: - self.pattern = QCheckBox("Pattern") - self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - - self.next_button = QToolButton() - self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown)) - self.next_button.released.connect(lambda: self.NextPrev(1)) - - self.prev_button = QToolButton() - self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp)) - self.prev_button.released.connect(lambda: self.NextPrev(-1)) - - self.close_button = QToolButton() - self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) - self.close_button.released.connect(self.Deactivate) - - self.hbox = QHBoxLayout() - self.hbox.setContentsMargins(0, 0, 0, 0) - - self.hbox.addWidget(label) - self.hbox.addWidget(self.textbox) - self.hbox.addWidget(self.progress) - self.hbox.addWidget(self.pattern) - self.hbox.addWidget(self.next_button) - self.hbox.addWidget(self.prev_button) - self.hbox.addWidget(self.close_button) - - self.bar = QWidget() - self.bar.setLayout(self.hbox) - self.bar.hide() - - def Widget(self): - return self.bar - - def Activate(self): - self.bar.show() - self.textbox.lineEdit().selectAll() - self.textbox.setFocus() - - def Deactivate(self): - self.bar.hide() - - def Busy(self): - self.textbox.setEnabled(False) - self.pattern.hide() - self.next_button.hide() - self.prev_button.hide() - self.progress.show() - - def Idle(self): - self.textbox.setEnabled(True) - self.progress.hide() - self.pattern.show() - self.next_button.show() - self.prev_button.show() - - def Find(self, direction): - value = self.textbox.currentText() - pattern = self.pattern.isChecked() - self.last_value = value - self.last_pattern = pattern - self.finder.Find(value, direction, pattern, self.context) - - def ValueChanged(self): - value = self.textbox.currentText() - pattern = self.pattern.isChecked() - index = self.textbox.currentIndex() - data = self.textbox.itemData(index) - # Store the pattern in the combo box to keep it with the text value - if data == None: - self.textbox.setItemData(index, pattern) - else: - self.pattern.setChecked(data) - self.Find(0) - - def NextPrev(self, direction): - value = self.textbox.currentText() - pattern = self.pattern.isChecked() - if value != self.last_value: - index = self.textbox.findText(value) - # Allow for a button press before the value has been added to the combo box - if index < 0: - index = self.textbox.count() - self.textbox.addItem(value, pattern) - self.textbox.setCurrentIndex(index) - return - else: - self.textbox.setItemData(index, pattern) - elif pattern != self.last_pattern: - # Keep the pattern recorded in the combo box up to date - index = self.textbox.currentIndex() - self.textbox.setItemData(index, pattern) - self.Find(direction) - - def NotFound(self): - QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found") - -# Context-sensitive call graph data model item base - -class CallGraphLevelItemBase(object): - - def __init__(self, glb, params, row, parent_item): - self.glb = glb - self.params = params - self.row = row - self.parent_item = parent_item - self.query_done = False - self.child_count = 0 - self.child_items = [] - if parent_item: - self.level = parent_item.level + 1 - else: - self.level = 0 - - def getChildItem(self, row): - return self.child_items[row] - - def getParentItem(self): - return self.parent_item - - def getRow(self): - return self.row - - def childCount(self): - if not self.query_done: - self.Select() - if not self.child_count: - return -1 - return self.child_count - - def hasChildren(self): - if not self.query_done: - return True - return self.child_count > 0 - - def getData(self, column): - return self.data[column] - -# Context-sensitive call graph data model level 2+ item base - -class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase): - - def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item): - super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item) - self.comm_id = comm_id - self.thread_id = thread_id - self.call_path_id = call_path_id - self.insn_cnt = insn_cnt - self.cyc_cnt = cyc_cnt - self.branch_count = branch_count - self.time = time - - def Select(self): - self.query_done = True - query = QSqlQuery(self.glb.db) - if self.params.have_ipc: - ipc_str = ", SUM(insn_count), SUM(cyc_count)" - else: - ipc_str = "" - QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)" - " FROM calls" - " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" - " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" - " INNER JOIN dsos ON symbols.dso_id = dsos.id" - " WHERE parent_call_path_id = " + str(self.call_path_id) + - " AND comm_id = " + str(self.comm_id) + - " AND thread_id = " + str(self.thread_id) + - " GROUP BY call_path_id, name, short_name" - " ORDER BY call_path_id") - while query.next(): - if self.params.have_ipc: - insn_cnt = int(query.value(5)) - cyc_cnt = int(query.value(6)) - branch_count = int(query.value(7)) - else: - insn_cnt = 0 - cyc_cnt = 0 - branch_count = int(query.value(5)) - child_item = CallGraphLevelThreeItem(self.glb, self.params, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), insn_cnt, cyc_cnt, branch_count, self) - self.child_items.append(child_item) - self.child_count += 1 - -# Context-sensitive call graph data model level three item - -class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase): - - def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item): - super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item) - dso = dsoname(dso) - if self.params.have_ipc: - insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt) - cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt) - br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count) - ipc = CalcIPC(cyc_cnt, insn_cnt) - self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ] - else: - self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ] - self.dbid = call_path_id - -# Context-sensitive call graph data model level two item - -class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase): - - def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item): - super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item) - if self.params.have_ipc: - self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""] - else: - self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""] - self.dbid = thread_id - - def Select(self): - super(CallGraphLevelTwoItem, self).Select() - for child_item in self.child_items: - self.time += child_item.time - self.insn_cnt += child_item.insn_cnt - self.cyc_cnt += child_item.cyc_cnt - self.branch_count += child_item.branch_count - for child_item in self.child_items: - child_item.data[4] = PercentToOneDP(child_item.time, self.time) - if self.params.have_ipc: - child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt) - child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt) - child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count) - else: - child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) - -# Context-sensitive call graph data model level one item - -class CallGraphLevelOneItem(CallGraphLevelItemBase): - - def __init__(self, glb, params, row, comm_id, comm, parent_item): - super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item) - if self.params.have_ipc: - self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""] - else: - self.data = [comm, "", "", "", "", "", ""] - self.dbid = comm_id - - def Select(self): - self.query_done = True - query = QSqlQuery(self.glb.db) - QueryExec(query, "SELECT thread_id, pid, tid" - " FROM comm_threads" - " INNER JOIN threads ON thread_id = threads.id" - " WHERE comm_id = " + str(self.dbid)) - while query.next(): - child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self) - self.child_items.append(child_item) - self.child_count += 1 - -# Context-sensitive call graph data model root item - -class CallGraphRootItem(CallGraphLevelItemBase): - - def __init__(self, glb, params): - super(CallGraphRootItem, self).__init__(glb, params, 0, None) - self.dbid = 0 - self.query_done = True - if_has_calls = "" - if IsSelectable(glb.db, "comms", columns = "has_calls"): - if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE - query = QSqlQuery(glb.db) - QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls) - while query.next(): - if not query.value(0): - continue - child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self) - self.child_items.append(child_item) - self.child_count += 1 - -# Call graph model parameters - -class CallGraphModelParams(): - - def __init__(self, glb, parent=None): - self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count") - -# Context-sensitive call graph data model base - -class CallGraphModelBase(TreeModel): - - def __init__(self, glb, parent=None): - super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent) - - def FindSelect(self, value, pattern, query): - if pattern: - # postgresql and sqlite pattern patching differences: - # postgresql LIKE is case sensitive but sqlite LIKE is not - # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not - # postgresql supports ILIKE which is case insensitive - # sqlite supports GLOB (text only) which uses * and ? and is case sensitive - if not self.glb.dbref.is_sqlite3: - # Escape % and _ - s = value.replace("%", "\\%") - s = s.replace("_", "\\_") - # Translate * and ? into SQL LIKE pattern characters % and _ - if sys.version_info[0] == 3: - trans = str.maketrans("*?", "%_") - else: - trans = string.maketrans("*?", "%_") - match = " LIKE '" + str(s).translate(trans) + "'" - else: - match = " GLOB '" + str(value) + "'" - else: - match = " = '" + str(value) + "'" - self.DoFindSelect(query, match) - - def Found(self, query, found): - if found: - return self.FindPath(query) - return [] - - def FindValue(self, value, pattern, query, last_value, last_pattern): - if last_value == value and pattern == last_pattern: - found = query.first() - else: - self.FindSelect(value, pattern, query) - found = query.next() - return self.Found(query, found) - - def FindNext(self, query): - found = query.next() - if not found: - found = query.first() - return self.Found(query, found) - - def FindPrev(self, query): - found = query.previous() - if not found: - found = query.last() - return self.Found(query, found) - - def FindThread(self, c): - if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern: - ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern) - elif c.direction > 0: - ids = self.FindNext(c.query) - else: - ids = self.FindPrev(c.query) - return (True, ids) - - def Find(self, value, direction, pattern, context, callback): - class Context(): - def __init__(self, *x): - self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x - def Update(self, *x): - self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern) - if len(context): - context[0].Update(value, direction, pattern) - else: - context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None)) - # Use a thread so the UI is not blocked during the SELECT - thread = Thread(self.FindThread, context[0]) - thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection) - thread.start() - - def FindDone(self, thread, callback, ids): - callback(ids) - -# Context-sensitive call graph data model - -class CallGraphModel(CallGraphModelBase): - - def __init__(self, glb, parent=None): - super(CallGraphModel, self).__init__(glb, parent) - - def GetRoot(self): - return CallGraphRootItem(self.glb, self.params) - - def columnCount(self, parent=None): - if self.params.have_ipc: - return 12 - else: - return 7 - - def columnHeader(self, column): - if self.params.have_ipc: - headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "] - else: - headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] - return headers[column] - - def columnAlignment(self, column): - if self.params.have_ipc: - alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] - else: - alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] - return alignment[column] - - def DoFindSelect(self, query, match): - QueryExec(query, "SELECT call_path_id, comm_id, thread_id" - " FROM calls" - " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" - " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" - " WHERE calls.id <> 0" - " AND symbols.name" + match + - " GROUP BY comm_id, thread_id, call_path_id" - " ORDER BY comm_id, thread_id, call_path_id") - - def FindPath(self, query): - # Turn the query result into a list of ids that the tree view can walk - # to open the tree at the right place. - ids = [] - parent_id = query.value(0) - while parent_id: - ids.insert(0, parent_id) - q2 = QSqlQuery(self.glb.db) - QueryExec(q2, "SELECT parent_id" - " FROM call_paths" - " WHERE id = " + str(parent_id)) - if not q2.next(): - break - parent_id = q2.value(0) - # The call path root is not used - if ids[0] == 1: - del ids[0] - ids.insert(0, query.value(2)) - ids.insert(0, query.value(1)) - return ids - -# Call tree data model level 2+ item base - -class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase): - - def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item): - super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item) - self.comm_id = comm_id - self.thread_id = thread_id - self.calls_id = calls_id - self.call_time = call_time - self.time = time - self.insn_cnt = insn_cnt - self.cyc_cnt = cyc_cnt - self.branch_count = branch_count - - def Select(self): - self.query_done = True - if self.calls_id == 0: - comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id) - else: - comm_thread = "" - if self.params.have_ipc: - ipc_str = ", insn_count, cyc_count" - else: - ipc_str = "" - query = QSqlQuery(self.glb.db) - QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count" - " FROM calls" - " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" - " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" - " INNER JOIN dsos ON symbols.dso_id = dsos.id" - " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread + - " ORDER BY call_time, calls.id") - while query.next(): - if self.params.have_ipc: - insn_cnt = int(query.value(5)) - cyc_cnt = int(query.value(6)) - branch_count = int(query.value(7)) - else: - insn_cnt = 0 - cyc_cnt = 0 - branch_count = int(query.value(5)) - child_item = CallTreeLevelThreeItem(self.glb, self.params, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), insn_cnt, cyc_cnt, branch_count, self) - self.child_items.append(child_item) - self.child_count += 1 - -# Call tree data model level three item - -class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase): - - def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item): - super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item) - dso = dsoname(dso) - if self.params.have_ipc: - insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt) - cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt) - br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count) - ipc = CalcIPC(cyc_cnt, insn_cnt) - self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ] - else: - self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ] - self.dbid = calls_id - -# Call tree data model level two item - -class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase): - - def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item): - super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item) - if self.params.have_ipc: - self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""] - else: - self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""] - self.dbid = thread_id - - def Select(self): - super(CallTreeLevelTwoItem, self).Select() - for child_item in self.child_items: - self.time += child_item.time - self.insn_cnt += child_item.insn_cnt - self.cyc_cnt += child_item.cyc_cnt - self.branch_count += child_item.branch_count - for child_item in self.child_items: - child_item.data[4] = PercentToOneDP(child_item.time, self.time) - if self.params.have_ipc: - child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt) - child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt) - child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count) - else: - child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) - -# Call tree data model level one item - -class CallTreeLevelOneItem(CallGraphLevelItemBase): - - def __init__(self, glb, params, row, comm_id, comm, parent_item): - super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item) - if self.params.have_ipc: - self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""] - else: - self.data = [comm, "", "", "", "", "", ""] - self.dbid = comm_id - - def Select(self): - self.query_done = True - query = QSqlQuery(self.glb.db) - QueryExec(query, "SELECT thread_id, pid, tid" - " FROM comm_threads" - " INNER JOIN threads ON thread_id = threads.id" - " WHERE comm_id = " + str(self.dbid)) - while query.next(): - child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self) - self.child_items.append(child_item) - self.child_count += 1 - -# Call tree data model root item - -class CallTreeRootItem(CallGraphLevelItemBase): - - def __init__(self, glb, params): - super(CallTreeRootItem, self).__init__(glb, params, 0, None) - self.dbid = 0 - self.query_done = True - if_has_calls = "" - if IsSelectable(glb.db, "comms", columns = "has_calls"): - if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE - query = QSqlQuery(glb.db) - QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls) - while query.next(): - if not query.value(0): - continue - child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self) - self.child_items.append(child_item) - self.child_count += 1 - -# Call Tree data model - -class CallTreeModel(CallGraphModelBase): - - def __init__(self, glb, parent=None): - super(CallTreeModel, self).__init__(glb, parent) - - def GetRoot(self): - return CallTreeRootItem(self.glb, self.params) - - def columnCount(self, parent=None): - if self.params.have_ipc: - return 12 - else: - return 7 - - def columnHeader(self, column): - if self.params.have_ipc: - headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "] - else: - headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] - return headers[column] - - def columnAlignment(self, column): - if self.params.have_ipc: - alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] - else: - alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] - return alignment[column] - - def DoFindSelect(self, query, match): - QueryExec(query, "SELECT calls.id, comm_id, thread_id" - " FROM calls" - " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" - " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" - " WHERE calls.id <> 0" - " AND symbols.name" + match + - " ORDER BY comm_id, thread_id, call_time, calls.id") - - def FindPath(self, query): - # Turn the query result into a list of ids that the tree view can walk - # to open the tree at the right place. - ids = [] - parent_id = query.value(0) - while parent_id: - ids.insert(0, parent_id) - q2 = QSqlQuery(self.glb.db) - QueryExec(q2, "SELECT parent_id" - " FROM calls" - " WHERE id = " + str(parent_id)) - if not q2.next(): - break - parent_id = q2.value(0) - ids.insert(0, query.value(2)) - ids.insert(0, query.value(1)) - return ids - -# Vertical layout - -class HBoxLayout(QHBoxLayout): - - def __init__(self, *children): - super(HBoxLayout, self).__init__() - - self.layout().setContentsMargins(0, 0, 0, 0) - for child in children: - if child.isWidgetType(): - self.layout().addWidget(child) - else: - self.layout().addLayout(child) - -# Horizontal layout - -class VBoxLayout(QVBoxLayout): - - def __init__(self, *children): - super(VBoxLayout, self).__init__() - - self.layout().setContentsMargins(0, 0, 0, 0) - for child in children: - if child.isWidgetType(): - self.layout().addWidget(child) - else: - self.layout().addLayout(child) - -# Vertical layout widget - -class VBox(): - - def __init__(self, *children): - self.vbox = QWidget() - self.vbox.setLayout(VBoxLayout(*children)) - - def Widget(self): - return self.vbox - -# Tree window base - -class TreeWindowBase(QMdiSubWindow): - - def __init__(self, parent=None): - super(TreeWindowBase, self).__init__(parent) - - self.model = None - self.find_bar = None - - self.view = QTreeView() - self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) - self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard - - self.context_menu = TreeContextMenu(self.view) - - def DisplayFound(self, ids): - if not len(ids): - return False - parent = QModelIndex() - for dbid in ids: - found = False - n = self.model.rowCount(parent) - for row in xrange(n): - child = self.model.index(row, 0, parent) - if child.internalPointer().dbid == dbid: - found = True - self.view.setExpanded(parent, True) - self.view.setCurrentIndex(child) - parent = child - break - if not found: - break - return found - - def Find(self, value, direction, pattern, context): - self.view.setFocus() - self.find_bar.Busy() - self.model.Find(value, direction, pattern, context, self.FindDone) - - def FindDone(self, ids): - found = True - if not self.DisplayFound(ids): - found = False - self.find_bar.Idle() - if not found: - self.find_bar.NotFound() - - -# Context-sensitive call graph window - -class CallGraphWindow(TreeWindowBase): - - def __init__(self, glb, parent=None): - super(CallGraphWindow, self).__init__(parent) - - self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x)) - - self.view.setModel(self.model) - - for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): - self.view.setColumnWidth(c, w) - - self.find_bar = FindBar(self, self) - - self.vbox = VBox(self.view, self.find_bar.Widget()) - - self.setWidget(self.vbox.Widget()) - - AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") - -# Call tree window - -class CallTreeWindow(TreeWindowBase): - - def __init__(self, glb, parent=None, thread_at_time=None): - super(CallTreeWindow, self).__init__(parent) - - self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x)) - - self.view.setModel(self.model) - - for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)): - self.view.setColumnWidth(c, w) - - self.find_bar = FindBar(self, self) - - self.vbox = VBox(self.view, self.find_bar.Widget()) - - self.setWidget(self.vbox.Widget()) - - AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree") - - if thread_at_time: - self.DisplayThreadAtTime(*thread_at_time) - - def DisplayThreadAtTime(self, comm_id, thread_id, time): - parent = QModelIndex() - for dbid in (comm_id, thread_id): - found = False - n = self.model.rowCount(parent) - for row in xrange(n): - child = self.model.index(row, 0, parent) - if child.internalPointer().dbid == dbid: - found = True - self.view.setExpanded(parent, True) - self.view.setCurrentIndex(child) - parent = child - break - if not found: - return - found = False - while True: - n = self.model.rowCount(parent) - if not n: - return - last_child = None - for row in xrange(n): - self.view.setExpanded(parent, True) - child = self.model.index(row, 0, parent) - child_call_time = child.internalPointer().call_time - if child_call_time < time: - last_child = child - elif child_call_time == time: - self.view.setCurrentIndex(child) - return - elif child_call_time > time: - break - if not last_child: - if not found: - child = self.model.index(0, 0, parent) - self.view.setExpanded(parent, True) - self.view.setCurrentIndex(child) - return - found = True - self.view.setExpanded(parent, True) - self.view.setCurrentIndex(last_child) - parent = last_child - -# ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name - -def ExecComm(db, thread_id, time): - query = QSqlQuery(db) - QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag" - " FROM comm_threads" - " INNER JOIN comms ON comms.id = comm_threads.comm_id" - " WHERE comm_threads.thread_id = " + str(thread_id) + - " ORDER BY comms.c_time, comms.id") - first = None - last = None - while query.next(): - if first is None: - first = query.value(0) - if query.value(2) and Decimal(query.value(1)) <= Decimal(time): - last = query.value(0) - if not(last is None): - return last - return first - -# Container for (x, y) data - -class XY(): - def __init__(self, x=0, y=0): - self.x = x - self.y = y - - def __str__(self): - return "XY({}, {})".format(str(self.x), str(self.y)) - -# Container for sub-range data - -class Subrange(): - def __init__(self, lo=0, hi=0): - self.lo = lo - self.hi = hi - - def __str__(self): - return "Subrange({}, {})".format(str(self.lo), str(self.hi)) - -# Graph data region base class - -class GraphDataRegion(object): - - def __init__(self, key, title = "", ordinal = ""): - self.key = key - self.title = title - self.ordinal = ordinal - -# Function to sort GraphDataRegion - -def GraphDataRegionOrdinal(data_region): - return data_region.ordinal - -# Attributes for a graph region - -class GraphRegionAttribute(): - - def __init__(self, colour): - self.colour = colour - -# Switch graph data region represents a task - -class SwitchGraphDataRegion(GraphDataRegion): - - def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id): - super(SwitchGraphDataRegion, self).__init__(key) - - self.title = str(pid) + " / " + str(tid) + " " + comm - # Order graph legend within exec comm by pid / tid / time - self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16) - self.exec_comm_id = exec_comm_id - self.pid = pid - self.tid = tid - self.comm = comm - self.thread_id = thread_id - self.comm_id = comm_id - -# Graph data point - -class GraphDataPoint(): - - def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None): - self.data = data - self.index = index - self.x = x - self.y = y - self.altx = altx - self.alty = alty - self.hregion = hregion - self.vregion = vregion - -# Graph data (single graph) base class - -class GraphData(object): - - def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)): - self.collection = collection - self.points = [] - self.xbase = xbase - self.ybase = ybase - self.title = "" - - def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None): - index = len(self.points) - - x = float(Decimal(x) - self.xbase) - y = float(Decimal(y) - self.ybase) - - self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion)) - - def XToData(self, x): - return Decimal(x) + self.xbase - - def YToData(self, y): - return Decimal(y) + self.ybase - -# Switch graph data (for one CPU) - -class SwitchGraphData(GraphData): - - def __init__(self, db, collection, cpu, xbase): - super(SwitchGraphData, self).__init__(collection, xbase) - - self.cpu = cpu - self.title = "CPU " + str(cpu) - self.SelectSwitches(db) - - def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time): - query = QSqlQuery(db) - QueryExec(query, "SELECT id, c_time" - " FROM comms" - " WHERE c_thread_id = " + str(thread_id) + - " AND exec_flag = " + self.collection.glb.dbref.TRUE + - " AND c_time >= " + str(start_time) + - " AND c_time <= " + str(end_time) + - " ORDER BY c_time, id") - while query.next(): - comm_id = query.value(0) - if comm_id == last_comm_id: - continue - time = query.value(1) - hregion = self.HRegion(db, thread_id, comm_id, time) - self.AddPoint(time, 1000, None, None, hregion) - - def SelectSwitches(self, db): - last_time = None - last_comm_id = None - last_thread_id = None - query = QSqlQuery(db) - QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags" - " FROM context_switches" - " WHERE machine_id = " + str(self.collection.machine_id) + - " AND cpu = " + str(self.cpu) + - " ORDER BY time, id") - while query.next(): - flags = int(query.value(5)) - if flags & 1: - # Schedule-out: detect and add exec's - if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3): - self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0)) - continue - # Schedule-in: add data point - if len(self.points) == 0: - start_time = self.collection.glb.StartTime(self.collection.machine_id) - hregion = self.HRegion(db, query.value(1), query.value(3), start_time) - self.AddPoint(start_time, 1000, None, None, hregion) - time = query.value(0) - comm_id = query.value(4) - thread_id = query.value(2) - hregion = self.HRegion(db, thread_id, comm_id, time) - self.AddPoint(time, 1000, None, None, hregion) - last_time = time - last_comm_id = comm_id - last_thread_id = thread_id - - def NewHRegion(self, db, key, thread_id, comm_id, time): - exec_comm_id = ExecComm(db, thread_id, time) - query = QSqlQuery(db) - QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id)) - if query.next(): - pid = query.value(0) - tid = query.value(1) - else: - pid = -1 - tid = -1 - query = QSqlQuery(db) - QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id)) - if query.next(): - comm = query.value(0) - else: - comm = "" - return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id) - - def HRegion(self, db, thread_id, comm_id, time): - key = str(thread_id) + ":" + str(comm_id) - hregion = self.collection.LookupHRegion(key) - if hregion is None: - hregion = self.NewHRegion(db, key, thread_id, comm_id, time) - self.collection.AddHRegion(key, hregion) - return hregion - -# Graph data collection (multiple related graphs) base class - -class GraphDataCollection(object): - - def __init__(self, glb): - self.glb = glb - self.data = [] - self.hregions = {} - self.xrangelo = None - self.xrangehi = None - self.yrangelo = None - self.yrangehi = None - self.dp = XY(0, 0) - - def AddGraphData(self, data): - self.data.append(data) - - def LookupHRegion(self, key): - if key in self.hregions: - return self.hregions[key] - return None - - def AddHRegion(self, key, hregion): - self.hregions[key] = hregion - -# Switch graph data collection (SwitchGraphData for each CPU) - -class SwitchGraphDataCollection(GraphDataCollection): - - def __init__(self, glb, db, machine_id): - super(SwitchGraphDataCollection, self).__init__(glb) - - self.machine_id = machine_id - self.cpus = self.SelectCPUs(db) - - self.xrangelo = glb.StartTime(machine_id) - self.xrangehi = glb.FinishTime(machine_id) - - self.yrangelo = Decimal(0) - self.yrangehi = Decimal(1000) - - for cpu in self.cpus: - self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo)) - - def SelectCPUs(self, db): - cpus = [] - query = QSqlQuery(db) - QueryExec(query, "SELECT DISTINCT cpu" - " FROM context_switches" - " WHERE machine_id = " + str(self.machine_id)) - while query.next(): - cpus.append(int(query.value(0))) - return sorted(cpus) - -# Switch graph data graphics item displays the graphed data - -class SwitchGraphDataGraphicsItem(QGraphicsItem): - - def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None): - super(SwitchGraphDataGraphicsItem, self).__init__(parent) - - self.data = data - self.graph_width = graph_width - self.graph_height = graph_height - self.attrs = attrs - self.event_handler = event_handler - self.setAcceptHoverEvents(True) - - def boundingRect(self): - return QRectF(0, 0, self.graph_width, self.graph_height) - - def PaintPoint(self, painter, last, x): - if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo): - if last.x < self.attrs.subrange.x.lo: - x0 = self.attrs.subrange.x.lo - else: - x0 = last.x - if x > self.attrs.subrange.x.hi: - x1 = self.attrs.subrange.x.hi - else: - x1 = x - 1 - x0 = self.attrs.XToPixel(x0) - x1 = self.attrs.XToPixel(x1) - - y0 = self.attrs.YToPixel(last.y) - - colour = self.attrs.region_attributes[last.hregion.key].colour - - width = x1 - x0 + 1 - if width < 2: - painter.setPen(colour) - painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height) - else: - painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour) - - def paint(self, painter, option, widget): - last = None - for point in self.data.points: - self.PaintPoint(painter, last, point.x) - if point.x > self.attrs.subrange.x.hi: - break; - last = point - self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1) - - def BinarySearchPoint(self, target): - lower_pos = 0 - higher_pos = len(self.data.points) - while True: - pos = int((lower_pos + higher_pos) / 2) - val = self.data.points[pos].x - if target >= val: - lower_pos = pos - else: - higher_pos = pos - if higher_pos <= lower_pos + 1: - return lower_pos - - def XPixelToData(self, x): - x = self.attrs.PixelToX(x) - if x < self.data.points[0].x: - x = 0 - pos = 0 - low = True - else: - pos = self.BinarySearchPoint(x) - low = False - return (low, pos, self.data.XToData(x)) - - def EventToData(self, event): - no_data = (None,) * 4 - if len(self.data.points) < 1: - return no_data - x = event.pos().x() - if x < 0: - return no_data - low0, pos0, time_from = self.XPixelToData(x) - low1, pos1, time_to = self.XPixelToData(x + 1) - hregions = set() - hregion_times = [] - if not low1: - for i in xrange(pos0, pos1 + 1): - hregion = self.data.points[i].hregion - hregions.add(hregion) - if i == pos0: - time = time_from - else: - time = self.data.XToData(self.data.points[i].x) - hregion_times.append((hregion, time)) - return (time_from, time_to, hregions, hregion_times) - - def hoverMoveEvent(self, event): - time_from, time_to, hregions, hregion_times = self.EventToData(event) - if time_from is not None: - self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions) - - def hoverLeaveEvent(self, event): - self.event_handler.NoPointEvent() - - def mousePressEvent(self, event): - if event.button() != Qt.RightButton: - super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event) - return - time_from, time_to, hregions, hregion_times = self.EventToData(event) - if hregion_times: - self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos()) - -# X-axis graphics item - -class XAxisGraphicsItem(QGraphicsItem): - - def __init__(self, width, parent=None): - super(XAxisGraphicsItem, self).__init__(parent) - - self.width = width - self.max_mark_sz = 4 - self.height = self.max_mark_sz + 1 - - def boundingRect(self): - return QRectF(0, 0, self.width, self.height) - - def Step(self): - attrs = self.parentItem().attrs - subrange = attrs.subrange.x - t = subrange.hi - subrange.lo - s = (3.0 * t) / self.width - n = 1.0 - while s > n: - n = n * 10.0 - return n - - def PaintMarks(self, painter, at_y, lo, hi, step, i): - attrs = self.parentItem().attrs - x = lo - while x <= hi: - xp = attrs.XToPixel(x) - if i % 10: - if i % 5: - sz = 1 - else: - sz = 2 - else: - sz = self.max_mark_sz - i = 0 - painter.drawLine(xp, at_y, xp, at_y + sz) - x += step - i += 1 - - def paint(self, painter, option, widget): - # Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1 - painter.drawLine(0, 0, self.width - 1, 0) - n = self.Step() - attrs = self.parentItem().attrs - subrange = attrs.subrange.x - if subrange.lo: - x_offset = n - (subrange.lo % n) - else: - x_offset = 0.0 - x = subrange.lo + x_offset - i = (x / n) % 10 - self.PaintMarks(painter, 0, x, subrange.hi, n, i) - - def ScaleDimensions(self): - n = self.Step() - attrs = self.parentItem().attrs - lo = attrs.subrange.x.lo - hi = (n * 10.0) + lo - width = attrs.XToPixel(hi) - if width > 500: - width = 0 - return (n, lo, hi, width) - - def PaintScale(self, painter, at_x, at_y): - n, lo, hi, width = self.ScaleDimensions() - if not width: - return - painter.drawLine(at_x, at_y, at_x + width, at_y) - self.PaintMarks(painter, at_y, lo, hi, n, 0) - - def ScaleWidth(self): - n, lo, hi, width = self.ScaleDimensions() - return width - - def ScaleHeight(self): - return self.height - - def ScaleUnit(self): - return self.Step() * 10 - -# Scale graphics item base class - -class ScaleGraphicsItem(QGraphicsItem): - - def __init__(self, axis, parent=None): - super(ScaleGraphicsItem, self).__init__(parent) - self.axis = axis - - def boundingRect(self): - scale_width = self.axis.ScaleWidth() - if not scale_width: - return QRectF() - return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight()) - - def paint(self, painter, option, widget): - scale_width = self.axis.ScaleWidth() - if not scale_width: - return - self.axis.PaintScale(painter, 0, 5) - x = scale_width + 4 - painter.drawText(QPointF(x, 10), self.Text()) - - def Unit(self): - return self.axis.ScaleUnit() - - def Text(self): - return "" - -# Switch graph scale graphics item - -class SwitchScaleGraphicsItem(ScaleGraphicsItem): - - def __init__(self, axis, parent=None): - super(SwitchScaleGraphicsItem, self).__init__(axis, parent) - - def Text(self): - unit = self.Unit() - if unit >= 1000000000: - unit = int(unit / 1000000000) - us = "s" - elif unit >= 1000000: - unit = int(unit / 1000000) - us = "ms" - elif unit >= 1000: - unit = int(unit / 1000) - us = "us" - else: - unit = int(unit) - us = "ns" - return " = " + str(unit) + " " + us - -# Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data - -class SwitchGraphGraphicsItem(QGraphicsItem): - - def __init__(self, collection, data, attrs, event_handler, first, parent=None): - super(SwitchGraphGraphicsItem, self).__init__(parent) - self.collection = collection - self.data = data - self.attrs = attrs - self.event_handler = event_handler - - margin = 20 - title_width = 50 - - self.title_graphics = QGraphicsSimpleTextItem(data.title, self) - - self.title_graphics.setPos(margin, margin) - graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1 - graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1 - - self.graph_origin_x = margin + title_width + margin - self.graph_origin_y = graph_height + margin - - x_axis_size = 1 - y_axis_size = 1 - self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self) - - self.x_axis = XAxisGraphicsItem(graph_width, self) - self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1) - - if first: - self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self) - self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10) - - self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height) - - self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self) - self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1) - - self.width = self.graph_origin_x + graph_width + margin - self.height = self.graph_origin_y + margin - - self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self) - self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height) - - if parent and 'EnableRubberBand' in dir(parent): - parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self) - - def boundingRect(self): - return QRectF(0, 0, self.width, self.height) - - def paint(self, painter, option, widget): - pass - - def RBXToPixel(self, x): - return self.attrs.PixelToX(x - self.graph_origin_x) - - def RBXRangeToPixel(self, x0, x1): - return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1)) - - def RBPixelToTime(self, x): - if x < self.data.points[0].x: - return self.data.XToData(0) - return self.data.XToData(x) - - def RBEventTimes(self, x0, x1): - x0, x1 = self.RBXRangeToPixel(x0, x1) - time_from = self.RBPixelToTime(x0) - time_to = self.RBPixelToTime(x1) - return (time_from, time_to) - - def RBEvent(self, x0, x1): - time_from, time_to = self.RBEventTimes(x0, x1) - self.event_handler.RangeEvent(time_from, time_to) - - def RBMoveEvent(self, x0, x1): - if x1 < x0: - x0, x1 = x1, x0 - self.RBEvent(x0, x1) - - def RBReleaseEvent(self, x0, x1, selection_state): - if x1 < x0: - x0, x1 = x1, x0 - x0, x1 = self.RBXRangeToPixel(x0, x1) - self.event_handler.SelectEvent(x0, x1, selection_state) - -# Graphics item to draw a vertical bracket (used to highlight "forward" sub-range) - -class VerticalBracketGraphicsItem(QGraphicsItem): - - def __init__(self, parent=None): - super(VerticalBracketGraphicsItem, self).__init__(parent) - - self.width = 0 - self.height = 0 - self.hide() - - def SetSize(self, width, height): - self.width = width + 1 - self.height = height + 1 - - def boundingRect(self): - return QRectF(0, 0, self.width, self.height) - - def paint(self, painter, option, widget): - colour = QColor(255, 255, 0, 32) - painter.fillRect(0, 0, self.width, self.height, colour) - x1 = self.width - 1 - y1 = self.height - 1 - painter.drawLine(0, 0, x1, 0) - painter.drawLine(0, 0, 0, 3) - painter.drawLine(x1, 0, x1, 3) - painter.drawLine(0, y1, x1, y1) - painter.drawLine(0, y1, 0, y1 - 3) - painter.drawLine(x1, y1, x1, y1 - 3) - -# Graphics item to contain graphs arranged vertically - -class VertcalGraphSetGraphicsItem(QGraphicsItem): - - def __init__(self, collection, attrs, event_handler, child_class, parent=None): - super(VertcalGraphSetGraphicsItem, self).__init__(parent) - - self.collection = collection - - self.top = 10 - - self.width = 0 - self.height = self.top - - self.rubber_band = None - self.rb_enabled = False - - first = True - for data in collection.data: - child = child_class(collection, data, attrs, event_handler, first, self) - child.setPos(0, self.height + 1) - rect = child.boundingRect() - if rect.right() > self.width: - self.width = rect.right() - self.height = self.height + rect.bottom() + 1 - first = False - - self.bracket = VerticalBracketGraphicsItem(self) - - def EnableRubberBand(self, xlo, xhi, rb_event_handler): - if self.rb_enabled: - return - self.rb_enabled = True - self.rb_in_view = False - self.setAcceptedMouseButtons(Qt.LeftButton) - self.rb_xlo = xlo - self.rb_xhi = xhi - self.rb_event_handler = rb_event_handler - self.mousePressEvent = self.MousePressEvent - self.mouseMoveEvent = self.MouseMoveEvent - self.mouseReleaseEvent = self.MouseReleaseEvent - - def boundingRect(self): - return QRectF(0, 0, self.width, self.height) - - def paint(self, painter, option, widget): - pass - - def RubberBandParent(self): - scene = self.scene() - view = scene.views()[0] - viewport = view.viewport() - return viewport - - def RubberBandSetGeometry(self, rect): - scene_rectf = self.mapRectToScene(QRectF(rect)) - scene = self.scene() - view = scene.views()[0] - poly = view.mapFromScene(scene_rectf) - self.rubber_band.setGeometry(poly.boundingRect()) - - def SetSelection(self, selection_state): - if self.rubber_band: - if selection_state: - self.RubberBandSetGeometry(selection_state) - self.rubber_band.show() - else: - self.rubber_band.hide() - - def SetBracket(self, rect): - if rect: - x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height() - self.bracket.setPos(x, y) - self.bracket.SetSize(width, height) - self.bracket.show() - else: - self.bracket.hide() - - def RubberBandX(self, event): - x = event.pos().toPoint().x() - if x < self.rb_xlo: - x = self.rb_xlo - elif x > self.rb_xhi: - x = self.rb_xhi - else: - self.rb_in_view = True - return x - - def RubberBandRect(self, x): - if self.rb_origin.x() <= x: - width = x - self.rb_origin.x() - rect = QRect(self.rb_origin, QSize(width, self.height)) - else: - width = self.rb_origin.x() - x - top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y()) - rect = QRect(top_left, QSize(width, self.height)) - return rect - - def MousePressEvent(self, event): - self.rb_in_view = False - x = self.RubberBandX(event) - self.rb_origin = QPoint(x, self.top) - if self.rubber_band is None: - self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent()) - self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height))) - if self.rb_in_view: - self.rubber_band.show() - self.rb_event_handler.RBMoveEvent(x, x) - else: - self.rubber_band.hide() - - def MouseMoveEvent(self, event): - x = self.RubberBandX(event) - rect = self.RubberBandRect(x) - self.RubberBandSetGeometry(rect) - if self.rb_in_view: - self.rubber_band.show() - self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x) - - def MouseReleaseEvent(self, event): - x = self.RubberBandX(event) - if self.rb_in_view: - selection_state = self.RubberBandRect(x) - else: - selection_state = None - self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state) - -# Switch graph legend data model - -class SwitchGraphLegendModel(QAbstractTableModel): - - def __init__(self, collection, region_attributes, parent=None): - super(SwitchGraphLegendModel, self).__init__(parent) - - self.region_attributes = region_attributes - - self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal) - self.child_count = len(self.child_items) - - self.highlight_set = set() - - self.column_headers = ("pid", "tid", "comm") - - def rowCount(self, parent): - return self.child_count - - def headerData(self, section, orientation, role): - if role != Qt.DisplayRole: - return None - if orientation != Qt.Horizontal: - return None - return self.columnHeader(section) - - def index(self, row, column, parent): - return self.createIndex(row, column, self.child_items[row]) - - def columnCount(self, parent=None): - return len(self.column_headers) - - def columnHeader(self, column): - return self.column_headers[column] - - def data(self, index, role): - if role == Qt.BackgroundRole: - child = self.child_items[index.row()] - if child in self.highlight_set: - return self.region_attributes[child.key].colour - return None - if role == Qt.ForegroundRole: - child = self.child_items[index.row()] - if child in self.highlight_set: - return QColor(255, 255, 255) - return self.region_attributes[child.key].colour - if role != Qt.DisplayRole: - return None - hregion = self.child_items[index.row()] - col = index.column() - if col == 0: - return hregion.pid - if col == 1: - return hregion.tid - if col == 2: - return hregion.comm - return None - - def SetHighlight(self, row, set_highlight): - child = self.child_items[row] - top_left = self.createIndex(row, 0, child) - bottom_right = self.createIndex(row, len(self.column_headers) - 1, child) - self.dataChanged.emit(top_left, bottom_right) - - def Highlight(self, highlight_set): - for row in xrange(self.child_count): - child = self.child_items[row] - if child in self.highlight_set: - if child not in highlight_set: - self.SetHighlight(row, False) - elif child in highlight_set: - self.SetHighlight(row, True) - self.highlight_set = highlight_set - -# Switch graph legend is a table - -class SwitchGraphLegend(QWidget): - - def __init__(self, collection, region_attributes, parent=None): - super(SwitchGraphLegend, self).__init__(parent) - - self.data_model = SwitchGraphLegendModel(collection, region_attributes) - - self.model = QSortFilterProxyModel() - self.model.setSourceModel(self.data_model) - - self.view = QTableView() - self.view.setModel(self.model) - self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.view.verticalHeader().setVisible(False) - self.view.sortByColumn(-1, Qt.AscendingOrder) - self.view.setSortingEnabled(True) - self.view.resizeColumnsToContents() - self.view.resizeRowsToContents() - - self.vbox = VBoxLayout(self.view) - self.setLayout(self.vbox) - - sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2 - sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width() - self.saved_size = sz1 - - def resizeEvent(self, event): - self.saved_size = self.size().width() - super(SwitchGraphLegend, self).resizeEvent(event) - - def Highlight(self, highlight_set): - self.data_model.Highlight(highlight_set) - self.update() - - def changeEvent(self, event): - if event.type() == QEvent.FontChange: - self.view.resizeRowsToContents() - self.view.resizeColumnsToContents() - # Need to resize rows again after column resize - self.view.resizeRowsToContents() - super(SwitchGraphLegend, self).changeEvent(event) - -# Random colour generation - -def RGBColourTooLight(r, g, b): - if g > 230: - return True - if g <= 160: - return False - if r <= 180 and g <= 180: - return False - if r < 60: - return False - return True - -def GenerateColours(x): - cs = [0] - for i in xrange(1, x): - cs.append(int((255.0 / i) + 0.5)) - colours = [] - for r in cs: - for g in cs: - for b in cs: - # Exclude black and colours that look too light against a white background - if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b): - continue - colours.append(QColor(r, g, b)) - return colours - -def GenerateNColours(n): - for x in xrange(2, n + 2): - colours = GenerateColours(x) - if len(colours) >= n: - return colours - return [] - -def GenerateNRandomColours(n, seed): - colours = GenerateNColours(n) - random.seed(seed) - random.shuffle(colours) - return colours - -# Graph attributes, in particular the scale and subrange that change when zooming - -class GraphAttributes(): - - def __init__(self, scale, subrange, region_attributes, dp): - self.scale = scale - self.subrange = subrange - self.region_attributes = region_attributes - # Rounding avoids errors due to finite floating point precision - self.dp = dp # data decimal places - self.Update() - - def XToPixel(self, x): - return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x)) - - def YToPixel(self, y): - return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y)) - - def PixelToXRounded(self, px): - return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo - - def PixelToYRounded(self, py): - return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo - - def PixelToX(self, px): - x = self.PixelToXRounded(px) - if self.pdp.x == 0: - rt = self.XToPixel(x) - if rt > px: - return x - 1 - return x - - def PixelToY(self, py): - y = self.PixelToYRounded(py) - if self.pdp.y == 0: - rt = self.YToPixel(y) - if rt > py: - return y - 1 - return y - - def ToPDP(self, dp, scale): - # Calculate pixel decimal places: - # (10 ** dp) is the minimum delta in the data - # scale it to get the minimum delta in pixels - # log10 gives the number of decimals places negatively - # subtrace 1 to divide by 10 - # round to the lower negative number - # change the sign to get the number of decimals positively - x = math.log10((10 ** dp) * scale) - if x < 0: - x -= 1 - x = -int(math.floor(x) - 0.1) - else: - x = 0 - return x - - def Update(self): - x = self.ToPDP(self.dp.x, self.scale.x) - y = self.ToPDP(self.dp.y, self.scale.y) - self.pdp = XY(x, y) # pixel decimal places - -# Switch graph splitter which divides the CPU graphs from the legend - -class SwitchGraphSplitter(QSplitter): - - def __init__(self, parent=None): - super(SwitchGraphSplitter, self).__init__(parent) - - self.first_time = False - - def resizeEvent(self, ev): - if self.first_time: - self.first_time = False - sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2 - sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width() - sz0 = self.size().width() - self.handleWidth() - sz1 - self.setSizes([sz0, sz1]) - elif not(self.widget(1).saved_size is None): - sz1 = self.widget(1).saved_size - sz0 = self.size().width() - self.handleWidth() - sz1 - self.setSizes([sz0, sz1]) - super(SwitchGraphSplitter, self).resizeEvent(ev) - -# Graph widget base class - -class GraphWidget(QWidget): - - graph_title_changed = Signal(object) - - def __init__(self, parent=None): - super(GraphWidget, self).__init__(parent) - - def GraphTitleChanged(self, title): - self.graph_title_changed.emit(title) - - def Title(self): - return "" - -# Display time in s, ms, us or ns - -def ToTimeStr(val): - val = Decimal(val) - if val >= 1000000000: - return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001"))) - if val >= 1000000: - return "{} ms".format((val / 1000000).quantize(Decimal("0.000001"))) - if val >= 1000: - return "{} us".format((val / 1000).quantize(Decimal("0.001"))) - return "{} ns".format(val.quantize(Decimal("1"))) - -# Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons - -class SwitchGraphWidget(GraphWidget): - - def __init__(self, glb, collection, parent=None): - super(SwitchGraphWidget, self).__init__(parent) - - self.glb = glb - self.collection = collection - - self.back_state = [] - self.forward_state = [] - self.selection_state = (None, None) - self.fwd_rect = None - self.start_time = self.glb.StartTime(collection.machine_id) - - i = 0 - hregions = collection.hregions.values() - colours = GenerateNRandomColours(len(hregions), 1013) - region_attributes = {} - for hregion in hregions: - if hregion.pid == 0 and hregion.tid == 0: - region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0)) - else: - region_attributes[hregion.key] = GraphRegionAttribute(colours[i]) - i = i + 1 - - # Default to entire range - xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0) - ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0) - subrange = XY(xsubrange, ysubrange) - - scale = self.GetScaleForRange(subrange) - - self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp) - - self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem) - - self.scene = QGraphicsScene() - self.scene.addItem(self.item) - - self.view = QGraphicsView(self.scene) - self.view.centerOn(0, 0) - self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop) - - self.legend = SwitchGraphLegend(collection, region_attributes) - - self.splitter = SwitchGraphSplitter() - self.splitter.addWidget(self.view) - self.splitter.addWidget(self.legend) - - self.point_label = QLabel("") - self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) - - self.back_button = QToolButton() - self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft)) - self.back_button.setDisabled(True) - self.back_button.released.connect(lambda: self.Back()) - - self.forward_button = QToolButton() - self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight)) - self.forward_button.setDisabled(True) - self.forward_button.released.connect(lambda: self.Forward()) - - self.zoom_button = QToolButton() - self.zoom_button.setText("Zoom") - self.zoom_button.setDisabled(True) - self.zoom_button.released.connect(lambda: self.Zoom()) - - self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label) - - self.vbox = VBoxLayout(self.splitter, self.hbox) - - self.setLayout(self.vbox) - - def GetScaleForRangeX(self, xsubrange): - # Default graph 1000 pixels wide - dflt = 1000.0 - r = xsubrange.hi - xsubrange.lo - return dflt / r - - def GetScaleForRangeY(self, ysubrange): - # Default graph 50 pixels high - dflt = 50.0 - r = ysubrange.hi - ysubrange.lo - return dflt / r - - def GetScaleForRange(self, subrange): - # Default graph 1000 pixels wide, 50 pixels high - xscale = self.GetScaleForRangeX(subrange.x) - yscale = self.GetScaleForRangeY(subrange.y) - return XY(xscale, yscale) - - def PointEvent(self, cpu, time_from, time_to, hregions): - text = "CPU: " + str(cpu) - time_from = time_from.quantize(Decimal(1)) - rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id) - text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")" - self.point_label.setText(text) - self.legend.Highlight(hregions) - - def RightClickEvent(self, cpu, hregion_times, pos): - if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"): - return - menu = QMenu(self.view) - for hregion, time in hregion_times: - thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time) - menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time) - menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view)) - menu.exec_(pos) - - def RightClickSelect(self, args): - CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args) - - def NoPointEvent(self): - self.point_label.setText("") - self.legend.Highlight({}) - - def RangeEvent(self, time_from, time_to): - time_from = time_from.quantize(Decimal(1)) - time_to = time_to.quantize(Decimal(1)) - if time_to <= time_from: - self.point_label.setText("") - return - rel_time_from = time_from - self.start_time - rel_time_to = time_to - self.start_time - text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")" - text = text + " duration: " + ToTimeStr(time_to - time_from) - self.point_label.setText(text) - - def BackState(self): - return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect) - - def PushBackState(self): - state = copy.deepcopy(self.BackState()) - self.back_state.append(state) - self.back_button.setEnabled(True) - - def PopBackState(self): - self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop() - self.attrs.Update() - if not self.back_state: - self.back_button.setDisabled(True) - - def PushForwardState(self): - state = copy.deepcopy(self.BackState()) - self.forward_state.append(state) - self.forward_button.setEnabled(True) - - def PopForwardState(self): - self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop() - self.attrs.Update() - if not self.forward_state: - self.forward_button.setDisabled(True) - - def Title(self): - time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo) - time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi) - rel_time_from = time_from - self.start_time - rel_time_to = time_to - self.start_time - title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to) - title = title + " (" + ToTimeStr(time_to - time_from) + ")" - return title - - def Update(self): - selected_subrange, selection_state = self.selection_state - self.item.SetSelection(selection_state) - self.item.SetBracket(self.fwd_rect) - self.zoom_button.setDisabled(selected_subrange is None) - self.GraphTitleChanged(self.Title()) - self.item.update(self.item.boundingRect()) - - def Back(self): - if not self.back_state: - return - self.PushForwardState() - self.PopBackState() - self.Update() - - def Forward(self): - if not self.forward_state: - return - self.PushBackState() - self.PopForwardState() - self.Update() - - def SelectEvent(self, x0, x1, selection_state): - if selection_state is None: - selected_subrange = None - else: - if x1 - x0 < 1.0: - x1 += 1.0 - selected_subrange = Subrange(x0, x1) - self.selection_state = (selected_subrange, selection_state) - self.zoom_button.setDisabled(selected_subrange is None) - - def Zoom(self): - selected_subrange, selection_state = self.selection_state - if selected_subrange is None: - return - self.fwd_rect = selection_state - self.item.SetSelection(None) - self.PushBackState() - self.attrs.subrange.x = selected_subrange - self.forward_state = [] - self.forward_button.setDisabled(True) - self.selection_state = (None, None) - self.fwd_rect = None - self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x) - self.attrs.Update() - self.Update() - -# Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting - -class SlowInitClass(): - - def __init__(self, glb, title, init_fn): - self.init_fn = init_fn - self.done = False - self.result = None - - self.msg_box = QMessageBox(glb.mainwindow) - self.msg_box.setText("Initializing " + title + ". Please wait.") - self.msg_box.setWindowTitle("Initializing " + title) - self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation)) - - self.init_thread = Thread(self.ThreadFn, glb) - self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection) - - self.init_thread.start() - - def Done(self): - self.msg_box.done(0) - - def ThreadFn(self, glb): - conn_name = "SlowInitClass" + str(os.getpid()) - db, dbname = glb.dbref.Open(conn_name) - self.result = self.init_fn(db) - self.done = True - return (True, 0) - - def Result(self): - while not self.done: - self.msg_box.exec_() - self.init_thread.wait() - return self.result - -def SlowInit(glb, title, init_fn): - init = SlowInitClass(glb, title, init_fn) - return init.Result() - -# Time chart by CPU window - -class TimeChartByCPUWindow(QMdiSubWindow): - - def __init__(self, glb, parent=None): - super(TimeChartByCPUWindow, self).__init__(parent) - - self.glb = glb - self.machine_id = glb.HostMachineId() - self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id) - - collection = LookupModel(self.collection_name) - if collection is None: - collection = SlowInit(glb, "Time Chart", self.Init) - - self.widget = SwitchGraphWidget(glb, collection, self) - self.view = self.widget - - self.base_title = "Time Chart by CPU" - self.setWindowTitle(self.base_title + self.widget.Title()) - self.widget.graph_title_changed.connect(self.GraphTitleChanged) - - self.setWidget(self.widget) - - AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle()) - - def Init(self, db): - return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id)) - - def GraphTitleChanged(self, title): - self.setWindowTitle(self.base_title + " : " + title) - -# Child data item finder - -class ChildDataItemFinder(): - - def __init__(self, root): - self.root = root - self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5 - self.rows = [] - self.pos = 0 - - def FindSelect(self): - self.rows = [] - if self.pattern: - pattern = re.compile(self.value) - for child in self.root.child_items: - for column_data in child.data: - if re.search(pattern, str(column_data)) is not None: - self.rows.append(child.row) - break - else: - for child in self.root.child_items: - for column_data in child.data: - if self.value in str(column_data): - self.rows.append(child.row) - break - - def FindValue(self): - self.pos = 0 - if self.last_value != self.value or self.pattern != self.last_pattern: - self.FindSelect() - if not len(self.rows): - return -1 - return self.rows[self.pos] - - def FindThread(self): - if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern: - row = self.FindValue() - elif len(self.rows): - if self.direction > 0: - self.pos += 1 - if self.pos >= len(self.rows): - self.pos = 0 - else: - self.pos -= 1 - if self.pos < 0: - self.pos = len(self.rows) - 1 - row = self.rows[self.pos] - else: - row = -1 - return (True, row) - - def Find(self, value, direction, pattern, context, callback): - self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern) - # Use a thread so the UI is not blocked - thread = Thread(self.FindThread) - thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection) - thread.start() - - def FindDone(self, thread, callback, row): - callback(row) - -# Number of database records to fetch in one go - -glb_chunk_sz = 10000 - -# Background process for SQL data fetcher - -class SQLFetcherProcess(): - - def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep): - # Need a unique connection name - conn_name = "SQLFetcher" + str(os.getpid()) - self.db, dbname = dbref.Open(conn_name) - self.sql = sql - self.buffer = buffer - self.head = head - self.tail = tail - self.fetch_count = fetch_count - self.fetching_done = fetching_done - self.process_target = process_target - self.wait_event = wait_event - self.fetched_event = fetched_event - self.prep = prep - self.query = QSqlQuery(self.db) - self.query_limit = 0 if "$$last_id$$" in sql else 2 - self.last_id = -1 - self.fetched = 0 - self.more = True - self.local_head = self.head.value - self.local_tail = self.tail.value - - def Select(self): - if self.query_limit: - if self.query_limit == 1: - return - self.query_limit -= 1 - stmt = self.sql.replace("$$last_id$$", str(self.last_id)) - QueryExec(self.query, stmt) - - def Next(self): - if not self.query.next(): - self.Select() - if not self.query.next(): - return None - self.last_id = self.query.value(0) - return self.prep(self.query) - - def WaitForTarget(self): - while True: - self.wait_event.clear() - target = self.process_target.value - if target > self.fetched or target < 0: - break - self.wait_event.wait() - return target - - def HasSpace(self, sz): - if self.local_tail <= self.local_head: - space = len(self.buffer) - self.local_head - if space > sz: - return True - if space >= glb_nsz: - # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer - nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL) - self.buffer[self.local_head : self.local_head + len(nd)] = nd - self.local_head = 0 - if self.local_tail - self.local_head > sz: - return True - return False - - def WaitForSpace(self, sz): - if self.HasSpace(sz): - return - while True: - self.wait_event.clear() - self.local_tail = self.tail.value - if self.HasSpace(sz): - return - self.wait_event.wait() - - def AddToBuffer(self, obj): - d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL) - n = len(d) - nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL) - sz = n + glb_nsz - self.WaitForSpace(sz) - pos = self.local_head - self.buffer[pos : pos + len(nd)] = nd - self.buffer[pos + glb_nsz : pos + sz] = d - self.local_head += sz - - def FetchBatch(self, batch_size): - fetched = 0 - while batch_size > fetched: - obj = self.Next() - if obj is None: - self.more = False - break - self.AddToBuffer(obj) - fetched += 1 - if fetched: - self.fetched += fetched - with self.fetch_count.get_lock(): - self.fetch_count.value += fetched - self.head.value = self.local_head - self.fetched_event.set() - - def Run(self): - while self.more: - target = self.WaitForTarget() - if target < 0: - break - batch_size = min(glb_chunk_sz, target - self.fetched) - self.FetchBatch(batch_size) - self.fetching_done.value = True - self.fetched_event.set() - -def SQLFetcherFn(*x): - process = SQLFetcherProcess(*x) - process.Run() - -# SQL data fetcher - -class SQLFetcher(QObject): - - done = Signal(object) - - def __init__(self, glb, sql, prep, process_data, parent=None): - super(SQLFetcher, self).__init__(parent) - self.process_data = process_data - self.more = True - self.target = 0 - self.last_target = 0 - self.fetched = 0 - self.buffer_size = 16 * 1024 * 1024 - self.buffer = Array(c_char, self.buffer_size, lock=False) - self.head = Value(c_longlong) - self.tail = Value(c_longlong) - self.local_tail = 0 - self.fetch_count = Value(c_longlong) - self.fetching_done = Value(c_bool) - self.last_count = 0 - self.process_target = Value(c_longlong) - self.wait_event = Event() - self.fetched_event = Event() - glb.AddInstanceToShutdownOnExit(self) - self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep)) - self.process.start() - self.thread = Thread(self.Thread) - self.thread.done.connect(self.ProcessData, Qt.QueuedConnection) - self.thread.start() - - def Shutdown(self): - # Tell the thread and process to exit - self.process_target.value = -1 - self.wait_event.set() - self.more = False - self.fetching_done.value = True - self.fetched_event.set() - - def Thread(self): - if not self.more: - return True, 0 - while True: - self.fetched_event.clear() - fetch_count = self.fetch_count.value - if fetch_count != self.last_count: - break - if self.fetching_done.value: - self.more = False - return True, 0 - self.fetched_event.wait() - count = fetch_count - self.last_count - self.last_count = fetch_count - self.fetched += count - return False, count - - def Fetch(self, nr): - if not self.more: - # -1 inidcates there are no more - return -1 - result = self.fetched - extra = result + nr - self.target - if extra > 0: - self.target += extra - # process_target < 0 indicates shutting down - if self.process_target.value >= 0: - self.process_target.value = self.target - self.wait_event.set() - return result - - def RemoveFromBuffer(self): - pos = self.local_tail - if len(self.buffer) - pos < glb_nsz: - pos = 0 - n = pickle.loads(self.buffer[pos : pos + glb_nsz]) - if n == 0: - pos = 0 - n = pickle.loads(self.buffer[0 : glb_nsz]) - pos += glb_nsz - obj = pickle.loads(self.buffer[pos : pos + n]) - self.local_tail = pos + n - return obj - - def ProcessData(self, count): - for i in xrange(count): - obj = self.RemoveFromBuffer() - self.process_data(obj) - self.tail.value = self.local_tail - self.wait_event.set() - self.done.emit(count) - -# Fetch more records bar - -class FetchMoreRecordsBar(): - - def __init__(self, model, parent): - self.model = model - - self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:") - self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - - self.fetch_count = QSpinBox() - self.fetch_count.setRange(1, 1000000) - self.fetch_count.setValue(10) - self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - - self.fetch = QPushButton("Go!") - self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.fetch.released.connect(self.FetchMoreRecords) - - self.progress = QProgressBar() - self.progress.setRange(0, 100) - self.progress.hide() - - self.done_label = QLabel("All records fetched") - self.done_label.hide() - - self.spacer = QLabel("") - - self.close_button = QToolButton() - self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) - self.close_button.released.connect(self.Deactivate) - - self.hbox = QHBoxLayout() - self.hbox.setContentsMargins(0, 0, 0, 0) - - self.hbox.addWidget(self.label) - self.hbox.addWidget(self.fetch_count) - self.hbox.addWidget(self.fetch) - self.hbox.addWidget(self.spacer) - self.hbox.addWidget(self.progress) - self.hbox.addWidget(self.done_label) - self.hbox.addWidget(self.close_button) - - self.bar = QWidget() - self.bar.setLayout(self.hbox) - self.bar.show() - - self.in_progress = False - self.model.progress.connect(self.Progress) - - self.done = False - - if not model.HasMoreRecords(): - self.Done() - - def Widget(self): - return self.bar - - def Activate(self): - self.bar.show() - self.fetch.setFocus() - - def Deactivate(self): - self.bar.hide() - - def Enable(self, enable): - self.fetch.setEnabled(enable) - self.fetch_count.setEnabled(enable) - - def Busy(self): - self.Enable(False) - self.fetch.hide() - self.spacer.hide() - self.progress.show() - - def Idle(self): - self.in_progress = False - self.Enable(True) - self.progress.hide() - self.fetch.show() - self.spacer.show() - - def Target(self): - return self.fetch_count.value() * glb_chunk_sz - - def Done(self): - self.done = True - self.Idle() - self.label.hide() - self.fetch_count.hide() - self.fetch.hide() - self.spacer.hide() - self.done_label.show() - - def Progress(self, count): - if self.in_progress: - if count: - percent = ((count - self.start) * 100) / self.Target() - if percent >= 100: - self.Idle() - else: - self.progress.setValue(percent) - if not count: - # Count value of zero means no more records - self.Done() - - def FetchMoreRecords(self): - if self.done: - return - self.progress.setValue(0) - self.Busy() - self.in_progress = True - self.start = self.model.FetchMoreRecords(self.Target()) - -# Brance data model level two item - -class BranchLevelTwoItem(): - - def __init__(self, row, col, text, parent_item): - self.row = row - self.parent_item = parent_item - self.data = [""] * (col + 1) - self.data[col] = text - self.level = 2 - - def getParentItem(self): - return self.parent_item - - def getRow(self): - return self.row - - def childCount(self): - return 0 - - def hasChildren(self): - return False - - def getData(self, column): - return self.data[column] - -# Brance data model level one item - -class BranchLevelOneItem(): - - def __init__(self, glb, row, data, parent_item): - self.glb = glb - self.row = row - self.parent_item = parent_item - self.child_count = 0 - self.child_items = [] - self.data = data[1:] - self.dbid = data[0] - self.level = 1 - self.query_done = False - self.br_col = len(self.data) - 1 - - def getChildItem(self, row): - return self.child_items[row] - - def getParentItem(self): - return self.parent_item - - def getRow(self): - return self.row - - def Select(self): - self.query_done = True - - if not self.glb.have_disassembler: - return - - query = QSqlQuery(self.glb.db) - - QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip" - " FROM samples" - " INNER JOIN dsos ON samples.to_dso_id = dsos.id" - " INNER JOIN symbols ON samples.to_symbol_id = symbols.id" - " WHERE samples.id = " + str(self.dbid)) - if not query.next(): - return - cpu = query.value(0) - dso = query.value(1) - sym = query.value(2) - if dso == 0 or sym == 0: - return - off = query.value(3) - short_name = query.value(4) - long_name = query.value(5) - build_id = query.value(6) - sym_start = query.value(7) - ip = query.value(8) - - QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start" - " FROM samples" - " INNER JOIN symbols ON samples.symbol_id = symbols.id" - " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) + - " ORDER BY samples.id" - " LIMIT 1") - if not query.next(): - return - if query.value(0) != dso: - # Cannot disassemble from one dso to another - return - bsym = query.value(1) - boff = query.value(2) - bsym_start = query.value(3) - if bsym == 0: - return - tot = bsym_start + boff + 1 - sym_start - off - if tot <= 0 or tot > 16384: - return - - inst = self.glb.disassembler.Instruction() - f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id) - if not f: - return - mode = 0 if Is64Bit(f) else 1 - self.glb.disassembler.SetMode(inst, mode) - - buf_sz = tot + 16 - buf = create_string_buffer(tot + 16) - f.seek(sym_start + off) - buf.value = f.read(buf_sz) - buf_ptr = addressof(buf) - i = 0 - while tot > 0: - cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip) - if cnt: - byte_str = tohex(ip).rjust(16) - for k in xrange(cnt): - byte_str += " %02x" % ord(buf[i]) - i += 1 - while k < 15: - byte_str += " " - k += 1 - self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self)) - self.child_count += 1 - else: - return - buf_ptr += cnt - tot -= cnt - buf_sz -= cnt - ip += cnt - - def childCount(self): - if not self.query_done: - self.Select() - if not self.child_count: - return -1 - return self.child_count - - def hasChildren(self): - if not self.query_done: - return True - return self.child_count > 0 - - def getData(self, column): - return self.data[column] - -# Brance data model root item - -class BranchRootItem(): - - def __init__(self): - self.child_count = 0 - self.child_items = [] - self.level = 0 - - def getChildItem(self, row): - return self.child_items[row] - - def getParentItem(self): - return None - - def getRow(self): - return 0 - - def childCount(self): - return self.child_count - - def hasChildren(self): - return self.child_count > 0 - - def getData(self, column): - return "" - -# Calculate instructions per cycle - -def CalcIPC(cyc_cnt, insn_cnt): - if cyc_cnt and insn_cnt: - ipc = Decimal(float(insn_cnt) / cyc_cnt) - ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP)) - else: - ipc = "0" - return ipc - -# Branch data preparation - -def BranchDataPrepBr(query, data): - data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) + - " (" + dsoname(query.value(11)) + ")" + " -> " + - tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) + - " (" + dsoname(query.value(15)) + ")") - -def BranchDataPrepIPC(query, data): - insn_cnt = query.value(16) - cyc_cnt = query.value(17) - ipc = CalcIPC(cyc_cnt, insn_cnt) - data.append(insn_cnt) - data.append(cyc_cnt) - data.append(ipc) - -def BranchDataPrep(query): - data = [] - for i in xrange(0, 8): - data.append(query.value(i)) - BranchDataPrepBr(query, data) - return data - -def BranchDataPrepWA(query): - data = [] - data.append(query.value(0)) - # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string - data.append("{:>19}".format(query.value(1))) - for i in xrange(2, 8): - data.append(query.value(i)) - BranchDataPrepBr(query, data) - return data - -def BranchDataWithIPCPrep(query): - data = [] - for i in xrange(0, 8): - data.append(query.value(i)) - BranchDataPrepIPC(query, data) - BranchDataPrepBr(query, data) - return data - -def BranchDataWithIPCPrepWA(query): - data = [] - data.append(query.value(0)) - # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string - data.append("{:>19}".format(query.value(1))) - for i in xrange(2, 8): - data.append(query.value(i)) - BranchDataPrepIPC(query, data) - BranchDataPrepBr(query, data) - return data - -# Branch data model - -class BranchModel(TreeModel): - - progress = Signal(object) - - def __init__(self, glb, event_id, where_clause, parent=None): - super(BranchModel, self).__init__(glb, None, parent) - self.event_id = event_id - self.more = True - self.populated = 0 - self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count") - if self.have_ipc: - select_ipc = ", insn_count, cyc_count" - prep_fn = BranchDataWithIPCPrep - prep_wa_fn = BranchDataWithIPCPrepWA - else: - select_ipc = "" - prep_fn = BranchDataPrep - prep_wa_fn = BranchDataPrepWA - sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name," - " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END," - " ip, symbols.name, sym_offset, dsos.short_name," - " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name" - + select_ipc + - " FROM samples" - " INNER JOIN comms ON comm_id = comms.id" - " INNER JOIN threads ON thread_id = threads.id" - " INNER JOIN branch_types ON branch_type = branch_types.id" - " INNER JOIN symbols ON symbol_id = symbols.id" - " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id" - " INNER JOIN dsos ON samples.dso_id = dsos.id" - " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id" - " WHERE samples.id > $$last_id$$" + where_clause + - " AND evsel_id = " + str(self.event_id) + - " ORDER BY samples.id" - " LIMIT " + str(glb_chunk_sz)) - if pyside_version_1 and sys.version_info[0] == 3: - prep = prep_fn - else: - prep = prep_wa_fn - self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample) - self.fetcher.done.connect(self.Update) - self.fetcher.Fetch(glb_chunk_sz) - - def GetRoot(self): - return BranchRootItem() - - def columnCount(self, parent=None): - if self.have_ipc: - return 11 - else: - return 8 - - def columnHeader(self, column): - if self.have_ipc: - return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column] - else: - return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column] - - def columnFont(self, column): - if self.have_ipc: - br_col = 10 - else: - br_col = 7 - if column != br_col: - return None - return QFont("Monospace") - - def DisplayData(self, item, index): - if item.level == 1: - self.FetchIfNeeded(item.row) - return item.getData(index.column()) - - def AddSample(self, data): - child = BranchLevelOneItem(self.glb, self.populated, data, self.root) - self.root.child_items.append(child) - self.populated += 1 - - def Update(self, fetched): - if not fetched: - self.more = False - self.progress.emit(0) - child_count = self.root.child_count - count = self.populated - child_count - if count > 0: - parent = QModelIndex() - self.beginInsertRows(parent, child_count, child_count + count - 1) - self.insertRows(child_count, count, parent) - self.root.child_count += count - self.endInsertRows() - self.progress.emit(self.root.child_count) - - def FetchMoreRecords(self, count): - current = self.root.child_count - if self.more: - self.fetcher.Fetch(count) - else: - self.progress.emit(0) - return current - - def HasMoreRecords(self): - return self.more - -# Report Variables - -class ReportVars(): - - def __init__(self, name = "", where_clause = "", limit = ""): - self.name = name - self.where_clause = where_clause - self.limit = limit - - def UniqueId(self): - return str(self.where_clause + ";" + self.limit) - -# Branch window - -class BranchWindow(QMdiSubWindow): - - def __init__(self, glb, event_id, report_vars, parent=None): - super(BranchWindow, self).__init__(parent) - - model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId() - - self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause)) - - self.view = QTreeView() - self.view.setUniformRowHeights(True) - self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) - self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard - self.view.setModel(self.model) - - self.ResizeColumnsToContents() - - self.context_menu = TreeContextMenu(self.view) - - self.find_bar = FindBar(self, self, True) - - self.finder = ChildDataItemFinder(self.model.root) - - self.fetch_bar = FetchMoreRecordsBar(self.model, self) - - self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) - - self.setWidget(self.vbox.Widget()) - - AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events") - - def ResizeColumnToContents(self, column, n): - # Using the view's resizeColumnToContents() here is extrememly slow - # so implement a crude alternative - mm = "MM" if column else "MMMM" - font = self.view.font() - metrics = QFontMetrics(font) - max = 0 - for row in xrange(n): - val = self.model.root.child_items[row].data[column] - len = metrics.width(str(val) + mm) - max = len if len > max else max - val = self.model.columnHeader(column) - len = metrics.width(str(val) + mm) - max = len if len > max else max - self.view.setColumnWidth(column, max) - - def ResizeColumnsToContents(self): - n = min(self.model.root.child_count, 100) - if n < 1: - # No data yet, so connect a signal to notify when there is - self.model.rowsInserted.connect(self.UpdateColumnWidths) - return - columns = self.model.columnCount() - for i in xrange(columns): - self.ResizeColumnToContents(i, n) - - def UpdateColumnWidths(self, *x): - # This only needs to be done once, so disconnect the signal now - self.model.rowsInserted.disconnect(self.UpdateColumnWidths) - self.ResizeColumnsToContents() - - def Find(self, value, direction, pattern, context): - self.view.setFocus() - self.find_bar.Busy() - self.finder.Find(value, direction, pattern, context, self.FindDone) - - def FindDone(self, row): - self.find_bar.Idle() - if row >= 0: - self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) - else: - self.find_bar.NotFound() - -# Line edit data item - -class LineEditDataItem(object): - - def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): - self.glb = glb - self.label = label - self.placeholder_text = placeholder_text - self.parent = parent - self.id = id - - self.value = default - - self.widget = QLineEdit(default) - self.widget.editingFinished.connect(self.Validate) - self.widget.textChanged.connect(self.Invalidate) - self.red = False - self.error = "" - self.validated = True - - if placeholder_text: - self.widget.setPlaceholderText(placeholder_text) - - def TurnTextRed(self): - if not self.red: - palette = QPalette() - palette.setColor(QPalette.Text,Qt.red) - self.widget.setPalette(palette) - self.red = True - - def TurnTextNormal(self): - if self.red: - palette = QPalette() - self.widget.setPalette(palette) - self.red = False - - def InvalidValue(self, value): - self.value = "" - self.TurnTextRed() - self.error = self.label + " invalid value '" + value + "'" - self.parent.ShowMessage(self.error) - - def Invalidate(self): - self.validated = False - - def DoValidate(self, input_string): - self.value = input_string.strip() - - def Validate(self): - self.validated = True - self.error = "" - self.TurnTextNormal() - self.parent.ClearMessage() - input_string = self.widget.text() - if not len(input_string.strip()): - self.value = "" - return - self.DoValidate(input_string) - - def IsValid(self): - if not self.validated: - self.Validate() - if len(self.error): - self.parent.ShowMessage(self.error) - return False - return True - - def IsNumber(self, value): - try: - x = int(value) - except: - x = 0 - return str(x) == value - -# Non-negative integer ranges dialog data item - -class NonNegativeIntegerRangesDataItem(LineEditDataItem): - - def __init__(self, glb, label, placeholder_text, column_name, parent): - super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent) - - self.column_name = column_name - - def DoValidate(self, input_string): - singles = [] - ranges = [] - for value in [x.strip() for x in input_string.split(",")]: - if "-" in value: - vrange = value.split("-") - if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): - return self.InvalidValue(value) - ranges.append(vrange) - else: - if not self.IsNumber(value): - return self.InvalidValue(value) - singles.append(value) - ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges] - if len(singles): - ranges.append(self.column_name + " IN (" + ",".join(singles) + ")") - self.value = " OR ".join(ranges) - -# Positive integer dialog data item - -class PositiveIntegerDataItem(LineEditDataItem): - - def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): - super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default) - - def DoValidate(self, input_string): - if not self.IsNumber(input_string.strip()): - return self.InvalidValue(input_string) - value = int(input_string.strip()) - if value <= 0: - return self.InvalidValue(input_string) - self.value = str(value) - -# Dialog data item converted and validated using a SQL table - -class SQLTableDataItem(LineEditDataItem): - - def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): - super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent) - - self.table_name = table_name - self.match_column = match_column - self.column_name1 = column_name1 - self.column_name2 = column_name2 - - def ValueToIds(self, value): - ids = [] - query = QSqlQuery(self.glb.db) - stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'" - ret = query.exec_(stmt) - if ret: - while query.next(): - ids.append(str(query.value(0))) - return ids - - def DoValidate(self, input_string): - all_ids = [] - for value in [x.strip() for x in input_string.split(",")]: - ids = self.ValueToIds(value) - if len(ids): - all_ids.extend(ids) - else: - return self.InvalidValue(value) - self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")" - if self.column_name2: - self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )" - -# Sample time ranges dialog data item converted and validated using 'samples' SQL table - -class SampleTimeRangesDataItem(LineEditDataItem): - - def __init__(self, glb, label, placeholder_text, column_name, parent): - self.column_name = column_name - - self.last_id = 0 - self.first_time = 0 - self.last_time = 2 ** 64 - - query = QSqlQuery(glb.db) - QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1") - if query.next(): - self.last_id = int(query.value(0)) - self.first_time = int(glb.HostStartTime()) - self.last_time = int(glb.HostFinishTime()) - if placeholder_text: - placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time) - - super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent) - - def IdBetween(self, query, lower_id, higher_id, order): - QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1") - if query.next(): - return True, int(query.value(0)) - else: - return False, 0 - - def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor): - query = QSqlQuery(self.glb.db) - while True: - next_id = int((lower_id + higher_id) / 2) - QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) - if not query.next(): - ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC") - if not ok: - ok, dbid = self.IdBetween(query, next_id, higher_id, "") - if not ok: - return str(higher_id) - next_id = dbid - QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) - next_time = int(query.value(0)) - if get_floor: - if target_time > next_time: - lower_id = next_id - else: - higher_id = next_id - if higher_id <= lower_id + 1: - return str(higher_id) - else: - if target_time >= next_time: - lower_id = next_id - else: - higher_id = next_id - if higher_id <= lower_id + 1: - return str(lower_id) - - def ConvertRelativeTime(self, val): - mult = 1 - suffix = val[-2:] - if suffix == "ms": - mult = 1000000 - elif suffix == "us": - mult = 1000 - elif suffix == "ns": - mult = 1 - else: - return val - val = val[:-2].strip() - if not self.IsNumber(val): - return val - val = int(val) * mult - if val >= 0: - val += self.first_time - else: - val += self.last_time - return str(val) - - def ConvertTimeRange(self, vrange): - if vrange[0] == "": - vrange[0] = str(self.first_time) - if vrange[1] == "": - vrange[1] = str(self.last_time) - vrange[0] = self.ConvertRelativeTime(vrange[0]) - vrange[1] = self.ConvertRelativeTime(vrange[1]) - if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): - return False - beg_range = max(int(vrange[0]), self.first_time) - end_range = min(int(vrange[1]), self.last_time) - if beg_range > self.last_time or end_range < self.first_time: - return False - vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) - vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) - return True - - def AddTimeRange(self, value, ranges): - n = value.count("-") - if n == 1: - pass - elif n == 2: - if value.split("-")[1].strip() == "": - n = 1 - elif n == 3: - n = 2 - else: - return False - pos = findnth(value, "-", n) - vrange = [value[:pos].strip() ,value[pos+1:].strip()] - if self.ConvertTimeRange(vrange): - ranges.append(vrange) - return True - return False - - def DoValidate(self, input_string): - ranges = [] - for value in [x.strip() for x in input_string.split(",")]: - if not self.AddTimeRange(value, ranges): - return self.InvalidValue(value) - ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges] - self.value = " OR ".join(ranges) - -# Report Dialog Base - -class ReportDialogBase(QDialog): - - def __init__(self, glb, title, items, partial, parent=None): - super(ReportDialogBase, self).__init__(parent) - - self.glb = glb - - self.report_vars = ReportVars() - - self.setWindowTitle(title) - self.setMinimumWidth(600) - - self.data_items = [x(glb, self) for x in items] - - self.partial = partial - - self.grid = QGridLayout() - - for row in xrange(len(self.data_items)): - self.grid.addWidget(QLabel(self.data_items[row].label), row, 0) - self.grid.addWidget(self.data_items[row].widget, row, 1) - - self.status = QLabel() - - self.ok_button = QPushButton("Ok", self) - self.ok_button.setDefault(True) - self.ok_button.released.connect(self.Ok) - self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - - self.cancel_button = QPushButton("Cancel", self) - self.cancel_button.released.connect(self.reject) - self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - - self.hbox = QHBoxLayout() - #self.hbox.addStretch() - self.hbox.addWidget(self.status) - self.hbox.addWidget(self.ok_button) - self.hbox.addWidget(self.cancel_button) - - self.vbox = QVBoxLayout() - self.vbox.addLayout(self.grid) - self.vbox.addLayout(self.hbox) - - self.setLayout(self.vbox) - - def Ok(self): - vars = self.report_vars - for d in self.data_items: - if d.id == "REPORTNAME": - vars.name = d.value - if not vars.name: - self.ShowMessage("Report name is required") - return - for d in self.data_items: - if not d.IsValid(): - return - for d in self.data_items[1:]: - if d.id == "LIMIT": - vars.limit = d.value - elif len(d.value): - if len(vars.where_clause): - vars.where_clause += " AND " - vars.where_clause += d.value - if len(vars.where_clause): - if self.partial: - vars.where_clause = " AND ( " + vars.where_clause + " ) " - else: - vars.where_clause = " WHERE " + vars.where_clause + " " - self.accept() - - def ShowMessage(self, msg): - self.status.setText("" + msg) - - def ClearMessage(self): - self.status.setText("") - -# Selected branch report creation dialog - -class SelectedBranchDialog(ReportDialogBase): - - def __init__(self, glb, parent=None): - title = "Selected Branches" - items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), - lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p), - lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p), - lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p), - lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p), - lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p), - lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p), - lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p), - lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p)) - super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent) - -# Event list - -def GetEventList(db): - events = [] - query = QSqlQuery(db) - QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id") - while query.next(): - events.append(query.value(0)) - return events - -# Is a table selectable - -def IsSelectable(db, table, sql = "", columns = "*"): - query = QSqlQuery(db) - try: - QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1") - except: - return False - return True - -# SQL table data model item - -class SQLTableItem(): - - def __init__(self, row, data): - self.row = row - self.data = data - - def getData(self, column): - return self.data[column] - -# SQL table data model - -class SQLTableModel(TableModel): - - progress = Signal(object) - - def __init__(self, glb, sql, column_headers, parent=None): - super(SQLTableModel, self).__init__(parent) - self.glb = glb - self.more = True - self.populated = 0 - self.column_headers = column_headers - self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample) - self.fetcher.done.connect(self.Update) - self.fetcher.Fetch(glb_chunk_sz) - - def DisplayData(self, item, index): - self.FetchIfNeeded(item.row) - return item.getData(index.column()) - - def AddSample(self, data): - child = SQLTableItem(self.populated, data) - self.child_items.append(child) - self.populated += 1 - - def Update(self, fetched): - if not fetched: - self.more = False - self.progress.emit(0) - child_count = self.child_count - count = self.populated - child_count - if count > 0: - parent = QModelIndex() - self.beginInsertRows(parent, child_count, child_count + count - 1) - self.insertRows(child_count, count, parent) - self.child_count += count - self.endInsertRows() - self.progress.emit(self.child_count) - - def FetchMoreRecords(self, count): - current = self.child_count - if self.more: - self.fetcher.Fetch(count) - else: - self.progress.emit(0) - return current - - def HasMoreRecords(self): - return self.more - - def columnCount(self, parent=None): - return len(self.column_headers) - - def columnHeader(self, column): - return self.column_headers[column] - - def SQLTableDataPrep(self, query, count): - data = [] - for i in xrange(count): - data.append(query.value(i)) - return data - -# SQL automatic table data model - -class SQLAutoTableModel(SQLTableModel): - - def __init__(self, glb, table_name, parent=None): - sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz) - if table_name == "comm_threads_view": - # For now, comm_threads_view has no id column - sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) - column_headers = [] - query = QSqlQuery(glb.db) - if glb.dbref.is_sqlite3: - QueryExec(query, "PRAGMA table_info(" + table_name + ")") - while query.next(): - column_headers.append(query.value(1)) - if table_name == "sqlite_master": - sql = "SELECT * FROM " + table_name - else: - if table_name[:19] == "information_schema.": - sql = "SELECT * FROM " + table_name - select_table_name = table_name[19:] - schema = "information_schema" - else: - select_table_name = table_name - schema = "public" - QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") - while query.next(): - column_headers.append(query.value(0)) - if pyside_version_1 and sys.version_info[0] == 3: - if table_name == "samples_view": - self.SQLTableDataPrep = self.samples_view_DataPrep - if table_name == "samples": - self.SQLTableDataPrep = self.samples_DataPrep - super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent) - - def samples_view_DataPrep(self, query, count): - data = [] - data.append(query.value(0)) - # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string - data.append("{:>19}".format(query.value(1))) - for i in xrange(2, count): - data.append(query.value(i)) - return data - - def samples_DataPrep(self, query, count): - data = [] - for i in xrange(9): - data.append(query.value(i)) - # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string - data.append("{:>19}".format(query.value(9))) - for i in xrange(10, count): - data.append(query.value(i)) - return data - -# Base class for custom ResizeColumnsToContents - -class ResizeColumnsToContentsBase(QObject): - - def __init__(self, parent=None): - super(ResizeColumnsToContentsBase, self).__init__(parent) - - def ResizeColumnToContents(self, column, n): - # Using the view's resizeColumnToContents() here is extrememly slow - # so implement a crude alternative - font = self.view.font() - metrics = QFontMetrics(font) - max = 0 - for row in xrange(n): - val = self.data_model.child_items[row].data[column] - len = metrics.width(str(val) + "MM") - max = len if len > max else max - val = self.data_model.columnHeader(column) - len = metrics.width(str(val) + "MM") - max = len if len > max else max - self.view.setColumnWidth(column, max) - - def ResizeColumnsToContents(self): - n = min(self.data_model.child_count, 100) - if n < 1: - # No data yet, so connect a signal to notify when there is - self.data_model.rowsInserted.connect(self.UpdateColumnWidths) - return - columns = self.data_model.columnCount() - for i in xrange(columns): - self.ResizeColumnToContents(i, n) - - def UpdateColumnWidths(self, *x): - # This only needs to be done once, so disconnect the signal now - self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths) - self.ResizeColumnsToContents() - -# Convert value to CSV - -def ToCSValue(val): - if '"' in val: - val = val.replace('"', '""') - if "," in val or '"' in val: - val = '"' + val + '"' - return val - -# Key to sort table model indexes by row / column, assuming fewer than 1000 columns - -glb_max_cols = 1000 - -def RowColumnKey(a): - return a.row() * glb_max_cols + a.column() - -# Copy selected table cells to clipboard - -def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False): - indexes = sorted(view.selectedIndexes(), key=RowColumnKey) - idx_cnt = len(indexes) - if not idx_cnt: - return - if idx_cnt == 1: - with_hdr=False - min_row = indexes[0].row() - max_row = indexes[0].row() - min_col = indexes[0].column() - max_col = indexes[0].column() - for i in indexes: - min_row = min(min_row, i.row()) - max_row = max(max_row, i.row()) - min_col = min(min_col, i.column()) - max_col = max(max_col, i.column()) - if max_col > glb_max_cols: - raise RuntimeError("glb_max_cols is too low") - max_width = [0] * (1 + max_col - min_col) - for i in indexes: - c = i.column() - min_col - max_width[c] = max(max_width[c], len(str(i.data()))) - text = "" - pad = "" - sep = "" - if with_hdr: - model = indexes[0].model() - for col in range(min_col, max_col + 1): - val = model.headerData(col, Qt.Horizontal, Qt.DisplayRole) - if as_csv: - text += sep + ToCSValue(val) - sep = "," - else: - c = col - min_col - max_width[c] = max(max_width[c], len(val)) - width = max_width[c] - align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole) - if align & Qt.AlignRight: - val = val.rjust(width) - text += pad + sep + val - pad = " " * (width - len(val)) - sep = " " - text += "\n" - pad = "" - sep = "" - last_row = min_row - for i in indexes: - if i.row() > last_row: - last_row = i.row() - text += "\n" - pad = "" - sep = "" - if as_csv: - text += sep + ToCSValue(str(i.data())) - sep = "," - else: - width = max_width[i.column() - min_col] - if i.data(Qt.TextAlignmentRole) & Qt.AlignRight: - val = str(i.data()).rjust(width) - else: - val = str(i.data()) - text += pad + sep + val - pad = " " * (width - len(val)) - sep = " " - QApplication.clipboard().setText(text) - -def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False): - indexes = view.selectedIndexes() - if not len(indexes): - return - - selection = view.selectionModel() - - first = None - for i in indexes: - above = view.indexAbove(i) - if not selection.isSelected(above): - first = i - break - - if first is None: - raise RuntimeError("CopyTreeCellsToClipboard internal error") - - model = first.model() - row_cnt = 0 - col_cnt = model.columnCount(first) - max_width = [0] * col_cnt - - indent_sz = 2 - indent_str = " " * indent_sz - - expanded_mark_sz = 2 - if sys.version_info[0] == 3: - expanded_mark = "\u25BC " - not_expanded_mark = "\u25B6 " - else: - expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8") - not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8") - leaf_mark = " " - - if not as_csv: - pos = first - while True: - row_cnt += 1 - row = pos.row() - for c in range(col_cnt): - i = pos.sibling(row, c) - if c: - n = len(str(i.data())) - else: - n = len(str(i.data()).strip()) - n += (i.internalPointer().level - 1) * indent_sz - n += expanded_mark_sz - max_width[c] = max(max_width[c], n) - pos = view.indexBelow(pos) - if not selection.isSelected(pos): - break - - text = "" - pad = "" - sep = "" - if with_hdr: - for c in range(col_cnt): - val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip() - if as_csv: - text += sep + ToCSValue(val) - sep = "," - else: - max_width[c] = max(max_width[c], len(val)) - width = max_width[c] - align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole) - if align & Qt.AlignRight: - val = val.rjust(width) - text += pad + sep + val - pad = " " * (width - len(val)) - sep = " " - text += "\n" - pad = "" - sep = "" - - pos = first - while True: - row = pos.row() - for c in range(col_cnt): - i = pos.sibling(row, c) - val = str(i.data()) - if not c: - if model.hasChildren(i): - if view.isExpanded(i): - mark = expanded_mark - else: - mark = not_expanded_mark - else: - mark = leaf_mark - val = indent_str * (i.internalPointer().level - 1) + mark + val.strip() - if as_csv: - text += sep + ToCSValue(val) - sep = "," - else: - width = max_width[c] - if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight: - val = val.rjust(width) - text += pad + sep + val - pad = " " * (width - len(val)) - sep = " " - pos = view.indexBelow(pos) - if not selection.isSelected(pos): - break - text = text.rstrip() + "\n" - pad = "" - sep = "" - - QApplication.clipboard().setText(text) - -def CopyCellsToClipboard(view, as_csv=False, with_hdr=False): - view.CopyCellsToClipboard(view, as_csv, with_hdr) - -def CopyCellsToClipboardHdr(view): - CopyCellsToClipboard(view, False, True) - -def CopyCellsToClipboardCSV(view): - CopyCellsToClipboard(view, True, True) - -# Context menu - -class ContextMenu(object): - - def __init__(self, view): - self.view = view - self.view.setContextMenuPolicy(Qt.CustomContextMenu) - self.view.customContextMenuRequested.connect(self.ShowContextMenu) - - def ShowContextMenu(self, pos): - menu = QMenu(self.view) - self.AddActions(menu) - menu.exec_(self.view.mapToGlobal(pos)) - - def AddCopy(self, menu): - menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view)) - menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view)) - - def AddActions(self, menu): - self.AddCopy(menu) - -class TreeContextMenu(ContextMenu): - - def __init__(self, view): - super(TreeContextMenu, self).__init__(view) - - def AddActions(self, menu): - i = self.view.currentIndex() - text = str(i.data()).strip() - if len(text): - menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view)) - self.AddCopy(menu) - -# Table window - -class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): - - def __init__(self, glb, table_name, parent=None): - super(TableWindow, self).__init__(parent) - - self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name)) - - self.model = QSortFilterProxyModel() - self.model.setSourceModel(self.data_model) - - self.view = QTableView() - self.view.setModel(self.model) - self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.view.verticalHeader().setVisible(False) - self.view.sortByColumn(-1, Qt.AscendingOrder) - self.view.setSortingEnabled(True) - self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) - self.view.CopyCellsToClipboard = CopyTableCellsToClipboard - - self.ResizeColumnsToContents() - - self.context_menu = ContextMenu(self.view) - - self.find_bar = FindBar(self, self, True) - - self.finder = ChildDataItemFinder(self.data_model) - - self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) - - self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) - - self.setWidget(self.vbox.Widget()) - - AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table") - - def Find(self, value, direction, pattern, context): - self.view.setFocus() - self.find_bar.Busy() - self.finder.Find(value, direction, pattern, context, self.FindDone) - - def FindDone(self, row): - self.find_bar.Idle() - if row >= 0: - self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex()))) - else: - self.find_bar.NotFound() - -# Table list - -def GetTableList(glb): - tables = [] - query = QSqlQuery(glb.db) - if glb.dbref.is_sqlite3: - QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name") - else: - QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name") - while query.next(): - tables.append(query.value(0)) - if glb.dbref.is_sqlite3: - tables.append("sqlite_master") - else: - tables.append("information_schema.tables") - tables.append("information_schema.views") - tables.append("information_schema.columns") - return tables - -# Top Calls data model - -class TopCallsModel(SQLTableModel): - - def __init__(self, glb, report_vars, parent=None): - text = "" - if not glb.dbref.is_sqlite3: - text = "::text" - limit = "" - if len(report_vars.limit): - limit = " LIMIT " + report_vars.limit - sql = ("SELECT comm, pid, tid, name," - " CASE" - " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text + - " ELSE short_name" - " END AS dso," - " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, " - " CASE" - " WHEN (calls.flags = 1) THEN 'no call'" + text + - " WHEN (calls.flags = 2) THEN 'no return'" + text + - " WHEN (calls.flags = 3) THEN 'no call/return'" + text + - " ELSE ''" + text + - " END AS flags" - " FROM calls" - " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" - " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" - " INNER JOIN dsos ON symbols.dso_id = dsos.id" - " INNER JOIN comms ON calls.comm_id = comms.id" - " INNER JOIN threads ON calls.thread_id = threads.id" + - report_vars.where_clause + - " ORDER BY elapsed_time DESC" + - limit - ) - column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags") - self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft) - super(TopCallsModel, self).__init__(glb, sql, column_headers, parent) - - def columnAlignment(self, column): - return self.alignment[column] - -# Top Calls report creation dialog - -class TopCallsDialog(ReportDialogBase): - - def __init__(self, glb, parent=None): - title = "Top Calls by Elapsed Time" - items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), - lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p), - lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p), - lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p), - lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p), - lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p), - lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p), - lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100")) - super(TopCallsDialog, self).__init__(glb, title, items, False, parent) - -# Top Calls window - -class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase): - - def __init__(self, glb, report_vars, parent=None): - super(TopCallsWindow, self).__init__(parent) - - self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars)) - self.model = self.data_model - - self.view = QTableView() - self.view.setModel(self.model) - self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.view.verticalHeader().setVisible(False) - self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) - self.view.CopyCellsToClipboard = CopyTableCellsToClipboard - - self.context_menu = ContextMenu(self.view) - - self.ResizeColumnsToContents() - - self.find_bar = FindBar(self, self, True) - - self.finder = ChildDataItemFinder(self.model) - - self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) - - self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) - - self.setWidget(self.vbox.Widget()) - - AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name) - - def Find(self, value, direction, pattern, context): - self.view.setFocus() - self.find_bar.Busy() - self.finder.Find(value, direction, pattern, context, self.FindDone) - - def FindDone(self, row): - self.find_bar.Idle() - if row >= 0: - self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) - else: - self.find_bar.NotFound() - -# Action Definition - -def CreateAction(label, tip, callback, parent=None, shortcut=None): - action = QAction(label, parent) - if shortcut != None: - action.setShortcuts(shortcut) - action.setStatusTip(tip) - action.triggered.connect(callback) - return action - -# Typical application actions - -def CreateExitAction(app, parent=None): - return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) - -# Typical MDI actions - -def CreateCloseActiveWindowAction(mdi_area): - return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) - -def CreateCloseAllWindowsAction(mdi_area): - return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) - -def CreateTileWindowsAction(mdi_area): - return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) - -def CreateCascadeWindowsAction(mdi_area): - return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) - -def CreateNextWindowAction(mdi_area): - return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) - -def CreatePreviousWindowAction(mdi_area): - return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) - -# Typical MDI window menu - -class WindowMenu(): - - def __init__(self, mdi_area, menu): - self.mdi_area = mdi_area - self.window_menu = menu.addMenu("&Windows") - self.close_active_window = CreateCloseActiveWindowAction(mdi_area) - self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) - self.tile_windows = CreateTileWindowsAction(mdi_area) - self.cascade_windows = CreateCascadeWindowsAction(mdi_area) - self.next_window = CreateNextWindowAction(mdi_area) - self.previous_window = CreatePreviousWindowAction(mdi_area) - self.window_menu.aboutToShow.connect(self.Update) - - def Update(self): - self.window_menu.clear() - sub_window_count = len(self.mdi_area.subWindowList()) - have_sub_windows = sub_window_count != 0 - self.close_active_window.setEnabled(have_sub_windows) - self.close_all_windows.setEnabled(have_sub_windows) - self.tile_windows.setEnabled(have_sub_windows) - self.cascade_windows.setEnabled(have_sub_windows) - self.next_window.setEnabled(have_sub_windows) - self.previous_window.setEnabled(have_sub_windows) - self.window_menu.addAction(self.close_active_window) - self.window_menu.addAction(self.close_all_windows) - self.window_menu.addSeparator() - self.window_menu.addAction(self.tile_windows) - self.window_menu.addAction(self.cascade_windows) - self.window_menu.addSeparator() - self.window_menu.addAction(self.next_window) - self.window_menu.addAction(self.previous_window) - if sub_window_count == 0: - return - self.window_menu.addSeparator() - nr = 1 - for sub_window in self.mdi_area.subWindowList(): - label = str(nr) + " " + sub_window.name - if nr < 10: - label = "&" + label - action = self.window_menu.addAction(label) - action.setCheckable(True) - action.setChecked(sub_window == self.mdi_area.activeSubWindow()) - action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x)) - self.window_menu.addAction(action) - nr += 1 - - def setActiveSubWindow(self, nr): - self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) - -# Help text - -glb_help_text = """ -

Contents

- -

1. Reports

-

1.1 Context-Sensitive Call Graph

-

1.2 Call Tree

-

1.3 All branches

-

1.4 Selected branches

-

1.5 Top calls by elapsed time

-

2. Charts

-

2.1 Time chart by CPU

-

3. Tables

-

1. Reports

-

1.1 Context-Sensitive Call Graph

-The result is a GUI window with a tree representing a context-sensitive -call-graph. Expanding a couple of levels of the tree and adjusting column -widths to suit will display something like: -
-                                         Call Graph: pt_example
-Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
-v- ls
-    v- 2638:2638
-        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
-          |- unknown               unknown       1        13198     0.1              1              0.0
-          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
-          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
-          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
-             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
-             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
-             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
-             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
-             v- main               ls            1      8182043    99.6         180254             99.9
-
-

Points to note:

-
    -
  • The top level is a command name (comm)
  • -
  • The next level is a thread (pid:tid)
  • -
  • Subsequent levels are functions
  • -
  • 'Count' is the number of calls
  • -
  • 'Time' is the elapsed time until the function returns
  • -
  • Percentages are relative to the level above
  • -
  • 'Branch Count' is the total number of branches for that function and all functions that it calls -
-

Find

-Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match. -The pattern matching symbols are ? for any character and * for zero or more characters. -

1.2 Call Tree

-The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated. -Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'. -

1.3 All branches

-The All branches report displays all branches in chronological order. -Not all data is fetched immediately. More records can be fetched using the Fetch bar provided. -

Disassembly

-Open a branch to display disassembly. This only works if: -
    -
  1. The disassembler is available. Currently, only Intel XED is supported - see Intel XED Setup
  2. -
  3. The object code is available. Currently, only the perf build ID cache is searched for object code. -The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR. -One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu), -or alternatively, set environment variable PERF_KCORE to the kcore file name.
  4. -
-

Intel XED Setup

-To use Intel XED, libxed.so must be present. To build and install libxed.so: -
-git clone https://github.com/intelxed/mbuild.git mbuild
-git clone https://github.com/intelxed/xed
-cd xed
-./mfile.py --share
-sudo ./mfile.py --prefix=/usr/local install
-sudo ldconfig
-
-

Instructions per Cycle (IPC)

-If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'. -

Intel PT note: The information applies to the blocks of code ending with, and including, that branch. -Due to the granularity of timing information, the number of cycles for some code blocks will not be known. -In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period -since the previous displayed 'IPC'. -

Find

-Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match. -Refer to Python documentation for the regular expression syntax. -All columns are searched, but only currently fetched rows are searched. -

1.4 Selected branches

-This is the same as the All branches report but with the data reduced -by various selection criteria. A dialog box displays available criteria which are AND'ed together. -

1.4.1 Time ranges

-The time ranges hint text shows the total time range. Relative time ranges can also be entered in -ms, us or ns. Also, negative values are relative to the end of trace. Examples: -
-	81073085947329-81073085958238	From 81073085947329 to 81073085958238
-	100us-200us		From 100us to 200us
-	10ms-			From 10ms to the end
-	-100ns			The first 100ns
-	-10ms-			The last 10ms
-
-N.B. Due to the granularity of timestamps, there could be no branches in any given time range. -

1.5 Top calls by elapsed time

-The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned. -The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together. -If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar. -

2. Charts

-

2.1 Time chart by CPU

-This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu. -

Features

-
    -
  1. Mouse over to highight the task and show the time
  2. -
  3. Drag the mouse to select a region and zoom by pushing the Zoom button
  4. -
  5. Go back and forward by pressing the arrow buttons
  6. -
  7. If call information is available, right-click to show a call tree opened to that task and time. -Note, the call tree may take some time to appear, and there may not be call information for the task or time selected. -
  8. -
-

Important

-The graph can be misleading in the following respects: -
    -
  1. The graph shows the first task on each CPU as running from the beginning of the time range. -Because tracing might start on different CPUs at different times, that is not necessarily the case. -Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.
  2. -
  3. Similarly, the last task on each CPU can be showing running longer than it really was. -Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.
  4. -
  5. When the mouse is over a task, the highlighted task might not be visible on the legend without scrolling if the legend does not fit fully in the window
  6. -
-

3. Tables

-The Tables menu shows all tables and views in the database. Most tables have an associated view -which displays the information in a more friendly way. Not all data for large tables is fetched -immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted, -but that can be slow for large tables. -

There are also tables of database meta-information. -For SQLite3 databases, the sqlite_master table is included. -For PostgreSQL databases, information_schema.tables/views/columns are included. -

Find

-Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match. -Refer to Python documentation for the regular expression syntax. -All columns are searched, but only currently fetched rows are searched. -

N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous -will go to the next/previous result in id order, instead of display order. -""" - -# Help window - -class HelpWindow(QMdiSubWindow): - - def __init__(self, glb, parent=None): - super(HelpWindow, self).__init__(parent) - - self.text = QTextBrowser() - self.text.setHtml(glb_help_text) - self.text.setReadOnly(True) - self.text.setOpenExternalLinks(True) - - self.setWidget(self.text) - - AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help") - -# Main window that only displays the help text - -class HelpOnlyWindow(QMainWindow): - - def __init__(self, parent=None): - super(HelpOnlyWindow, self).__init__(parent) - - self.setMinimumSize(200, 100) - self.resize(800, 600) - self.setWindowTitle("Exported SQL Viewer Help") - self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation)) - - self.text = QTextBrowser() - self.text.setHtml(glb_help_text) - self.text.setReadOnly(True) - self.text.setOpenExternalLinks(True) - - self.setCentralWidget(self.text) - -# PostqreSQL server version - -def PostqreSQLServerVersion(db): - query = QSqlQuery(db) - QueryExec(query, "SELECT VERSION()") - if query.next(): - v_str = query.value(0) - v_list = v_str.strip().split(" ") - if v_list[0] == "PostgreSQL" and v_list[2] == "on": - return v_list[1] - return v_str - return "Unknown" - -# SQLite version - -def SQLiteVersion(db): - query = QSqlQuery(db) - QueryExec(query, "SELECT sqlite_version()") - if query.next(): - return query.value(0) - return "Unknown" - -# About dialog - -class AboutDialog(QDialog): - - def __init__(self, glb, parent=None): - super(AboutDialog, self).__init__(parent) - - self.setWindowTitle("About Exported SQL Viewer") - self.setMinimumWidth(300) - - pyside_version = "1" if pyside_version_1 else "2" - - text = "

"
-		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
-		text += "PySide version:     " + pyside_version + "\n"
-		text += "Qt version:         " + qVersion() + "\n"
-		if glb.dbref.is_sqlite3:
-			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
-		else:
-			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
-		text += "
" - - self.text = QTextBrowser() - self.text.setHtml(text) - self.text.setReadOnly(True) - self.text.setOpenExternalLinks(True) - - self.vbox = QVBoxLayout() - self.vbox.addWidget(self.text) - - self.setLayout(self.vbox) - -# Font resize - -def ResizeFont(widget, diff): - font = widget.font() - sz = font.pointSize() - font.setPointSize(sz + diff) - widget.setFont(font) - -def ShrinkFont(widget): - ResizeFont(widget, -1) - -def EnlargeFont(widget): - ResizeFont(widget, 1) - -# Unique name for sub-windows - -def NumberedWindowName(name, nr): - if nr > 1: - name += " <" + str(nr) + ">" - return name - -def UniqueSubWindowName(mdi_area, name): - nr = 1 - while True: - unique_name = NumberedWindowName(name, nr) - ok = True - for sub_window in mdi_area.subWindowList(): - if sub_window.name == unique_name: - ok = False - break - if ok: - return unique_name - nr += 1 - -# Add a sub-window - -def AddSubWindow(mdi_area, sub_window, name): - unique_name = UniqueSubWindowName(mdi_area, name) - sub_window.setMinimumSize(200, 100) - sub_window.resize(800, 600) - sub_window.setWindowTitle(unique_name) - sub_window.setAttribute(Qt.WA_DeleteOnClose) - sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon)) - sub_window.name = unique_name - mdi_area.addSubWindow(sub_window) - sub_window.show() - -# Main window - -class MainWindow(QMainWindow): - - def __init__(self, glb, parent=None): - super(MainWindow, self).__init__(parent) - - self.glb = glb - - self.setWindowTitle("Exported SQL Viewer: " + glb.dbname) - self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) - self.setMinimumSize(200, 100) - - self.mdi_area = QMdiArea() - self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - - self.setCentralWidget(self.mdi_area) - - menu = self.menuBar() - - file_menu = menu.addMenu("&File") - file_menu.addAction(CreateExitAction(glb.app, self)) - - edit_menu = menu.addMenu("&Edit") - edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy)) - edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self)) - edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) - edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)])) - edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")])) - edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) - - reports_menu = menu.addMenu("&Reports") - if IsSelectable(glb.db, "calls"): - reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) - - if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"): - reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self)) - - self.EventMenu(GetEventList(glb.db), reports_menu) - - if IsSelectable(glb.db, "calls"): - reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self)) - - if IsSelectable(glb.db, "context_switches"): - charts_menu = menu.addMenu("&Charts") - charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self)) - - self.TableMenu(GetTableList(glb), menu) - - self.window_menu = WindowMenu(self.mdi_area, menu) - - help_menu = menu.addMenu("&Help") - help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents)) - help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self)) - - def Try(self, fn): - win = self.mdi_area.activeSubWindow() - if win: - try: - fn(win.view) - except: - pass - - def CopyToClipboard(self): - self.Try(CopyCellsToClipboardHdr) - - def CopyToClipboardCSV(self): - self.Try(CopyCellsToClipboardCSV) - - def Find(self): - win = self.mdi_area.activeSubWindow() - if win: - try: - win.find_bar.Activate() - except: - pass - - def FetchMoreRecords(self): - win = self.mdi_area.activeSubWindow() - if win: - try: - win.fetch_bar.Activate() - except: - pass - - def ShrinkFont(self): - self.Try(ShrinkFont) - - def EnlargeFont(self): - self.Try(EnlargeFont) - - def EventMenu(self, events, reports_menu): - branches_events = 0 - for event in events: - event = event.split(":")[0] - if event == "branches": - branches_events += 1 - dbid = 0 - for event in events: - dbid += 1 - event = event.split(":")[0] - if event == "branches": - label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")" - reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self)) - label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")" - reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self)) - - def TimeChartByCPU(self): - TimeChartByCPUWindow(self.glb, self) - - def TableMenu(self, tables, menu): - table_menu = menu.addMenu("&Tables") - for table in tables: - table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self)) - - def NewCallGraph(self): - CallGraphWindow(self.glb, self) - - def NewCallTree(self): - CallTreeWindow(self.glb, self) - - def NewTopCalls(self): - dialog = TopCallsDialog(self.glb, self) - ret = dialog.exec_() - if ret: - TopCallsWindow(self.glb, dialog.report_vars, self) - - def NewBranchView(self, event_id): - BranchWindow(self.glb, event_id, ReportVars(), self) - - def NewSelectedBranchView(self, event_id): - dialog = SelectedBranchDialog(self.glb, self) - ret = dialog.exec_() - if ret: - BranchWindow(self.glb, event_id, dialog.report_vars, self) - - def NewTableView(self, table_name): - TableWindow(self.glb, table_name, self) - - def Help(self): - HelpWindow(self.glb, self) - - def About(self): - dialog = AboutDialog(self.glb, self) - dialog.exec_() - -def TryOpen(file_name): - try: - return open(file_name, "rb") - except: - return None - -def Is64Bit(f): - result = sizeof(c_void_p) - # ELF support only - pos = f.tell() - f.seek(0) - header = f.read(7) - f.seek(pos) - magic = header[0:4] - if sys.version_info[0] == 2: - eclass = ord(header[4]) - encoding = ord(header[5]) - version = ord(header[6]) - else: - eclass = header[4] - encoding = header[5] - version = header[6] - if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1: - result = True if eclass == 2 else False - return result - -# Global data - -class Glb(): - - def __init__(self, dbref, db, dbname): - self.dbref = dbref - self.db = db - self.dbname = dbname - self.home_dir = os.path.expanduser("~") - self.buildid_dir = os.getenv("PERF_BUILDID_DIR") - if self.buildid_dir: - self.buildid_dir += "/.build-id/" - else: - self.buildid_dir = self.home_dir + "/.debug/.build-id/" - self.app = None - self.mainwindow = None - self.instances_to_shutdown_on_exit = weakref.WeakSet() - try: - self.disassembler = LibXED() - self.have_disassembler = True - except: - self.have_disassembler = False - self.host_machine_id = 0 - self.host_start_time = 0 - self.host_finish_time = 0 - - def FileFromBuildId(self, build_id): - file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf" - return TryOpen(file_name) - - def FileFromNamesAndBuildId(self, short_name, long_name, build_id): - # Assume current machine i.e. no support for virtualization - if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore": - file_name = os.getenv("PERF_KCORE") - f = TryOpen(file_name) if file_name else None - if f: - return f - # For now, no special handling if long_name is /proc/kcore - f = TryOpen(long_name) - if f: - return f - f = self.FileFromBuildId(build_id) - if f: - return f - return None - - def AddInstanceToShutdownOnExit(self, instance): - self.instances_to_shutdown_on_exit.add(instance) - - # Shutdown any background processes or threads - def ShutdownInstances(self): - for x in self.instances_to_shutdown_on_exit: - try: - x.Shutdown() - except: - pass - - def GetHostMachineId(self): - query = QSqlQuery(self.db) - QueryExec(query, "SELECT id FROM machines WHERE pid = -1") - if query.next(): - self.host_machine_id = query.value(0) - else: - self.host_machine_id = 0 - return self.host_machine_id - - def HostMachineId(self): - if self.host_machine_id: - return self.host_machine_id - return self.GetHostMachineId() - - def SelectValue(self, sql): - query = QSqlQuery(self.db) - try: - QueryExec(query, sql) - except: - return None - if query.next(): - return Decimal(query.value(0)) - return None - - def SwitchesMinTime(self, machine_id): - return self.SelectValue("SELECT time" - " FROM context_switches" - " WHERE time != 0 AND machine_id = " + str(machine_id) + - " ORDER BY id LIMIT 1") - - def SwitchesMaxTime(self, machine_id): - return self.SelectValue("SELECT time" - " FROM context_switches" - " WHERE time != 0 AND machine_id = " + str(machine_id) + - " ORDER BY id DESC LIMIT 1") - - def SamplesMinTime(self, machine_id): - return self.SelectValue("SELECT time" - " FROM samples" - " WHERE time != 0 AND machine_id = " + str(machine_id) + - " ORDER BY id LIMIT 1") - - def SamplesMaxTime(self, machine_id): - return self.SelectValue("SELECT time" - " FROM samples" - " WHERE time != 0 AND machine_id = " + str(machine_id) + - " ORDER BY id DESC LIMIT 1") - - def CallsMinTime(self, machine_id): - return self.SelectValue("SELECT calls.call_time" - " FROM calls" - " INNER JOIN threads ON threads.thread_id = calls.thread_id" - " WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) + - " ORDER BY calls.id LIMIT 1") - - def CallsMaxTime(self, machine_id): - return self.SelectValue("SELECT calls.return_time" - " FROM calls" - " INNER JOIN threads ON threads.thread_id = calls.thread_id" - " WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) + - " ORDER BY calls.return_time DESC LIMIT 1") - - def GetStartTime(self, machine_id): - t0 = self.SwitchesMinTime(machine_id) - t1 = self.SamplesMinTime(machine_id) - t2 = self.CallsMinTime(machine_id) - if t0 is None or (not(t1 is None) and t1 < t0): - t0 = t1 - if t0 is None or (not(t2 is None) and t2 < t0): - t0 = t2 - return t0 - - def GetFinishTime(self, machine_id): - t0 = self.SwitchesMaxTime(machine_id) - t1 = self.SamplesMaxTime(machine_id) - t2 = self.CallsMaxTime(machine_id) - if t0 is None or (not(t1 is None) and t1 > t0): - t0 = t1 - if t0 is None or (not(t2 is None) and t2 > t0): - t0 = t2 - return t0 - - def HostStartTime(self): - if self.host_start_time: - return self.host_start_time - self.host_start_time = self.GetStartTime(self.HostMachineId()) - return self.host_start_time - - def HostFinishTime(self): - if self.host_finish_time: - return self.host_finish_time - self.host_finish_time = self.GetFinishTime(self.HostMachineId()) - return self.host_finish_time - - def StartTime(self, machine_id): - if machine_id == self.HostMachineId(): - return self.HostStartTime() - return self.GetStartTime(machine_id) - - def FinishTime(self, machine_id): - if machine_id == self.HostMachineId(): - return self.HostFinishTime() - return self.GetFinishTime(machine_id) - -# Database reference - -class DBRef(): - - def __init__(self, is_sqlite3, dbname): - self.is_sqlite3 = is_sqlite3 - self.dbname = dbname - self.TRUE = "TRUE" - self.FALSE = "FALSE" - # SQLite prior to version 3.23 does not support TRUE and FALSE - if self.is_sqlite3: - self.TRUE = "1" - self.FALSE = "0" - - def Open(self, connection_name): - dbname = self.dbname - if self.is_sqlite3: - db = QSqlDatabase.addDatabase("QSQLITE", connection_name) - else: - db = QSqlDatabase.addDatabase("QPSQL", connection_name) - opts = dbname.split() - for opt in opts: - if "=" in opt: - opt = opt.split("=") - if opt[0] == "hostname": - db.setHostName(opt[1]) - elif opt[0] == "port": - db.setPort(int(opt[1])) - elif opt[0] == "username": - db.setUserName(opt[1]) - elif opt[0] == "password": - db.setPassword(opt[1]) - elif opt[0] == "dbname": - dbname = opt[1] - else: - dbname = opt - - db.setDatabaseName(dbname) - if not db.open(): - raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text()) - return db, dbname - -# Main - -def Main(): - usage_str = "exported-sql-viewer.py [--pyside-version-1] \n" \ - " or: exported-sql-viewer.py --help-only" - ap = argparse.ArgumentParser(usage = usage_str, add_help = False) - ap.add_argument("--pyside-version-1", action='store_true') - ap.add_argument("dbname", nargs="?") - ap.add_argument("--help-only", action='store_true') - args = ap.parse_args() - - if args.help_only: - app = QApplication(sys.argv) - mainwindow = HelpOnlyWindow() - mainwindow.show() - err = app.exec_() - sys.exit(err) - - dbname = args.dbname - if dbname is None: - ap.print_usage() - print("Too few arguments") - sys.exit(1) - - is_sqlite3 = False - try: - f = open(dbname, "rb") - if f.read(15) == b'SQLite format 3': - is_sqlite3 = True - f.close() - except: - pass - - dbref = DBRef(is_sqlite3, dbname) - db, dbname = dbref.Open("main") - glb = Glb(dbref, db, dbname) - app = QApplication(sys.argv) - glb.app = app - mainwindow = MainWindow(glb) - glb.mainwindow = mainwindow - mainwindow.show() - err = app.exec_() - glb.ShutdownInstances() - db.close() - sys.exit(err) - -if __name__ == "__main__": - Main() diff --git a/tools/perf/scripts/python/failed-syscalls-by-pid.py b/tools/perf/scripts/python/failed-syscalls-by-pid.py deleted file mode 100644 index 310efe5e7e23..000000000000 --- a/tools/perf/scripts/python/failed-syscalls-by-pid.py +++ /dev/null @@ -1,79 +0,0 @@ -# failed system call counts, by pid -# (c) 2010, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 -# -# Displays system-wide failed system call totals, broken down by pid. -# If a [comm] arg is specified, only syscalls called by [comm] are displayed. - -from __future__ import print_function - -import os -import sys - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import * - -usage = "perf script -s syscall-counts-by-pid.py [comm|pid]\n"; - -for_comm = None -for_pid = None - -if len(sys.argv) > 2: - sys.exit(usage) - -if len(sys.argv) > 1: - try: - for_pid = int(sys.argv[1]) - except: - for_comm = sys.argv[1] - -syscalls = autodict() - -def trace_begin(): - print("Press control+C to stop and show the summary") - -def trace_end(): - print_error_totals() - -def raw_syscalls__sys_exit(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, id, ret): - if (for_comm and common_comm != for_comm) or \ - (for_pid and common_pid != for_pid ): - return - - if ret < 0: - try: - syscalls[common_comm][common_pid][id][ret] += 1 - except TypeError: - syscalls[common_comm][common_pid][id][ret] = 1 - -def syscalls__sys_exit(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - id, ret): - raw_syscalls__sys_exit(**locals()) - -def print_error_totals(): - if for_comm is not None: - print("\nsyscall errors for %s:\n" % (for_comm)) - else: - print("\nsyscall errors:\n") - - print("%-30s %10s" % ("comm [pid]", "count")) - print("%-30s %10s" % ("------------------------------", "----------")) - - comm_keys = syscalls.keys() - for comm in comm_keys: - pid_keys = syscalls[comm].keys() - for pid in pid_keys: - print("\n%s [%d]" % (comm, pid)) - id_keys = syscalls[comm][pid].keys() - for id in id_keys: - print(" syscall: %-16s" % syscall_name(id)) - ret_keys = syscalls[comm][pid][id].keys() - for ret, val in sorted(syscalls[comm][pid][id].items(), key = lambda kv: (kv[1], kv[0]), reverse = True): - print(" err = %-20s %10d" % (strerror(ret), val)) diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py deleted file mode 100755 index ad735990c5be..000000000000 --- a/tools/perf/scripts/python/flamegraph.py +++ /dev/null @@ -1,267 +0,0 @@ -# flamegraph.py - create flame graphs from perf samples -# SPDX-License-Identifier: GPL-2.0 -# -# Usage: -# -# perf record -a -g -F 99 sleep 60 -# perf script report flamegraph -# -# Combined: -# -# perf script flamegraph -a -F 99 sleep 60 -# -# Written by Andreas Gerstmayr -# Flame Graphs invented by Brendan Gregg -# Works in tandem with d3-flame-graph by Martin Spier -# -# pylint: disable=missing-module-docstring -# pylint: disable=missing-class-docstring -# pylint: disable=missing-function-docstring - -import argparse -import hashlib -import io -import json -import os -import subprocess -import sys -from typing import Dict, Optional, Union -import urllib.request - -MINIMAL_HTML = """ - - - -
- - - - -""" - -# pylint: disable=too-few-public-methods -class Node: - def __init__(self, name: str, libtype: str): - self.name = name - # "root" | "kernel" | "" - # "" indicates user space - self.libtype = libtype - self.value: int = 0 - self.children: list[Node] = [] - - def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]: - return { - "n": self.name, - "l": self.libtype, - "v": self.value, - "c": [x.to_json() for x in self.children] - } - - -class FlameGraphCLI: - def __init__(self, args): - self.args = args - self.stack = Node("all", "root") - - @staticmethod - def get_libtype_from_dso(dso: Optional[str]) -> str: - """ - when kernel-debuginfo is installed, - dso points to /usr/lib/debug/lib/modules/*/vmlinux - """ - if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")): - return "kernel" - - return "" - - @staticmethod - def find_or_create_node(node: Node, name: str, libtype: str) -> Node: - for child in node.children: - if child.name == name: - return child - - child = Node(name, libtype) - node.children.append(child) - return child - - def process_event(self, event) -> None: - # ignore events where the event name does not match - # the one specified by the user - if self.args.event_name and event.get("ev_name") != self.args.event_name: - return - - pid = event.get("sample", {}).get("pid", 0) - # event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux - # for user-space processes; let's use pid for kernel or user-space distinction - if pid == 0: - comm = event["comm"] - libtype = "kernel" - else: - comm = f"{event['comm']} ({pid})" - libtype = "" - node = self.find_or_create_node(self.stack, comm, libtype) - - if "callchain" in event: - for entry in reversed(event["callchain"]): - name = entry.get("sym", {}).get("name", "[unknown]") - libtype = self.get_libtype_from_dso(entry.get("dso")) - node = self.find_or_create_node(node, name, libtype) - else: - name = event.get("symbol", "[unknown]") - libtype = self.get_libtype_from_dso(event.get("dso")) - node = self.find_or_create_node(node, name, libtype) - node.value += 1 - - def get_report_header(self) -> str: - if self.args.input == "-": - # when this script is invoked with "perf script flamegraph", - # no perf.data is created and we cannot read the header of it - return "" - - try: - # if the file name other than perf.data is given, - # we read the header of that file - if self.args.input: - output = subprocess.check_output(["perf", "report", "--header-only", - "-i", self.args.input]) - else: - output = subprocess.check_output(["perf", "report", "--header-only"]) - - result = output.decode("utf-8") - if self.args.event_name: - result += "\nFocused event: " + self.args.event_name - return result - except Exception as err: # pylint: disable=broad-except - print(f"Error reading report header: {err}", file=sys.stderr) - return "" - - def trace_end(self) -> None: - stacks_json = json.dumps(self.stack, default=lambda x: x.to_json()) - - if self.args.format == "html": - report_header = self.get_report_header() - options = { - "colorscheme": self.args.colorscheme, - "context": report_header - } - 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) - sys.exit(1) - s = None - while s not in ["y", "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": - sys.exit(1) - template = ("https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/" - "d3-flamegraph-base.html") - template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36" - - try: - with urllib.request.urlopen(template) as url_template: - output_str = "".join([ - l.decode("utf-8") for l in url_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 not in ["y", "n"]: - s = input(f"""Unexpected template md5sum. -{download_md5sum} != {template_md5sum}, for: -{output_str} -continue?[yn] """).lower() - if s == "n": - sys.exit(1) - - 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 - output_fn = self.args.output or "stacks.json" - - if output_fn == "-": - with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out: - out.write(output_str) - else: - print(f"dumping data to {output_fn}") - try: - with io.open(output_fn, "w", encoding="utf-8") as out: - out.write(output_str) - except IOError as err: - print(f"Error writing output file: {err}", file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Create flame graphs.") - parser.add_argument("-f", "--format", - default="html", choices=["json", "html"], - help="output file format") - parser.add_argument("-o", "--output", - help="output file name") - parser.add_argument("--template", - default="/usr/share/d3-flame-graph/d3-flamegraph-base.html", - help="path to flame graph HTML template") - parser.add_argument("--colorscheme", - default="blue-green", - help="flame graph color scheme", - 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") - parser.add_argument("-e", "--event", - default="", - dest="event_name", - type=str, - help="specify the event to generate flamegraph for") - - cli_args = parser.parse_args() - cli = FlameGraphCLI(cli_args) - - process_event = cli.process_event - trace_end = cli.trace_end diff --git a/tools/perf/scripts/python/futex-contention.py b/tools/perf/scripts/python/futex-contention.py deleted file mode 100644 index 7e884d46f920..000000000000 --- a/tools/perf/scripts/python/futex-contention.py +++ /dev/null @@ -1,57 +0,0 @@ -# futex contention -# (c) 2010, Arnaldo Carvalho de Melo -# Licensed under the terms of the GNU GPL License version 2 -# -# Translation of: -# -# http://sourceware.org/systemtap/wiki/WSFutexContention -# -# to perf python scripting. -# -# Measures futex contention - -from __future__ import print_function - -import os -import sys -sys.path.append(os.environ['PERF_EXEC_PATH'] + - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') -from Util import * - -process_names = {} -thread_thislock = {} -thread_blocktime = {} - -lock_waits = {} # long-lived stats on (tid,lock) blockage elapsed time -process_names = {} # long-lived pid-to-execname mapping - - -def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm, callchain, - nr, uaddr, op, val, utime, uaddr2, val3): - cmd = op & FUTEX_CMD_MASK - if cmd != FUTEX_WAIT: - return # we don't care about originators of WAKE events - - process_names[tid] = comm - thread_thislock[tid] = uaddr - thread_blocktime[tid] = nsecs(s, ns) - - -def syscalls__sys_exit_futex(event, ctxt, cpu, s, ns, tid, comm, callchain, - nr, ret): - if tid in thread_blocktime: - elapsed = nsecs(s, ns) - thread_blocktime[tid] - add_stats(lock_waits, (tid, thread_thislock[tid]), elapsed) - del thread_blocktime[tid] - del thread_thislock[tid] - - -def trace_begin(): - print("Press control+C to stop and show the summary") - - -def trace_end(): - for (tid, lock) in lock_waits: - min, max, avg, count = lock_waits[tid, lock] - print("%s[%d] lock %x contended %d times, %d avg ns [max: %d ns, min %d ns]" % - (process_names[tid], tid, lock, count, avg, max, min)) diff --git a/tools/perf/scripts/python/gecko.py b/tools/perf/scripts/python/gecko.py deleted file mode 100644 index bc5a72f94bfa..000000000000 --- a/tools/perf/scripts/python/gecko.py +++ /dev/null @@ -1,395 +0,0 @@ -# gecko.py - Convert perf record output to Firefox's gecko profile format -# SPDX-License-Identifier: GPL-2.0 -# -# The script converts perf.data to Gecko Profile Format, -# which can be read by https://profiler.firefox.com/. -# -# Usage: -# -# perf record -a -g -F 99 sleep 60 -# perf script report gecko -# -# Combined: -# -# perf script gecko -F 99 -a sleep 60 - -import os -import sys -import time -import json -import string -import random -import argparse -import threading -import webbrowser -import urllib.parse -from os import system -from functools import reduce -from dataclasses import dataclass, field -from http.server import HTTPServer, SimpleHTTPRequestHandler, test -from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any - -# Add the Perf-Trace-Util library to the Python path -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * - -StringID = int -StackID = int -FrameID = int -CategoryID = int -Milliseconds = float - -# start_time is intialiazed only once for the all event traces. -start_time = None - -# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425 -# Follow Brendan Gregg's Flamegraph convention: orange for kernel and yellow for user space by default. -CATEGORIES = None - -# The product name is used by the profiler UI to show the Operating system and Processor. -PRODUCT = os.popen('uname -op').read().strip() - -# store the output file -output_file = None - -# Here key = tid, value = Thread -tid_to_thread = dict() - -# The HTTP server is used to serve the profile to the profiler UI. -http_server_thread = None - -# The category index is used by the profiler UI to show the color of the flame graph. -USER_CATEGORY_INDEX = 0 -KERNEL_CATEGORY_INDEX = 1 - -# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156 -class Frame(NamedTuple): - string_id: StringID - relevantForJS: bool - innerWindowID: int - implementation: None - optimizations: None - line: None - column: None - category: CategoryID - subcategory: int - -# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216 -class Stack(NamedTuple): - prefix_id: Optional[StackID] - frame_id: FrameID - -# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90 -class Sample(NamedTuple): - stack_id: Optional[StackID] - time_ms: Milliseconds - responsiveness: int - -@dataclass -class Thread: - """A builder for a profile of the thread. - - Attributes: - comm: Thread command-line (name). - pid: process ID of containing process. - tid: thread ID. - samples: Timeline of profile samples. - frameTable: interned stack frame ID -> stack frame. - stringTable: interned string ID -> string. - stringMap: interned string -> string ID. - stackTable: interned stack ID -> stack. - stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID. - frameMap: Stack Frame string -> interned Frame ID. - comm: str - pid: int - tid: int - samples: List[Sample] = field(default_factory=list) - frameTable: List[Frame] = field(default_factory=list) - stringTable: List[str] = field(default_factory=list) - stringMap: Dict[str, int] = field(default_factory=dict) - stackTable: List[Stack] = field(default_factory=list) - stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict) - frameMap: Dict[str, int] = field(default_factory=dict) - """ - comm: str - pid: int - tid: int - samples: List[Sample] = field(default_factory=list) - frameTable: List[Frame] = field(default_factory=list) - stringTable: List[str] = field(default_factory=list) - stringMap: Dict[str, int] = field(default_factory=dict) - stackTable: List[Stack] = field(default_factory=list) - stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict) - frameMap: Dict[str, int] = field(default_factory=dict) - - def _intern_stack(self, frame_id: int, prefix_id: Optional[int]) -> int: - """Gets a matching stack, or saves the new stack. Returns a Stack ID.""" - key = f"{frame_id}" if prefix_id is None else f"{frame_id},{prefix_id}" - # key = (prefix_id, frame_id) - stack_id = self.stackMap.get(key) - if stack_id is None: - # return stack_id - stack_id = len(self.stackTable) - self.stackTable.append(Stack(prefix_id=prefix_id, frame_id=frame_id)) - self.stackMap[key] = stack_id - return stack_id - - def _intern_string(self, string: str) -> int: - """Gets a matching string, or saves the new string. Returns a String ID.""" - string_id = self.stringMap.get(string) - if string_id is not None: - return string_id - string_id = len(self.stringTable) - self.stringTable.append(string) - self.stringMap[string] = string_id - return string_id - - def _intern_frame(self, frame_str: str) -> int: - """Gets a matching stack frame, or saves the new frame. Returns a Frame ID.""" - frame_id = self.frameMap.get(frame_str) - if frame_id is not None: - return frame_id - frame_id = len(self.frameTable) - self.frameMap[frame_str] = frame_id - string_id = self._intern_string(frame_str) - - symbol_name_to_category = KERNEL_CATEGORY_INDEX if frame_str.find('kallsyms') != -1 \ - or frame_str.find('/vmlinux') != -1 \ - or frame_str.endswith('.ko)') \ - else USER_CATEGORY_INDEX - - self.frameTable.append(Frame( - string_id=string_id, - relevantForJS=False, - innerWindowID=0, - implementation=None, - optimizations=None, - line=None, - column=None, - category=symbol_name_to_category, - subcategory=None, - )) - return frame_id - - def _add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None: - """Add a timestamped stack trace sample to the thread builder. - Args: - comm: command-line (name) of the thread at this sample - stack: sampled stack frames. Root first, leaf last. - time_ms: timestamp of sample in milliseconds. - """ - # Ihreads may not set their names right after they are created. - # Instead, they might do it later. In such situations, to use the latest name they have set. - if self.comm != comm: - self.comm = comm - - prefix_stack_id = reduce(lambda prefix_id, frame: self._intern_stack - (self._intern_frame(frame), prefix_id), stack, None) - if prefix_stack_id is not None: - self.samples.append(Sample(stack_id=prefix_stack_id, - time_ms=time_ms, - responsiveness=0)) - - def _to_json_dict(self) -> Dict: - """Converts current Thread to GeckoThread JSON format.""" - # Gecko profile format is row-oriented data as List[List], - # And a schema for interpreting each index. - # Schema: - # https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md - # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230 - return { - "tid": self.tid, - "pid": self.pid, - "name": self.comm, - # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51 - "markers": { - "schema": { - "name": 0, - "startTime": 1, - "endTime": 2, - "phase": 3, - "category": 4, - "data": 5, - }, - "data": [], - }, - - # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90 - "samples": { - "schema": { - "stack": 0, - "time": 1, - "responsiveness": 2, - }, - "data": self.samples - }, - - # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156 - "frameTable": { - "schema": { - "location": 0, - "relevantForJS": 1, - "innerWindowID": 2, - "implementation": 3, - "optimizations": 4, - "line": 5, - "column": 6, - "category": 7, - "subcategory": 8, - }, - "data": self.frameTable, - }, - - # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216 - "stackTable": { - "schema": { - "prefix": 0, - "frame": 1, - }, - "data": self.stackTable, - }, - "stringTable": self.stringTable, - "registerTime": 0, - "unregisterTime": None, - "processType": "default", - } - -# Uses perf script python interface to parse each -# event and store the data in the thread builder. -def process_event(param_dict: Dict) -> None: - global start_time - global tid_to_thread - time_stamp = (param_dict['sample']['time'] // 1000) / 1000 - pid = param_dict['sample']['pid'] - tid = param_dict['sample']['tid'] - comm = param_dict['comm'] - - # Start time is the time of the first sample - if not start_time: - start_time = time_stamp - - # Parse and append the callchain of the current sample into a stack. - stack = [] - if param_dict['callchain']: - for call in param_dict['callchain']: - if 'sym' not in call: - continue - stack.append(f'{call["sym"]["name"]} (in {call["dso"]})') - if len(stack) != 0: - # Reverse the stack, as root come first and the leaf at the end. - stack = stack[::-1] - - # During perf record if -g is not used, the callchain is not available. - # In that case, the symbol and dso are available in the event parameters. - else: - func = param_dict['symbol'] if 'symbol' in param_dict else '[unknown]' - dso = param_dict['dso'] if 'dso' in param_dict else '[unknown]' - stack.append(f'{func} (in {dso})') - - # Add sample to the specific thread. - thread = tid_to_thread.get(tid) - if thread is None: - thread = Thread(comm=comm, pid=pid, tid=tid) - tid_to_thread[tid] = thread - thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp) - -def trace_begin() -> None: - global output_file - if (output_file is None): - print("Staring Firefox Profiler on your default browser...") - global http_server_thread - http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,)) - http_server_thread.daemon = True - http_server_thread.start() - -# Trace_end runs at the end and will be used to aggregate -# the data into the final json object and print it out to stdout. -def trace_end() -> None: - global output_file - threads = [thread._to_json_dict() for thread in tid_to_thread.values()] - - # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305 - gecko_profile_with_meta = { - "meta": { - "interval": 1, - "processType": 0, - "product": PRODUCT, - "stackwalk": 1, - "debug": 0, - "gcpoison": 0, - "asyncstack": 1, - "startTime": start_time, - "shutdownTime": None, - "version": 24, - "presymbolicated": True, - "categories": CATEGORIES, - "markerSchema": [], - }, - "libs": [], - "threads": threads, - "processes": [], - "pausedRanges": [], - } - # launch the profiler on local host if not specified --save-only args, otherwise print to file - if (output_file is None): - output_file = 'gecko_profile.json' - with open(output_file, 'w') as f: - json.dump(gecko_profile_with_meta, f, indent=2) - launchFirefox(output_file) - time.sleep(1) - print(f'[ perf gecko: Captured and wrote into {output_file} ]') - else: - print(f'[ perf gecko: Captured and wrote into {output_file} ]') - with open(output_file, 'w') as f: - json.dump(gecko_profile_with_meta, f, indent=2) - -# Used to enable Cross-Origin Resource Sharing (CORS) for requests coming from 'https://profiler.firefox.com', allowing it to access resources from this server. -class CORSRequestHandler(SimpleHTTPRequestHandler): - def end_headers (self): - self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com') - SimpleHTTPRequestHandler.end_headers(self) - -# start a local server to serve the gecko_profile.json file to the profiler.firefox.com -def launchFirefox(file): - safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}') - url = 'https://profiler.firefox.com/from-url/' + safe_string - webbrowser.open(f'{url}') - -def main() -> None: - global output_file - global CATEGORIES - parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization") - - # Add the command-line options - # Colors must be defined according to this: - # https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css - parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) - parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) - # If --save-only is specified, the output will be saved to a file instead of opening Firefox's profiler directly. - parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler') - - # Parse the command-line arguments - args = parser.parse_args() - # Access the values provided by the user - user_color = args.user_color - kernel_color = args.kernel_color - output_file = args.save_only - - CATEGORIES = [ - { - "name": 'User', - "color": user_color, - "subcategories": ['Other'] - }, - { - "name": 'Kernel', - "color": kernel_color, - "subcategories": ['Other'] - }, - ] - -if __name__ == '__main__': - main() diff --git a/tools/perf/scripts/python/intel-pt-events.py b/tools/perf/scripts/python/intel-pt-events.py deleted file mode 100644 index 346c89bd16d6..000000000000 --- a/tools/perf/scripts/python/intel-pt-events.py +++ /dev/null @@ -1,494 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# intel-pt-events.py: Print Intel PT Events including Power Events and PTWRITE -# Copyright (c) 2017-2021, Intel Corporation. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms and conditions of the GNU General Public License, -# version 2, as published by the Free Software Foundation. -# -# This program is distributed in the hope it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. - -from __future__ import division, print_function - -import io -import os -import sys -import struct -import argparse -import contextlib - -from libxed import LibXED -from ctypes import create_string_buffer, addressof - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import perf_set_itrace_options, \ - perf_sample_insn, perf_sample_srccode - -try: - broken_pipe_exception = BrokenPipeError -except: - broken_pipe_exception = IOError - -glb_switch_str = {} -glb_insn = False -glb_disassembler = None -glb_src = False -glb_source_file_name = None -glb_line_number = None -glb_dso = None -glb_stash_dict = {} -glb_output = None -glb_output_pos = 0 -glb_cpu = -1 -glb_time = 0 - -def get_optional_null(perf_dict, field): - if field in perf_dict: - return perf_dict[field] - return "" - -def get_optional_zero(perf_dict, field): - if field in perf_dict: - return perf_dict[field] - return 0 - -def get_optional_bytes(perf_dict, field): - if field in perf_dict: - return perf_dict[field] - return bytes() - -def get_optional(perf_dict, field): - if field in perf_dict: - return perf_dict[field] - return "[unknown]" - -def get_offset(perf_dict, field): - if field in perf_dict: - return "+%#x" % perf_dict[field] - return "" - -def trace_begin(): - ap = argparse.ArgumentParser(usage = "", add_help = False) - ap.add_argument("--insn-trace", action='store_true') - ap.add_argument("--src-trace", action='store_true') - ap.add_argument("--all-switch-events", action='store_true') - ap.add_argument("--interleave", type=int, nargs='?', const=4, default=0) - global glb_args - global glb_insn - global glb_src - glb_args = ap.parse_args() - if glb_args.insn_trace: - print("Intel PT Instruction Trace") - itrace = "i0nsepwxI" - glb_insn = True - elif glb_args.src_trace: - print("Intel PT Source Trace") - itrace = "i0nsepwxI" - glb_insn = True - glb_src = True - else: - print("Intel PT Branch Trace, Power Events, Event Trace and PTWRITE") - itrace = "bepwxI" - global glb_disassembler - try: - glb_disassembler = LibXED() - except: - glb_disassembler = None - perf_set_itrace_options(perf_script_context, itrace) - -def trace_end(): - if glb_args.interleave: - flush_stashed_output() - print("End") - -def trace_unhandled(event_name, context, event_fields_dict): - print(' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())])) - -def stash_output(): - global glb_stash_dict - global glb_output_pos - output_str = glb_output.getvalue()[glb_output_pos:] - n = len(output_str) - if n: - glb_output_pos += n - if glb_cpu not in glb_stash_dict: - glb_stash_dict[glb_cpu] = [] - glb_stash_dict[glb_cpu].append(output_str) - -def flush_stashed_output(): - global glb_stash_dict - while glb_stash_dict: - cpus = list(glb_stash_dict.keys()) - # Output at most glb_args.interleave output strings per cpu - for cpu in cpus: - items = glb_stash_dict[cpu] - countdown = glb_args.interleave - while len(items) and countdown: - sys.stdout.write(items[0]) - del items[0] - countdown -= 1 - if not items: - del glb_stash_dict[cpu] - -def print_ptwrite(raw_buf): - data = struct.unpack_from("> 32) & 0x3 - print("hints: %#x extensions: %#x" % (hints, extensions), end=' ') - -def print_pwre(raw_buf): - data = struct.unpack_from("> 7) & 1 - cstate = (payload >> 12) & 0xf - subcstate = (payload >> 8) & 0xf - print("hw: %u cstate: %u sub-cstate: %u" % (hw, cstate, subcstate), - end=' ') - -def print_exstop(raw_buf): - data = struct.unpack_from("> 4) & 0xf - wake_reason = (payload >> 8) & 0xf - print("deepest cstate: %u last cstate: %u wake reason: %#x" % - (deepest_cstate, last_cstate, wake_reason), end=' ') - -def print_psb(raw_buf): - data = struct.unpack_from("> 7 - vector = data[1] - evd_cnt = data[2] - s = glb_cfe[typ] - if s: - print(" cfe: %s IP: %u vector: %u" % (s, ip_flag, vector), end=' ') - else: - print(" cfe: %u IP: %u vector: %u" % (typ, ip_flag, vector), end=' ') - pos = 4 - for i in range(evd_cnt): - data = struct.unpack_from("%u %s branch" % (old_iflag, iflag, s), end=' ') - -def common_start_str(comm, sample): - ts = sample["time"] - cpu = sample["cpu"] - pid = sample["pid"] - tid = sample["tid"] - if "machine_pid" in sample: - machine_pid = sample["machine_pid"] - vcpu = sample["vcpu"] - return "VM:%5d VCPU:%03d %16s %5u/%-5u [%03u] %9u.%09u " % (machine_pid, vcpu, comm, pid, tid, cpu, ts / 1000000000, ts %1000000000) - else: - return "%16s %5u/%-5u [%03u] %9u.%09u " % (comm, pid, tid, cpu, ts / 1000000000, ts %1000000000) - -def print_common_start(comm, sample, name): - flags_disp = get_optional_null(sample, "flags_disp") - # Unused fields: - # period = sample["period"] - # phys_addr = sample["phys_addr"] - # weight = sample["weight"] - # transaction = sample["transaction"] - # cpumode = get_optional_zero(sample, "cpumode") - print(common_start_str(comm, sample) + "%8s %21s" % (name, flags_disp), end=' ') - -def print_instructions_start(comm, sample): - if "x" in get_optional_null(sample, "flags"): - print(common_start_str(comm, sample) + "x", end=' ') - else: - print(common_start_str(comm, sample), end=' ') - -def disassem(insn, ip): - inst = glb_disassembler.Instruction() - glb_disassembler.SetMode(inst, 0) # Assume 64-bit - buf = create_string_buffer(64) - buf.value = insn - return glb_disassembler.DisassembleOne(inst, addressof(buf), len(insn), ip) - -def print_common_ip(param_dict, sample, symbol, dso): - ip = sample["ip"] - offs = get_offset(param_dict, "symoff") - if "cyc_cnt" in sample: - cyc_cnt = sample["cyc_cnt"] - insn_cnt = get_optional_zero(sample, "insn_cnt") - ipc_str = " IPC: %#.2f (%u/%u)" % (insn_cnt / cyc_cnt, insn_cnt, cyc_cnt) - else: - ipc_str = "" - if glb_insn and glb_disassembler is not None: - insn = perf_sample_insn(perf_script_context) - if insn and len(insn): - cnt, text = disassem(insn, ip) - byte_str = ("%x" % ip).rjust(16) - if sys.version_info.major >= 3: - for k in range(cnt): - byte_str += " %02x" % insn[k] - else: - for k in xrange(cnt): - byte_str += " %02x" % ord(insn[k]) - print("%-40s %-30s" % (byte_str, text), end=' ') - print("%s%s (%s)" % (symbol, offs, dso), end=' ') - else: - print("%16x %s%s (%s)" % (ip, symbol, offs, dso), end=' ') - if "addr_correlates_sym" in sample: - addr = sample["addr"] - dso = get_optional(sample, "addr_dso") - symbol = get_optional(sample, "addr_symbol") - offs = get_offset(sample, "addr_symoff") - print("=> %x %s%s (%s)%s" % (addr, symbol, offs, dso, ipc_str)) - else: - print(ipc_str) - -def print_srccode(comm, param_dict, sample, symbol, dso, with_insn): - ip = sample["ip"] - if symbol == "[unknown]": - start_str = common_start_str(comm, sample) + ("%x" % ip).rjust(16).ljust(40) - else: - offs = get_offset(param_dict, "symoff") - start_str = common_start_str(comm, sample) + (symbol + offs).ljust(40) - - if with_insn and glb_insn and glb_disassembler is not None: - insn = perf_sample_insn(perf_script_context) - if insn and len(insn): - cnt, text = disassem(insn, ip) - start_str += text.ljust(30) - - global glb_source_file_name - global glb_line_number - global glb_dso - - source_file_name, line_number, source_line = perf_sample_srccode(perf_script_context) - if source_file_name: - if glb_line_number == line_number and glb_source_file_name == source_file_name: - src_str = "" - else: - if len(source_file_name) > 40: - src_file = ("..." + source_file_name[-37:]) + " " - else: - src_file = source_file_name.ljust(41) - if source_line is None: - src_str = src_file + str(line_number).rjust(4) + " " - else: - src_str = src_file + str(line_number).rjust(4) + " " + source_line - glb_dso = None - elif dso == glb_dso: - src_str = "" - else: - src_str = dso - glb_dso = dso - - glb_line_number = line_number - glb_source_file_name = source_file_name - - print(start_str, src_str) - -def do_process_event(param_dict): - sample = param_dict["sample"] - raw_buf = param_dict["raw_buf"] - comm = param_dict["comm"] - name = param_dict["ev_name"] - # Unused fields: - # callchain = param_dict["callchain"] - # brstack = param_dict["brstack"] - # brstacksym = param_dict["brstacksym"] - # event_attr = param_dict["attr"] - - # Symbol and dso info are not always resolved - dso = get_optional(param_dict, "dso") - symbol = get_optional(param_dict, "symbol") - - cpu = sample["cpu"] - if cpu in glb_switch_str: - print(glb_switch_str[cpu]) - del glb_switch_str[cpu] - - if name.startswith("instructions"): - if glb_src: - print_srccode(comm, param_dict, sample, symbol, dso, True) - else: - print_instructions_start(comm, sample) - print_common_ip(param_dict, sample, symbol, dso) - elif name.startswith("branches"): - if glb_src: - print_srccode(comm, param_dict, sample, symbol, dso, False) - else: - print_common_start(comm, sample, name) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "ptwrite": - print_common_start(comm, sample, name) - print_ptwrite(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "cbr": - print_common_start(comm, sample, name) - print_cbr(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "mwait": - print_common_start(comm, sample, name) - print_mwait(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "pwre": - print_common_start(comm, sample, name) - print_pwre(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "exstop": - print_common_start(comm, sample, name) - print_exstop(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "pwrx": - print_common_start(comm, sample, name) - print_pwrx(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "psb": - print_common_start(comm, sample, name) - print_psb(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "evt": - print_common_start(comm, sample, name) - print_evt(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - elif name == "iflag": - print_common_start(comm, sample, name) - print_iflag(raw_buf) - print_common_ip(param_dict, sample, symbol, dso) - else: - print_common_start(comm, sample, name) - print_common_ip(param_dict, sample, symbol, dso) - -def interleave_events(param_dict): - global glb_cpu - global glb_time - global glb_output - global glb_output_pos - - sample = param_dict["sample"] - glb_cpu = sample["cpu"] - ts = sample["time"] - - if glb_time != ts: - glb_time = ts - flush_stashed_output() - - glb_output_pos = 0 - with contextlib.redirect_stdout(io.StringIO()) as glb_output: - do_process_event(param_dict) - - stash_output() - -def process_event(param_dict): - try: - if glb_args.interleave: - interleave_events(param_dict) - else: - do_process_event(param_dict) - except broken_pipe_exception: - # Stop python printing broken pipe errors and traceback - sys.stdout = open(os.devnull, 'w') - sys.exit(1) - -def auxtrace_error(typ, code, cpu, pid, tid, ip, ts, msg, cpumode, *x): - if glb_args.interleave: - flush_stashed_output() - if len(x) >= 2 and x[0]: - machine_pid = x[0] - vcpu = x[1] - else: - machine_pid = 0 - vcpu = -1 - try: - if machine_pid: - print("VM:%5d VCPU:%03d %16s %5u/%-5u [%03u] %9u.%09u error type %u code %u: %s ip 0x%16x" % - (machine_pid, vcpu, "Trace error", pid, tid, cpu, ts / 1000000000, ts %1000000000, typ, code, msg, ip)) - else: - print("%16s %5u/%-5u [%03u] %9u.%09u error type %u code %u: %s ip 0x%16x" % - ("Trace error", pid, tid, cpu, ts / 1000000000, ts %1000000000, typ, code, msg, ip)) - except broken_pipe_exception: - # Stop python printing broken pipe errors and traceback - sys.stdout = open(os.devnull, 'w') - sys.exit(1) - -def context_switch(ts, cpu, pid, tid, np_pid, np_tid, machine_pid, out, out_preempt, *x): - if glb_args.interleave: - flush_stashed_output() - if out: - out_str = "Switch out " - else: - out_str = "Switch In " - if out_preempt: - preempt_str = "preempt" - else: - preempt_str = "" - if len(x) >= 2 and x[0]: - machine_pid = x[0] - vcpu = x[1] - else: - vcpu = None; - if machine_pid == -1: - machine_str = "" - elif vcpu is None: - machine_str = "machine PID %d" % machine_pid - else: - machine_str = "machine PID %d VCPU %d" % (machine_pid, vcpu) - switch_str = "%16s %5d/%-5d [%03u] %9u.%09u %5d/%-5d %s %s" % \ - (out_str, pid, tid, cpu, ts / 1000000000, ts %1000000000, np_pid, np_tid, machine_str, preempt_str) - if glb_args.all_switch_events: - print(switch_str) - else: - global glb_switch_str - glb_switch_str[cpu] = switch_str diff --git a/tools/perf/scripts/python/libxed.py b/tools/perf/scripts/python/libxed.py deleted file mode 100644 index 2c70a5a7eb9c..000000000000 --- a/tools/perf/scripts/python/libxed.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python -# SPDX-License-Identifier: GPL-2.0 -# libxed.py: Python wrapper for libxed.so -# Copyright (c) 2014-2021, Intel Corporation. - -# To use Intel XED, libxed.so must be present. To build and install -# libxed.so: -# git clone https://github.com/intelxed/mbuild.git mbuild -# git clone https://github.com/intelxed/xed -# cd xed -# ./mfile.py --share -# sudo ./mfile.py --prefix=/usr/local install -# sudo ldconfig -# - -import sys - -from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \ - c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong - -# XED Disassembler - -class xed_state_t(Structure): - - _fields_ = [ - ("mode", c_int), - ("width", c_int) - ] - -class XEDInstruction(): - - def __init__(self, libxed): - # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion - xedd_t = c_byte * 512 - self.xedd = xedd_t() - self.xedp = addressof(self.xedd) - libxed.xed_decoded_inst_zero(self.xedp) - self.state = xed_state_t() - self.statep = addressof(self.state) - # Buffer for disassembled instruction text - self.buffer = create_string_buffer(256) - self.bufferp = addressof(self.buffer) - -class LibXED(): - - def __init__(self): - try: - self.libxed = CDLL("libxed.so") - except: - self.libxed = None - if not self.libxed: - self.libxed = CDLL("/usr/local/lib/libxed.so") - - self.xed_tables_init = self.libxed.xed_tables_init - self.xed_tables_init.restype = None - self.xed_tables_init.argtypes = [] - - self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero - self.xed_decoded_inst_zero.restype = None - self.xed_decoded_inst_zero.argtypes = [ c_void_p ] - - self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode - self.xed_operand_values_set_mode.restype = None - self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ] - - self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode - self.xed_decoded_inst_zero_keep_mode.restype = None - self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ] - - self.xed_decode = self.libxed.xed_decode - self.xed_decode.restype = c_int - self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ] - - self.xed_format_context = self.libxed.xed_format_context - self.xed_format_context.restype = c_uint - self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ] - - self.xed_tables_init() - - def Instruction(self): - return XEDInstruction(self) - - def SetMode(self, inst, mode): - if mode: - inst.state.mode = 4 # 32-bit - inst.state.width = 4 # 4 bytes - else: - inst.state.mode = 1 # 64-bit - inst.state.width = 8 # 8 bytes - self.xed_operand_values_set_mode(inst.xedp, inst.statep) - - def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip): - self.xed_decoded_inst_zero_keep_mode(inst.xedp) - err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt) - if err: - return 0, "" - # Use AT&T mode (2), alternative is Intel (3) - ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0) - if not ok: - return 0, "" - if sys.version_info[0] == 2: - result = inst.buffer.value - else: - result = inst.buffer.value.decode() - # Return instruction length and the disassembled instruction text - # For now, assume the length is in byte 166 - return inst.xedd[166], result diff --git a/tools/perf/scripts/python/mem-phys-addr.py b/tools/perf/scripts/python/mem-phys-addr.py deleted file mode 100644 index 5e237a5a5f1b..000000000000 --- a/tools/perf/scripts/python/mem-phys-addr.py +++ /dev/null @@ -1,127 +0,0 @@ -# mem-phys-addr.py: Resolve physical address samples -# SPDX-License-Identifier: GPL-2.0 -# -# Copyright (c) 2018, Intel Corporation. - -import os -import sys -import re -import bisect -import collections -from dataclasses import dataclass -from typing import (Dict, Optional) - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -@dataclass(frozen=True) -class IomemEntry: - """Read from a line in /proc/iomem""" - begin: int - end: int - indent: int - label: str - -# Physical memory layout from /proc/iomem. Key is the indent and then -# a list of ranges. -iomem: Dict[int, list[IomemEntry]] = collections.defaultdict(list) -# Child nodes from the iomem parent. -children: Dict[IomemEntry, set[IomemEntry]] = collections.defaultdict(set) -# Maximum indent seen before an entry in the iomem file. -max_indent: int = 0 -# Count for each range of memory. -load_mem_type_cnt: Dict[IomemEntry, int] = collections.Counter() -# Perf event name set from the first sample in the data. -event_name: Optional[str] = None - -def parse_iomem(): - """Populate iomem from /proc/iomem file""" - global iomem - global max_indent - global children - with open('/proc/iomem', 'r', encoding='ascii') as f: - for line in f: - indent = 0 - while line[indent] == ' ': - indent += 1 - if indent > max_indent: - max_indent = indent - m = re.split('-|:', line, 2) - begin = int(m[0], 16) - end = int(m[1], 16) - label = m[2].strip() - entry = IomemEntry(begin, end, indent, label) - # Before adding entry, search for a parent node using its begin. - if indent > 0: - parent = find_memory_type(begin) - assert parent, f"Given indent expected a parent for {label}" - children[parent].add(entry) - iomem[indent].append(entry) - -def find_memory_type(phys_addr) -> Optional[IomemEntry]: - """Search iomem for the range containing phys_addr with the maximum indent""" - for i in range(max_indent, -1, -1): - if i not in iomem: - continue - position = bisect.bisect_right(iomem[i], phys_addr, - key=lambda entry: entry.begin) - if position is None: - continue - iomem_entry = iomem[i][position-1] - if iomem_entry.begin <= phys_addr <= iomem_entry.end: - return iomem_entry - print(f"Didn't find {phys_addr}") - return None - -def print_memory_type(): - print(f"Event: {event_name}") - print(f"{'Memory type':<40} {'count':>10} {'percentage':>10}") - print(f"{'-' * 40:<40} {'-' * 10:>10} {'-' * 10:>10}") - total = sum(load_mem_type_cnt.values()) - # Add count from children into the parent. - for i in range(max_indent, -1, -1): - if i not in iomem: - continue - for entry in iomem[i]: - global children - for child in children[entry]: - if load_mem_type_cnt[child] > 0: - load_mem_type_cnt[entry] += load_mem_type_cnt[child] - - def print_entries(entries): - """Print counts from parents down to their children""" - global children - for entry in sorted(entries, - key = lambda entry: load_mem_type_cnt[entry], - reverse = True): - count = load_mem_type_cnt[entry] - if count > 0: - mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}" - percent = 100 * count / total - print(f"{mem_type:<40} {count:>10} {percent:>10.1f}") - print_entries(children[entry]) - - print_entries(iomem[0]) - -def trace_begin(): - parse_iomem() - -def trace_end(): - print_memory_type() - -def process_event(param_dict): - if "sample" not in param_dict: - return - - sample = param_dict["sample"] - if "phys_addr" not in sample: - return - - phys_addr = sample["phys_addr"] - entry = find_memory_type(phys_addr) - if entry: - load_mem_type_cnt[entry] += 1 - - global event_name - if event_name is None: - event_name = param_dict["ev_name"] diff --git a/tools/perf/scripts/python/net_dropmonitor.py b/tools/perf/scripts/python/net_dropmonitor.py deleted file mode 100755 index a97e7a6e0940..000000000000 --- a/tools/perf/scripts/python/net_dropmonitor.py +++ /dev/null @@ -1,78 +0,0 @@ -# Monitor the system for dropped packets and proudce a report of drop locations and counts -# SPDX-License-Identifier: GPL-2.0 - -from __future__ import print_function - -import os -import sys - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import * - -drop_log = {} -kallsyms = [] - -def get_kallsyms_table(): - global kallsyms - - try: - f = open("/proc/kallsyms", "r") - except: - return - - for line in f: - loc = int(line.split()[0], 16) - name = line.split()[2] - kallsyms.append((loc, name)) - kallsyms.sort() - -def get_sym(sloc): - loc = int(sloc) - - # Invariant: kallsyms[i][0] <= loc for all 0 <= i <= start - # kallsyms[i][0] > loc for all end <= i < len(kallsyms) - start, end = -1, len(kallsyms) - while end != start + 1: - pivot = (start + end) // 2 - if loc < kallsyms[pivot][0]: - end = pivot - else: - start = pivot - - # Now (start == -1 or kallsyms[start][0] <= loc) - # and (start == len(kallsyms) - 1 or loc < kallsyms[start + 1][0]) - if start >= 0: - symloc, name = kallsyms[start] - return (name, loc - symloc) - else: - return (None, 0) - -def print_drop_table(): - print("%25s %25s %25s" % ("LOCATION", "OFFSET", "COUNT")) - for i in drop_log.keys(): - (sym, off) = get_sym(i) - if sym == None: - sym = i - print("%25s %25s %25s" % (sym, off, drop_log[i])) - - -def trace_begin(): - print("Starting trace (Ctrl-C to dump results)") - -def trace_end(): - print("Gathering kallsyms data") - get_kallsyms_table() - print_drop_table() - -# called from perf, when it finds a corresponding event -def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, callchain, - skbaddr, location, protocol, reason): - slocation = str(location) - try: - drop_log[slocation] = drop_log[slocation] + 1 - except: - drop_log[slocation] = 1 diff --git a/tools/perf/scripts/python/netdev-times.py b/tools/perf/scripts/python/netdev-times.py deleted file mode 100644 index 30c4bccee5b2..000000000000 --- a/tools/perf/scripts/python/netdev-times.py +++ /dev/null @@ -1,473 +0,0 @@ -# Display a process of packets and processed time. -# SPDX-License-Identifier: GPL-2.0 -# It helps us to investigate networking or network device. -# -# options -# tx: show only tx chart -# rx: show only rx chart -# dev=: show only thing related to specified device -# debug: work with debug mode. It shows buffer status. - -from __future__ import print_function - -import os -import sys - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import * -from functools import cmp_to_key - -all_event_list = []; # insert all tracepoint event related with this script -irq_dic = {}; # key is cpu and value is a list which stacks irqs - # which raise NET_RX softirq -net_rx_dic = {}; # key is cpu and value include time of NET_RX softirq-entry - # and a list which stacks receive -receive_hunk_list = []; # a list which include a sequence of receive events -rx_skb_list = []; # received packet list for matching - # skb_copy_datagram_iovec - -buffer_budget = 65536; # the budget of rx_skb_list, tx_queue_list and - # tx_xmit_list -of_count_rx_skb_list = 0; # overflow count - -tx_queue_list = []; # list of packets which pass through dev_queue_xmit -of_count_tx_queue_list = 0; # overflow count - -tx_xmit_list = []; # list of packets which pass through dev_hard_start_xmit -of_count_tx_xmit_list = 0; # overflow count - -tx_free_list = []; # list of packets which is freed - -# options -show_tx = 0; -show_rx = 0; -dev = 0; # store a name of device specified by option "dev=" -debug = 0; - -# indices of event_info tuple -EINFO_IDX_NAME= 0 -EINFO_IDX_CONTEXT=1 -EINFO_IDX_CPU= 2 -EINFO_IDX_TIME= 3 -EINFO_IDX_PID= 4 -EINFO_IDX_COMM= 5 - -# Calculate a time interval(msec) from src(nsec) to dst(nsec) -def diff_msec(src, dst): - return (dst - src) / 1000000.0 - -# Display a process of transmitting a packet -def print_transmit(hunk): - if dev != 0 and hunk['dev'].find(dev) < 0: - return - print("%7s %5d %6d.%06dsec %12.3fmsec %12.3fmsec" % - (hunk['dev'], hunk['len'], - nsecs_secs(hunk['queue_t']), - nsecs_nsecs(hunk['queue_t'])/1000, - diff_msec(hunk['queue_t'], hunk['xmit_t']), - diff_msec(hunk['xmit_t'], hunk['free_t']))) - -# Format for displaying rx packet processing -PF_IRQ_ENTRY= " irq_entry(+%.3fmsec irq=%d:%s)" -PF_SOFT_ENTRY=" softirq_entry(+%.3fmsec)" -PF_NAPI_POLL= " napi_poll_exit(+%.3fmsec %s)" -PF_JOINT= " |" -PF_WJOINT= " | |" -PF_NET_RECV= " |---netif_receive_skb(+%.3fmsec skb=%x len=%d)" -PF_NET_RX= " |---netif_rx(+%.3fmsec skb=%x)" -PF_CPY_DGRAM= " | skb_copy_datagram_iovec(+%.3fmsec %d:%s)" -PF_KFREE_SKB= " | kfree_skb(+%.3fmsec location=%x)" -PF_CONS_SKB= " | consume_skb(+%.3fmsec)" - -# Display a process of received packets and interrputs associated with -# a NET_RX softirq -def print_receive(hunk): - show_hunk = 0 - irq_list = hunk['irq_list'] - cpu = irq_list[0]['cpu'] - base_t = irq_list[0]['irq_ent_t'] - # check if this hunk should be showed - if dev != 0: - for i in range(len(irq_list)): - if irq_list[i]['name'].find(dev) >= 0: - show_hunk = 1 - break - else: - show_hunk = 1 - if show_hunk == 0: - return - - print("%d.%06dsec cpu=%d" % - (nsecs_secs(base_t), nsecs_nsecs(base_t)/1000, cpu)) - for i in range(len(irq_list)): - print(PF_IRQ_ENTRY % - (diff_msec(base_t, irq_list[i]['irq_ent_t']), - irq_list[i]['irq'], irq_list[i]['name'])) - print(PF_JOINT) - irq_event_list = irq_list[i]['event_list'] - for j in range(len(irq_event_list)): - irq_event = irq_event_list[j] - if irq_event['event'] == 'netif_rx': - print(PF_NET_RX % - (diff_msec(base_t, irq_event['time']), - irq_event['skbaddr'])) - print(PF_JOINT) - print(PF_SOFT_ENTRY % - diff_msec(base_t, hunk['sirq_ent_t'])) - print(PF_JOINT) - event_list = hunk['event_list'] - for i in range(len(event_list)): - event = event_list[i] - if event['event_name'] == 'napi_poll': - print(PF_NAPI_POLL % - (diff_msec(base_t, event['event_t']), - event['dev'])) - if i == len(event_list) - 1: - print("") - else: - print(PF_JOINT) - else: - print(PF_NET_RECV % - (diff_msec(base_t, event['event_t']), - event['skbaddr'], - event['len'])) - if 'comm' in event.keys(): - print(PF_WJOINT) - print(PF_CPY_DGRAM % - (diff_msec(base_t, event['comm_t']), - event['pid'], event['comm'])) - elif 'handle' in event.keys(): - print(PF_WJOINT) - if event['handle'] == "kfree_skb": - print(PF_KFREE_SKB % - (diff_msec(base_t, - event['comm_t']), - event['location'])) - elif event['handle'] == "consume_skb": - print(PF_CONS_SKB % - diff_msec(base_t, - event['comm_t'])) - print(PF_JOINT) - -def trace_begin(): - global show_tx - global show_rx - global dev - global debug - - for i in range(len(sys.argv)): - if i == 0: - continue - arg = sys.argv[i] - if arg == 'tx': - show_tx = 1 - elif arg =='rx': - show_rx = 1 - elif arg.find('dev=',0, 4) >= 0: - dev = arg[4:] - elif arg == 'debug': - debug = 1 - if show_tx == 0 and show_rx == 0: - show_tx = 1 - show_rx = 1 - -def trace_end(): - # order all events in time - all_event_list.sort(key=cmp_to_key(lambda a,b :a[EINFO_IDX_TIME] < b[EINFO_IDX_TIME])) - # process all events - for i in range(len(all_event_list)): - event_info = all_event_list[i] - name = event_info[EINFO_IDX_NAME] - if name == 'irq__softirq_exit': - handle_irq_softirq_exit(event_info) - elif name == 'irq__softirq_entry': - handle_irq_softirq_entry(event_info) - elif name == 'irq__softirq_raise': - handle_irq_softirq_raise(event_info) - elif name == 'irq__irq_handler_entry': - handle_irq_handler_entry(event_info) - elif name == 'irq__irq_handler_exit': - handle_irq_handler_exit(event_info) - elif name == 'napi__napi_poll': - handle_napi_poll(event_info) - elif name == 'net__netif_receive_skb': - handle_netif_receive_skb(event_info) - elif name == 'net__netif_rx': - handle_netif_rx(event_info) - elif name == 'skb__skb_copy_datagram_iovec': - handle_skb_copy_datagram_iovec(event_info) - elif name == 'net__net_dev_queue': - handle_net_dev_queue(event_info) - elif name == 'net__net_dev_xmit': - handle_net_dev_xmit(event_info) - elif name == 'skb__kfree_skb': - handle_kfree_skb(event_info) - elif name == 'skb__consume_skb': - handle_consume_skb(event_info) - # display receive hunks - if show_rx: - for i in range(len(receive_hunk_list)): - print_receive(receive_hunk_list[i]) - # display transmit hunks - if show_tx: - print(" dev len Qdisc " - " netdevice free") - for i in range(len(tx_free_list)): - print_transmit(tx_free_list[i]) - if debug: - print("debug buffer status") - print("----------------------------") - print("xmit Qdisc:remain:%d overflow:%d" % - (len(tx_queue_list), of_count_tx_queue_list)) - print("xmit netdevice:remain:%d overflow:%d" % - (len(tx_xmit_list), of_count_tx_xmit_list)) - print("receive:remain:%d overflow:%d" % - (len(rx_skb_list), of_count_rx_skb_list)) - -# called from perf, when it finds a correspoinding event -def irq__softirq_entry(name, context, cpu, sec, nsec, pid, comm, callchain, vec): - if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": - return - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) - all_event_list.append(event_info) - -def irq__softirq_exit(name, context, cpu, sec, nsec, pid, comm, callchain, vec): - if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": - return - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) - all_event_list.append(event_info) - -def irq__softirq_raise(name, context, cpu, sec, nsec, pid, comm, callchain, vec): - if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": - return - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) - all_event_list.append(event_info) - -def irq__irq_handler_entry(name, context, cpu, sec, nsec, pid, comm, - callchain, irq, irq_name): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - irq, irq_name) - all_event_list.append(event_info) - -def irq__irq_handler_exit(name, context, cpu, sec, nsec, pid, comm, callchain, irq, ret): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, irq, ret) - all_event_list.append(event_info) - -def napi__napi_poll(name, context, cpu, sec, nsec, pid, comm, callchain, napi, - dev_name, work=None, budget=None): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - napi, dev_name, work, budget) - all_event_list.append(event_info) - -def net__netif_receive_skb(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, - skblen, dev_name): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr, skblen, dev_name) - all_event_list.append(event_info) - -def net__netif_rx(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, - skblen, dev_name): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr, skblen, dev_name) - all_event_list.append(event_info) - -def net__net_dev_queue(name, context, cpu, sec, nsec, pid, comm, callchain, - skbaddr, skblen, dev_name): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr, skblen, dev_name) - all_event_list.append(event_info) - -def net__net_dev_xmit(name, context, cpu, sec, nsec, pid, comm, callchain, - skbaddr, skblen, rc, dev_name): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr, skblen, rc ,dev_name) - all_event_list.append(event_info) - -def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, callchain, - skbaddr, location, protocol, reason): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr, location, protocol, reason) - all_event_list.append(event_info) - -def skb__consume_skb(name, context, cpu, sec, nsec, pid, comm, callchain, - skbaddr, location): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr) - all_event_list.append(event_info) - -def skb__skb_copy_datagram_iovec(name, context, cpu, sec, nsec, pid, comm, callchain, - skbaddr, skblen): - event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, - skbaddr, skblen) - all_event_list.append(event_info) - -def handle_irq_handler_entry(event_info): - (name, context, cpu, time, pid, comm, irq, irq_name) = event_info - if cpu not in irq_dic.keys(): - irq_dic[cpu] = [] - irq_record = {'irq':irq, 'name':irq_name, 'cpu':cpu, 'irq_ent_t':time} - irq_dic[cpu].append(irq_record) - -def handle_irq_handler_exit(event_info): - (name, context, cpu, time, pid, comm, irq, ret) = event_info - if cpu not in irq_dic.keys(): - return - irq_record = irq_dic[cpu].pop() - if irq != irq_record['irq']: - return - irq_record.update({'irq_ext_t':time}) - # if an irq doesn't include NET_RX softirq, drop. - if 'event_list' in irq_record.keys(): - irq_dic[cpu].append(irq_record) - -def handle_irq_softirq_raise(event_info): - (name, context, cpu, time, pid, comm, vec) = event_info - if cpu not in irq_dic.keys() \ - or len(irq_dic[cpu]) == 0: - return - irq_record = irq_dic[cpu].pop() - if 'event_list' in irq_record.keys(): - irq_event_list = irq_record['event_list'] - else: - irq_event_list = [] - irq_event_list.append({'time':time, 'event':'sirq_raise'}) - irq_record.update({'event_list':irq_event_list}) - irq_dic[cpu].append(irq_record) - -def handle_irq_softirq_entry(event_info): - (name, context, cpu, time, pid, comm, vec) = event_info - net_rx_dic[cpu] = {'sirq_ent_t':time, 'event_list':[]} - -def handle_irq_softirq_exit(event_info): - (name, context, cpu, time, pid, comm, vec) = event_info - irq_list = [] - event_list = 0 - if cpu in irq_dic.keys(): - irq_list = irq_dic[cpu] - del irq_dic[cpu] - if cpu in net_rx_dic.keys(): - sirq_ent_t = net_rx_dic[cpu]['sirq_ent_t'] - event_list = net_rx_dic[cpu]['event_list'] - del net_rx_dic[cpu] - if irq_list == [] or event_list == 0: - return - rec_data = {'sirq_ent_t':sirq_ent_t, 'sirq_ext_t':time, - 'irq_list':irq_list, 'event_list':event_list} - # merge information related to a NET_RX softirq - receive_hunk_list.append(rec_data) - -def handle_napi_poll(event_info): - (name, context, cpu, time, pid, comm, napi, dev_name, - work, budget) = event_info - if cpu in net_rx_dic.keys(): - event_list = net_rx_dic[cpu]['event_list'] - rec_data = {'event_name':'napi_poll', - 'dev':dev_name, 'event_t':time, - 'work':work, 'budget':budget} - event_list.append(rec_data) - -def handle_netif_rx(event_info): - (name, context, cpu, time, pid, comm, - skbaddr, skblen, dev_name) = event_info - if cpu not in irq_dic.keys() \ - or len(irq_dic[cpu]) == 0: - return - irq_record = irq_dic[cpu].pop() - if 'event_list' in irq_record.keys(): - irq_event_list = irq_record['event_list'] - else: - irq_event_list = [] - irq_event_list.append({'time':time, 'event':'netif_rx', - 'skbaddr':skbaddr, 'skblen':skblen, 'dev_name':dev_name}) - irq_record.update({'event_list':irq_event_list}) - irq_dic[cpu].append(irq_record) - -def handle_netif_receive_skb(event_info): - global of_count_rx_skb_list - - (name, context, cpu, time, pid, comm, - skbaddr, skblen, dev_name) = event_info - if cpu in net_rx_dic.keys(): - rec_data = {'event_name':'netif_receive_skb', - 'event_t':time, 'skbaddr':skbaddr, 'len':skblen} - event_list = net_rx_dic[cpu]['event_list'] - event_list.append(rec_data) - rx_skb_list.insert(0, rec_data) - if len(rx_skb_list) > buffer_budget: - rx_skb_list.pop() - of_count_rx_skb_list += 1 - -def handle_net_dev_queue(event_info): - global of_count_tx_queue_list - - (name, context, cpu, time, pid, comm, - skbaddr, skblen, dev_name) = event_info - skb = {'dev':dev_name, 'skbaddr':skbaddr, 'len':skblen, 'queue_t':time} - tx_queue_list.insert(0, skb) - if len(tx_queue_list) > buffer_budget: - tx_queue_list.pop() - of_count_tx_queue_list += 1 - -def handle_net_dev_xmit(event_info): - global of_count_tx_xmit_list - - (name, context, cpu, time, pid, comm, - skbaddr, skblen, rc, dev_name) = event_info - if rc == 0: # NETDEV_TX_OK - for i in range(len(tx_queue_list)): - skb = tx_queue_list[i] - if skb['skbaddr'] == skbaddr: - skb['xmit_t'] = time - tx_xmit_list.insert(0, skb) - del tx_queue_list[i] - if len(tx_xmit_list) > buffer_budget: - tx_xmit_list.pop() - of_count_tx_xmit_list += 1 - return - -def handle_kfree_skb(event_info): - (name, context, cpu, time, pid, comm, - skbaddr, location, protocol, reason) = event_info - for i in range(len(tx_queue_list)): - skb = tx_queue_list[i] - if skb['skbaddr'] == skbaddr: - del tx_queue_list[i] - return - for i in range(len(tx_xmit_list)): - skb = tx_xmit_list[i] - if skb['skbaddr'] == skbaddr: - skb['free_t'] = time - tx_free_list.append(skb) - del tx_xmit_list[i] - return - for i in range(len(rx_skb_list)): - rec_data = rx_skb_list[i] - if rec_data['skbaddr'] == skbaddr: - rec_data.update({'handle':"kfree_skb", - 'comm':comm, 'pid':pid, 'comm_t':time}) - del rx_skb_list[i] - return - -def handle_consume_skb(event_info): - (name, context, cpu, time, pid, comm, skbaddr) = event_info - for i in range(len(tx_xmit_list)): - skb = tx_xmit_list[i] - if skb['skbaddr'] == skbaddr: - skb['free_t'] = time - tx_free_list.append(skb) - del tx_xmit_list[i] - return - -def handle_skb_copy_datagram_iovec(event_info): - (name, context, cpu, time, pid, comm, skbaddr, skblen) = event_info - for i in range(len(rx_skb_list)): - rec_data = rx_skb_list[i] - if skbaddr == rec_data['skbaddr']: - rec_data.update({'handle':"skb_copy_datagram_iovec", - 'comm':comm, 'pid':pid, 'comm_t':time}) - del rx_skb_list[i] - return diff --git a/tools/perf/scripts/python/parallel-perf.py b/tools/perf/scripts/python/parallel-perf.py deleted file mode 100755 index be85fd7f6632..000000000000 --- a/tools/perf/scripts/python/parallel-perf.py +++ /dev/null @@ -1,989 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0 -# -# Run a perf script command multiple times in parallel, using perf script -# options --cpu and --time so that each job processes a different chunk -# of the data. -# -# Copyright (c) 2024, Intel Corporation. - -import subprocess -import argparse -import pathlib -import shlex -import time -import copy -import sys -import os -import re - -glb_prog_name = "parallel-perf.py" -glb_min_interval = 10.0 -glb_min_samples = 64 - -class Verbosity(): - - def __init__(self, quiet=False, verbose=False, debug=False): - self.normal = True - self.verbose = verbose - self.debug = debug - self.self_test = True - if self.debug: - self.verbose = True - if self.verbose: - quiet = False - if quiet: - self.normal = False - -# Manage work (Start/Wait/Kill), as represented by a subprocess.Popen command -class Work(): - - def __init__(self, cmd, pipe_to, output_dir="."): - self.popen = None - self.consumer = None - self.cmd = cmd - self.pipe_to = pipe_to - self.output_dir = output_dir - self.cmdout_name = f"{output_dir}/cmd.txt" - self.stdout_name = f"{output_dir}/out.txt" - self.stderr_name = f"{output_dir}/err.txt" - - def Command(self): - sh_cmd = [ shlex.quote(x) for x in self.cmd ] - return " ".join(self.cmd) - - def Stdout(self): - return open(self.stdout_name, "w") - - def Stderr(self): - return open(self.stderr_name, "w") - - def CreateOutputDir(self): - pathlib.Path(self.output_dir).mkdir(parents=True, exist_ok=True) - - def Start(self): - if self.popen: - return - self.CreateOutputDir() - with open(self.cmdout_name, "w") as f: - f.write(self.Command()) - f.write("\n") - stdout = self.Stdout() - stderr = self.Stderr() - if self.pipe_to: - self.popen = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=stderr) - args = shlex.split(self.pipe_to) - self.consumer = subprocess.Popen(args, stdin=self.popen.stdout, stdout=stdout, stderr=stderr) - else: - self.popen = subprocess.Popen(self.cmd, stdout=stdout, stderr=stderr) - - def RemoveEmptyErrFile(self): - if os.path.exists(self.stderr_name): - if os.path.getsize(self.stderr_name) == 0: - os.unlink(self.stderr_name) - - def Errors(self): - if os.path.exists(self.stderr_name): - if os.path.getsize(self.stderr_name) != 0: - return [ f"Non-empty error file {self.stderr_name}" ] - return [] - - def TidyUp(self): - self.RemoveEmptyErrFile() - - def RawPollWait(self, p, wait): - if wait: - return p.wait() - return p.poll() - - def Poll(self, wait=False): - if not self.popen: - return None - result = self.RawPollWait(self.popen, wait) - if self.consumer: - res = result - result = self.RawPollWait(self.consumer, wait) - if result != None and res == None: - self.popen.kill() - result = None - elif result == 0 and res != None and res != 0: - result = res - if result != None: - self.TidyUp() - return result - - def Wait(self): - return self.Poll(wait=True) - - def Kill(self): - if not self.popen: - return - self.popen.kill() - if self.consumer: - self.consumer.kill() - -def KillWork(worklist, verbosity): - for w in worklist: - w.Kill() - for w in worklist: - w.Wait() - -def NumberOfCPUs(): - return os.sysconf("SC_NPROCESSORS_ONLN") - -def NanoSecsToSecsStr(x): - if x == None: - return "" - x = str(x) - if len(x) < 10: - x = "0" * (10 - len(x)) + x - return x[:len(x) - 9] + "." + x[-9:] - -def InsertOptionAfter(cmd, option, after): - try: - pos = cmd.index(after) - cmd.insert(pos + 1, option) - except: - cmd.append(option) - -def CreateWorkList(cmd, pipe_to, output_dir, cpus, time_ranges_by_cpu): - max_len = len(str(cpus[-1])) - cpu_dir_fmt = f"cpu-%.{max_len}u" - worklist = [] - pos = 0 - for cpu in cpus: - if cpu >= 0: - cpu_dir = os.path.join(output_dir, cpu_dir_fmt % cpu) - cpu_option = f"--cpu={cpu}" - else: - cpu_dir = output_dir - cpu_option = None - - tr_dir_fmt = "time-range" - - if len(time_ranges_by_cpu) > 1: - time_ranges = time_ranges_by_cpu[pos] - tr_dir_fmt += f"-{pos}" - pos += 1 - else: - time_ranges = time_ranges_by_cpu[0] - - max_len = len(str(len(time_ranges))) - tr_dir_fmt += f"-%.{max_len}u" - - i = 0 - for r in time_ranges: - if r == [None, None]: - time_option = None - work_output_dir = cpu_dir - else: - time_option = "--time=" + NanoSecsToSecsStr(r[0]) + "," + NanoSecsToSecsStr(r[1]) - work_output_dir = os.path.join(cpu_dir, tr_dir_fmt % i) - i += 1 - work_cmd = list(cmd) - if time_option != None: - InsertOptionAfter(work_cmd, time_option, "script") - if cpu_option != None: - InsertOptionAfter(work_cmd, cpu_option, "script") - w = Work(work_cmd, pipe_to, work_output_dir) - worklist.append(w) - return worklist - -def DoRunWork(worklist, nr_jobs, verbosity): - nr_to_do = len(worklist) - not_started = list(worklist) - running = [] - done = [] - chg = False - while True: - nr_done = len(done) - if chg and verbosity.normal: - nr_run = len(running) - print(f"\rThere are {nr_to_do} jobs: {nr_done} completed, {nr_run} running", flush=True, end=" ") - if verbosity.verbose: - print() - chg = False - if nr_done == nr_to_do: - break - while len(running) < nr_jobs and len(not_started): - w = not_started.pop(0) - running.append(w) - if verbosity.verbose: - print("Starting:", w.Command()) - w.Start() - chg = True - if len(running): - time.sleep(0.1) - finished = [] - not_finished = [] - while len(running): - w = running.pop(0) - r = w.Poll() - if r == None: - not_finished.append(w) - continue - if r == 0: - if verbosity.verbose: - print("Finished:", w.Command()) - finished.append(w) - chg = True - continue - if verbosity.normal and not verbosity.verbose: - print() - print("Job failed!\n return code:", r, "\n command: ", w.Command()) - if w.pipe_to: - print(" piped to: ", w.pipe_to) - print("Killing outstanding jobs") - KillWork(not_finished, verbosity) - KillWork(running, verbosity) - return False - running = not_finished - done += finished - errorlist = [] - for w in worklist: - errorlist += w.Errors() - if len(errorlist): - print("Errors:") - for e in errorlist: - print(e) - elif verbosity.normal: - print("\r"," "*50, "\rAll jobs finished successfully", flush=True) - return True - -def RunWork(worklist, nr_jobs=NumberOfCPUs(), verbosity=Verbosity()): - try: - return DoRunWork(worklist, nr_jobs, verbosity) - except: - for w in worklist: - w.Kill() - raise - return True - -def ReadHeader(perf, file_name): - return subprocess.Popen([perf, "script", "--header-only", "--input", file_name], stdout=subprocess.PIPE).stdout.read().decode("utf-8") - -def ParseHeader(hdr): - result = {} - lines = hdr.split("\n") - for line in lines: - if ":" in line and line[0] == "#": - pos = line.index(":") - name = line[1:pos-1].strip() - value = line[pos+1:].strip() - if name in result: - orig_name = name - nr = 2 - while True: - name = f"{orig_name} {nr}" - if name not in result: - break - nr += 1 - result[name] = value - return result - -def HeaderField(hdr_dict, hdr_fld): - if hdr_fld not in hdr_dict: - raise Exception(f"'{hdr_fld}' missing from header information") - return hdr_dict[hdr_fld] - -# Represent the position of an option within a command string -# and provide the option value and/or remove the option -class OptPos(): - - def Init(self, opt_element=-1, value_element=-1, opt_pos=-1, value_pos=-1, error=None): - self.opt_element = opt_element # list element that contains option - self.value_element = value_element # list element that contains option value - self.opt_pos = opt_pos # string position of option - self.value_pos = value_pos # string position of value - self.error = error # error message string - - def __init__(self, args, short_name, long_name, default=None): - self.args = list(args) - self.default = default - n = 2 + len(long_name) - m = len(short_name) - pos = -1 - for opt in args: - pos += 1 - if m and opt[:2] == f"-{short_name}": - if len(opt) == 2: - if pos + 1 < len(args): - self.Init(pos, pos + 1, 0, 0) - else: - self.Init(error = f"-{short_name} option missing value") - else: - self.Init(pos, pos, 0, 2) - return - if opt[:n] == f"--{long_name}": - if len(opt) == n: - if pos + 1 < len(args): - self.Init(pos, pos + 1, 0, 0) - else: - self.Init(error = f"--{long_name} option missing value") - elif opt[n] == "=": - self.Init(pos, pos, 0, n + 1) - else: - self.Init(error = f"--{long_name} option expected '='") - return - if m and opt[:1] == "-" and opt[:2] != "--" and short_name in opt: - ipos = opt.index(short_name) - if "-" in opt[1:]: - hpos = opt[1:].index("-") - if hpos < ipos: - continue - if ipos + 1 == len(opt): - if pos + 1 < len(args): - self.Init(pos, pos + 1, ipos, 0) - else: - self.Init(error = f"-{short_name} option missing value") - else: - self.Init(pos, pos, ipos, ipos + 1) - return - self.Init() - - def Value(self): - if self.opt_element >= 0: - if self.opt_element != self.value_element: - return self.args[self.value_element] - else: - return self.args[self.value_element][self.value_pos:] - return self.default - - def Remove(self, args): - if self.opt_element == -1: - return - if self.opt_element != self.value_element: - del args[self.value_element] - if self.opt_pos: - args[self.opt_element] = args[self.opt_element][:self.opt_pos] - else: - del args[self.opt_element] - -def DetermineInputFileName(cmd): - p = OptPos(cmd, "i", "input", "perf.data") - if p.error: - raise Exception(f"perf command {p.error}") - file_name = p.Value() - if not os.path.exists(file_name): - raise Exception(f"perf command input file '{file_name}' not found") - return file_name - -def ReadOption(args, short_name, long_name, err_prefix, remove=False): - p = OptPos(args, short_name, long_name) - if p.error: - raise Exception(f"{err_prefix}{p.error}") - value = p.Value() - if remove: - p.Remove(args) - return value - -def ExtractOption(args, short_name, long_name, err_prefix): - return ReadOption(args, short_name, long_name, err_prefix, True) - -def ReadPerfOption(args, short_name, long_name): - return ReadOption(args, short_name, long_name, "perf command ") - -def ExtractPerfOption(args, short_name, long_name): - return ExtractOption(args, short_name, long_name, "perf command ") - -def PerfDoubleQuickCommands(cmd, file_name): - cpu_str = ReadPerfOption(cmd, "C", "cpu") - time_str = ReadPerfOption(cmd, "", "time") - # Use double-quick sampling to determine trace data density - times_cmd = ["perf", "script", "--ns", "--input", file_name, "--itrace=qqi"] - if cpu_str != None and cpu_str != "": - times_cmd.append(f"--cpu={cpu_str}") - if time_str != None and time_str != "": - times_cmd.append(f"--time={time_str}") - cnts_cmd = list(times_cmd) - cnts_cmd.append("-Fcpu") - times_cmd.append("-Fcpu,time") - return cnts_cmd, times_cmd - -class CPUTimeRange(): - def __init__(self, cpu): - self.cpu = cpu - self.sample_cnt = 0 - self.time_ranges = None - self.interval = 0 - self.interval_remaining = 0 - self.remaining = 0 - self.tr_pos = 0 - -def CalcTimeRangesByCPU(line, cpu, cpu_time_ranges, max_time): - cpu_time_range = cpu_time_ranges[cpu] - cpu_time_range.remaining -= 1 - cpu_time_range.interval_remaining -= 1 - if cpu_time_range.remaining == 0: - cpu_time_range.time_ranges[cpu_time_range.tr_pos][1] = max_time - return - if cpu_time_range.interval_remaining == 0: - time = TimeVal(line[1][:-1], 0) - time_ranges = cpu_time_range.time_ranges - time_ranges[cpu_time_range.tr_pos][1] = time - 1 - time_ranges.append([time, max_time]) - cpu_time_range.tr_pos += 1 - cpu_time_range.interval_remaining = cpu_time_range.interval - -def CountSamplesByCPU(line, cpu, cpu_time_ranges): - try: - cpu_time_ranges[cpu].sample_cnt += 1 - except: - print("exception") - print("cpu", cpu) - print("len(cpu_time_ranges)", len(cpu_time_ranges)) - raise - -def ProcessCommandOutputLines(cmd, per_cpu, fn, *x): - # Assume CPU number is at beginning of line and enclosed by [] - pat = re.compile(r"\s*\[[0-9]+\]") - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - while True: - line = p.stdout.readline() - if line: - line = line.decode("utf-8") - if pat.match(line): - line = line.split() - if per_cpu: - # Assumes CPU number is enclosed by [] - cpu = int(line[0][1:-1]) - else: - cpu = 0 - fn(line, cpu, *x) - else: - break - p.wait() - -def IntersectTimeRanges(new_time_ranges, time_ranges): - pos = 0 - new_pos = 0 - # Can assume len(time_ranges) != 0 and len(new_time_ranges) != 0 - # Note also, there *must* be at least one intersection. - while pos < len(time_ranges) and new_pos < len(new_time_ranges): - # new end < old start => no intersection, remove new - if new_time_ranges[new_pos][1] < time_ranges[pos][0]: - del new_time_ranges[new_pos] - continue - # new start > old end => no intersection, check next - if new_time_ranges[new_pos][0] > time_ranges[pos][1]: - pos += 1 - if pos < len(time_ranges): - continue - # no next, so remove remaining - while new_pos < len(new_time_ranges): - del new_time_ranges[new_pos] - return - # Found an intersection - # new start < old start => adjust new start = old start - if new_time_ranges[new_pos][0] < time_ranges[pos][0]: - new_time_ranges[new_pos][0] = time_ranges[pos][0] - # new end > old end => keep the overlap, insert the remainder - if new_time_ranges[new_pos][1] > time_ranges[pos][1]: - r = [ time_ranges[pos][1] + 1, new_time_ranges[new_pos][1] ] - new_time_ranges[new_pos][1] = time_ranges[pos][1] - new_pos += 1 - new_time_ranges.insert(new_pos, r) - continue - # new [start, end] is within old [start, end] - new_pos += 1 - -def SplitTimeRangesByTraceDataDensity(time_ranges, cpus, nr, cmd, file_name, per_cpu, min_size, min_interval, verbosity): - if verbosity.normal: - print("\rAnalyzing...", flush=True, end=" ") - if verbosity.verbose: - print() - cnts_cmd, times_cmd = PerfDoubleQuickCommands(cmd, file_name) - - nr_cpus = cpus[-1] + 1 if per_cpu else 1 - if per_cpu: - nr_cpus = cpus[-1] + 1 - cpu_time_ranges = [ CPUTimeRange(cpu) for cpu in range(nr_cpus) ] - else: - nr_cpus = 1 - cpu_time_ranges = [ CPUTimeRange(-1) ] - - if verbosity.debug: - print("nr_cpus", nr_cpus) - print("cnts_cmd", cnts_cmd) - print("times_cmd", times_cmd) - - # Count the number of "double quick" samples per CPU - ProcessCommandOutputLines(cnts_cmd, per_cpu, CountSamplesByCPU, cpu_time_ranges) - - tot = 0 - mx = 0 - for cpu_time_range in cpu_time_ranges: - cnt = cpu_time_range.sample_cnt - tot += cnt - if cnt > mx: - mx = cnt - if verbosity.debug: - print("cpu:", cpu_time_range.cpu, "sample_cnt", cnt) - - if min_size < 1: - min_size = 1 - - if mx < min_size: - # Too little data to be worth splitting - if verbosity.debug: - print("Too little data to split by time") - if nr == 0: - nr = 1 - return [ SplitTimeRangesIntoN(time_ranges, nr, min_interval) ] - - if nr: - divisor = nr - min_size = 1 - else: - divisor = NumberOfCPUs() - - interval = int(round(tot / divisor, 0)) - if interval < min_size: - interval = min_size - - if verbosity.debug: - print("divisor", divisor) - print("min_size", min_size) - print("interval", interval) - - min_time = time_ranges[0][0] - max_time = time_ranges[-1][1] - - for cpu_time_range in cpu_time_ranges: - cnt = cpu_time_range.sample_cnt - if cnt == 0: - cpu_time_range.time_ranges = copy.deepcopy(time_ranges) - continue - # Adjust target interval for CPU to give approximately equal interval sizes - # Determine number of intervals, rounding to nearest integer - n = int(round(cnt / interval, 0)) - if n < 1: - n = 1 - # Determine interval size, rounding up - d, m = divmod(cnt, n) - if m: - d += 1 - cpu_time_range.interval = d - cpu_time_range.interval_remaining = d - cpu_time_range.remaining = cnt - # Init. time ranges for each CPU with the start time - cpu_time_range.time_ranges = [ [min_time, max_time] ] - - # Set time ranges so that the same number of "double quick" samples - # will fall into each time range. - ProcessCommandOutputLines(times_cmd, per_cpu, CalcTimeRangesByCPU, cpu_time_ranges, max_time) - - for cpu_time_range in cpu_time_ranges: - if cpu_time_range.sample_cnt: - IntersectTimeRanges(cpu_time_range.time_ranges, time_ranges) - - return [cpu_time_ranges[cpu].time_ranges for cpu in cpus] - -def SplitSingleTimeRangeIntoN(time_range, n): - if n <= 1: - return [time_range] - start = time_range[0] - end = time_range[1] - duration = int((end - start + 1) / n) - if duration < 1: - return [time_range] - time_ranges = [] - for i in range(n): - time_ranges.append([start, start + duration - 1]) - start += duration - time_ranges[-1][1] = end - return time_ranges - -def TimeRangeDuration(r): - return r[1] - r[0] + 1 - -def TotalDuration(time_ranges): - duration = 0 - for r in time_ranges: - duration += TimeRangeDuration(r) - return duration - -def SplitTimeRangesByInterval(time_ranges, interval): - new_ranges = [] - for r in time_ranges: - duration = TimeRangeDuration(r) - n = duration / interval - n = int(round(n, 0)) - new_ranges += SplitSingleTimeRangeIntoN(r, n) - return new_ranges - -def SplitTimeRangesIntoN(time_ranges, n, min_interval): - if n <= len(time_ranges): - return time_ranges - duration = TotalDuration(time_ranges) - interval = duration / n - if interval < min_interval: - interval = min_interval - return SplitTimeRangesByInterval(time_ranges, interval) - -def RecombineTimeRanges(tr): - new_tr = copy.deepcopy(tr) - n = len(new_tr) - i = 1 - while i < len(new_tr): - # if prev end + 1 == cur start, combine them - if new_tr[i - 1][1] + 1 == new_tr[i][0]: - new_tr[i][0] = new_tr[i - 1][0] - del new_tr[i - 1] - else: - i += 1 - return new_tr - -def OpenTimeRangeEnds(time_ranges, min_time, max_time): - if time_ranges[0][0] <= min_time: - time_ranges[0][0] = None - if time_ranges[-1][1] >= max_time: - time_ranges[-1][1] = None - -def BadTimeStr(time_str): - raise Exception(f"perf command bad time option: '{time_str}'\nCheck also 'time of first sample' and 'time of last sample' in perf script --header-only") - -def ValidateTimeRanges(time_ranges, time_str): - n = len(time_ranges) - for i in range(n): - start = time_ranges[i][0] - end = time_ranges[i][1] - if i != 0 and start <= time_ranges[i - 1][1]: - BadTimeStr(time_str) - if start > end: - BadTimeStr(time_str) - -def TimeVal(s, dflt): - s = s.strip() - if s == "": - return dflt - a = s.split(".") - if len(a) > 2: - raise Exception(f"Bad time value'{s}'") - x = int(a[0]) - if x < 0: - raise Exception("Negative time not allowed") - x *= 1000000000 - if len(a) > 1: - x += int((a[1] + "000000000")[:9]) - return x - -def BadCPUStr(cpu_str): - raise Exception(f"perf command bad cpu option: '{cpu_str}'\nCheck also 'nrcpus avail' in perf script --header-only") - -def ParseTimeStr(time_str, min_time, max_time): - if time_str == None or time_str == "": - return [[min_time, max_time]] - time_ranges = [] - for r in time_str.split(): - a = r.split(",") - if len(a) != 2: - BadTimeStr(time_str) - try: - start = TimeVal(a[0], min_time) - end = TimeVal(a[1], max_time) - except: - BadTimeStr(time_str) - time_ranges.append([start, end]) - ValidateTimeRanges(time_ranges, time_str) - return time_ranges - -def ParseCPUStr(cpu_str, nr_cpus): - if cpu_str == None or cpu_str == "": - return [-1] - cpus = [] - for r in cpu_str.split(","): - a = r.split("-") - if len(a) < 1 or len(a) > 2: - BadCPUStr(cpu_str) - try: - start = int(a[0].strip()) - if len(a) > 1: - end = int(a[1].strip()) - else: - end = start - except: - BadCPUStr(cpu_str) - if start < 0 or end < 0 or end < start or end >= nr_cpus: - BadCPUStr(cpu_str) - cpus.extend(range(start, end + 1)) - cpus = list(set(cpus)) # Remove duplicates - cpus.sort() - return cpus - -class ParallelPerf(): - - def __init__(self, a): - for arg_name in vars(a): - setattr(self, arg_name, getattr(a, arg_name)) - self.orig_nr = self.nr - self.orig_cmd = list(self.cmd) - self.perf = self.cmd[0] - if os.path.exists(self.output_dir): - raise Exception(f"Output '{self.output_dir}' already exists") - if self.jobs < 0 or self.nr < 0 or self.interval < 0: - raise Exception("Bad options (negative values): try -h option for help") - if self.nr != 0 and self.interval != 0: - raise Exception("Cannot specify number of time subdivisions and time interval") - if self.jobs == 0: - self.jobs = NumberOfCPUs() - if self.nr == 0 and self.interval == 0: - if self.per_cpu: - self.nr = 1 - else: - self.nr = self.jobs - - def Init(self): - if self.verbosity.debug: - print("cmd", self.cmd) - self.file_name = DetermineInputFileName(self.cmd) - self.hdr = ReadHeader(self.perf, self.file_name) - self.hdr_dict = ParseHeader(self.hdr) - self.cmd_line = HeaderField(self.hdr_dict, "cmdline") - - def ExtractTimeInfo(self): - self.min_time = TimeVal(HeaderField(self.hdr_dict, "time of first sample"), 0) - self.max_time = TimeVal(HeaderField(self.hdr_dict, "time of last sample"), 0) - self.time_str = ExtractPerfOption(self.cmd, "", "time") - self.time_ranges = ParseTimeStr(self.time_str, self.min_time, self.max_time) - if self.verbosity.debug: - print("time_ranges", self.time_ranges) - - def ExtractCPUInfo(self): - if self.per_cpu: - nr_cpus = int(HeaderField(self.hdr_dict, "nrcpus avail")) - self.cpu_str = ExtractPerfOption(self.cmd, "C", "cpu") - if self.cpu_str == None or self.cpu_str == "": - self.cpus = [ x for x in range(nr_cpus) ] - else: - self.cpus = ParseCPUStr(self.cpu_str, nr_cpus) - else: - self.cpu_str = None - self.cpus = [-1] - if self.verbosity.debug: - print("cpus", self.cpus) - - def IsIntelPT(self): - return self.cmd_line.find("intel_pt") >= 0 - - def SplitTimeRanges(self): - if self.IsIntelPT() and self.interval == 0: - self.split_time_ranges_for_each_cpu = \ - SplitTimeRangesByTraceDataDensity(self.time_ranges, self.cpus, self.orig_nr, - self.orig_cmd, self.file_name, self.per_cpu, - self.min_size, self.min_interval, self.verbosity) - elif self.nr: - self.split_time_ranges_for_each_cpu = [ SplitTimeRangesIntoN(self.time_ranges, self.nr, self.min_interval) ] - else: - self.split_time_ranges_for_each_cpu = [ SplitTimeRangesByInterval(self.time_ranges, self.interval) ] - - def CheckTimeRanges(self): - for tr in self.split_time_ranges_for_each_cpu: - # Re-combined time ranges should be the same - new_tr = RecombineTimeRanges(tr) - if new_tr != self.time_ranges: - if self.verbosity.debug: - print("tr", tr) - print("new_tr", new_tr) - raise Exception("Self test failed!") - - def OpenTimeRangeEnds(self): - for time_ranges in self.split_time_ranges_for_each_cpu: - OpenTimeRangeEnds(time_ranges, self.min_time, self.max_time) - - def CreateWorkList(self): - self.worklist = CreateWorkList(self.cmd, self.pipe_to, self.output_dir, self.cpus, self.split_time_ranges_for_each_cpu) - - def PerfDataRecordedPerCPU(self): - if "--per-thread" in self.cmd_line.split(): - return False - return True - - def DefaultToPerCPU(self): - # --no-per-cpu option takes precedence - if self.no_per_cpu: - return False - if not self.PerfDataRecordedPerCPU(): - return False - # Default to per-cpu for Intel PT data that was recorded per-cpu, - # because decoding can be done for each CPU separately. - if self.IsIntelPT(): - return True - return False - - def Config(self): - self.Init() - self.ExtractTimeInfo() - if not self.per_cpu: - self.per_cpu = self.DefaultToPerCPU() - if self.verbosity.debug: - print("per_cpu", self.per_cpu) - self.ExtractCPUInfo() - self.SplitTimeRanges() - if self.verbosity.self_test: - self.CheckTimeRanges() - # Prefer open-ended time range to starting / ending with min_time / max_time resp. - self.OpenTimeRangeEnds() - self.CreateWorkList() - - def Run(self): - if self.dry_run: - print(len(self.worklist),"jobs:") - for w in self.worklist: - print(w.Command()) - return True - result = RunWork(self.worklist, self.jobs, verbosity=self.verbosity) - if self.verbosity.verbose: - print(glb_prog_name, "done") - return result - -def RunParallelPerf(a): - pp = ParallelPerf(a) - pp.Config() - return pp.Run() - -def Main(args): - ap = argparse.ArgumentParser( - prog=glb_prog_name, formatter_class = argparse.RawDescriptionHelpFormatter, - description = -""" -Run a perf script command multiple times in parallel, using perf script options ---cpu and --time so that each job processes a different chunk of the data. -""", - epilog = -""" -Follow the options by '--' and then the perf script command e.g. - - $ perf record -a -- sleep 10 - $ parallel-perf.py --nr=4 -- perf script --ns - All jobs finished successfully - $ tree parallel-perf-output/ - parallel-perf-output/ - ├── time-range-0 - │   ├── cmd.txt - │   └── out.txt - ├── time-range-1 - │   ├── cmd.txt - │   └── out.txt - ├── time-range-2 - │   ├── cmd.txt - │   └── out.txt - └── time-range-3 - ├── cmd.txt - └── out.txt - $ find parallel-perf-output -name cmd.txt | sort | xargs grep -H . - parallel-perf-output/time-range-0/cmd.txt:perf script --time=,9466.504461499 --ns - parallel-perf-output/time-range-1/cmd.txt:perf script --time=9466.504461500,9469.005396999 --ns - parallel-perf-output/time-range-2/cmd.txt:perf script --time=9469.005397000,9471.506332499 --ns - parallel-perf-output/time-range-3/cmd.txt:perf script --time=9471.506332500, --ns - -Any perf script command can be used, including the use of perf script options ---dlfilter and --script, so that the benefit of running parallel jobs -naturally extends to them also. - -If option --pipe-to is used, standard output is first piped through that -command. Beware, if the command fails (e.g. grep with no matches), it will be -considered a fatal error. - -Final standard output is redirected to files named out.txt in separate -subdirectories under the output directory. Similarly, standard error is -written to files named err.txt. In addition, files named cmd.txt contain the -corresponding perf script command. After processing, err.txt files are removed -if they are empty. - -If any job exits with a non-zero exit code, then all jobs are killed and no -more are started. A message is printed if any job results in a non-empty -err.txt file. - -There is a separate output subdirectory for each time range. If the --per-cpu -option is used, these are further grouped under cpu-n subdirectories, e.g. - - $ parallel-perf.py --per-cpu --nr=2 -- perf script --ns --cpu=0,1 - All jobs finished successfully - $ tree parallel-perf-output - parallel-perf-output/ - ├── cpu-0 - │   ├── time-range-0 - │   │   ├── cmd.txt - │   │   └── out.txt - │   └── time-range-1 - │   ├── cmd.txt - │   └── out.txt - └── cpu-1 - ├── time-range-0 - │   ├── cmd.txt - │   └── out.txt - └── time-range-1 - ├── cmd.txt - └── out.txt - $ find parallel-perf-output -name cmd.txt | sort | xargs grep -H . - parallel-perf-output/cpu-0/time-range-0/cmd.txt:perf script --cpu=0 --time=,9469.005396999 --ns - parallel-perf-output/cpu-0/time-range-1/cmd.txt:perf script --cpu=0 --time=9469.005397000, --ns - parallel-perf-output/cpu-1/time-range-0/cmd.txt:perf script --cpu=1 --time=,9469.005396999 --ns - parallel-perf-output/cpu-1/time-range-1/cmd.txt:perf script --cpu=1 --time=9469.005397000, --ns - -Subdivisions of time range, and cpus if the --per-cpu option is used, are -expressed by the --time and --cpu perf script options respectively. If the -supplied perf script command has a --time option, then that time range is -subdivided, otherwise the time range given by 'time of first sample' to -'time of last sample' is used (refer perf script --header-only). Similarly, the -supplied perf script command may provide a --cpu option, and only those CPUs -will be processed. - -To prevent time intervals becoming too small, the --min-interval option can -be used. - -Note there is special handling for processing Intel PT traces. If an interval is -not specified and the perf record command contained the intel_pt event, then the -time range will be subdivided in order to produce subdivisions that contain -approximately the same amount of trace data. That is accomplished by counting -double-quick (--itrace=qqi) samples, and choosing time ranges that encompass -approximately the same number of samples. In that case, time ranges may not be -the same for each CPU processed. For Intel PT, --per-cpu is the default, but -that can be overridden by --no-per-cpu. Note, for Intel PT, double-quick -decoding produces 1 sample for each PSB synchronization packet, which in turn -come after a certain number of bytes output, determined by psb_period (refer -perf Intel PT documentation). The minimum number of double-quick samples that -will define a time range can be set by the --min_size option, which defaults to -64. -""") - ap.add_argument("-o", "--output-dir", default="parallel-perf-output", help="output directory (default 'parallel-perf-output')") - ap.add_argument("-j", "--jobs", type=int, default=0, help="maximum number of jobs to run in parallel at one time (default is the number of CPUs)") - ap.add_argument("-n", "--nr", type=int, default=0, help="number of time subdivisions (default is the number of jobs)") - ap.add_argument("-i", "--interval", type=float, default=0, help="subdivide the time range using this time interval (in seconds e.g. 0.1 for a tenth of a second)") - ap.add_argument("-c", "--per-cpu", action="store_true", help="process data for each CPU in parallel") - ap.add_argument("-m", "--min-interval", type=float, default=glb_min_interval, help=f"minimum interval (default {glb_min_interval} seconds)") - ap.add_argument("-p", "--pipe-to", help="command to pipe output to (optional)") - ap.add_argument("-N", "--no-per-cpu", action="store_true", help="do not process data for each CPU in parallel") - ap.add_argument("-b", "--min_size", type=int, default=glb_min_samples, help="minimum data size (for Intel PT in PSBs)") - ap.add_argument("-D", "--dry-run", action="store_true", help="do not run any jobs, just show the perf script commands") - ap.add_argument("-q", "--quiet", action="store_true", help="do not print any messages except errors") - ap.add_argument("-v", "--verbose", action="store_true", help="print more messages") - ap.add_argument("-d", "--debug", action="store_true", help="print debugging messages") - cmd_line = list(args) - try: - split_pos = cmd_line.index("--") - cmd = cmd_line[split_pos + 1:] - args = cmd_line[:split_pos] - except: - cmd = None - args = cmd_line - a = ap.parse_args(args=args[1:]) - a.cmd = cmd - a.verbosity = Verbosity(a.quiet, a.verbose, a.debug) - try: - if a.cmd == None: - if len(args) <= 1: - ap.print_help() - return True - raise Exception("Command line must contain '--' before perf command") - return RunParallelPerf(a) - except Exception as e: - print("Fatal error: ", str(e)) - if a.debug: - raise - return False - -if __name__ == "__main__": - if not Main(sys.argv): - sys.exit(1) diff --git a/tools/perf/scripts/python/sched-migration.py b/tools/perf/scripts/python/sched-migration.py deleted file mode 100644 index 8196e3087c9e..000000000000 --- a/tools/perf/scripts/python/sched-migration.py +++ /dev/null @@ -1,462 +0,0 @@ -# Cpu task migration overview toy -# -# Copyright (C) 2010 Frederic Weisbecker -# -# perf script event handlers have been generated by perf script -g python -# -# This software is distributed under the terms of the GNU General -# Public License ("GPL") version 2 as published by the Free Software -# Foundation. -from __future__ import print_function - -import os -import sys - -from collections import defaultdict -try: - from UserList import UserList -except ImportError: - # Python 3: UserList moved to the collections package - from collections import UserList - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') -sys.path.append('scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from SchedGui import * - - -threads = { 0 : "idle"} - -def thread_name(pid): - return "%s:%d" % (threads[pid], pid) - -class RunqueueEventUnknown: - @staticmethod - def color(): - return None - - def __repr__(self): - return "unknown" - -class RunqueueEventSleep: - @staticmethod - def color(): - return (0, 0, 0xff) - - def __init__(self, sleeper): - self.sleeper = sleeper - - def __repr__(self): - return "%s gone to sleep" % thread_name(self.sleeper) - -class RunqueueEventWakeup: - @staticmethod - def color(): - return (0xff, 0xff, 0) - - def __init__(self, wakee): - self.wakee = wakee - - def __repr__(self): - return "%s woke up" % thread_name(self.wakee) - -class RunqueueEventFork: - @staticmethod - def color(): - return (0, 0xff, 0) - - def __init__(self, child): - self.child = child - - def __repr__(self): - return "new forked task %s" % thread_name(self.child) - -class RunqueueMigrateIn: - @staticmethod - def color(): - return (0, 0xf0, 0xff) - - def __init__(self, new): - self.new = new - - def __repr__(self): - return "task migrated in %s" % thread_name(self.new) - -class RunqueueMigrateOut: - @staticmethod - def color(): - return (0xff, 0, 0xff) - - def __init__(self, old): - self.old = old - - def __repr__(self): - return "task migrated out %s" % thread_name(self.old) - -class RunqueueSnapshot: - def __init__(self, tasks = [0], event = RunqueueEventUnknown()): - self.tasks = tuple(tasks) - self.event = event - - def sched_switch(self, prev, prev_state, next): - event = RunqueueEventUnknown() - - if taskState(prev_state) == "R" and next in self.tasks \ - and prev in self.tasks: - return self - - if taskState(prev_state) != "R": - event = RunqueueEventSleep(prev) - - next_tasks = list(self.tasks[:]) - if prev in self.tasks: - if taskState(prev_state) != "R": - next_tasks.remove(prev) - elif taskState(prev_state) == "R": - next_tasks.append(prev) - - if next not in next_tasks: - next_tasks.append(next) - - return RunqueueSnapshot(next_tasks, event) - - def migrate_out(self, old): - if old not in self.tasks: - return self - next_tasks = [task for task in self.tasks if task != old] - - return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old)) - - def __migrate_in(self, new, event): - if new in self.tasks: - self.event = event - return self - next_tasks = self.tasks[:] + tuple([new]) - - return RunqueueSnapshot(next_tasks, event) - - def migrate_in(self, new): - return self.__migrate_in(new, RunqueueMigrateIn(new)) - - def wake_up(self, new): - return self.__migrate_in(new, RunqueueEventWakeup(new)) - - def wake_up_new(self, new): - return self.__migrate_in(new, RunqueueEventFork(new)) - - def load(self): - """ Provide the number of tasks on the runqueue. - Don't count idle""" - return len(self.tasks) - 1 - - def __repr__(self): - ret = self.tasks.__repr__() - ret += self.origin_tostring() - - return ret - -class TimeSlice: - def __init__(self, start, prev): - self.start = start - self.prev = prev - self.end = start - # cpus that triggered the event - self.event_cpus = [] - if prev is not None: - self.total_load = prev.total_load - self.rqs = prev.rqs.copy() - else: - self.rqs = defaultdict(RunqueueSnapshot) - self.total_load = 0 - - def __update_total_load(self, old_rq, new_rq): - diff = new_rq.load() - old_rq.load() - self.total_load += diff - - def sched_switch(self, ts_list, prev, prev_state, next, cpu): - old_rq = self.prev.rqs[cpu] - new_rq = old_rq.sched_switch(prev, prev_state, next) - - if old_rq is new_rq: - return - - self.rqs[cpu] = new_rq - self.__update_total_load(old_rq, new_rq) - ts_list.append(self) - self.event_cpus = [cpu] - - def migrate(self, ts_list, new, old_cpu, new_cpu): - if old_cpu == new_cpu: - return - old_rq = self.prev.rqs[old_cpu] - out_rq = old_rq.migrate_out(new) - self.rqs[old_cpu] = out_rq - self.__update_total_load(old_rq, out_rq) - - new_rq = self.prev.rqs[new_cpu] - in_rq = new_rq.migrate_in(new) - self.rqs[new_cpu] = in_rq - self.__update_total_load(new_rq, in_rq) - - ts_list.append(self) - - if old_rq is not out_rq: - self.event_cpus.append(old_cpu) - self.event_cpus.append(new_cpu) - - def wake_up(self, ts_list, pid, cpu, fork): - old_rq = self.prev.rqs[cpu] - if fork: - new_rq = old_rq.wake_up_new(pid) - else: - new_rq = old_rq.wake_up(pid) - - if new_rq is old_rq: - return - self.rqs[cpu] = new_rq - self.__update_total_load(old_rq, new_rq) - ts_list.append(self) - self.event_cpus = [cpu] - - def next(self, t): - self.end = t - return TimeSlice(t, self) - -class TimeSliceList(UserList): - def __init__(self, arg = []): - self.data = arg - - def get_time_slice(self, ts): - if len(self.data) == 0: - slice = TimeSlice(ts, TimeSlice(-1, None)) - else: - slice = self.data[-1].next(ts) - return slice - - def find_time_slice(self, ts): - start = 0 - end = len(self.data) - found = -1 - searching = True - while searching: - if start == end or start == end - 1: - searching = False - - i = (end + start) / 2 - if self.data[i].start <= ts and self.data[i].end >= ts: - found = i - end = i - continue - - if self.data[i].end < ts: - start = i - - elif self.data[i].start > ts: - end = i - - return found - - def set_root_win(self, win): - self.root_win = win - - def mouse_down(self, cpu, t): - idx = self.find_time_slice(t) - if idx == -1: - return - - ts = self[idx] - rq = ts.rqs[cpu] - raw = "CPU: %d\n" % cpu - raw += "Last event : %s\n" % rq.event.__repr__() - raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000) - raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6)) - raw += "Load = %d\n" % rq.load() - for t in rq.tasks: - raw += "%s \n" % thread_name(t) - - self.root_win.update_summary(raw) - - def update_rectangle_cpu(self, slice, cpu): - rq = slice.rqs[cpu] - - if slice.total_load != 0: - load_rate = rq.load() / float(slice.total_load) - else: - load_rate = 0 - - red_power = int(0xff - (0xff * load_rate)) - color = (0xff, red_power, red_power) - - top_color = None - - if cpu in slice.event_cpus: - top_color = rq.event.color() - - self.root_win.paint_rectangle_zone(cpu, color, top_color, slice.start, slice.end) - - def fill_zone(self, start, end): - i = self.find_time_slice(start) - if i == -1: - return - - for i in range(i, len(self.data)): - timeslice = self.data[i] - if timeslice.start > end: - return - - for cpu in timeslice.rqs: - self.update_rectangle_cpu(timeslice, cpu) - - def interval(self): - if len(self.data) == 0: - return (0, 0) - - return (self.data[0].start, self.data[-1].end) - - def nr_rectangles(self): - last_ts = self.data[-1] - max_cpu = 0 - for cpu in last_ts.rqs: - if cpu > max_cpu: - max_cpu = cpu - return max_cpu - - -class SchedEventProxy: - def __init__(self): - self.current_tsk = defaultdict(lambda : -1) - self.timeslices = TimeSliceList() - - def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state, - next_comm, next_pid, next_prio): - """ Ensure the task we sched out this cpu is really the one - we logged. Otherwise we may have missed traces """ - - on_cpu_task = self.current_tsk[headers.cpu] - - if on_cpu_task != -1 and on_cpu_task != prev_pid: - print("Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \ - headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid) - - threads[prev_pid] = prev_comm - threads[next_pid] = next_comm - self.current_tsk[headers.cpu] = next_pid - - ts = self.timeslices.get_time_slice(headers.ts()) - ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu) - - def migrate(self, headers, pid, prio, orig_cpu, dest_cpu): - ts = self.timeslices.get_time_slice(headers.ts()) - ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu) - - def wake_up(self, headers, comm, pid, success, target_cpu, fork): - if success == 0: - return - ts = self.timeslices.get_time_slice(headers.ts()) - ts.wake_up(self.timeslices, pid, target_cpu, fork) - - -def trace_begin(): - global parser - parser = SchedEventProxy() - -def trace_end(): - app = wx.App(False) - timeslices = parser.timeslices - frame = RootFrame(timeslices, "Migration") - app.MainLoop() - -def sched__sched_stat_runtime(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, runtime, vruntime): - pass - -def sched__sched_stat_iowait(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, delay): - pass - -def sched__sched_stat_sleep(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, delay): - pass - -def sched__sched_stat_wait(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, delay): - pass - -def sched__sched_process_fork(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, parent_comm, parent_pid, child_comm, child_pid): - pass - -def sched__sched_process_wait(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio): - pass - -def sched__sched_process_exit(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio): - pass - -def sched__sched_process_free(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio): - pass - -def sched__sched_migrate_task(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio, orig_cpu, - dest_cpu): - headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm, common_callchain) - parser.migrate(headers, pid, prio, orig_cpu, dest_cpu) - -def sched__sched_switch(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, common_callchain, - prev_comm, prev_pid, prev_prio, prev_state, - next_comm, next_pid, next_prio): - - headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm, common_callchain) - parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state, - next_comm, next_pid, next_prio) - -def sched__sched_wakeup_new(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio, success, - target_cpu): - headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm, common_callchain) - parser.wake_up(headers, comm, pid, success, target_cpu, 1) - -def sched__sched_wakeup(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio, success, - target_cpu): - headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm, common_callchain) - parser.wake_up(headers, comm, pid, success, target_cpu, 0) - -def sched__sched_wait_task(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid, prio): - pass - -def sched__sched_kthread_stop_ret(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, ret): - pass - -def sched__sched_kthread_stop(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, comm, pid): - pass - -def trace_unhandled(event_name, context, event_fields_dict): - pass diff --git a/tools/perf/scripts/python/sctop.py b/tools/perf/scripts/python/sctop.py deleted file mode 100644 index 6e0278dcb092..000000000000 --- a/tools/perf/scripts/python/sctop.py +++ /dev/null @@ -1,89 +0,0 @@ -# system call top -# (c) 2010, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 -# -# Periodically displays system-wide system call totals, broken down by -# syscall. If a [comm] arg is specified, only syscalls called by -# [comm] are displayed. If an [interval] arg is specified, the display -# will be refreshed every [interval] seconds. The default interval is -# 3 seconds. - -from __future__ import print_function - -import os, sys, time - -try: - import thread -except ImportError: - import _thread as thread - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import * - -usage = "perf script -s sctop.py [comm] [interval]\n"; - -for_comm = None -default_interval = 3 -interval = default_interval - -if len(sys.argv) > 3: - sys.exit(usage) - -if len(sys.argv) > 2: - for_comm = sys.argv[1] - interval = int(sys.argv[2]) -elif len(sys.argv) > 1: - try: - interval = int(sys.argv[1]) - except ValueError: - for_comm = sys.argv[1] - interval = default_interval - -syscalls = autodict() - -def trace_begin(): - thread.start_new_thread(print_syscall_totals, (interval,)) - pass - -def raw_syscalls__sys_enter(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, id, args): - if for_comm is not None: - if common_comm != for_comm: - return - try: - syscalls[id] += 1 - except TypeError: - syscalls[id] = 1 - -def syscalls__sys_enter(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - id, args): - raw_syscalls__sys_enter(**locals()) - -def print_syscall_totals(interval): - while 1: - clear_term() - if for_comm is not None: - print("\nsyscall events for %s:\n" % (for_comm)) - else: - print("\nsyscall events:\n") - - print("%-40s %10s" % ("event", "count")) - print("%-40s %10s" % - ("----------------------------------------", - "----------")) - - for id, val in sorted(syscalls.items(), - key = lambda kv: (kv[1], kv[0]), - reverse = True): - try: - print("%-40s %10d" % (syscall_name(id), val)) - except TypeError: - pass - syscalls.clear() - time.sleep(interval) diff --git a/tools/perf/scripts/python/stackcollapse.py b/tools/perf/scripts/python/stackcollapse.py deleted file mode 100755 index b1c4def1410a..000000000000 --- a/tools/perf/scripts/python/stackcollapse.py +++ /dev/null @@ -1,127 +0,0 @@ -# stackcollapse.py - format perf samples with one line per distinct call stack -# SPDX-License-Identifier: GPL-2.0 -# -# This script's output has two space-separated fields. The first is a semicolon -# separated stack including the program name (from the "comm" field) and the -# function names from the call stack. The second is a count: -# -# swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 2 -# -# The file is sorted according to the first field. -# -# Input may be created and processed using: -# -# perf record -a -g -F 99 sleep 60 -# perf script report stackcollapse > out.stacks-folded -# -# (perf script record stackcollapse works too). -# -# Written by Paolo Bonzini -# Based on Brendan Gregg's stackcollapse-perf.pl script. - -from __future__ import print_function - -import os -import sys -from collections import defaultdict -from optparse import OptionParser, make_option - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from EventClass import * - -# command line parsing - -option_list = [ - # formatting options for the bottom entry of the stack - make_option("--include-tid", dest="include_tid", - action="store_true", default=False, - help="include thread id in stack"), - make_option("--include-pid", dest="include_pid", - action="store_true", default=False, - help="include process id in stack"), - make_option("--no-comm", dest="include_comm", - action="store_false", default=True, - help="do not separate stacks according to comm"), - make_option("--tidy-java", dest="tidy_java", - action="store_true", default=False, - help="beautify Java signatures"), - make_option("--kernel", dest="annotate_kernel", - action="store_true", default=False, - help="annotate kernel functions with _[k]") -] - -parser = OptionParser(option_list=option_list) -(opts, args) = parser.parse_args() - -if len(args) != 0: - parser.error("unexpected command line argument") -if opts.include_tid and not opts.include_comm: - parser.error("requesting tid but not comm is invalid") -if opts.include_pid and not opts.include_comm: - parser.error("requesting pid but not comm is invalid") - -# event handlers - -lines = defaultdict(lambda: 0) - -def process_event(param_dict): - def tidy_function_name(sym, dso): - if sym is None: - sym = '[unknown]' - - sym = sym.replace(';', ':') - if opts.tidy_java: - # the original stackcollapse-perf.pl script gives the - # example of converting this: - # Lorg/mozilla/javascript/MemberBox;.(Ljava/lang/reflect/Method;)V - # to this: - # org/mozilla/javascript/MemberBox:.init - sym = sym.replace('<', '') - sym = sym.replace('>', '') - if sym[0] == 'L' and sym.find('/'): - sym = sym[1:] - try: - sym = sym[:sym.index('(')] - except ValueError: - pass - - if opts.annotate_kernel and dso == '[kernel.kallsyms]': - return sym + '_[k]' - else: - return sym - - stack = list() - if 'callchain' in param_dict: - for entry in param_dict['callchain']: - entry.setdefault('sym', dict()) - entry['sym'].setdefault('name', None) - entry.setdefault('dso', None) - stack.append(tidy_function_name(entry['sym']['name'], - entry['dso'])) - else: - param_dict.setdefault('symbol', None) - param_dict.setdefault('dso', None) - stack.append(tidy_function_name(param_dict['symbol'], - param_dict['dso'])) - - if opts.include_comm: - comm = param_dict["comm"].replace(' ', '_') - sep = "-" - if opts.include_pid: - comm = comm + sep + str(param_dict['sample']['pid']) - sep = "/" - if opts.include_tid: - comm = comm + sep + str(param_dict['sample']['tid']) - stack.append(comm) - - stack_string = ';'.join(reversed(stack)) - lines[stack_string] = lines[stack_string] + 1 - -def trace_end(): - list = sorted(lines) - for stack in list: - print("%s %d" % (stack, lines[stack])) diff --git a/tools/perf/scripts/python/stat-cpi.py b/tools/perf/scripts/python/stat-cpi.py deleted file mode 100644 index 01fa933ff3cf..000000000000 --- a/tools/perf/scripts/python/stat-cpi.py +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 - -from __future__ import print_function - -data = {} -times = [] -threads = [] -cpus = [] - -def get_key(time, event, cpu, thread): - return "%d-%s-%d-%d" % (time, event, cpu, thread) - -def store_key(time, cpu, thread): - if (time not in times): - times.append(time) - - if (cpu not in cpus): - cpus.append(cpu) - - if (thread not in threads): - threads.append(thread) - -def store(time, event, cpu, thread, val, ena, run): - #print("event %s cpu %d, thread %d, time %d, val %d, ena %d, run %d" % - # (event, cpu, thread, time, val, ena, run)) - - store_key(time, cpu, thread) - key = get_key(time, event, cpu, thread) - data[key] = [ val, ena, run] - -def get(time, event, cpu, thread): - key = get_key(time, event, cpu, thread) - return data[key][0] - -def stat__cycles_k(cpu, thread, time, val, ena, run): - store(time, "cycles", cpu, thread, val, ena, run); - -def stat__instructions_k(cpu, thread, time, val, ena, run): - store(time, "instructions", cpu, thread, val, ena, run); - -def stat__cycles_u(cpu, thread, time, val, ena, run): - store(time, "cycles", cpu, thread, val, ena, run); - -def stat__instructions_u(cpu, thread, time, val, ena, run): - store(time, "instructions", cpu, thread, val, ena, run); - -def stat__cycles(cpu, thread, time, val, ena, run): - store(time, "cycles", cpu, thread, val, ena, run); - -def stat__instructions(cpu, thread, time, val, ena, run): - store(time, "instructions", cpu, thread, val, ena, run); - -def stat__interval(time): - for cpu in cpus: - for thread in threads: - cyc = get(time, "cycles", cpu, thread) - ins = get(time, "instructions", cpu, thread) - cpi = 0 - - if ins != 0: - cpi = cyc/float(ins) - - print("%15f: cpu %d, thread %d -> cpi %f (%d/%d)" % (time/(float(1000000000)), cpu, thread, cpi, cyc, ins)) - -def trace_end(): - pass -# XXX trace_end callback could be used as an alternative place -# to compute same values as in the script above: -# -# for time in times: -# for cpu in cpus: -# for thread in threads: -# cyc = get(time, "cycles", cpu, thread) -# ins = get(time, "instructions", cpu, thread) -# -# if ins != 0: -# cpi = cyc/float(ins) -# -# print("time %.9f, cpu %d, thread %d -> cpi %f" % (time/(float(1000000000)), cpu, thread, cpi)) diff --git a/tools/perf/scripts/python/syscall-counts-by-pid.py b/tools/perf/scripts/python/syscall-counts-by-pid.py deleted file mode 100644 index f254e40c6f0f..000000000000 --- a/tools/perf/scripts/python/syscall-counts-by-pid.py +++ /dev/null @@ -1,75 +0,0 @@ -# system call counts, by pid -# (c) 2010, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 -# -# Displays system-wide system call totals, broken down by syscall. -# If a [comm] arg is specified, only syscalls called by [comm] are displayed. - -from __future__ import print_function - -import os, sys - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import syscall_name - -usage = "perf script -s syscall-counts-by-pid.py [comm]\n"; - -for_comm = None -for_pid = None - -if len(sys.argv) > 2: - sys.exit(usage) - -if len(sys.argv) > 1: - try: - for_pid = int(sys.argv[1]) - except: - for_comm = sys.argv[1] - -syscalls = autodict() - -def trace_begin(): - print("Press control+C to stop and show the summary") - -def trace_end(): - print_syscall_totals() - -def raw_syscalls__sys_enter(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, id, args): - if (for_comm and common_comm != for_comm) or \ - (for_pid and common_pid != for_pid ): - return - try: - syscalls[common_comm][common_pid][id] += 1 - except TypeError: - syscalls[common_comm][common_pid][id] = 1 - -def syscalls__sys_enter(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - id, args): - raw_syscalls__sys_enter(**locals()) - -def print_syscall_totals(): - if for_comm is not None: - print("\nsyscall events for %s:\n" % (for_comm)) - else: - print("\nsyscall events by comm/pid:\n") - - print("%-40s %10s" % ("comm [pid]/syscalls", "count")) - print("%-40s %10s" % ("----------------------------------------", - "----------")) - - comm_keys = syscalls.keys() - for comm in comm_keys: - pid_keys = syscalls[comm].keys() - for pid in pid_keys: - print("\n%s [%d]" % (comm, pid)) - id_keys = syscalls[comm][pid].keys() - for id, val in sorted(syscalls[comm][pid].items(), - key = lambda kv: (kv[1], kv[0]), reverse = True): - print(" %-38s %10d" % (syscall_name(id), val)) diff --git a/tools/perf/scripts/python/syscall-counts.py b/tools/perf/scripts/python/syscall-counts.py deleted file mode 100644 index 8adb95ff1664..000000000000 --- a/tools/perf/scripts/python/syscall-counts.py +++ /dev/null @@ -1,65 +0,0 @@ -# system call counts -# (c) 2010, Tom Zanussi -# Licensed under the terms of the GNU GPL License version 2 -# -# Displays system-wide system call totals, broken down by syscall. -# If a [comm] arg is specified, only syscalls called by [comm] are displayed. - -from __future__ import print_function - -import os -import sys - -sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') - -from perf_trace_context import * -from Core import * -from Util import syscall_name - -usage = "perf script -s syscall-counts.py [comm]\n"; - -for_comm = None - -if len(sys.argv) > 2: - sys.exit(usage) - -if len(sys.argv) > 1: - for_comm = sys.argv[1] - -syscalls = autodict() - -def trace_begin(): - print("Press control+C to stop and show the summary") - -def trace_end(): - print_syscall_totals() - -def raw_syscalls__sys_enter(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, - common_callchain, id, args): - if for_comm is not None: - if common_comm != for_comm: - return - try: - syscalls[id] += 1 - except TypeError: - syscalls[id] = 1 - -def syscalls__sys_enter(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, id, args): - raw_syscalls__sys_enter(**locals()) - -def print_syscall_totals(): - if for_comm is not None: - print("\nsyscall events for %s:\n" % (for_comm)) - else: - print("\nsyscall events:\n") - - print("%-40s %10s" % ("event", "count")) - print("%-40s %10s" % ("----------------------------------------", - "-----------")) - - for id, val in sorted(syscalls.items(), - key = lambda kv: (kv[1], kv[0]), reverse = True): - print("%-40s %10d" % (syscall_name(id), val)) diff --git a/tools/perf/scripts/python/task-analyzer.py b/tools/perf/scripts/python/task-analyzer.py deleted file mode 100755 index 3f1df9894246..000000000000 --- a/tools/perf/scripts/python/task-analyzer.py +++ /dev/null @@ -1,934 +0,0 @@ -# task-analyzer.py - comprehensive perf tasks analysis -# SPDX-License-Identifier: GPL-2.0 -# Copyright (c) 2022, Hagen Paul Pfeifer -# Licensed under the terms of the GNU GPL License version 2 -# -# Usage: -# -# perf record -e sched:sched_switch -a -- sleep 10 -# perf script report task-analyzer -# - -from __future__ import print_function -import sys -import os -import string -import argparse -import decimal - - -sys.path.append( - os.environ["PERF_EXEC_PATH"] + "/scripts/python/Perf-Trace-Util/lib/Perf/Trace" -) -from perf_trace_context import * -from Core import * - -# Definition of possible ASCII color codes -_COLORS = { - "grey": "\033[90m", - "red": "\033[91m", - "green": "\033[92m", - "yellow": "\033[93m", - "blue": "\033[94m", - "violet": "\033[95m", - "reset": "\033[0m", -} - -# Columns will have a static size to align everything properly -# Support of 116 days of active update with nano precision -LEN_SWITCHED_IN = len("9999999.999999999") # 17 -LEN_SWITCHED_OUT = len("9999999.999999999") # 17 -LEN_CPU = len("000") -LEN_PID = len("maxvalue") # 8 -LEN_TID = len("maxvalue") # 8 -LEN_COMM = len("max-comms-length") # 16 -LEN_RUNTIME = len("999999.999") # 10 -# Support of 3.45 hours of timespans -LEN_OUT_IN = len("99999999999.999") # 15 -LEN_OUT_OUT = len("99999999999.999") # 15 -LEN_IN_IN = len("99999999999.999") # 15 -LEN_IN_OUT = len("99999999999.999") # 15 - - -# py2/py3 compatibility layer, see PEP469 -try: - dict.iteritems -except AttributeError: - # py3 - def itervalues(d): - return iter(d.values()) - - def iteritems(d): - return iter(d.items()) - -else: - # py2 - def itervalues(d): - return d.itervalues() - - def iteritems(d): - return d.iteritems() - - -def _check_color(): - global _COLORS - """user enforced no-color or if stdout is no tty we disable colors""" - if sys.stdout.isatty() and args.stdio_color != "never": - return - _COLORS = { - "grey": "", - "red": "", - "green": "", - "yellow": "", - "blue": "", - "violet": "", - "reset": "", - } - - -def _parse_args(): - global args - parser = argparse.ArgumentParser(description="Analyze tasks behavior") - parser.add_argument( - "--time-limit", - default=[], - help= - "print tasks only in time[s] window e.g" - " --time-limit 123.111:789.222(print all between 123.111 and 789.222)" - " --time-limit 123: (print all from 123)" - " --time-limit :456 (print all until incl. 456)", - ) - parser.add_argument( - "--summary", action="store_true", help="print addtional runtime information" - ) - parser.add_argument( - "--summary-only", action="store_true", help="print only summary without traces" - ) - parser.add_argument( - "--summary-extended", - action="store_true", - help="print the summary with additional information of max inter task times" - " relative to the prev task", - ) - parser.add_argument( - "--ns", action="store_true", help="show timestamps in nanoseconds" - ) - parser.add_argument( - "--ms", action="store_true", help="show timestamps in milliseconds" - ) - parser.add_argument( - "--extended-times", - action="store_true", - help="Show the elapsed times between schedule in/schedule out" - " of this task and the schedule in/schedule out of previous occurrence" - " of the same task", - ) - parser.add_argument( - "--filter-tasks", - default=[], - help="filter out unneeded tasks by tid, pid or processname." - " E.g --filter-task 1337,/sbin/init ", - ) - parser.add_argument( - "--limit-to-tasks", - default=[], - help="limit output to selected task by tid, pid, processname." - " E.g --limit-to-tasks 1337,/sbin/init", - ) - parser.add_argument( - "--highlight-tasks", - default="", - help="colorize special tasks by their pid/tid/comm." - " E.g. --highlight-tasks 1:red,mutt:yellow" - " Colors available: red,grey,yellow,blue,violet,green", - ) - parser.add_argument( - "--rename-comms-by-tids", - default="", - help="rename task names by using tid (:,:)" - " This option is handy for inexpressive processnames like python interpreted" - " process. E.g --rename 1337:my-python-app", - ) - parser.add_argument( - "--stdio-color", - default="auto", - choices=["always", "never", "auto"], - help="always, never or auto, allowing configuring color output" - " via the command line", - ) - parser.add_argument( - "--csv", - default="", - help="Write trace to file selected by user. Options, like --ns or --extended" - "-times are used.", - ) - parser.add_argument( - "--csv-summary", - default="", - help="Write summary to file selected by user. Options, like --ns or" - " --summary-extended are used.", - ) - args = parser.parse_args() - args.tid_renames = dict() - - _argument_filter_sanity_check() - _argument_prepare_check() - - -def time_uniter(unit): - picker = { - "s": 1, - "ms": 1e3, - "us": 1e6, - "ns": 1e9, - } - return picker[unit] - - -def _init_db(): - global db - db = dict() - db["running"] = dict() - db["cpu"] = dict() - db["tid"] = dict() - db["global"] = [] - if args.summary or args.summary_extended or args.summary_only: - db["task_info"] = dict() - db["runtime_info"] = dict() - # min values for summary depending on the header - db["task_info"]["pid"] = len("PID") - db["task_info"]["tid"] = len("TID") - db["task_info"]["comm"] = len("Comm") - db["runtime_info"]["runs"] = len("Runs") - db["runtime_info"]["acc"] = len("Accumulated") - db["runtime_info"]["max"] = len("Max") - db["runtime_info"]["max_at"] = len("Max At") - db["runtime_info"]["min"] = len("Min") - db["runtime_info"]["mean"] = len("Mean") - db["runtime_info"]["median"] = len("Median") - if args.summary_extended: - db["inter_times"] = dict() - db["inter_times"]["out_in"] = len("Out-In") - db["inter_times"]["inter_at"] = len("At") - db["inter_times"]["out_out"] = len("Out-Out") - db["inter_times"]["in_in"] = len("In-In") - db["inter_times"]["in_out"] = len("In-Out") - - -def _median(numbers): - """phython3 hat statistics module - we have nothing""" - n = len(numbers) - index = n // 2 - if n % 2: - return sorted(numbers)[index] - return sum(sorted(numbers)[index - 1 : index + 1]) / 2 - - -def _mean(numbers): - return sum(numbers) / len(numbers) - - -class Timespans(object): - """ - The elapsed time between two occurrences of the same task is being tracked with the - help of this class. There are 4 of those Timespans Out-Out, In-Out, Out-In and - In-In. - The first half of the name signals the first time point of the - first task. The second half of the name represents the second - timepoint of the second task. - """ - - def __init__(self): - self._last_start = None - self._last_finish = None - self.out_out = -1 - self.in_out = -1 - self.out_in = -1 - self.in_in = -1 - if args.summary_extended: - self._time_in = -1 - self.max_out_in = -1 - self.max_at = -1 - self.max_in_out = -1 - self.max_in_in = -1 - self.max_out_out = -1 - - def feed(self, task): - """ - Called for every recorded trace event to find process pair and calculate the - task timespans. Chronological ordering, feed does not do reordering - """ - if not self._last_finish: - self._last_start = task.time_in(time_unit) - self._last_finish = task.time_out(time_unit) - return - self._time_in = task.time_in() - time_in = task.time_in(time_unit) - time_out = task.time_out(time_unit) - self.in_in = time_in - self._last_start - self.out_in = time_in - self._last_finish - self.in_out = time_out - self._last_start - self.out_out = time_out - self._last_finish - if args.summary_extended: - self._update_max_entries() - self._last_finish = task.time_out(time_unit) - self._last_start = task.time_in(time_unit) - - def _update_max_entries(self): - if self.in_in > self.max_in_in: - self.max_in_in = self.in_in - if self.out_out > self.max_out_out: - self.max_out_out = self.out_out - if self.in_out > self.max_in_out: - self.max_in_out = self.in_out - if self.out_in > self.max_out_in: - self.max_out_in = self.out_in - self.max_at = self._time_in - - - -class Summary(object): - """ - Primary instance for calculating the summary output. Processes the whole trace to - find and memorize relevant data such as mean, max et cetera. This instance handles - dynamic alignment aspects for summary output. - """ - - def __init__(self): - self._body = [] - - class AlignmentHelper: - """ - Used to calculated the alignment for the output of the summary. - """ - def __init__(self, pid, tid, comm, runs, acc, mean, - median, min, max, max_at): - self.pid = pid - self.tid = tid - self.comm = comm - self.runs = runs - self.acc = acc - self.mean = mean - self.median = median - self.min = min - self.max = max - self.max_at = max_at - if args.summary_extended: - self.out_in = None - self.inter_at = None - self.out_out = None - self.in_in = None - self.in_out = None - - def _print_header(self): - ''' - Output is trimmed in _format_stats thus additional adjustment in the header - is needed, depending on the choice of timeunit. The adjustment corresponds - to the amount of column titles being adjusted in _column_titles. - ''' - decimal_precision = 6 if not args.ns else 9 - fmt = " {{:^{}}}".format(sum(db["task_info"].values())) - fmt += " {{:^{}}}".format( - sum(db["runtime_info"].values()) - 2 * decimal_precision - ) - _header = ("Task Information", "Runtime Information") - - if args.summary_extended: - fmt += " {{:^{}}}".format( - sum(db["inter_times"].values()) - 4 * decimal_precision - ) - _header += ("Max Inter Task Times",) - fd_sum.write(fmt.format(*_header) + "\n") - - def _column_titles(self): - """ - Cells are being processed and displayed in different way so an alignment adjust - is implemented depeding on the choice of the timeunit. The positions of the max - values are being displayed in grey. Thus in their format two additional {}, - are placed for color set and reset. - """ - separator, fix_csv_align = _prepare_fmt_sep() - decimal_precision, time_precision = _prepare_fmt_precision() - fmt = "{{:>{}}}".format(db["task_info"]["pid"] * fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, db["task_info"]["tid"] * fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, db["task_info"]["comm"] * fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["runs"] * fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["acc"] * fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["mean"] * fix_csv_align) - fmt += "{}{{:>{}}}".format( - separator, db["runtime_info"]["median"] * fix_csv_align - ) - fmt += "{}{{:>{}}}".format( - separator, (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align - ) - fmt += "{}{{:>{}}}".format( - separator, (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align - ) - fmt += "{}{{}}{{:>{}}}{{}}".format( - separator, (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align - ) - - column_titles = ("PID", "TID", "Comm") - column_titles += ("Runs", "Accumulated", "Mean", "Median", "Min", "Max") - column_titles += (_COLORS["grey"], "Max At", _COLORS["reset"]) - - if args.summary_extended: - fmt += "{}{{:>{}}}".format( - separator, - (db["inter_times"]["out_in"] - decimal_precision) * fix_csv_align - ) - fmt += "{}{{}}{{:>{}}}{{}}".format( - separator, - (db["inter_times"]["inter_at"] - time_precision) * fix_csv_align - ) - fmt += "{}{{:>{}}}".format( - separator, - (db["inter_times"]["out_out"] - decimal_precision) * fix_csv_align - ) - fmt += "{}{{:>{}}}".format( - separator, - (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align - ) - fmt += "{}{{:>{}}}".format( - separator, - (db["inter_times"]["in_out"] - decimal_precision) * fix_csv_align - ) - - column_titles += ("Out-In", _COLORS["grey"], "Max At", _COLORS["reset"], - "Out-Out", "In-In", "In-Out") - - fd_sum.write(fmt.format(*column_titles) + "\n") - - - def _task_stats(self): - """calculates the stats of every task and constructs the printable summary""" - for tid in sorted(db["tid"]): - color_one_sample = _COLORS["grey"] - color_reset = _COLORS["reset"] - no_executed = 0 - runtimes = [] - time_in = [] - timespans = Timespans() - for task in db["tid"][tid]: - pid = task.pid - comm = task.comm - no_executed += 1 - runtimes.append(task.runtime(time_unit)) - time_in.append(task.time_in()) - timespans.feed(task) - if len(runtimes) > 1: - color_one_sample = "" - color_reset = "" - time_max = max(runtimes) - time_min = min(runtimes) - max_at = time_in[runtimes.index(max(runtimes))] - - # The size of the decimal after sum,mean and median varies, thus we cut - # the decimal number, by rounding it. It has no impact on the output, - # because we have a precision of the decimal points at the output. - time_sum = round(sum(runtimes), 3) - time_mean = round(_mean(runtimes), 3) - time_median = round(_median(runtimes), 3) - - align_helper = self.AlignmentHelper(pid, tid, comm, no_executed, time_sum, - time_mean, time_median, time_min, time_max, max_at) - self._body.append([pid, tid, comm, no_executed, time_sum, color_one_sample, - time_mean, time_median, time_min, time_max, - _COLORS["grey"], max_at, _COLORS["reset"], color_reset]) - if args.summary_extended: - self._body[-1].extend([timespans.max_out_in, - _COLORS["grey"], timespans.max_at, - _COLORS["reset"], timespans.max_out_out, - timespans.max_in_in, - timespans.max_in_out]) - align_helper.out_in = timespans.max_out_in - align_helper.inter_at = timespans.max_at - align_helper.out_out = timespans.max_out_out - align_helper.in_in = timespans.max_in_in - align_helper.in_out = timespans.max_in_out - self._calc_alignments_summary(align_helper) - - def _format_stats(self): - separator, fix_csv_align = _prepare_fmt_sep() - decimal_precision, time_precision = _prepare_fmt_precision() - len_pid = db["task_info"]["pid"] * fix_csv_align - len_tid = db["task_info"]["tid"] * fix_csv_align - len_comm = db["task_info"]["comm"] * fix_csv_align - len_runs = db["runtime_info"]["runs"] * fix_csv_align - len_acc = db["runtime_info"]["acc"] * fix_csv_align - len_mean = db["runtime_info"]["mean"] * fix_csv_align - len_median = db["runtime_info"]["median"] * fix_csv_align - len_min = (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align - len_max = (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align - len_max_at = (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align - if args.summary_extended: - len_out_in = ( - db["inter_times"]["out_in"] - decimal_precision - ) * fix_csv_align - len_inter_at = ( - db["inter_times"]["inter_at"] - time_precision - ) * fix_csv_align - len_out_out = ( - db["inter_times"]["out_out"] - decimal_precision - ) * fix_csv_align - len_in_in = (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align - len_in_out = ( - db["inter_times"]["in_out"] - decimal_precision - ) * fix_csv_align - - fmt = "{{:{}d}}".format(len_pid) - fmt += "{}{{:{}d}}".format(separator, len_tid) - fmt += "{}{{:>{}}}".format(separator, len_comm) - fmt += "{}{{:{}d}}".format(separator, len_runs) - fmt += "{}{{:{}.{}f}}".format(separator, len_acc, time_precision) - fmt += "{}{{}}{{:{}.{}f}}".format(separator, len_mean, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, len_median, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, len_min, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, len_max, time_precision) - fmt += "{}{{}}{{:{}.{}f}}{{}}{{}}".format( - separator, len_max_at, decimal_precision - ) - if args.summary_extended: - fmt += "{}{{:{}.{}f}}".format(separator, len_out_in, time_precision) - fmt += "{}{{}}{{:{}.{}f}}{{}}".format( - separator, len_inter_at, decimal_precision - ) - fmt += "{}{{:{}.{}f}}".format(separator, len_out_out, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, len_in_in, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, len_in_out, time_precision) - return fmt - - - def _calc_alignments_summary(self, align_helper): - # Length is being cut in 3 groups so that further addition is easier to handle. - # The length of every argument from the alignment helper is being checked if it - # is longer than the longest until now. In that case the length is being saved. - for key in db["task_info"]: - if len(str(getattr(align_helper, key))) > db["task_info"][key]: - db["task_info"][key] = len(str(getattr(align_helper, key))) - for key in db["runtime_info"]: - if len(str(getattr(align_helper, key))) > db["runtime_info"][key]: - db["runtime_info"][key] = len(str(getattr(align_helper, key))) - if args.summary_extended: - for key in db["inter_times"]: - if len(str(getattr(align_helper, key))) > db["inter_times"][key]: - db["inter_times"][key] = len(str(getattr(align_helper, key))) - - - def print(self): - self._task_stats() - fmt = self._format_stats() - - if not args.csv_summary: - print("\nSummary") - self._print_header() - self._column_titles() - for i in range(len(self._body)): - fd_sum.write(fmt.format(*tuple(self._body[i])) + "\n") - - - -class Task(object): - """ The class is used to handle the information of a given task.""" - - def __init__(self, id, tid, cpu, comm): - self.id = id - self.tid = tid - self.cpu = cpu - self.comm = comm - self.pid = None - self._time_in = None - self._time_out = None - - def schedule_in_at(self, time): - """set the time where the task was scheduled in""" - self._time_in = time - - def schedule_out_at(self, time): - """set the time where the task was scheduled out""" - self._time_out = time - - def time_out(self, unit="s"): - """return time where a given task was scheduled out""" - factor = time_uniter(unit) - return self._time_out * decimal.Decimal(factor) - - def time_in(self, unit="s"): - """return time where a given task was scheduled in""" - factor = time_uniter(unit) - return self._time_in * decimal.Decimal(factor) - - def runtime(self, unit="us"): - factor = time_uniter(unit) - return (self._time_out - self._time_in) * decimal.Decimal(factor) - - def update_pid(self, pid): - self.pid = pid - - -def _task_id(pid, cpu): - """returns a "unique-enough" identifier, please do not change""" - return "{}-{}".format(pid, cpu) - - -def _filter_non_printable(unfiltered): - """comm names may contain loony chars like '\x00000'""" - filtered = "" - for char in unfiltered: - if char not in string.printable: - continue - filtered += char - return filtered - - -def _fmt_header(): - separator, fix_csv_align = _prepare_fmt_sep() - fmt = "{{:>{}}}".format(LEN_SWITCHED_IN*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_SWITCHED_OUT*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_CPU*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_PID*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_TID*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_RUNTIME*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_OUT_IN*fix_csv_align) - if args.extended_times: - fmt += "{}{{:>{}}}".format(separator, LEN_OUT_OUT*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_IN_IN*fix_csv_align) - fmt += "{}{{:>{}}}".format(separator, LEN_IN_OUT*fix_csv_align) - return fmt - - -def _fmt_body(): - separator, fix_csv_align = _prepare_fmt_sep() - decimal_precision, time_precision = _prepare_fmt_precision() - fmt = "{{}}{{:{}.{}f}}".format(LEN_SWITCHED_IN*fix_csv_align, decimal_precision) - fmt += "{}{{:{}.{}f}}".format( - separator, LEN_SWITCHED_OUT*fix_csv_align, decimal_precision - ) - fmt += "{}{{:{}d}}".format(separator, LEN_CPU*fix_csv_align) - fmt += "{}{{:{}d}}".format(separator, LEN_PID*fix_csv_align) - fmt += "{}{{}}{{:{}d}}{{}}".format(separator, LEN_TID*fix_csv_align) - fmt += "{}{{}}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align) - fmt += "{}{{:{}.{}f}}".format(separator, LEN_RUNTIME*fix_csv_align, time_precision) - if args.extended_times: - fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_IN*fix_csv_align, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_OUT*fix_csv_align, time_precision) - fmt += "{}{{:{}.{}f}}".format(separator, LEN_IN_IN*fix_csv_align, time_precision) - fmt += "{}{{:{}.{}f}}{{}}".format( - separator, LEN_IN_OUT*fix_csv_align, time_precision - ) - else: - fmt += "{}{{:{}.{}f}}{{}}".format( - separator, LEN_OUT_IN*fix_csv_align, time_precision - ) - return fmt - - -def _print_header(): - fmt = _fmt_header() - header = ("Switched-In", "Switched-Out", "CPU", "PID", "TID", "Comm", "Runtime", - "Time Out-In") - if args.extended_times: - header += ("Time Out-Out", "Time In-In", "Time In-Out") - fd_task.write(fmt.format(*header) + "\n") - - - -def _print_task_finish(task): - """calculating every entry of a row and printing it immediately""" - c_row_set = "" - c_row_reset = "" - out_in = -1 - out_out = -1 - in_in = -1 - in_out = -1 - fmt = _fmt_body() - # depending on user provided highlight option we change the color - # for particular tasks - if str(task.tid) in args.highlight_tasks_map: - c_row_set = _COLORS[args.highlight_tasks_map[str(task.tid)]] - c_row_reset = _COLORS["reset"] - if task.comm in args.highlight_tasks_map: - c_row_set = _COLORS[args.highlight_tasks_map[task.comm]] - c_row_reset = _COLORS["reset"] - # grey-out entries if PID == TID, they - # are identical, no threaded model so the - # thread id (tid) do not matter - c_tid_set = "" - c_tid_reset = "" - if task.pid == task.tid: - c_tid_set = _COLORS["grey"] - c_tid_reset = _COLORS["reset"] - if task.tid in db["tid"]: - # get last task of tid - last_tid_task = db["tid"][task.tid][-1] - # feed the timespan calculate, last in tid db - # and second the current one - timespan_gap_tid = Timespans() - timespan_gap_tid.feed(last_tid_task) - timespan_gap_tid.feed(task) - out_in = timespan_gap_tid.out_in - out_out = timespan_gap_tid.out_out - in_in = timespan_gap_tid.in_in - in_out = timespan_gap_tid.in_out - - - if args.extended_times: - line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu, - task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm, - task.runtime(time_unit), out_in, out_out, in_in, in_out, - c_row_reset) + "\n" - else: - line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu, - task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm, - task.runtime(time_unit), out_in, c_row_reset) + "\n" - try: - fd_task.write(line_out) - except(IOError): - # don't mangle the output if user SIGINT this script - sys.exit() - -def _record_cleanup(_list): - """ - no need to store more then one element if --summarize - is not enabled - """ - if not args.summary and len(_list) > 1: - _list = _list[len(_list) - 1 :] - - -def _record_by_tid(task): - tid = task.tid - if tid not in db["tid"]: - db["tid"][tid] = [] - db["tid"][tid].append(task) - _record_cleanup(db["tid"][tid]) - - -def _record_by_cpu(task): - cpu = task.cpu - if cpu not in db["cpu"]: - db["cpu"][cpu] = [] - db["cpu"][cpu].append(task) - _record_cleanup(db["cpu"][cpu]) - - -def _record_global(task): - """record all executed task, ordered by finish chronological""" - db["global"].append(task) - _record_cleanup(db["global"]) - - -def _handle_task_finish(tid, cpu, time, perf_sample_dict): - if tid == 0: - return - _id = _task_id(tid, cpu) - if _id not in db["running"]: - # may happen, if we missed the switch to - # event. Seen in combination with --exclude-perf - # where the start is filtered out, but not the - # switched in. Probably a bug in exclude-perf - # option. - return - task = db["running"][_id] - task.schedule_out_at(time) - - # record tid, during schedule in the tid - # is not available, update now - pid = int(perf_sample_dict["sample"]["pid"]) - - task.update_pid(pid) - del db["running"][_id] - - # print only tasks which are not being filtered and no print of trace - # for summary only, but record every task. - if not _limit_filtered(tid, pid, task.comm) and not args.summary_only: - _print_task_finish(task) - _record_by_tid(task) - _record_by_cpu(task) - _record_global(task) - - -def _handle_task_start(tid, cpu, comm, time): - if tid == 0: - return - if tid in args.tid_renames: - comm = args.tid_renames[tid] - _id = _task_id(tid, cpu) - if _id in db["running"]: - # handle corner cases where already running tasks - # are switched-to again - saw this via --exclude-perf - # recorded traces. We simple ignore this "second start" - # event. - return - assert _id not in db["running"] - task = Task(_id, tid, cpu, comm) - task.schedule_in_at(time) - db["running"][_id] = task - - -def _time_to_internal(time_ns): - """ - To prevent float rounding errors we use Decimal internally - """ - return decimal.Decimal(time_ns) / decimal.Decimal(1e9) - - -def _limit_filtered(tid, pid, comm): - if args.filter_tasks: - if str(tid) in args.filter_tasks or comm in args.filter_tasks: - return True - else: - return False - if args.limit_to_tasks: - if str(tid) in args.limit_to_tasks or comm in args.limit_to_tasks: - return False - else: - return True - - -def _argument_filter_sanity_check(): - if args.limit_to_tasks and args.filter_tasks: - sys.exit("Error: Filter and Limit at the same time active.") - if args.extended_times and args.summary_only: - sys.exit("Error: Summary only and extended times active.") - if args.time_limit and ":" not in args.time_limit: - sys.exit( - "Error: No bound set for time limit. Please set bound by ':' e.g :123." - ) - if args.time_limit and (args.summary or args.summary_only or args.summary_extended): - sys.exit("Error: Cannot set time limit and print summary") - if args.csv_summary: - args.summary = True - if args.csv == args.csv_summary: - sys.exit("Error: Chosen files for csv and csv summary are the same") - if args.csv and (args.summary_extended or args.summary) and not args.csv_summary: - sys.exit("Error: No file chosen to write summary to. Choose with --csv-summary " - "") - if args.csv and args.summary_only: - sys.exit("Error: --csv chosen and --summary-only. Standard task would not be" - "written to csv file.") - -def _argument_prepare_check(): - global time_unit, fd_task, fd_sum - if args.filter_tasks: - args.filter_tasks = args.filter_tasks.split(",") - if args.limit_to_tasks: - args.limit_to_tasks = args.limit_to_tasks.split(",") - if args.time_limit: - args.time_limit = args.time_limit.split(":") - for rename_tuple in args.rename_comms_by_tids.split(","): - tid_name = rename_tuple.split(":") - if len(tid_name) != 2: - continue - args.tid_renames[int(tid_name[0])] = tid_name[1] - args.highlight_tasks_map = dict() - for highlight_tasks_tuple in args.highlight_tasks.split(","): - tasks_color_map = highlight_tasks_tuple.split(":") - # default highlight color to red if no color set by user - if len(tasks_color_map) == 1: - tasks_color_map.append("red") - if args.highlight_tasks and tasks_color_map[1].lower() not in _COLORS: - sys.exit( - "Error: Color not defined, please choose from grey,red,green,yellow,blue," - "violet" - ) - if len(tasks_color_map) != 2: - continue - args.highlight_tasks_map[tasks_color_map[0]] = tasks_color_map[1] - time_unit = "us" - if args.ns: - time_unit = "ns" - elif args.ms: - time_unit = "ms" - - - fd_task = sys.stdout - if args.csv: - args.stdio_color = "never" - fd_task = open(args.csv, "w") - print("generating csv at",args.csv,) - - fd_sum = sys.stdout - if args.csv_summary: - args.stdio_color = "never" - fd_sum = open(args.csv_summary, "w") - print("generating csv summary at",args.csv_summary) - if not args.csv: - args.summary_only = True - - -def _is_within_timelimit(time): - """ - Check if a time limit was given by parameter, if so ignore the rest. If not, - process the recorded trace in its entirety. - """ - if not args.time_limit: - return True - lower_time_limit = args.time_limit[0] - upper_time_limit = args.time_limit[1] - # check for upper limit - if upper_time_limit == "": - if time >= decimal.Decimal(lower_time_limit): - return True - # check for lower limit - if lower_time_limit == "": - if time <= decimal.Decimal(upper_time_limit): - return True - # quit if time exceeds upper limit. Good for big datasets - else: - quit() - if lower_time_limit != "" and upper_time_limit != "": - if (time >= decimal.Decimal(lower_time_limit) and - time <= decimal.Decimal(upper_time_limit)): - return True - # quit if time exceeds upper limit. Good for big datasets - elif time > decimal.Decimal(upper_time_limit): - quit() - -def _prepare_fmt_precision(): - decimal_precision = 6 - time_precision = 3 - if args.ns: - decimal_precision = 9 - time_precision = 0 - return decimal_precision, time_precision - -def _prepare_fmt_sep(): - separator = " " - fix_csv_align = 1 - if args.csv or args.csv_summary: - separator = ";" - fix_csv_align = 0 - return separator, fix_csv_align - -def trace_unhandled(event_name, context, event_fields_dict, perf_sample_dict): - pass - - -def trace_begin(): - _parse_args() - _check_color() - _init_db() - if not args.summary_only: - _print_header() - -def trace_end(): - if args.summary or args.summary_extended or args.summary_only: - Summary().print() - -def sched__sched_switch(event_name, context, common_cpu, common_secs, common_nsecs, - common_pid, common_comm, common_callchain, prev_comm, - prev_pid, prev_prio, prev_state, next_comm, next_pid, - next_prio, perf_sample_dict): - # ignore common_secs & common_nsecs cause we need - # high res timestamp anyway, using the raw value is - # faster - time = _time_to_internal(perf_sample_dict["sample"]["time"]) - if not _is_within_timelimit(time): - # user specific --time-limit a:b set - return - - next_comm = _filter_non_printable(next_comm) - _handle_task_finish(prev_pid, common_cpu, time, perf_sample_dict) - _handle_task_start(next_pid, common_cpu, next_comm, time) diff --git a/tools/perf/scripts/sync-headers.sh b/tools/perf/scripts/sync-headers.sh new file mode 100755 index 000000000000..01e2d108432f --- /dev/null +++ b/tools/perf/scripts/sync-headers.sh @@ -0,0 +1,1676 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2026, Red Hat Inc, Arnaldo Carvalho de Melo +# +# Sync tools/ header copies with the kernel sources, generate beauty +# string table diffs, verify perf still builds, and create git commits. + +set -euo pipefail + +YELLOW='\033[0;33m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +PERF_DIR=$(cd "$(dirname "$0")/.." && pwd) +KERNEL_DIR=$(cd "$PERF_DIR/../.." && pwd) +DRY_RUN=0 +BUILD=1 +COMMIT=1 +FORCE=0 +OUTPUT_DIR="${OUTPUT_DIR:-${TMPDIR:-/tmp}/build/perf-tools-$(id -u)}" +SIGN_OFF_NAME="${SIGN_OFF_NAME:-}" +SIGN_OFF_EMAIL="${SIGN_OFF_EMAIL:-}" +HEADER_FILTER="" +VERBOSE=0 + +# Track build successes and failures for end-of-run summary +declare -a SYNCED_OK=() +declare -a BUILD_FAILED=() +declare -a COPY_FAILED=() +declare -a COMMIT_FAILED=() +declare -a TOOLS_AHEAD=() +declare -a HAND_SYNC=() +declare -a MANUAL=() +declare -a FORCED=() + +info() { echo -e "${GREEN}==>${NC} $*"; } +warn() { echo -e "${YELLOW}Warning:${NC} $*"; } +err() { echo -e "${RED}Error:${NC} $*" >&2; } +detail(){ [[ $VERBOSE -eq 1 ]] && echo -e " $*"; return 0; } + +usage() { + local status="${1:-0}" + + cat <" +} + +# --- Detect stale headers ------------------------------------------------ + +detect_stale_headers() { + local tmpfile + tmpfile=$(mktemp) + + # check-headers.sh must be run from tools/perf/ (it does cd ../.. internally) + (cd "$PERF_DIR" && bash check-headers.sh 2>/dev/null) | \ + grep '^ *diff -u ' | \ + sed 's/^ *diff -u //' > "$tmpfile" + + if [[ ! -s "$tmpfile" ]]; then + rm -f "$tmpfile" + return 1 + fi + + cat "$tmpfile" + rm -f "$tmpfile" + return 0 +} + +# --- Classify a header --------------------------------------------------- +# +# Returns via globals: +# HDR_CLASS: "files" | "beauty" +classify_header() { + local tools_path="$1" + + if [[ "$tools_path" == tools/perf/trace/beauty/* ]]; then + HDR_CLASS="beauty" + else + HDR_CLASS="files" + fi +} + +# --- Find kernel commits that changed a header since our copy ------------ + +# Copies checked with -I or hunk-ignore rules in check-headers.sh carry +# intentional edits, so a sync commit never leaves the two blobs equal. +has_ignore_rules() { + local kernel_path="$1" + local check_headers="$PERF_DIR/check-headers.sh" + + grep -Eq "^[[:space:]]*(check[[:space:]]+${kernel_path}[[:space:]]+.*-I|check_ignore_some_hunks[[:space:]]+${kernel_path}([[:space:]]|$))" \ + "$check_headers" 2>/dev/null && return 0 + sed -n '/^declare -a SYNC_CHECK_FILES=/,/)/p' "$check_headers" 2>/dev/null | \ + grep -Fq "\"$kernel_path\"" +} + +# The last commit that left the two copies equal: an in-tree kernel commit +# can update both files while an earlier kernel change is still missing. +find_sync_point() { + local kernel_path="$1" + local tools_path="$2" + local -a candidates blobs + local i + + # Files with ignore rules carry intentional edits and will never have + # identical blobs; fall back to the last commit touching tools/. + if ! has_ignore_rules "$kernel_path"; then + mapfile -t candidates < <(git log --no-merges --format='%H' -- "$kernel_path" "$tools_path" 2>/dev/null) + mapfile -t blobs < <( + printf '%s\n' "${candidates[@]}" | \ + awk -v k="$kernel_path" -v t="$tools_path" '{ print $1 ":" k ORS $1 ":" t }' | \ + git cat-file --batch-check) + + for ((i = 0; i < ${#candidates[@]}; i++)); do + # " missing" keeps the ':' of the spec as its first field, + # which no object id has. + if [[ "${blobs[2*i]%% *}" == "${blobs[2*i+1]%% *}" && \ + "${blobs[2*i]%% *}" != *:* ]]; then + echo "${candidates[i]}" + return + fi + done + fi + + git log --no-merges --format='%H' -1 -- "$tools_path" 2>/dev/null +} + +# A kernel commit that changed both the header and the tools copy already +# carried its change into tools/, so it has nothing left to pick up here. +find_kernel_commits() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local copied sha + + copied=$(git log --no-merges --format='%H' \ + "${sync_point}..HEAD" -- "$tools_path" 2>/dev/null) + [[ -n "$copied" ]] && copied+=$'\n' + + while IFS= read -r sha; do + [[ "$copied" == *"$sha"$'\n'* ]] && continue + echo "$sha" + done < <(git log --no-merges --format='%H' \ + "${sync_point}..HEAD" -- "$kernel_path" 2>/dev/null) +} + +# The lines the kernel copy has and the tools/ copy lacks, as "start,end" +# ranges on the kernel side. +added_line_ranges() { + local kernel_path="$1" + local tools_path="$2" + local out + + out=$(diff -U0 "$tools_path" "$kernel_path" 2>/dev/null) || true + printf '%s\n' "$out" | awk ' + /^@@/ { + split($3, a, ","); + start = substr(a[1], 2); + count = (a[2] == "" ? 1 : a[2]); + if (count > 0) + print start "," start + count - 1; + }' +} + +# A merge that resolves a conflict brings content of its own, e.g. a +# capability renumbered to avoid clashing with another one; git log +# --no-merges does not see those, so credit the merge with the lines it +# introduced. +merge_pickup_commits() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local range sha + local -a blame_args=() + local -a shas=() + + [[ -n "$sync_point" ]] || return 0 + + while read -r range; do + [[ -z "$range" ]] && continue + blame_args+=(-L "$range") + done < <(added_line_ranges "$kernel_path" "$tools_path") + + if [[ ${#blame_args[@]} -eq 0 ]]; then + return 0 + fi + + # Blame only what changed since the last sync, the lines older than + # it come out as the boundary commit and are not to be picked up. + while read -r sha; do + [[ -z "$sha" ]] && continue + shas+=("$sha") + done < <(git blame --porcelain "${sync_point}..HEAD" \ + "${blame_args[@]}" -- "$kernel_path" 2>/dev/null | + awk ' + /^[0-9a-f]{40} / { + sha = $1; + if (sha !~ /^0+$/) + order[++n] = sha; + next; + } + /^boundary$/ { + skip[sha] = 1; + next; + } + END { + for (i = 1; i <= n; i++) + if (!(order[i] in skip)) + print order[i]; + }') + + if [[ ${#shas[@]} -eq 0 ]]; then + return 0 + fi + + printf '%s\n' "${shas[@]}" | awk 'NF && !seen[$0]++' | + git rev-list --no-walk=unsorted --stdin --min-parents=2 2>/dev/null +} + +# Render commit ids with FORMAT at the usual 12 chars, keeping their order. +format_commits() { + local format="$1" + local ids="$2" + + [[ -n "$ids" ]] || return 0 + printf '%s\n' "$ids" | git log --no-walk=unsorted --abbrev=12 \ + --format="$format" --stdin 2>/dev/null +} + +# "add" when only the kernel changed since the sync point, "delete" when +# only the tools/ copy did, "both" when each side did; the sync point is +# where the blobs were equal, so comparing them is exact. +sync_direction() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local tools_base kernel_base tools_now kernel_now + + [[ -n "$sync_point" ]] || { echo add; return; } + tools_base=$(git -C "$KERNEL_DIR" rev-parse -q --verify "$sync_point:$tools_path") || { + echo add + return + } + kernel_base=$(git -C "$KERNEL_DIR" rev-parse -q --verify "$sync_point:$kernel_path") || { + echo add + return + } + tools_now=$(git -C "$KERNEL_DIR" hash-object --path="$tools_path" "$tools_path") || { + echo add + return + } + kernel_now=$(git -C "$KERNEL_DIR" hash-object --path="$kernel_path" "$kernel_path") || { + echo add + return + } + + if [[ "$tools_now" == "$tools_base" ]]; then + echo add # only the kernel moved + elif [[ "$kernel_now" == "$kernel_base" ]]; then + echo delete # only tools/ moved, it is ahead + else + echo both + fi +} + +# A clean merge that matches the kernel means the tools/ side changed only +# what the kernel already has, so a copy drops nothing unique. +merge_matches_kernel() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local base merged status + + [[ -n "$sync_point" ]] || return 1 + base=$(mktemp) || return 1 + merged=$(mktemp) || { rm -f "$base"; return 1; } + if ! git -C "$KERNEL_DIR" show "$sync_point:$tools_path" > "$base" 2>/dev/null; then + rm -f "$base" "$merged" + return 1 + fi + if ! git merge-file -p "$KERNEL_DIR/$tools_path" "$base" \ + "$KERNEL_DIR/$kernel_path" > "$merged" 2>/dev/null; then + rm -f "$base" "$merged" + return 1 + fi + cmp -s "$merged" "$KERNEL_DIR/$kernel_path" + status=$? + rm -f "$base" "$merged" + return $status +} + +# The additions only the tools/ copy added since the sync point, what a +# forced sync drops; diffing against the sync-point blob gives it exactly. +tools_only_lines() { + local tools_path="$1" + local sync_point="$2" + local line + + [[ -n "$sync_point" ]] || return 0 + git -C "$KERNEL_DIR" rev-parse -q --verify "$sync_point:$tools_path" \ + >/dev/null || return 0 + + while IFS= read -r line; do + case "$line" in + ---*|+++*) continue ;; + -*) echo " $line" ;; + esac + done < <(git -C "$KERNEL_DIR" cat-file blob "$sync_point:$tools_path" | \ + diff -u "$KERNEL_DIR/$tools_path" - 2>/dev/null) +} + +# --- Map a kernel header path to its beauty script(s) -------------------- +# +# The mapping comes from the beauty Build files: a script reads the +# headers listed as its prerequisites and the directories passed as its +# arguments. The header name still has to appear in the script so that +# one reading a whole tree, like syscalltbl.sh, doesn't match everything. + +declare -A BEAUTY_MAKE_DIRS=() +declare -A BEAUTY_MAKE_SCRIPTS=() +declare -A BEAUTY_SCRIPT_ARGS=() +declare -A BEAUTY_SCRIPT_PATH=() +declare -A BEAUTY_SCRIPT_ROOTS=() +declare -A BEAUTY_SCRIPT_FILES=() +declare -a BEAUTY_SCRIPTS=() +BEAUTY_ARCH="" + +beauty_add_script() { + local script="$1" + + [[ -n "$script" ]] || return 0 + [[ -n "${BEAUTY_SCRIPT_FILES[$script]+x}" ]] && return 0 + BEAUTY_SCRIPT_FILES["$script"]="" + BEAUTY_SCRIPT_ROOTS["$script"]="" + BEAUTY_SCRIPTS+=("$script") +} + +find_beauty_scripts() { + local kernel_path="$1" + local tools_path="$2" + local header_basename script script_path root matched + + header_basename=$(basename "$kernel_path") + + for script in "${BEAUTY_SCRIPTS[@]+"${BEAUTY_SCRIPTS[@]}"}"; do + script_path="${BEAUTY_SCRIPT_PATH[$script]:-$script}" + matched=0 + case $'\n'"${BEAUTY_SCRIPT_FILES[$script]:-}"$'\n' in + *$'\n'"$tools_path"$'\n'*) matched=1 ;; + esac + if [[ $matched -eq 0 ]]; then + while IFS= read -r root; do + [[ -n "$root" ]] || continue + [[ "$tools_path" == "$root" || \ + "$tools_path" == "$root"/* ]] || continue + if grep -F -q -e "/$header_basename" \ + -e "\"$header_basename\"" \ + "$KERNEL_DIR/$script_path" 2>/dev/null; then + matched=1 + break + fi + done <<< "${BEAUTY_SCRIPT_ROOTS[$script]:-}" + fi + [[ $matched -eq 1 ]] && echo "$script_path" + done +} + +# --- Find buildable targets that consume a header ------------------------- +# +# Given a tools/ header path, grep tools/ source files to find which +# buildable directories include it, then return them as build commands. +# This is discovered dynamically so new consumers are picked up automatically. + +# C sources include tools/ headers by their include-relative path, e.g. +# for tools/include/uapi/linux/socket.h. +include_relative_path() { + local tools_path="$1" + + case "$tools_path" in + tools/include/uapi/*) + echo "${tools_path#tools/include/uapi/}" + ;; + tools/include/*) + echo "${tools_path#tools/include/}" + ;; + tools/arch/*/include/uapi/*) + echo "${tools_path#tools/arch/*/include/uapi/}" + ;; + tools/arch/*/include/*) + echo "${tools_path#tools/arch/*/include/}" + ;; + *) + basename "$tools_path" + ;; + esac +} + +# Map from a source file path to its buildable target directory and make command. +# Returns empty if the file doesn't belong to a known buildable target. +buildable_target() { + local src="$1" + case "$src" in + tools/perf/*) + echo "perf:$PERF_DIR" + ;; + tools/lib/bpf/*) + echo "libbpf:$KERNEL_DIR/tools/lib/bpf" + ;; + tools/bpf/bpftool/*) + echo "bpftool:$KERNEL_DIR/tools/bpf/bpftool" + ;; + tools/objtool/*) + echo "objtool:$KERNEL_DIR/tools/objtool" + ;; + tools/lib/subcmd/*) + echo "libsubcmd:$KERNEL_DIR/tools/lib/subcmd" + ;; + tools/lib/api/*) + echo "libapi:$KERNEL_DIR/tools/lib/api" + ;; + tools/lib/perf/*) + echo "libperf:$KERNEL_DIR/tools/lib/perf" + ;; + tools/lib/symbol/*) + echo "libsymbol:$KERNEL_DIR/tools/lib/symbol" + ;; + tools/tracing/*) + echo "tracing:$KERNEL_DIR/tools/tracing" + ;; + esac +} + +find_build_targets() { + local tools_path="$1" + local header_class="$2" + local include_path + local targets=() + local seen=() + + # Beauty copies are only on the include path of the beauty scripts. + if [[ "$header_class" == "beauty" ]]; then + echo "perf:$PERF_DIR" + return + fi + + include_path=$(include_relative_path "$tools_path") + + while IFS= read -r src; do + [[ -z "$src" ]] && continue + local target + target=$(buildable_target "$src") + [[ -z "$target" ]] && continue + + # Deduplicate + local already=0 + for s in "${seen[@]+"${seen[@]}"}"; do + if [[ "$s" == "$target" ]]; then + already=1 + break + fi + done + if [[ $already -eq 0 ]]; then + seen+=("$target") + targets+=("$target") + fi + done < <(grep -rlF -e "<$include_path>" -e "\"$include_path\"" -e "/$include_path" \ + --include='*.c' --include='*.h' --include='*.S' \ + "$KERNEL_DIR/tools/" 2>/dev/null | \ + sed "s|^$KERNEL_DIR/||" | grep -v '^tools/include/') + + # Always include perf + local has_perf=0 + for t in "${targets[@]+"${targets[@]}"}"; do + [[ "$t" == perf:* ]] && has_perf=1 + done + if [[ $has_perf -eq 0 ]]; then + targets+=("perf:$PERF_DIR") + fi + + printf '%s\n' "${targets[@]}" +} + +# Print the consumers that will be built for a header or group. +show_build_targets() { + local what="$1" + local target_list="$2" + + info "Build-testing consumers of $what:" + while IFS=: read -r name dir; do + [[ -z "$name" ]] && continue + echo " $name ($dir)" + done <<< "$target_list" +} + +# Keep build output out of the source tree; perf, tracing and objtool take +# O=, the libraries and bpftool take OUTPUT=. +build_target() { + local name="$1" + local dir="$2" + + case "$name" in + perf) + make -C "$dir" O="$OUTPUT_DIR" -j"$(nproc)" + ;; + tracing|objtool) + (umask 077 && mkdir -p "$OUTPUT_DIR/$name") + make -C "$dir" O="$OUTPUT_DIR/$name" -j"$(nproc)" + ;; + *) + (umask 077 && mkdir -p "$OUTPUT_DIR/$name") + make -C "$dir" OUTPUT="$OUTPUT_DIR/$name/" -j"$(nproc)" + ;; + esac +} + +# Run build tests for all targets that consume a header. +# Returns 0 if all builds pass, 1 if any fail. +build_test_targets() { + local tools_path="$1" + local header_class="$2" + local header_basename log_name + local target_list failed=0 + + header_basename=$(basename "$tools_path") + # Key the log by tools path, same-basename headers in different + # directories would otherwise share a log. + log_name="${tools_path//\//-}" + target_list=$(find_build_targets "$tools_path" "$header_class") + show_build_targets "$header_basename" "$target_list" + + while IFS=: read -r name dir; do + [[ -z "$name" ]] && continue + local log="$OUTPUT_DIR/$name.$log_name.log" + if ! build_target "$name" "$dir" > "$log" 2>&1; then + err " $name build failed, see $log" + failed=1 + fi + done <<< "$target_list" + + return $failed +} + +# --- Run a beauty script capturing output -------------------------------- +# +# The arguments come from the same Build files the build uses, so a new +# script or a changed directory needs no edit here; run bare, +# arch_errno_names.sh misses the header copies. + +# Resolve a Build variable token, $(srctree)/path or $(dir)/suffix, to a +# tree-relative path. +beauty_resolve_token() { + local token="$1" + local name suffix + + case "$token" in + '$(srctree)'*) + suffix="${token//\$(srctree)/}" + echo "${suffix#/}" + ;; + '$('*')'*) + name="${token#\$(}" + name="${name%%)*}" + suffix="${token#*\)}" + [[ -n "${BEAUTY_MAKE_DIRS[$name]:-}" ]] || return 1 + printf '%s\n' "${BEAUTY_MAKE_DIRS[$name]}$suffix" | \ + sed "s/\$(SRCARCH)/$BEAUTY_ARCH/g" + ;; + *) + return 1 + ;; + esac +} + +# Collect, from one Build file, the script each rule runs with its +# arguments and the headers it takes as prerequisites. +parse_beauty_build() { + local build="$1" + local -a rule_lines=() + local line last_rule="" var script args token value roots files needle + local name path re_dir re_tbl + + [[ -r "$build" ]] || return 0 + + # _dir :=, _tbl := assignments, and the $(SHELL) rules using them. + re_dir='^([A-Za-z0-9_]+_(dir|dirs))[[:space:]]*(:=|\?=)[[:space:]]*' + re_dir+='\$\(srctree\)/(.*)$' + re_tbl='^([A-Za-z0-9_]+_(tbl|tbls))[[:space:]]*(:=|\?=)[[:space:]]*' + re_tbl+='\$\(srctree\)/((.*\.sh))[[:space:]]*$' + + while IFS= read -r line; do + if [[ "$line" =~ $re_dir ]]; then + BEAUTY_MAKE_DIRS["${BASH_REMATCH[1]}"]=$(printf '%s' \ + "${BASH_REMATCH[4]%/}" | \ + sed "s/\$(SRCARCH)/$BEAUTY_ARCH/g") + elif [[ "$line" =~ $re_tbl ]]; then + BEAUTY_MAKE_SCRIPTS["${BASH_REMATCH[1]}"]="${BASH_REMATCH[4]}" + elif [[ "$line" == *'$(SHELL)'* && -n "$last_rule" ]]; then + rule_lines+=("$last_rule"$'\n'"$line") + elif [[ "$line" != '#'* && "$line" != $'\t'* && \ + "$line" == *:* && "$line" != *':='* && \ + "$line" != *'?='* && "$line" != *'+='* ]]; then + last_rule="$line" + fi + done < "$build" + + local rule prereqs command + for rule in "${rule_lines[@]+"${rule_lines[@]}"}"; do + prereqs="${rule%%$'\n'*}" + command="${rule#*$'\n'}" + + script="" + for var in "${!BEAUTY_MAKE_SCRIPTS[@]}"; do + needle="'\$($var)'" + if [[ "$command" == *"$needle"* ]]; then + script="${BEAUTY_MAKE_SCRIPTS[$var]##*/}" + BEAUTY_SCRIPT_PATH["$script"]="${BEAUTY_MAKE_SCRIPTS[$var]}" + args="${command#*"$needle"}" + break + fi + done + [[ -n "$script" ]] || continue + beauty_add_script "$script" + + args="${args//\'}" + args=$(printf '%s\n' "$args" | \ + sed 's/\$(patsubst -%,,\$(CC))/gcc/g') + args="${args%' > $@'}" + value="" + roots="" + for token in $args; do + case "$token" in + '$@') + token='@OUTFILE@' + ;; + '$(srctree)'*|'$('*')'*) + path=$(beauty_resolve_token "$token") || { + value="" + break + } + case "$token" in + '$(srctree)'*) + roots+="${path}"$'\n' + ;; + *) + name="${token#\$(}" + name="${name%%)*}" + roots+="${BEAUTY_MAKE_DIRS[$name]:-}"$'\n' + ;; + esac + token="$KERNEL_DIR/$path" + ;; + *) ;; + esac + token=$(printf '%s' "$token" | sed "s/\$(SRCARCH)/$BEAUTY_ARCH/g") + if [[ "$token" == *'$('* ]]; then + value="" + break + fi + value+="${value:+ }$token" + done + BEAUTY_SCRIPT_ROOTS["$script"]+="$roots" + [[ -n "$value" ]] && BEAUTY_SCRIPT_ARGS["$script"]="$value" + + prereqs="${prereqs#*:}" + files="" + for token in $prereqs; do + path=$(beauty_resolve_token "$token") || continue + [[ "$path" == *'*'* ]] && continue + [[ -n "$path" ]] && files+="$path"$'\n' + done + BEAUTY_SCRIPT_FILES["$script"]+="$files" + done +} + +setup_beauty_args() { + local build found=0 + + BEAUTY_ARCH=$(uname -m) + case "$BEAUTY_ARCH" in + x86_64|i[3-6]86) BEAUTY_ARCH=x86 ;; + aarch64|arm64) BEAUTY_ARCH=arm64 ;; + s390*) BEAUTY_ARCH=s390 ;; + riscv*) BEAUTY_ARCH=riscv ;; + loongarch*) BEAUTY_ARCH=loongarch ;; + ppc*) BEAUTY_ARCH=powerpc ;; + sparc*) BEAUTY_ARCH=sparc ;; + mips*) BEAUTY_ARCH=mips ;; + esac + + for build in "$PERF_DIR/trace/beauty/Build" \ + "$PERF_DIR/trace/beauty/tracepoints/Build"; do + [[ -r "$build" ]] || continue + parse_beauty_build "$build" + found=1 + done + + [[ $found -eq 1 ]] || \ + detail "No beauty Build file, running the scripts without arguments" +} + +run_beauty_script() { + local script="$1" + local -a cmd=() + local outfile="" i + + read -ra cmd <<< "${BEAUTY_SCRIPT_ARGS["${script##*/}"]:-}" + for ((i = 0; i < ${#cmd[@]}; i++)); do + if [[ "${cmd[$i]}" == '@OUTFILE@' ]]; then + outfile=$(mktemp) || continue + cmd[$i]="$outfile" + fi + done + bash "$script" "${cmd[@]+"${cmd[@]}"}" 2>/dev/null || true + if [[ -n "$outfile" ]]; then + cat "$outfile" 2>/dev/null || true + rm -f "$outfile" + fi +} + +# Run one beauty script against the kernel content swapped into the tools +# copy, capturing its output before and after; the saved copy restores the +# tools copy, also when the script is interrupted. +run_beauty_pass() { + local script="$1" + local kernel_path="$2" + local tools_path="$3" + local tmpdir="$4" + local idx="$5" + local before="$tmpdir/beauty.$idx.before" + local after="$tmpdir/beauty.$idx.after" + local saved="$tmpdir/beauty.$idx.saved" + local restore + + if ! cp -p "$KERNEL_DIR/$tools_path" "$saved"; then + err "Failed to save $tools_path" + return 1 + fi + restore=$(printf 'cp -p %q %q' "$saved" "$KERNEL_DIR/$tools_path") + # shellcheck disable=SC2064 # expand now, locals may be gone + trap "$restore" EXIT + # shellcheck disable=SC2064 # expand now, locals may be gone + trap "$restore; trap - EXIT; exit 130" INT + # shellcheck disable=SC2064 # expand now, locals may be gone + trap "$restore; trap - EXIT; exit 143" TERM + (cd "$KERNEL_DIR" && run_beauty_script "$script") > "$before" + if ! cp "$KERNEL_DIR/$kernel_path" "$KERNEL_DIR/$tools_path"; then + err "Failed to copy $kernel_path -> $tools_path" + cp -p "$saved" "$KERNEL_DIR/$tools_path" || \ + err "Failed to restore $tools_path" + trap - INT TERM EXIT + return 1 + fi + (cd "$KERNEL_DIR" && run_beauty_script "$script") > "$after" + if ! cp -p "$saved" "$KERNEL_DIR/$tools_path"; then + err "Failed to restore $tools_path" + trap - INT TERM EXIT + return 1 + fi + trap - INT TERM EXIT +} + +# The tools/perf/trace/beauty/ relative path of a beauty script, as shown +# in the commit messages. +beauty_script_relpath() { + echo "tools/perf/trace/beauty/${1##*/trace/beauty/}" +} + +# The commit message fragment for a beauty script whose table changed: +# the recipe the pre-pass ran and the difference between its two runs. +beauty_body_fragment() { + local script="$1" + local kernel_path="$2" + local tools_path="$3" + local before="$4" + local after="$5" + local rel_script args + + rel_script=$(beauty_script_relpath "$script") + # The runs are from the tree root, show the dirs relative to it. + args="${BEAUTY_SCRIPT_ARGS["${script##*/}"]:-}" + args="${args//"$KERNEL_DIR/"/}" + local before_args="$args" after_args="$args" + local before_tail=" > before.txt" after_tail=" > after.txt" + + # Scripts taking the output file as an argument write it themselves. + if [[ "$args" == *@OUTFILE@* ]]; then + before_args="${args//@OUTFILE@/out.before}" + after_args="${args//@OUTFILE@/out.after}" + before_tail="" + after_tail="" + fi + + printf '\n' + printf ' $ %s%s%s\n' "$rel_script" "${before_args:+ $before_args}" "$before_tail" + printf ' $ cp %s %s\n' "$kernel_path" "$tools_path" + printf ' $ %s%s%s\n' "$rel_script" "${after_args:+ $after_args}" "$after_tail" + if [[ "$args" == *@OUTFILE@* ]]; then + printf ' $ diff -u out.before out.after\n' + else + printf ' $ diff -u before.txt after.txt\n' + fi + printf '\n' + diff -u "$before" "$after" | tail -n +3 | sed 's/^/ /' + printf '\n' +} + +cleanup_tmpdir() { + [[ -n "$1" ]] || return 0 + rm -rf "$1" +} + +# --- Copy header and create commit --------------------------------------- + +process_header() { + local tools_path="$1" + local kernel_path="$2" + + classify_header "$tools_path" + + if [[ -n "$HEADER_FILTER" ]] && [[ "$kernel_path" != *"$HEADER_FILTER"* ]]; then + detail "Skipping $kernel_path (does not match filter)" + return + fi + + info "Processing: $kernel_path" + detail " Class: $HDR_CLASS" + detail " Tools: $tools_path" + detail " Kernel: $kernel_path" + + # Find kernel commits that changed this file + local sync_point pickup_commits="" + sync_point=$(find_sync_point "$kernel_path" "$tools_path") + if [[ -n "$sync_point" ]]; then + pickup_commits=$(find_kernel_commits "$kernel_path" \ + "$tools_path" "$sync_point") + else + warn "No previous sync commit found for $tools_path" + fi + + local kernel_commits="" + if [[ -n "$pickup_commits" ]]; then + kernel_commits=$(format_commits ' %h ("%s")' "$pickup_commits") + fi + + # A merge can bring content of its own; list it too, it is not the + # author of a change, so it gets no Cc. + local merge_commits="" + merge_commits=$(merge_pickup_commits "$kernel_path" "$tools_path" \ + "$sync_point") + if [[ -n "$merge_commits" ]]; then + if [[ -n "$kernel_commits" ]]; then + kernel_commits+=$'\n' + fi + kernel_commits+=$(format_commits ' %h ("%s")' "$merge_commits") + fi + + if [[ -n "$kernel_commits" ]]; then + echo "" + echo " Kernel commits since last sync:" + echo "$kernel_commits" + fi + + # Copies with ignore rules carry deliberate edits that a plain copy + # would drop; leave them for a hand sync, printing the Cc list for them. + if has_ignore_rules "$kernel_path"; then + local cc_lines="" + if [[ -n "$pickup_commits" ]]; then + cc_lines=$(format_commits 'Cc: %an <%ae>' "$pickup_commits" | sort -u) + if [[ -n "$cc_lines" ]]; then + echo "" + echo "$cc_lines" | sed 's/^/ /' + fi + echo "" + fi + warn "$tools_path carries intentional edits, sync by hand" + HAND_SYNC+=("$tools_path") + return + fi + + # The kernel is the reference, but only copy when that doesn't drop + # content only the tools/ copy has. + local direction dropped="" forced=0 + direction=$(sync_direction "$kernel_path" "$tools_path" "$sync_point") + case "$direction" in + delete) + warn "$tools_path is ahead of the kernel sources, leaving it alone" + TOOLS_AHEAD+=("$tools_path") + return + ;; + both) + if merge_matches_kernel "$kernel_path" "$tools_path" "$sync_point"; then + detail "Tools/ changes are already in the kernel" + elif [[ $FORCE -eq 0 ]]; then + err "$tools_path and $kernel_path both have content the other lacks, needs manual investigation" + MANUAL+=("$tools_path") + return + else + warn "Both copies have unique content, dropping the tools/ one (--force)" + dropped=$(tools_only_lines "$tools_path" "$sync_point") + forced=1 + fi + ;; + esac + + # For beauty files, capture the string table changes and keep the + # before/after files for the commit message. + local beauty_changed=0 + local beauty_tmpdir="" + local beauty_scripts + local -a beauty_script_list=() + beauty_scripts=$(find_beauty_scripts "$kernel_path" "$tools_path") + while IFS= read -r script; do + [[ -z "$script" ]] && continue + beauty_script_list+=("$script") + done <<< "$beauty_scripts" + if [[ ${#beauty_script_list[@]} -gt 0 ]]; then + beauty_tmpdir=$(mktemp -d) + + local idx=0 + for script in "${beauty_script_list[@]}"; do + local script_name + script_name=$(basename "$script") + local before="$beauty_tmpdir/beauty.$idx.before" + local after="$beauty_tmpdir/beauty.$idx.after" + + # The script reads the tools/ copy, so swap in the kernel one + # and restore the saved copy, also in a dry run. + if ! run_beauty_pass "$script" "$kernel_path" "$tools_path" \ + "$beauty_tmpdir" "$idx"; then + COPY_FAILED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return 1 + fi + + if ! diff -q "$before" "$after" > /dev/null 2>&1; then + echo "" + info "Beauty string changes from $script_name:" + diff -u "$before" "$after" | head -60 || true + beauty_changed=1 + fi + idx=$((idx + 1)) + done + fi + + if [[ $DRY_RUN -eq 1 ]]; then + [[ $forced -eq 1 ]] && FORCED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + info "(dry run) Would copy $kernel_path -> $tools_path" + return + fi + + # Do the actual copy + if ! cp "$KERNEL_DIR/$kernel_path" "$KERNEL_DIR/$tools_path"; then + err "Failed to copy $kernel_path -> $tools_path" + COPY_FAILED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return 1 + fi + info "Copied $kernel_path -> $tools_path" + + # Verify the consumers still build + if [[ $BUILD -eq 1 ]]; then + if ! build_test_targets "$tools_path" "$HDR_CLASS"; then + err "Build failed after copying $kernel_path — reverting." + git -C "$KERNEL_DIR" checkout -- "$tools_path" || \ + err "Failed to revert $tools_path" + BUILD_FAILED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return 1 + fi + info "Build OK." + fi + + [[ $forced -eq 1 ]] && FORCED+=("$tools_path") + + if [[ $COMMIT -eq 0 ]]; then + SYNCED_OK+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return + fi + + # Compose commit message + local header_short + header_short=$(basename "$kernel_path") + + # The subject follows the path of the copy, one wording per family. + local subject arch uapi_rel + case "$kernel_path" in + include/uapi/*) + uapi_rel="${kernel_path#include/uapi/}" + if [[ "$HDR_CLASS" == "beauty" ]]; then + subject="tools headers: Sync UAPI $uapi_rel with the kernel sources" + else + subject="tools headers UAPI: Sync $uapi_rel with the kernel sources" + fi + ;; + include/*) + subject="tools headers: Sync ${kernel_path#include/} with the kernel sources" + ;; + arch/*/include/uapi/*) + arch="${kernel_path#arch/}" + arch="${arch%%/*}" + subject="tools headers UAPI: Sync $arch/asm/${header_short} with the kernel sources" + ;; + arch/*) + arch="${kernel_path#arch/}" + arch="${arch%%/*}" + subject="tools arch $arch: Sync the $header_short copy with the kernel sources" + ;; + *) + subject="tools headers: Sync $kernel_path with the kernel sources" + ;; + esac + + # Build commit body + local body="" + + if [[ -n "$kernel_commits" ]]; then + body+="To pick up the changes in:" + body+=$'\n' + body+=$'\n' + body+="$kernel_commits" + body+=$'\n' + fi + + if [[ $beauty_changed -eq 1 ]]; then + # Show the beauty diff in the commit message. + body+=$'\n' + body+="That introduce these new entries in the string tables:" + body+=$'\n' + + local idx=0 + for script in "${beauty_script_list[@]}"; do + local before_msg="$beauty_tmpdir/beauty.$idx.before" + local after_msg="$beauty_tmpdir/beauty.$idx.after" + + if ! diff -q "$before_msg" "$after_msg" > /dev/null 2>&1; then + body+=$(beauty_body_fragment "$script" "$kernel_path" \ + "$tools_path" "$before_msg" "$after_msg") + body+=$'\n' + fi + idx=$((idx + 1)) + done + elif [[ ${#beauty_script_list[@]} -gt 0 ]]; then + body+=$'\n' + body+="That don't result in changes to the string tables generated" + body+=" from this header." + else + body+=$'\n' + body+="That just rebuilds perf, silencing this build warning." + fi + + if [[ -n "$dropped" ]]; then + body+=$'\n' + body+=$'\n' + body+="The tools/ copy had content not present in the kernel sources," + body+=$'\n' + body+="dropped here after manual review:" + body+=$'\n' + body+=$'\n' + body+="$dropped" + fi + + body+=$'\n' + body+=$'\n' + body+="This addresses this perf build warning:" + body+=$'\n' + body+=$'\n' + body+=" Warning: Kernel ABI header differences:" + body+=$'\n' + body+=" diff -u $tools_path $kernel_path" + body+=$'\n' + body+=$'\n' + body+="Please see tools/include/uapi/README for further details." + + # Add Cc for kernel commit authors + local cc_lines="" + if [[ -n "$pickup_commits" ]]; then + cc_lines=$(format_commits 'Cc: %an <%ae>' "$pickup_commits" | sort -u) + fi + + body+=$'\n' + body+=$'\n' + if [[ -n "$cc_lines" ]]; then + body+="$cc_lines"$'\n' + fi + body+="$(sign_off)" + + cleanup_tmpdir "$beauty_tmpdir" + + if ! git -C "$KERNEL_DIR" add "$tools_path"; then + err "Failed to stage $tools_path" + COMMIT_FAILED+=("$tools_path") + return 1 + fi + if ! git -C "$KERNEL_DIR" commit "$tools_path" \ + -m "$(printf '%s\n\n%s\n' "$subject" "$body")"; then + err "Commit failed for $tools_path" + COMMIT_FAILED+=("$tools_path") + return 1 + fi + + SYNCED_OK+=("$tools_path") + info "Committed: $subject" + echo "" +} + +# --- Group related headers ----------------------------------------------- +# +# Some headers should be committed together (e.g. all KVM arch headers, +# all syscall tables). This function processes groups. + +process_group() { + local group_name="$1" + shift + local pairs=("$@") + + if [[ ${#pairs[@]} -eq 0 ]]; then + return + fi + + local file_count + file_count=$((${#pairs[@]} / 2)) + info "Processing group: $group_name ($file_count files)" + + local group_slug="${group_name// /-}" + + local all_kernel_commits="" + local all_cc_lines="" + local all_warnings="" + local group_beauty_tmpdir="" + local group_beauty_body="" + local group_beauty_scripts=0 + local group_dropped="" + local -a group_forced=() + local files_to_add=() + + # Collect info for all files in the group + local i=0 + while [[ $i -lt ${#pairs[@]} ]]; do + local tools_path="${pairs[$i]}" + local kernel_path="${pairs[$((i+1))]}" + local fidx=$i + i=$((i + 2)) + + if [[ -n "$HEADER_FILTER" ]] && [[ "$kernel_path" != *"$HEADER_FILTER"* ]]; then + continue + fi + + if has_ignore_rules "$kernel_path"; then + local sync_point pickup_commits="" merge_commits="" + sync_point=$(find_sync_point "$kernel_path" "$tools_path") + if [[ -n "$sync_point" ]]; then + pickup_commits=$(find_kernel_commits "$kernel_path" \ + "$tools_path" "$sync_point") + merge_commits=$(merge_pickup_commits "$kernel_path" \ + "$tools_path" "$sync_point") + fi + if [[ -n "$pickup_commits" || -n "$merge_commits" ]]; then + echo "" + echo " Kernel commits since last sync ($tools_path):" + if [[ -n "$pickup_commits" ]]; then + format_commits ' %h ("%s")' "$pickup_commits" + fi + if [[ -n "$merge_commits" ]]; then + format_commits ' %h ("%s")' "$merge_commits" + fi + local ccs + ccs=$(format_commits 'Cc: %an <%ae>' "$pickup_commits" | sort -u) + if [[ -n "$ccs" ]]; then + echo "" + echo "$ccs" | sed 's/^/ /' + fi + echo "" + fi + warn "$tools_path carries intentional edits, sync by hand" + HAND_SYNC+=("$tools_path") + continue + fi + + local sync_point direction + sync_point=$(find_sync_point "$kernel_path" "$tools_path") + direction=$(sync_direction "$kernel_path" "$tools_path" \ + "$sync_point") + case "$direction" in + delete) + warn "$tools_path is ahead of the kernel sources, leaving it alone" + TOOLS_AHEAD+=("$tools_path") + continue + ;; + both) + if merge_matches_kernel "$kernel_path" "$tools_path" "$sync_point"; then + detail "Tools/ changes are already in the kernel" + elif [[ $FORCE -eq 0 ]]; then + err "$tools_path and $kernel_path both have content the other lacks, needs manual investigation" + MANUAL+=("$tools_path") + continue + else + warn "Both copies have unique content, dropping the tools/ one (--force)" + local file_dropped + file_dropped=$(tools_only_lines "$tools_path" "$sync_point") + if [[ -n "$file_dropped" ]]; then + group_dropped+="$file_dropped"$'\n' + fi + group_forced+=("$tools_path") + fi + ;; + esac + + local pickup_commits="" + if [[ -n "$sync_point" ]]; then + pickup_commits=$(find_kernel_commits "$kernel_path" \ + "$tools_path" "$sync_point") + else + warn "No previous sync commit found for $tools_path" + fi + if [[ -n "$pickup_commits" ]]; then + all_kernel_commits+=$(format_commits ' %h ("%s")' "$pickup_commits") + all_kernel_commits+=$'\n' + + local ccs + ccs=$(format_commits 'Cc: %an <%ae>' "$pickup_commits") + if [[ -n "$ccs" ]]; then + all_cc_lines+="$ccs"$'\n' + fi + fi + + # A merge can bring content of its own; list it too, it is not the + # author of a change, so it gets no Cc. + local merge_commits="" + merge_commits=$(merge_pickup_commits "$kernel_path" "$tools_path" \ + "$sync_point") + if [[ -n "$merge_commits" ]]; then + all_kernel_commits+=$(format_commits ' %h ("%s")' "$merge_commits") + all_kernel_commits+=$'\n' + fi + + all_warnings+=" diff -u $tools_path $kernel_path"$'\n' + + # Beauty string tables; the script reads the tools/ copy, so this + # runs before the copy too, also in a dry run. + local -a group_script_list=() + while IFS= read -r script; do + [[ -z "$script" ]] && continue + group_script_list+=("$script") + done < <(find_beauty_scripts "$kernel_path" "$tools_path") + if [[ ${#group_script_list[@]} -gt 0 ]]; then + group_beauty_scripts=1 + [[ -z "$group_beauty_tmpdir" ]] && group_beauty_tmpdir=$(mktemp -d) + local sidx=0 + for script in "${group_script_list[@]}"; do + local script_name + script_name=$(basename "$script") + local before="$group_beauty_tmpdir/beauty.$fidx.$sidx.before" + local after="$group_beauty_tmpdir/beauty.$fidx.$sidx.after" + + if ! run_beauty_pass "$script" "$kernel_path" "$tools_path" \ + "$group_beauty_tmpdir" "$fidx.$sidx"; then + COPY_FAILED+=("$tools_path") + for f in "${files_to_add[@]}"; do + git -C "$KERNEL_DIR" checkout -- "$f" || \ + err "Failed to revert $f" + done + cleanup_tmpdir "$group_beauty_tmpdir" + return 1 + fi + + if ! diff -q "$before" "$after" > /dev/null 2>&1; then + echo "" + info "Beauty string changes from $script_name:" + diff -u "$before" "$after" | head -60 || true + group_beauty_body+=$(beauty_body_fragment "$script" \ + "$kernel_path" "$tools_path" "$before" "$after") + group_beauty_body+=$'\n' + fi + sidx=$((sidx + 1)) + done + fi + + if [[ $DRY_RUN -eq 0 ]]; then + if ! cp "$KERNEL_DIR/$kernel_path" "$KERNEL_DIR/$tools_path"; then + err "Failed to copy $kernel_path -> $tools_path" + COPY_FAILED+=("$tools_path") + for f in "${files_to_add[@]}"; do + git -C "$KERNEL_DIR" checkout -- "$f" || \ + err "Failed to revert $f" + done + cleanup_tmpdir "$group_beauty_tmpdir" + return 1 + fi + files_to_add+=("$tools_path") + info "Copied $kernel_path -> $tools_path" + else + info "(dry run) Would copy $kernel_path -> $tools_path" + fi + done + + # The beauty before/after files are captured into the fragments above. + cleanup_tmpdir "$group_beauty_tmpdir" + + if [[ ${#files_to_add[@]} -eq 0 ]]; then + FORCED+=("${group_forced[@]+"${group_forced[@]}"}") + return + fi + + if [[ $BUILD -eq 1 ]]; then + # Collect all unique build targets for the group + local group_targets="" + for f in "${files_to_add[@]}"; do + classify_header "$f" + group_targets+=$(find_build_targets "$f" "$HDR_CLASS") + group_targets+=$'\n' + done + group_targets=$(echo "$group_targets" | sort -u | grep -v '^$') + + show_build_targets "group $group_name" "$group_targets" + local build_failed=0 + while IFS=: read -r name dir; do + [[ -z "$name" ]] && continue + detail " Build-testing $name ($dir)..." + local log="$OUTPUT_DIR/$name.$group_slug.log" + if ! build_target "$name" "$dir" > "$log" 2>&1; then + err " $name build failed, see $log" + build_failed=1 + fi + done <<< "$group_targets" + + if [[ $build_failed -eq 1 ]]; then + err "Build failed after copying group $group_name — reverting." + for f in "${files_to_add[@]}"; do + git -C "$KERNEL_DIR" checkout -- "$f" || \ + err "Failed to revert $f" + BUILD_FAILED+=("$f (group: $group_name)") + done + return 1 + fi + info "Build OK." + fi + + FORCED+=("${group_forced[@]+"${group_forced[@]}"}") + + if [[ $DRY_RUN -eq 1 ]]; then + return + fi + + if [[ $COMMIT -eq 0 ]]; then + for f in "${files_to_add[@]}"; do + SYNCED_OK+=("$f") + done + return + fi + + # Deduplicate kernel commits and Cc lines; the commits keep the git log + # order they came in, the Cc lines stay sorted. + all_kernel_commits=$(printf '%s\n' "$all_kernel_commits" | \ + awk 'NF && !seen[$0]++') + all_cc_lines=$(echo "$all_cc_lines" | sort -u | grep -v '^$' || true) + + local subject="tools headers: Sync $group_name with the kernel sources" + local body="" + + if [[ -n "$all_kernel_commits" ]]; then + body+="To pick up the changes in:" + body+=$'\n' + body+=$'\n' + body+="$all_kernel_commits" + body+=$'\n' + fi + + if [[ -n "$group_beauty_body" ]]; then + body+=$'\n' + body+="That introduce these new entries in the string tables:" + body+=$'\n' + body+="$group_beauty_body" + elif [[ $group_beauty_scripts -eq 1 ]]; then + body+=$'\n' + body+="That don't result in changes to the string tables generated" + body+=" from these headers." + fi + + if [[ -n "$group_dropped" ]]; then + body+=$'\n' + body+=$'\n' + body+="The tools/ copies had content not present in the kernel" + body+=" sources, dropped here after manual review:" + body+=$'\n' + body+=$'\n' + body+="${group_dropped%$'\n'}" + fi + + body+=$'\n' + body+=$'\n' + body+="This addresses these perf build warnings:" + body+=$'\n' + body+=$'\n' + body+=" Warning: Kernel ABI header differences:" + body+=$'\n' + body+="$all_warnings" + body+=$'\n' + body+="Please see tools/include/uapi/README for further details." + body+=$'\n' + body+=$'\n' + if [[ -n "$all_cc_lines" ]]; then + body+="$all_cc_lines"$'\n' + fi + body+="$(sign_off)" + + for f in "${files_to_add[@]}"; do + if ! git -C "$KERNEL_DIR" add "$f"; then + err "Failed to stage $f" + for g in "${files_to_add[@]}"; do + COMMIT_FAILED+=("$g") + done + return 1 + fi + done + + if ! git -C "$KERNEL_DIR" commit "${files_to_add[@]}" \ + -m "$(printf '%s\n\n%s\n' "$subject" "$body")"; then + err "Commit failed for group $group_name" + for f in "${files_to_add[@]}"; do + COMMIT_FAILED+=("$f") + done + return 1 + fi + + for f in "${files_to_add[@]}"; do + SYNCED_OK+=("$f") + done + + info "Committed: $subject" + echo "" +} + +# --- Main ----------------------------------------------------------------- + +main() { + cd "$KERNEL_DIR" + + if [[ ! -d include/uapi ]]; then + err "Must be run from a kernel tree root (or tools/perf/)" + exit 1 + fi + + if [[ ! -d tools/perf ]]; then + err "Cannot find tools/perf/ in kernel tree" + exit 1 + fi + + setup_beauty_args || true + + if [[ $BUILD -eq 1 ]]; then + # umask covers every level created, -m with -p only the last. + (umask 077 && mkdir -p "$OUTPUT_DIR") + fi + + # Check for uncommitted changes in tools/ + if ! git diff --quiet -- tools/include/ tools/perf/trace/beauty/ tools/perf/util/ tools/perf/arch/*/entry/syscalls/ tools/scripts/ tools/lib/ tools/arch/; then + warn "You have uncommitted changes in tools/. Commit or stash them first." + if [[ $DRY_RUN -eq 0 ]]; then + exit 1 + fi + fi + + info "Running check-headers to detect stale copies..." + echo "" + + local stale + if ! stale=$(detect_stale_headers); then + info "All headers are up to date." + exit 0 + fi + + echo "Stale headers found:" + echo "$stale" + echo "" + + # Parse the stale header pairs and group them + # Each line is: tools_path kernel_path + local kvm_group=() + local syscall_group=() + local singles=() + + while IFS=' ' read -r tools_path kernel_path; do + case "$kernel_path" in + *kvm.h|*svm.h|*vmx.h|*sie.h) + kvm_group+=("$tools_path" "$kernel_path") + ;; + *syscall*.tbl|*unistd.h) + syscall_group+=("$tools_path" "$kernel_path") + ;; + *) + singles+=("$tools_path" "$kernel_path") + ;; + esac + done <<< "$stale" + + # Process individual headers first (beauty and simple copies). + # || true: continue on failure; failures are tracked in BUILD_FAILED[], + # COPY_FAILED[] and COMMIT_FAILED[] and reported in the summary. + local i=0 + while [[ $i -lt ${#singles[@]} ]]; do + process_header "${singles[$i]}" "${singles[$((i+1))]}" || true + i=$((i + 2)) + done + + # Process grouped headers + if [[ ${#kvm_group[@]} -gt 0 ]]; then + process_group "KVM headers" "${kvm_group[@]}" || true + fi + + if [[ ${#syscall_group[@]} -gt 0 ]]; then + process_group "syscall tables and unistd.h" "${syscall_group[@]}" || true + fi + + # Summary + echo "" + echo "=========================================" + echo " Summary" + echo "=========================================" + + if [[ ${#SYNCED_OK[@]} -gt 0 ]]; then + info "Synced successfully (${#SYNCED_OK[@]}):" + for f in "${SYNCED_OK[@]}"; do + echo " $f" + done + fi + + if [[ ${#BUILD_FAILED[@]} -gt 0 ]]; then + echo "" + err "Build failures (${#BUILD_FAILED[@]}) — reverted, need manual attention:" + for f in "${BUILD_FAILED[@]}"; do + echo " $f" + done + fi + + if [[ ${#COPY_FAILED[@]} -gt 0 ]]; then + echo "" + err "Copy failures (${#COPY_FAILED[@]}) — need manual attention:" + for f in "${COPY_FAILED[@]}"; do + echo " $f" + done + fi + + if [[ ${#COMMIT_FAILED[@]} -gt 0 ]]; then + echo "" + err "Commit failures (${#COMMIT_FAILED[@]}) — need manual attention:" + for f in "${COMMIT_FAILED[@]}"; do + echo " $f" + done + fi + + if [[ ${#TOOLS_AHEAD[@]} -gt 0 ]]; then + echo "" + warn "tools/ copies ahead of the kernel, left alone (${#TOOLS_AHEAD[@]}):" + for f in "${TOOLS_AHEAD[@]}"; do + echo " $f" + done + fi + + if [[ ${#HAND_SYNC[@]} -gt 0 ]]; then + echo "" + warn "Carry intentional edits, need manual sync (${#HAND_SYNC[@]}):" + for f in "${HAND_SYNC[@]}"; do + echo " $f" + done + fi + + if [[ ${#MANUAL[@]} -gt 0 ]]; then + echo "" + err "Left for manual investigation (${#MANUAL[@]}):" + for f in "${MANUAL[@]}"; do + echo " $f" + done + fi + + if [[ ${#FORCED[@]} -gt 0 ]]; then + echo "" + info "Synced with --force, tools/ additions dropped (${#FORCED[@]}):" + for f in "${FORCED[@]}"; do + echo " $f" + done + fi + + # Final check-headers run to see what's still out of sync + echo "" + local remaining + if remaining=$(detect_stale_headers); then + warn "Headers still out of sync:" + echo "$remaining" + else + info "All headers are now in sync." + fi + + if [[ ${#BUILD_FAILED[@]} -gt 0 || ${#COPY_FAILED[@]} -gt 0 || \ + ${#COMMIT_FAILED[@]} -gt 0 || ${#MANUAL[@]} -gt 0 ]]; then + exit 1 + fi +} + +main "$@" diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index 66944a4f4968..58858f3d2bf9 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -23,6 +23,7 @@ perf-test-y += hists_output.o perf-test-y += hists_cumulate.o perf-test-y += bp_signal.o perf-test-y += bp_signal_overflow.o +perf-test-y += event_refresh.o perf-test-y += bp_account.o perf-test-y += wp.o perf-test-y += task-exit.o @@ -65,6 +66,7 @@ perf-test-y += perf-time-to-tsc.o perf-test-y += dlfilter-test.o perf-test-y += sigtrap.o perf-test-y += event_groups.o +perf-test-y += hybrid-merge.o perf-test-y += symbols.o perf-test-y += util.o perf-test-y += hwmon_pmu.o @@ -72,7 +74,7 @@ perf-test-y += tool_pmu.o perf-test-y += subcmd-help.o perf-test-y += kallsyms-split.o -ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc)) +ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc riscv)) perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o endif @@ -92,7 +94,9 @@ $(OUTPUT)%.shellcheck_log: % $(call rule_mkdir) $(Q)$(call echo-cmd,test)$(SHELLCHECK) "$<" > $@ || (cat $@ && rm $@ && false) -perf-test-y += $(SHELL_TEST_LOGS) +.PHONY: shellcheck +shellcheck: $(addprefix $(OUTPUT)tests/,$(SHELL_TEST_LOGS)) + @: ifdef MYPY PY_TESTS := $(shell find tests/shell -type f -name '*.py') @@ -103,9 +107,11 @@ endif $(OUTPUT)%.mypy_log: % $(call rule_mkdir) - $(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false) + $(Q)$(call echo-cmd,test)$(MYPY) "$<" > $@ || (cat $@ && rm $@ && false) -perf-test-y += $(MYPY_TEST_LOGS) +.PHONY: mypy +mypy: $(addprefix $(OUTPUT)tests/,$(MYPY_TEST_LOGS)) + @: ifdef PYLINT PY_TESTS := $(shell find tests/shell -type f -name '*.py') @@ -116,6 +122,8 @@ endif $(OUTPUT)%.pylint_log: % $(call rule_mkdir) - $(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false) + $(Q)$(call echo-cmd,test)$(PYLINT) "$<" > $@ || (cat $@ && rm $@ && false) -perf-test-y += $(PYLINT_TEST_LOGS) +.PHONY: pylint +pylint: $(addprefix $(OUTPUT)tests/,$(PYLINT_TEST_LOGS)) + @: diff --git a/tools/perf/tests/backward-ring-buffer.c b/tools/perf/tests/backward-ring-buffer.c index 2735cc26d7ee..9314696858ff 100644 --- a/tools/perf/tests/backward-ring-buffer.c +++ b/tools/perf/tests/backward-ring-buffer.c @@ -167,4 +167,4 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, in return ret; } -DEFINE_SUITE("Read backward ring buffer", backward_ring_buffer); +DEFINE_SUITE_NEEDS_MLOCK("Read backward ring buffer", backward_ring_buffer); diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 4d0784b16723..0b8897fd42ca 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -11,9 +11,12 @@ #include #endif #include +#include #include #include +#include "util/config.h" + #include #include #include @@ -30,6 +33,8 @@ #include #include +#include + #include "builtin.h" #include "color.h" #include "config.h" @@ -40,6 +45,9 @@ #include "symbol.h" #include "tests-scripts.h" #include "tests.h" +#include "util/cap.h" +#include "util/event.h" +#include "util/mutex.h" #include "util/rlimit.h" #include "util/strbuf.h" #include "util/term.h" @@ -54,10 +62,23 @@ static struct strbuf junit_xml_buf = STRBUF_INIT; static bool dont_fork; /* Fork the tests in parallel and wait for their completion. */ static bool sequential; +/* List what each test needs, set by the "requisites" subcommand. */ +static bool list_requisites; /* Number of times each test is run. */ static unsigned int runs_per_test = 1; /* Number of lines to include in failure snippet. */ static unsigned int failure_snippet_lines = 10; +/* + * The tests mapping ring buffers take turns using their share of + * kernel.perf_event_mlock_kb via this mutex, shared with the forked test + * processes and robust so that a test killed while holding it doesn't leave + * the budget locked, see mutex_init_pshared_robust(). + */ +struct mlock_budget { + struct mutex lock; +}; + +static struct mlock_budget *mlock_budget; const char *dso_to_test; const char *test_objdump_path = "objdump"; static const char *workload_control; @@ -67,7 +88,8 @@ static const char *workload_control; * dependent on the initialization, as such GCC with LTO complains of * conflicting definitions with a weak symbol. */ -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__powerpc64__) +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ + defined(__powerpc64__) || defined(__riscv) extern struct test_suite *arch_tests[]; #else static struct test_suite *arch_tests[] = { @@ -98,6 +120,7 @@ static struct test_suite *generic_tests[] = { &suite__hists_link, &suite__bp_signal, &suite__bp_signal_overflow, + &suite__event_refresh, &suite__bp_accounting, &suite__wp, &suite__task_exit, @@ -149,6 +172,7 @@ static struct test_suite *generic_tests[] = { &suite__dlfilter, &suite__sigtrap, &suite__event_groups, + &suite__hybrid_merge, &suite__symbols, &suite__util, &suite__subcmd_help, @@ -171,6 +195,7 @@ static struct test_workload *workloads[] = { &workload__context_switch_loop, &workload__deterministic, &workload__callchain, + &workload__false_sharing, #ifdef HAVE_RUST_SUPPORT &workload__code_with_type, @@ -278,6 +303,30 @@ static const char *test_description(const struct test_suite *t, int test_case) return t->desc; } +static void requisites__fprintf(FILE *fp, const struct test_suite *t, int test_case) +{ + bool needs_mlock = t->needs_mlock, exclusive; + + if (test_case < 0) + test_case = test_suite__num_test_cases(t) == 1 ? 0 : -1; + + if (test_case >= 0) { + exclusive = t->test_cases[test_case].exclusive; + /* The catch-all implies every other requisite. */ + needs_mlock = needs_mlock || exclusive; + } else { + exclusive = false; + } + + if (!exclusive && !needs_mlock) + return; + + fprintf(fp, " ("); + if (exclusive) + fprintf(fp, "exclusive, "); + fprintf(fp, "mlock)"); +} + static test_fnptr test_function(const struct test_suite *t, int test_case) { if (test_case <= 0) @@ -347,12 +396,30 @@ static void child_test_sig_handler(int sig) siglongjmp(run_test_jmp_buf, sig); } +static int run_test_case(struct child_test *child) +{ + return test_function(child->test, child->test_case_num)(child->test, child->test_case_num); +} + +/* The thread safety analysis can't express a lock held conditionally. */ +static int run_test_case_mlocked(struct child_test *child) +{ + int err; + + mutex_lock(&mlock_budget->lock); + err = run_test_case(child); + mutex_unlock(&mlock_budget->lock); + + return err; +} + static int run_test_child(struct child_process *process) { const int signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, }; struct child_test *child = container_of(process, struct child_test, process); + bool needs_mlock = mlock_budget && child->test->needs_mlock; int err; close_parent_fds(); @@ -369,7 +436,12 @@ static int run_test_child(struct child_process *process) pr_debug("---- start ----\n"); pr_debug("test child forked, pid %d\n", getpid()); - err = test_function(child->test, child->test_case_num)(child->test, child->test_case_num); + + if (needs_mlock) + err = run_test_case_mlocked(child); + else + err = run_test_case(child); + pr_debug("---- end(%d) ----\n", err); check_leaks(); @@ -623,6 +695,47 @@ static void print_line_highlighted(FILE *fp, const char *line, size_t len) } +static bool is_blank_line(const char *line, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (!isspace((unsigned char)line[i])) + return false; + } + return true; +} + +static bool is_stack_line(const char *line, size_t len) +{ + size_t i = 0; + + while (i < len && isspace((unsigned char)line[i])) + i++; + return i + 1 < len && line[i] == '#' && isdigit((unsigned char)line[i + 1]); +} + +static bool is_ignorable_snippet_line(const char *line, size_t len) +{ + return is_blank_line(line, len) || is_stack_line(line, len); +} + +static bool has_non_blank_between(const char **lines, const size_t *line_lens, + size_t start, size_t end) +{ + for (size_t j = start + 1; j < end; j++) { + if (!is_blank_line(lines[j], line_lens[j])) + return true; + } + return false; +} + +static void pick_snippet_line(bool *pick, size_t idx, unsigned int *picked_count) +{ + if (!pick[idx] && *picked_count < failure_snippet_lines) { + pick[idx] = true; + (*picked_count)++; + } +} + static void print_test_failure_snippet(FILE *fp, const char *buf) { size_t num_lines = 0; @@ -693,43 +806,63 @@ static void print_test_failure_snippet(FILE *fp, const char *buf) } /* Pass 0: Always pick the very first line */ - if (num_lines > 0 && picked_count < failure_snippet_lines) { - pick[0] = true; - picked_count++; - } + if (num_lines > 0) + pick_snippet_line(pick, 0, &picked_count); /* Pass 1: Pick lines with failure keywords from start (Highest Priority) */ for (i = 0; i < num_lines && picked_count < failure_snippet_lines; i++) { size_t dummy; - if (find_next_keyword(lines[i], line_lens[i], &dummy)) { - if (!pick[i]) { - pick[i] = true; - picked_count++; - } - /* Prioritize getting the immediate next line for context */ - if (i + 1 < num_lines && !pick[i + 1] && - picked_count < failure_snippet_lines) { - pick[i + 1] = true; - picked_count++; + if (is_ignorable_snippet_line(lines[i], line_lens[i])) + continue; + if (!find_next_keyword(lines[i], line_lens[i], &dummy)) + continue; + + pick_snippet_line(pick, i, &picked_count); + /* + * For unexpected signals, prioritize the preceding non-blank + * line showing where the test had run to before the signal. + */ + if (!strncmp(lines[i], "---- unexpected signal", 22)) { + size_t j = i; + + while (j > 0) { + j--; + if (!is_ignorable_snippet_line(lines[j], line_lens[j])) { + pick_snippet_line(pick, j, &picked_count); + break; + } } } + /* Prioritize getting the immediate next line for context */ + if (i + 1 < num_lines && + !is_ignorable_snippet_line(lines[i + 1], line_lens[i + 1])) + pick_snippet_line(pick, i + 1, &picked_count); } - /* Pass 2: Fill remaining quota from the end backwards */ + /* + * Pass 2: Fill remaining quota from the end backwards, skipping blank + * and stack trace lines so trailing test progress is shown instead of + * outermost stack frames like main/_start. + */ i = num_lines; while (i > 0 && picked_count < failure_snippet_lines) { i--; - if (!pick[i]) { - pick[i] = true; - picked_count++; - } + if (!is_ignorable_snippet_line(lines[i], line_lens[i])) + pick_snippet_line(pick, i, &picked_count); + } + + /* Pass 3: Fallback to top stack frames if quota remains */ + for (i = 0; i < num_lines && picked_count < failure_snippet_lines; i++) { + if (!is_blank_line(lines[i], line_lens[i])) + pick_snippet_line(pick, i, &picked_count); } for (i = 0; i < num_lines; i++) { if (!pick[i]) continue; - if (last_printed != -1 && (int)i > last_printed + 1) { + if (last_printed != -1 && + has_non_blank_between(lines, line_lens, last_printed, i)) { if (perf_use_color_default) fprintf(fp, "%s...%s\n", PERF_COLOR_BLUE, PERF_COLOR_RESET); else @@ -1445,14 +1578,20 @@ static int perf_test__list(FILE *fp, struct test_suite **suites, int argc, const if (!perf_test__matches(test_description(*t, -1), curr_suite, argc, argv)) continue; - fprintf(fp, "%3d: %s\n", curr_suite + 1, test_description(*t, -1)); + fprintf(fp, "%3d: %s", curr_suite + 1, test_description(*t, -1)); + if (list_requisites) + requisites__fprintf(fp, *t, /*test_case=*/-1); + fprintf(fp, "\n"); if (test_suite__num_test_cases(*t) <= 1) continue; test_suite__for_each_test_case(*t, curr_test_case) { - fprintf(fp, "%3d.%1d: %s\n", curr_suite + 1, curr_test_case + 1, + fprintf(fp, "%3d.%1d: %s", curr_suite + 1, curr_test_case + 1, test_description(*t, curr_test_case)); + if (list_requisites) + requisites__fprintf(fp, *t, curr_test_case); + fprintf(fp, "\n"); } } return 0; @@ -1654,11 +1793,30 @@ static int run_workload(const char *work, int argc, const char **argv) return -1; } +/* + * Owns the string test_objdump_path points at when it came from the config. It + * is reachable for the lifetime of the process so leak checking won't report + * it. + */ +static char *test_objdump_config_path; + static int perf_test__config(const char *var, const char *value, void *data __maybe_unused) { - if (!strcmp(var, "annotate.objdump")) - test_objdump_path = value; + if (!strcmp(var, "annotate.objdump")) { + /* + * The config, and so value, is freed by perf_config__exit() + * below, take a copy that lives as long as the tests. + */ + char *dup = strdup(value); + + if (!dup) + return -ENOMEM; + + free(test_objdump_config_path); + test_objdump_config_path = dup; + test_objdump_path = dup; + } return 0; } @@ -1720,10 +1878,97 @@ static struct test_suite **build_suites(void) #undef for_each_suite } +static bool perf_test__shares_mlock_budget(struct test_suite **suites, int argc, + const char *argv[], struct intlist *skiplist) +{ + int sharing = 0; + + /* The selection rules mirrored here are __cmd_test()'s: when they change, change both. */ + for (int curr_suite = 0; suites[curr_suite]; curr_suite++) { + struct test_suite *t = suites[curr_suite]; + int curr_test_case; + bool suite_matched; + + if (!t->needs_mlock || intlist__find(skiplist, curr_suite + 1)) + continue; + + suite_matched = perf_test__matches(test_description(t, /*test_case=*/-1), + curr_suite, argc, argv); + test_suite__for_each_test_case(t, curr_test_case) { + if (test_exclusive(t, curr_test_case)) + continue; + + if (suite_matched || + perf_test__matches(test_description(t, curr_test_case), + curr_suite, argc, argv)) + sharing += runs_per_test; + } + if (sharing > 1) + return true; + } + return false; +} + +/* + * Once the per user budget set in kernel.perf_event_mlock_kb is spent, what + * maps on top of it is charged against RLIMIT_MEMLOCK and the mapping is + * refused with EPERM when that runs out too, but only for the processes the + * kernel is paranoid about and that have no CAP_IPC_LOCK to lift the check, + * see perf_mmap_calc_limits() in the kernel: used by + * perf_test__setup_mlock_budget() below, that has nothing to take turns on + * when neither applies. + */ +static bool perf_test__mlock_budget_exhaustible(void) +{ + if (perf_cap__capable(CAP_IPC_LOCK)) + return false; + + return perf_event_paranoid() > -1; +} + +static void perf_test__setup_mlock_budget(struct test_suite **suites, int argc, + const char *argv[], struct intlist *skiplist) +{ + /* Nothing can be denied the budget, nothing to take turns for. */ + if (!perf_test__mlock_budget_exhaustible()) { + pr_debug("Not taking turns using the perf mlock budget: nothing can exhaust it\n"); + return; + } + + /* Nothing runs at the same time, nothing to share the budget with. */ + if (sequential || !perf_test__shares_mlock_budget(suites, argc, argv, skiplist)) + return; + + mlock_budget = mmap(NULL, sizeof(*mlock_budget), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, /*fd=*/-1, /*offset=*/0); + if (mlock_budget == MAP_FAILED) { + mlock_budget = NULL; + pr_err("Failed to map the shared area used to take turns using the perf mlock budget\n"); + return; + } + + if (!mutex_init_pshared_robust(&mlock_budget->lock)) { + munmap(mlock_budget, sizeof(*mlock_budget)); + mlock_budget = NULL; + pr_debug("Not taking turns using the perf mlock budget\n"); + } +} + +static void perf_test__teardown_mlock_budget(void) +{ + if (!mlock_budget) + return; + + mutex_destroy(&mlock_budget->lock); + munmap(mlock_budget, sizeof(*mlock_budget)); + mlock_budget = NULL; +} + int cmd_test(int argc, const char **argv) { const char *test_usage[] = { - "perf test [] [{list |[|]}]", + "perf test [] [{list|requisites} []|" + "[|]]", NULL, }; const char *skip = NULL; @@ -1752,24 +1997,43 @@ int cmd_test(int argc, const char **argv) "Generate junit XML output, default test.xml", "test.xml"), OPT_END() }; - const char * const test_subcommands[] = { "list", NULL }; + const char * const test_subcommands[] = { "list", "requisites", NULL }; struct intlist *skiplist = NULL; - int ret = hists__init(); struct test_suite **suites; + int ret; - if (ret < 0) + ret = hists__init(); + if (ret < 0) return ret; - perf_config(perf_test__config, NULL); + /* Read test related config, like annotate.objdump, before isolating. */ + ret = perf_config(perf_test__config, NULL); + if (ret < 0) + return ret; + + /* + * Isolate the test suite from the user's ~/.perfconfig to ensure + * behavior isn't impacted by custom variables (e.g. core.hybrid-merge). + * Setting PERF_CONFIG isolates the perf commands run by the shell + * tests, config_exclusive_filename does the same for this process. + * Config values already read by main's perf_config are reset here, so + * far only the hybrid-merge value is known to alter test output. + */ + if (setenv("PERF_CONFIG", "/dev/null", 1) < 0) + return -1; + config_exclusive_filename = "/dev/null"; + perf_config__exit(); + symbol_conf.hybrid_merge = false; /* Unbuffered output */ setvbuf(stdout, NULL, _IONBF, 0); argc = parse_options_subcommand(argc, argv, test_options, test_subcommands, test_usage, 0); - if (argc >= 1 && !strcmp(argv[0], "list")) { + if (argc >= 1 && (!strcmp(argv[0], "list") || !strcmp(argv[0], "requisites"))) { suites = build_suites(); if (!suites) return errno ? -errno : -ENOMEM; + list_requisites = !strcmp(argv[0], "requisites"); ret = perf_test__list(stdout, suites, argc - 1, argv + 1); free(suites); return ret; @@ -1808,7 +2072,9 @@ int cmd_test(int argc, const char **argv) intlist__delete(skiplist); return err ? -err : -ENOMEM; } + perf_test__setup_mlock_budget(suites, argc, argv, skiplist); ret = __cmd_test(suites, argc, argv, skiplist); + perf_test__teardown_mlock_budget(); free(suites); intlist__delete(skiplist); return ret; diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index f0e8ea8754ef..130cd6021f93 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -716,10 +716,8 @@ static int do_test_code_reading(bool try_kcore) perf_env__init(&host_env); machine = machine__new_host(&host_env); - - ret = machine__create_kernel_maps(machine); - if (ret < 0) { - pr_debug("machine__create_kernel_maps failed\n"); + if (!machine) { + pr_debug("machine__new_host failed\n"); goto out_err; } @@ -739,8 +737,10 @@ static int do_test_code_reading(bool try_kcore) have_kcore = dso__is_kcore(dso); /* 2nd time through we just try kcore */ - if (try_kcore && !have_kcore) - return TEST_CODE_READING_NO_KCORE; + if (try_kcore && !have_kcore) { + err = TEST_CODE_READING_NO_KCORE; + goto out_err; + } /* No point getting kernel events if there is no kernel object */ if (!have_vmlinux && !have_kcore) @@ -885,4 +885,4 @@ static int test__code_reading(struct test_suite *test __maybe_unused, int subtes }; } -DEFINE_SUITE("Object code reading", code_reading); +DEFINE_SUITE_NEEDS_MLOCK("Object code reading", code_reading); diff --git a/tools/perf/tests/demangle-java-test.c b/tools/perf/tests/demangle-java-test.c index 0fb3e5a4a0ed..a8eb0f4effad 100644 --- a/tools/perf/tests/demangle-java-test.c +++ b/tools/perf/tests/demangle-java-test.c @@ -33,6 +33,7 @@ static int test__demangle_java(struct test_suite *test __maybe_unused, int subte if (!buf) { pr_debug("FAILED to demangle: \"%s\"\n \"%s\"\n", test_cases[i].mangled, test_cases[i].demangled); + ret = TEST_FAIL; continue; } if (strcmp(buf, test_cases[i].demangled)) { diff --git a/tools/perf/tests/demangle-rust-v0-test.c b/tools/perf/tests/demangle-rust-v0-test.c index 904f966c65d7..ee4ddb61174b 100644 --- a/tools/perf/tests/demangle-rust-v0-test.c +++ b/tools/perf/tests/demangle-rust-v0-test.c @@ -58,6 +58,7 @@ static int test__demangle_rust(struct test_suite *test __maybe_unused, int subte if (!buf) { pr_debug("FAILED to demangle: \"%s\"\n \"%s\"\n", test_cases[i].mangled, test_cases[i].demangled); + ret = TEST_FAIL; continue; } if (strcmp(buf, test_cases[i].demangled)) { diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 46bc3f597260..18b7052a2dc5 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -138,6 +138,10 @@ static int test__dso_data(struct test_suite *test __maybe_unused, int subtest __ dsos__init(&machine.dsos); dso = dso__new(file); + TEST_ASSERT_VAL("Failed to get dso", dso); + /* override the binary type for dso data access */ + dso__set_binary_type(dso, DSO_BINARY_TYPE__SYSTEM_PATH_DSO); + dso__set_bintype_searched(dso); TEST_ASSERT_VAL("Failed to add dso", !dsos__add(&machine.dsos, dso)); TEST_ASSERT_VAL("Failed to access to dso", dso__data_fd(dso, &machine) >= 0); @@ -226,6 +230,9 @@ static int dsos__create(int cnt, int size, struct dsos *dsos) TEST_ASSERT_VAL("failed to get dso file", file); dso = dso__new(file); TEST_ASSERT_VAL("failed to get dso", dso); + /* override the binary type for dso data access */ + dso__set_binary_type(dso, DSO_BINARY_TYPE__SYSTEM_PATH_DSO); + dso__set_bintype_searched(dso); TEST_ASSERT_VAL("failed to add dso", !dsos__add(dsos, dso)); dso__put(dso); } diff --git a/tools/perf/tests/event_refresh.c b/tools/perf/tests/event_refresh.c new file mode 100644 index 000000000000..726e5de4c795 --- /dev/null +++ b/tools/perf/tests/event_refresh.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Re-arm a sampling event from its overflow signal handler with + * PERF_EVENT_IOC_REFRESH and check that every overflow is delivered. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cloexec.h" +#include "debug.h" +#include "tests.h" +#include "tp_pmu.h" +#include // page_size +#include "../perf-sys.h" + +#define NR_EVENTS 1000 +/* A real-time signal is queued, so no overflow can be coalesced away. */ +#define REFRESH_SIGNAL (SIGRTMIN + 1) + +static volatile sig_atomic_t overflows, hups, refresh_errno; + +static void sig_handler(int signum __maybe_unused, siginfo_t *info, + void *uc __maybe_unused) +{ + int saved_errno = errno; + + overflows++; + if (info->si_code == POLL_HUP) + hups++; + if (ioctl(info->si_fd, PERF_EVENT_IOC_REFRESH, 1) < 0) + refresh_errno = errno; + errno = saved_errno; +} + +/* Count overflow signals for NR_EVENTS calls of trigger(), period 1. */ +static int count_overflows(struct perf_event_attr *attr, void (*trigger)(int i)) +{ + struct sigaction sa, old_sa; + uint64_t count = 0; + int fd, i, err = TEST_FAIL; + + attr->size = sizeof(*attr); + attr->sample_period = 1; + attr->disabled = 1; + + fd = sys_perf_event_open(attr, 0, -1, -1, perf_event_open_cloexec_flag()); + if (fd < 0) { + int open_errno = errno; + + pr_debug("failed opening event: %s\n", strerror(open_errno)); + return open_errno == EACCES || open_errno == EPERM ? TEST_SKIP : TEST_FAIL; + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = sig_handler; + sa.sa_flags = SA_SIGINFO; + if (sigaction(REFRESH_SIGNAL, &sa, &old_sa) < 0) { + pr_debug("failed setting up signal handler\n"); + close(fd); + return TEST_FAIL; + } + + if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) < 0 || + fcntl(fd, F_SETSIG, REFRESH_SIGNAL) < 0 || + fcntl(fd, F_SETOWN, getpid()) < 0) { + pr_debug("failed setting up signal delivery: %s\n", strerror(errno)); + goto out; + } + + overflows = 0; + hups = 0; + refresh_errno = 0; + if (ioctl(fd, PERF_EVENT_IOC_REFRESH, 1) < 0) { + pr_debug("PERF_EVENT_IOC_REFRESH failed: %s\n", strerror(errno)); + goto out; + } + for (i = 0; i < NR_EVENTS; i++) + trigger(i); + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + if (read(fd, &count, sizeof(count)) != sizeof(count)) + pr_debug("failed to read the event count\n"); + pr_debug("%d events, count %" PRIu64 ", %d overflows (%d POLL_HUP), re-arm errno %d\n", + NR_EVENTS, count, overflows, hups, refresh_errno); + /* Unrelated page faults can only add overflows, never hide one. */ + if (overflows >= NR_EVENTS) + err = TEST_OK; +out: + /* Close first: a late signal must still find our handler. */ + close(fd); + sigaction(REFRESH_SIGNAL, &old_sa, NULL); + return err; +} + +static char *pages; + +static void touch_page(int i) +{ + pages[(long)i * page_size] = 1; +} + +static int test__event_refresh_sw(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_SOFTWARE, + .config = PERF_COUNT_SW_PAGE_FAULTS, + }; + int err; + + pages = mmap(NULL, (long)NR_EVENTS * page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pages == MAP_FAILED) + return TEST_FAIL; + /* One fault per touched page: a huge page would cover many of them. */ + madvise(pages, (long)NR_EVENTS * page_size, MADV_NOHUGEPAGE); + + err = count_overflows(&attr, touch_page); + munmap(pages, (long)NR_EVENTS * page_size); + return err; +} + +static void call_getppid(int i __maybe_unused) +{ + syscall(SYS_getppid); +} + +static int test__event_refresh_tp(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_TRACEPOINT, + }; + int id = tp_pmu__id("syscalls", "sys_enter_getppid"); + + if (id < 0) { + pr_debug("no syscalls:sys_enter_getppid tracepoint\n"); + return TEST_SKIP; + } + attr.config = id; + + return count_overflows(&attr, call_getppid); +} + +static struct test_case tests__event_refresh[] = { + TEST_CASE_REASON("Software event", event_refresh_sw, "permissions"), + TEST_CASE_REASON("Tracepoint event", event_refresh_tp, "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__event_refresh = { + .desc = "Event re-armed with PERF_EVENT_IOC_REFRESH", + .test_cases = tests__event_refresh, +}; diff --git a/tools/perf/tests/fdarray.c b/tools/perf/tests/fdarray.c index 40983c3574b1..23860edb8ef0 100644 --- a/tools/perf/tests/fdarray.c +++ b/tools/perf/tests/fdarray.c @@ -80,6 +80,38 @@ static int test__fdarray__filter(struct test_suite *test __maybe_unused, int sub goto out_delete; } + fdarray__init_revents(fda, POLLHUP); + fda->priv[2].flags = fdarray_flag__nonfilterable; + + pr_debug("\nfiltering all but fda->entries[2] (nonfilterable):"); + fdarray__fprintf_prefix(fda, "before", stderr); + + /* + * Note: fdarray__filter() in tools/lib/api/fd/array.c evaluates the + * fdarray_flag__nonfilterable flag at the very top of its loop via an + * early continue. Therefore, it completely skips all processing for this + * descriptor, guaranteeing its fd, events, and revents fields remain + * entirely untouched by the filter mask evaluation below it. + */ + nr_fds = fdarray__filter(fda, POLLHUP, NULL, NULL); + fdarray__fprintf_prefix(fda, " after", stderr); + + if (nr_fds != 0) { + pr_debug("\nfdarray__filter()=%d != 0, should be 0\n", + nr_fds); + goto out_delete; + } + if (fda->entries[2].fd == -1) { + pr_debug("\nfdarray__filter() illegally modified nonfilterable fd!"); + goto out_delete; + } + if (fda->entries[2].revents != POLLHUP) { + pr_debug("\nfdarray__filter() illegally modified nonfilterable revents!"); + goto out_delete; + } + + fda->priv[2].flags = 0; /* reset flags */ + pr_debug("\n"); err = 0; diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c index 9356451a172e..c4a11a3255bc 100644 --- a/tools/perf/tests/hists_cumulate.c +++ b/tools/perf/tests/hists_cumulate.c @@ -723,7 +723,7 @@ static int test__hists_cumulate(struct test_suite *test __maybe_unused, int subt goto out; err = TEST_FAIL; - if (machines__init(&machines)) + if (machines__init(&machines, /*session=*/NULL)) goto out; /* setup threads/dso/map/symbols also */ diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c index f9eaa511487b..6c7e4cdc71a7 100644 --- a/tools/perf/tests/hists_filter.c +++ b/tools/perf/tests/hists_filter.c @@ -131,7 +131,7 @@ static int test__hists_filter(struct test_suite *test __maybe_unused, int subtes goto out; err = TEST_FAIL; - if (machines__init(&machines)) + if (machines__init(&machines, /*session=*/NULL)) goto out; /* setup threads/dso/map/symbols also */ diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index d88591bcbe50..cd32c8277627 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -303,7 +303,7 @@ static int test__hists_link(struct test_suite *test __maybe_unused, int subtest goto out; err = TEST_FAIL; - if (machines__init(&machines)) + if (machines__init(&machines, /*session=*/NULL)) goto out; /* setup threads/dso/map/symbols also */ diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c index f58c8d18fe33..35d230a83e43 100644 --- a/tools/perf/tests/hists_output.c +++ b/tools/perf/tests/hists_output.c @@ -610,7 +610,7 @@ static int test__hists_output(struct test_suite *test __maybe_unused, int subtes goto out; err = TEST_FAIL; - if (machines__init(&machines)) + if (machines__init(&machines, /*session=*/NULL)) goto out; /* setup threads/dso/map/symbols also */ diff --git a/tools/perf/tests/hybrid-merge.c b/tools/perf/tests/hybrid-merge.c new file mode 100644 index 000000000000..426b1d09618e --- /dev/null +++ b/tools/perf/tests/hybrid-merge.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include "debug.h" +#include "env.h" +#include "evlist.h" +#include "evsel.h" +#include "tests.h" + +/* A hybrid machine with 2 core PMUs, as read from a perf.data file. */ +static struct hybrid_node two_core_pmus[] = { + { .pmu_name = (char *)"cpu_atom", .cpus = (char *)"0-3", }, + { .pmu_name = (char *)"cpu_core", .cpus = (char *)"4-7", }, +}; + +/* A machine with more than 2 kinds of core, like some Arm big.LITTLE. */ +static struct hybrid_node three_core_pmus[] = { + { .pmu_name = (char *)"cpu_atom", .cpus = (char *)"0-3", }, + { .pmu_name = (char *)"cpu_core", .cpus = (char *)"4-7", }, + { .pmu_name = (char *)"cpu_lowpower", .cpus = (char *)"8", }, +}; + +static struct evsel *test_evsel__new(struct evlist *evlist, const char *name) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_RAW, + .size = sizeof(attr), + .config = 0x3c, + }; + struct evsel *evsel = evsel__new(&attr); + + if (!evsel) + return NULL; + + evsel->name = strdup(name); + if (!evsel->name) { + evsel__put(evsel); + return NULL; + } + evlist__add(evlist, evsel); + return evsel; +} + +/* + * The assertions are in helpers taking an already allocated evlist, so that the + * caller can release the evlist however an assertion fails. + */ +static int check_merge_events(struct evlist *evlist, struct perf_env *env) +{ + struct evsel *atom_cycles, *core_cycles, *atom_insns, *core_insns, *pos; + + /* As if "perf record -e cycles,instructions" ran on a hybrid machine. */ + atom_cycles = test_evsel__new(evlist, "cpu_atom/cycles/"); + core_cycles = test_evsel__new(evlist, "cpu_core/cycles/"); + atom_insns = test_evsel__new(evlist, "cpu_atom/instructions/"); + core_insns = test_evsel__new(evlist, "cpu_core/instructions/"); + TEST_ASSERT_VAL("failed to allocate evsels", + atom_cycles && core_cycles && atom_insns && core_insns); + + TEST_ASSERT_VAL("events should be mergeable", + evlist__can_merge_hybrid(evlist, env)); + + /* Testing for merging must not alter the evlist. */ + evlist__for_each_entry(evlist, pos) { + TEST_ASSERT_VAL("evlist modified by evlist__can_merge_hybrid", + !pos->first_wildcard_match); + } + + evlist__merge_hybrid(evlist, env); + + TEST_ASSERT_VAL("cycles not merged", + core_cycles->first_wildcard_match == atom_cycles); + /* All the events must be merged, not just the first pair found. */ + TEST_ASSERT_VAL("instructions not merged", + core_insns->first_wildcard_match == atom_insns); + TEST_ASSERT_VAL("wrong cycles leader", + evsel__leader(core_cycles) == atom_cycles); + TEST_ASSERT_VAL("wrong instructions leader", + evsel__leader(core_insns) == atom_insns); + TEST_ASSERT_VAL("wrong cycles group size", atom_cycles->core.nr_members == 2); + TEST_ASSERT_VAL("wrong instructions group size", atom_insns->core.nr_members == 2); + + return TEST_OK; +} + +static int test__hybrid_merge_events(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_env env = { + .nr_hybrid_nodes = ARRAY_SIZE(two_core_pmus), + .hybrid_nodes = two_core_pmus, + }; + struct evlist *evlist = evlist__new(); + int ret; + + TEST_ASSERT_VAL("failed to allocate evlist", evlist); + + ret = check_merge_events(evlist, &env); + evlist__put(evlist); + return ret; +} + +static int check_merge_3_core_pmus(struct evlist *evlist, struct perf_env *env) +{ + struct evsel *atom_cycles, *core_cycles, *lowpower_cycles; + + atom_cycles = test_evsel__new(evlist, "cpu_atom/cycles/"); + core_cycles = test_evsel__new(evlist, "cpu_core/cycles/"); + lowpower_cycles = test_evsel__new(evlist, "cpu_lowpower/cycles/"); + TEST_ASSERT_VAL("failed to allocate evsels", + atom_cycles && core_cycles && lowpower_cycles); + + evlist__merge_hybrid(evlist, env); + + TEST_ASSERT_VAL("second core PMU not merged", + core_cycles->first_wildcard_match == atom_cycles); + TEST_ASSERT_VAL("third core PMU not merged", + lowpower_cycles->first_wildcard_match == atom_cycles); + TEST_ASSERT_VAL("wrong group size", atom_cycles->core.nr_members == 3); + + return TEST_OK; +} + +static int test__hybrid_merge_3_core_pmus(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_env env = { + .nr_hybrid_nodes = ARRAY_SIZE(three_core_pmus), + .hybrid_nodes = three_core_pmus, + }; + struct evlist *evlist = evlist__new(); + int ret; + + TEST_ASSERT_VAL("failed to allocate evlist", evlist); + + ret = check_merge_3_core_pmus(evlist, &env); + evlist__put(evlist); + return ret; +} + +static int check_unmergeable_core_events(struct evlist *evlist, struct perf_env *env) +{ + /* A single event has nothing to merge with. */ + TEST_ASSERT_VAL("failed to allocate evsel", + test_evsel__new(evlist, "cpu_core/cycles/")); + TEST_ASSERT_VAL("a single event shouldn't merge", + !evlist__can_merge_hybrid(evlist, env)); + + /* Events of different names shouldn't merge. */ + TEST_ASSERT_VAL("failed to allocate evsel", + test_evsel__new(evlist, "cpu_atom/instructions/")); + TEST_ASSERT_VAL("events with different names shouldn't merge", + !evlist__can_merge_hybrid(evlist, env)); + + return TEST_OK; +} + +static int check_unmergeable_uncore_events(struct evlist *evlist, struct perf_env *env) +{ + /* Matching events on non-core PMUs shouldn't merge. */ + TEST_ASSERT_VAL("failed to allocate evsels", + test_evsel__new(evlist, "uncore_imc_0/clockticks/") && + test_evsel__new(evlist, "uncore_imc_1/clockticks/")); + TEST_ASSERT_VAL("uncore events shouldn't merge", + !evlist__can_merge_hybrid(evlist, env)); + + return TEST_OK; +} + +static int test__hybrid_merge_unmergeable(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_env env = { + .nr_hybrid_nodes = ARRAY_SIZE(two_core_pmus), + .hybrid_nodes = two_core_pmus, + }; + struct evlist *evlist = evlist__new(); + int ret; + + TEST_ASSERT_VAL("failed to allocate evlist", evlist); + + ret = check_unmergeable_core_events(evlist, &env); + evlist__put(evlist); + if (ret != TEST_OK) + return ret; + + evlist = evlist__new(); + TEST_ASSERT_VAL("failed to allocate evlist", evlist); + + ret = check_unmergeable_uncore_events(evlist, &env); + evlist__put(evlist); + return ret; +} + +static struct test_case tests__hybrid_merge[] = { + TEST_CASE("Merge events of 2 core PMUs", hybrid_merge_events), + TEST_CASE("Merge events of 3 core PMUs", hybrid_merge_3_core_pmus), + TEST_CASE("Events that shouldn't merge", hybrid_merge_unmergeable), + { .name = NULL, } +}; + +struct test_suite suite__hybrid_merge = { + .desc = "Hybrid event merging", + .test_cases = tests__hybrid_merge, +}; diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c index b760041bed30..6d8d8cd57200 100644 --- a/tools/perf/tests/keep-tracking.c +++ b/tools/perf/tests/keep-tracking.c @@ -161,4 +161,4 @@ static int test__keep_tracking(struct test_suite *test __maybe_unused, int subte return err; } -DEFINE_SUITE("Use a dummy software event to keep tracking", keep_tracking); +DEFINE_SUITE_NEEDS_MLOCK("Use a dummy software event to keep tracking", keep_tracking); diff --git a/tools/perf/tests/make b/tools/perf/tests/make index d2c2f526e1db..7524de522802 100644 --- a/tools/perf/tests/make +++ b/tools/perf/tests/make @@ -28,15 +28,17 @@ endif PARALLEL_OPT= ifeq ($(SET_PARALLEL),1) - ifeq ($(JOBS),) - cores := $(shell (getconf _NPROCESSORS_ONLN || grep -E -c '^processor|^CPU[0-9]' /proc/cpuinfo) 2>/dev/null) - ifeq ($(cores),0) - cores := 1 + ifeq ($(findstring jobserver,$(MAKEFLAGS))$(findstring -j,$(MAKEFLAGS)),) + ifeq ($(JOBS),) + cores := $(shell (getconf _NPROCESSORS_ONLN || grep -E -c '^processor|^CPU[0-9]' /proc/cpuinfo) 2>/dev/null) + ifeq ($(cores),0) + cores := 1 + endif + else + cores=$(JOBS) endif - else - cores=$(JOBS) + PARALLEL_OPT="-j$(cores)" endif - PARALLEL_OPT="-j$(cores)" endif # As per kernel Makefile, avoid funny character set dependencies @@ -69,14 +71,16 @@ make_clean_all := clean all make_python_perf_so := $(python_perf_so) make_debug := DEBUG=1 make_nondistro := BUILD_NONDISTRO=1 +make_nondistro_no_demangle := BUILD_NONDISTRO=1 NO_DEMANGLE=1 +make_clang := CC=clang +make_clang_nondistro := CC=clang BUILD_NONDISTRO=1 make_extra_tests := EXTRA_TESTS=1 make_no_jevents := NO_JEVENTS=1 make_jevents_all := JEVENTS_ARCH=all make_no_bpf_skel := BUILD_BPF_SKEL=0 make_gen_vmlinux_h := GEN_VMLINUX_H=1 -make_libperl := LIBPERL=1 -make_no_libpython := NO_LIBPYTHON=1 -make_no_scripts := NO_LIBPYTHON=1 +make_no_python_module := NO_PYTHON_MODULE=1 +make_no_scripts := NO_PYTHON_MODULE=1 make_no_slang := NO_SLANG=1 make_no_demangle := NO_DEMANGLE=1 make_no_libelf := NO_LIBELF=1 @@ -95,7 +99,7 @@ make_no_babeltrace2 := NO_BABELTRACE2=1 make_with_coresight := CORESIGHT=1 make_no_sdt := NO_SDT=1 make_no_libpfm4 := NO_LIBPFM4=1 -make_with_gtk2 := GTK2=1 +make_with_gtk4 := GTK4=1 make_refcnt_check := EXTRA_CFLAGS="-DREFCNT_CHECKING=1" make_tags := tags make_cscope := cscope @@ -116,7 +120,7 @@ make_install_prefix_slash := install prefix=/tmp/krava/ make_static := LDFLAGS=-static NO_PERF_READ_VDSO32=1 NO_PERF_READ_VDSOX32=1 NO_JVMTI=1 NO_LIBTRACEEVENT=1 NO_LIBELF=1 # all the NO_* variable combined -make_minimal := NO_LIBPYTHON=1 +make_minimal := NO_PYTHON_MODULE=1 make_minimal += NO_DEMANGLE=1 NO_LIBELF=1 NO_BACKTRACE=1 make_minimal += NO_LIBNUMA=1 NO_LIBBIONIC=1 NO_LIBDW=1 make_minimal += NO_LIBBPF=1 @@ -126,6 +130,18 @@ make_minimal += NO_CAPSTONE=1 # binutils 2_42 and newer have bfd_thread_init() new_libbfd := $(shell echo '#include ' | $(CC) -E -x c - | grep bfd_thread_init) +# Whether libLLVM can be built, which needs the llvm-devel/llvm-dev headers and +# libraries of version 13 or newer, not just llvm-config. This mirrors the +# llvm-perf feature test in tools/build/feature/test-llvm-perf.cpp so that +# libLLVM is only asserted below when the default compiler can really build it. +ifneq ($(call has,$(LLVM_CONFIG)),) +have_libllvm := $(shell printf '#include \n#include \n#if LLVM_VERSION_MAJOR < 13\n#error "llvm-devel/llvm-dev version 13 or greater is required"\n#endif\nint main(){llvm::errs()<<"";llvm::llvm_shutdown();return 0;}\n' | \ + $(CXX) -x c++ -std=gnu++17 -I$(shell $(LLVM_CONFIG) --includedir 2>/dev/null) - -o /dev/null \ + -L$(shell $(LLVM_CONFIG) --libdir 2>/dev/null) \ + $(shell $(LLVM_CONFIG) --libs Core BPF 2>/dev/null) \ + $(shell $(LLVM_CONFIG) --system-libs 2>/dev/null) >/dev/null 2>&1 && echo y) +endif + # $(run) contains all available tests run := make_pure # Targets 'clean all' can be run together only through top level @@ -141,21 +157,30 @@ run += make_python_perf_so run += make_debug ifneq ($(new_libbfd),) run += make_nondistro +# Demangling with libbfd is only built when the C++ ABI's __cxa_demangle +# isn't available, so it needs a build of its own to be compiled at all. +run += make_nondistro_no_demangle +endif +# CXX is only set to clang++ by LLVM=1, so a CC=clang build has to cope with +# a C compiler and a C++ compiler that don't match. +ifneq ($(call has,clang),) +run += make_clang +ifneq ($(new_libbfd),) +run += make_clang_nondistro +endif endif run += make_extra_tests run += make_no_jevents run += make_jevents_all run += make_no_bpf_skel run += make_gen_vmlinux_h -run += make_libperl -run += make_no_libpython +run += make_no_python_module run += make_no_scripts run += make_no_slang run += make_no_demangle run += make_no_libelf run += make_no_libdw run += make_libunwind -run += make_no_libdw_dwarf_unwind run += make_no_backtrace run += make_no_libcapstone run += make_libcapstone_dlopen @@ -165,10 +190,8 @@ run += make_no_libbpf run += make_no_libbpf_DEBUG run += make_no_libllvm run += make_no_sdt -run += make_no_syscall_tbl run += make_no_babeltrace2 run += make_with_coresight -run += make_with_clangllvm run += make_no_libpfm4 run += make_refcnt_check run += make_help @@ -230,7 +253,7 @@ test_make_util_pmu_bison_o := test -f $(PERF_O)/util/pmu-bison.o define test_dest_files for file in $(1); do \ - if [ ! -x $$TMP_DEST/$$file ]; then \ + if [ ! -f $$TMP_DEST/$$file ]; then \ echo " failed to find: $$file"; \ fi \ done @@ -279,6 +302,17 @@ test_make_install_pdf_O := $(test_ok) test_make_libbpf_dynamic := ldd $(PERF_O)/perf | grep -q libbpf test_make_libbpf_dynamic_O := ldd $$TMP_O/perf | grep -q libbpf +# Feature tests are allowed to fail, so a broken C++ compiler command line +# doesn't fail the build, it just quietly turns off everything that needs a +# C++ compiler. Check a feature that does, otherwise these tests would pass +# while producing a perf with less in it than the default build has. +ifneq ($(have_libllvm),) +test_make_clang := test -x $(PERF_O)/perf && $(PERF_O)/perf check -q feature libLLVM +test_make_clang_O := test -x $$TMP_O/perf && $$TMP_O/perf check -q feature libLLVM +test_make_clang_nondistro := $(test_make_clang) +test_make_clang_nondistro_O := $(test_make_clang_O) +endif + test_make_python_perf_so_O := test -f $$TMP_O/python/perf.so test_make_perf_o_O := test -f $$TMP_O/perf.o test_make_util_map_o_O := test -f $$TMP_O/util/map.o @@ -310,7 +344,8 @@ clean := @(cd $(PERF); $(MAKE_F) -s $(O_OPT) clean >/dev/null && $(MAKE) -s $(O_ $(run): $(call clean) - @TMP_DEST=$$(mktemp -d); \ + +@TMP_DEST=$$(mktemp -d); \ + trap 'rm -rf $@ $$TMP_DEST; exit 130' INT TERM; \ cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \ printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \ ( eval $$cmd ) >> $@ 2>&1; \ @@ -318,9 +353,10 @@ $(run): $(call test,$@) && \ rm -rf $@ $$TMP_DEST || (cat $@ ; false) -make_with_gtk2: +make_with_gtk4: $(call clean) - @TMP_DEST=$$(mktemp -d); \ + +@TMP_DEST=$$(mktemp -d); \ + trap 'rm -rf $@ $$TMP_DEST; exit 130' INT TERM; \ cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \ printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \ ( eval $$cmd ) >> $@ 2>&1; \ @@ -330,7 +366,8 @@ make_with_gtk2: make_static: $(call clean) - @TMP_DEST=$$(mktemp -d); \ + +@TMP_DEST=$$(mktemp -d); \ + trap 'rm -rf $@ $$TMP_DEST; exit 130' INT TERM; \ cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \ printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \ ( eval $$cmd ) >> $@ 2>&1; \ @@ -339,9 +376,12 @@ make_static: rm -rf $@ $$TMP_DEST || (cat $@ ; false) $(run_O): +ifneq ($(O_CLEAN),0) $(call clean) - @TMP_O=$$(mktemp -d); \ +endif + +@TMP_O=$$(mktemp -d); \ TMP_DEST=$$(mktemp -d); \ + trap 'rm -rf $@ $$TMP_O $$TMP_DEST; exit 130' INT TERM; \ cmd="cd $(PERF) && $(MAKE_F) $($(patsubst %_O,%,$@)) $(PARALLEL_OPT) O=$$TMP_O DESTDIR=$$TMP_DEST"; \ printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \ ( eval $$cmd ) >> $@ 2>&1 && \ @@ -363,18 +403,18 @@ endif make_kernelsrc: @echo "- make -C $(PARALLEL_OPT) $(K_O_OPT) tools/perf" $(call clean); \ - (make -C ../.. $(PARALLEL_OPT) $(K_O_OPT) tools/perf) > $@ 2>&1 && \ + ($(MAKE) -C ../.. $(PARALLEL_OPT) $(K_O_OPT) tools/perf) > $@ 2>&1 && \ test -x $(KERNEL_O)/tools/perf/perf && rm -f $@ || (cat $@ ; false) make_kernelsrc_tools: @echo "- make -C /tools $(PARALLEL_OPT) $(K_O_OPT) perf" $(call clean); \ - (make -C ../../tools $(PARALLEL_OPT) $(K_O_OPT) perf) > $@ 2>&1 && \ + ($(MAKE) -C ../../tools $(PARALLEL_OPT) $(K_O_OPT) perf) > $@ 2>&1 && \ test -x $(KERNEL_O)/tools/perf/perf && rm -f $@ || (cat $@ ; false) make_libperf: @echo "- make -C lib"; - make -C lib clean >$@ 2>&1; make -C lib >>$@ 2>&1 && rm $@ + $(MAKE) -C lib clean >$@ 2>&1; $(MAKE) -C lib >>$@ 2>&1 && rm $@ FEATURES_DUMP_FILE := $(FULL_O)/BUILD_TEST_FEATURE_DUMP FEATURES_DUMP_FILE_STATIC := $(FULL_O)/BUILD_TEST_FEATURE_DUMP_STATIC @@ -383,25 +423,33 @@ all: $(run) $(run_O) tarpkg make_kernelsrc make_kernelsrc_tools @echo OK @rm -f $(FEATURES_DUMP_FILE) $(FEATURES_DUMP_FILE_STATIC) -out: $(run_O) +out: + $(call clean) + @$(MAKE) -f tests/make $(run_O) PARALLEL_OPT= O_CLEAN=0 $(if $(findstring jobserver,$(MAKEFLAGS))$(findstring -j,$(MAKEFLAGS)),,$(if $(cores),-j$(cores))) @echo OK @rm -f $(FEATURES_DUMP_FILE) $(FEATURES_DUMP_FILE_STATIC) ifeq ($(REUSE_FEATURES_DUMP),1) $(FEATURES_DUMP_FILE): $(call clean) - @cmd="cd $(PERF) && make FEATURE_DUMP_COPY=$@ $(O_OPT) feature-dump"; \ + @cmd="cd $(PERF) && $(MAKE) FEATURE_DUMP_COPY=$@ $(O_OPT) feature-dump"; \ echo "- $@: $$cmd" && echo $$cmd && \ ( eval $$cmd ) > /dev/null 2>&1 $(FEATURES_DUMP_FILE_STATIC): $(call clean) - @cmd="cd $(PERF) && make FEATURE_DUMP_COPY=$@ $(O_OPT) LDFLAGS='-static' feature-dump"; \ + @cmd="cd $(PERF) && $(MAKE) FEATURE_DUMP_COPY=$@ $(O_OPT) LDFLAGS='-static' feature-dump"; \ echo "- $@: $$cmd" && echo $$cmd && \ ( eval $$cmd ) > /dev/null 2>&1 +# The clang tests exist to run feature detection with a compiler other than +# the default one, so they have to do their own and are left out of both the +# dependency and the 'FEATURES_DUMP=' append below. +no_features_dump := make_clang make_clang_nondistro +no_features_dump += $(addsuffix _O,$(no_features_dump)) + # Add feature dump dependency for run/run_O targets -$(foreach t,$(run) $(run_O),$(eval \ +$(foreach t,$(filter-out $(no_features_dump),$(run) $(run_O)),$(eval \ $(t): $(if $(findstring make_static,$(t)),\ $(FEATURES_DUMP_FILE_STATIC),\ $(FEATURES_DUMP_FILE)))) @@ -409,10 +457,14 @@ $(foreach t,$(run) $(run_O),$(eval \ # Append 'FEATURES_DUMP=' option to all test cases. For example: # make_no_libbpf: NO_LIBBPF=1 --> NO_LIBBPF=1 FEATURES_DUMP=/a/b/BUILD_TEST_FEATURE_DUMP # make_static: LDFLAGS=-static --> LDFLAGS=-static FEATURES_DUMP=/a/b/BUILD_TEST_FEATURE_DUMP_STATIC -$(foreach t,$(run),$(if $(findstring make_static,$(t)),\ +$(foreach t,$(filter-out $(no_features_dump),$(run)),$(if $(findstring make_static,$(t)),\ $(eval $(t) := $($(t)) FEATURES_DUMP=$(FEATURES_DUMP_FILE_STATIC)),\ $(eval $(t) := $($(t)) FEATURES_DUMP=$(FEATURES_DUMP_FILE)))) endif .PHONY: all $(run) $(run_O) tarpkg clean make_kernelsrc make_kernelsrc_tools make_libperf endif # ifndef MK + +# Serialize execution of tasks writing to shared in-tree files +make_tags_O: | make_clean_all_O +make_cscope_O: | make_clean_all_O diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index 5cec7644952c..602a78d48b10 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,8 @@ #include #include +#include + #include "cpumap.h" #include "debug.h" #include "event.h" @@ -182,50 +185,83 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest } enum user_read_state { - USER_READ_ENABLED, - USER_READ_DISABLED, - USER_READ_UNKNOWN, + USER_READ_ERR_PERM = -2, + USER_READ_ERR_NO_FILE = -1, + + USER_READ_DISABLED = '0', + USER_READ_ENABLED = '1', +#if defined(__i386__) || defined(__x86_64__) || (defined(__riscv) && __riscv_xlen == 64) + /* Unrestricted access on x86, legacy access on RISC-V. */ + USER_READ_LEGACY = '2', +#endif + USER_READ_MAX }; -static enum user_read_state set_user_read(struct perf_pmu *pmu, enum user_read_state enabled) +static enum user_read_state user_read_file_op(int fd, enum user_read_state enabled) { char buf[2] = {0, '\n'}; - ssize_t len; - int events_fd, rdpmc_fd; - enum user_read_state old_user_read = USER_READ_UNKNOWN; + ssize_t len = read(fd, buf, 1); + enum user_read_state old_user_read = USER_READ_ERR_NO_FILE; - if (enabled == USER_READ_UNKNOWN) - return USER_READ_UNKNOWN; - - events_fd = perf_pmu__event_source_devices_fd(); - if (events_fd < 0) - return USER_READ_UNKNOWN; - - rdpmc_fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR); - if (rdpmc_fd < 0) { - close(events_fd); - return USER_READ_UNKNOWN; + if (len <= 0) { + pr_debug("%s read failed\n", __func__); + goto out; } - len = read(rdpmc_fd, buf, sizeof(buf)); - if (len != sizeof(buf)) - pr_debug("%s read failed\n", __func__); - - // Note, on Intel hybrid disabling on 1 PMU will implicitly disable on - // all the core PMUs. - old_user_read = (buf[0] == '1') ? USER_READ_ENABLED : USER_READ_DISABLED; + old_user_read = buf[0]; if (enabled != old_user_read) { - buf[0] = (enabled == USER_READ_ENABLED) ? '1' : '0'; - len = write(rdpmc_fd, buf, sizeof(buf)); + buf[0] = enabled; + lseek(fd, 0, SEEK_SET); + len = write(fd, buf, sizeof(buf)); if (len != sizeof(buf)) pr_debug("%s write failed\n", __func__); } - close(rdpmc_fd); - close(events_fd); + +out: + close(fd); return old_user_read; } +static enum user_read_state set_user_read(struct perf_pmu *pmu, + enum user_read_state enabled) +{ + int events_fd, fd; + enum user_read_state ret = USER_READ_ERR_NO_FILE; + + if (enabled == USER_READ_ERR_NO_FILE || enabled == USER_READ_ERR_PERM) + return enabled; + + events_fd = perf_pmu__event_source_devices_fd(); + if (events_fd >= 0) { + fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR); + if (fd >= 0) { + /* + * Note, on Intel hybrid disabling on 1 PMU will implicitly + * disable on all the core PMUs. + */ + ret = user_read_file_op(fd, enabled); + close(events_fd); + return ret; + } else if (errno == EACCES || errno == EROFS) { + /* Permissions failure, flag the failure for a skip. */ + close(events_fd); + return USER_READ_ERR_PERM; + } + close(events_fd); + } + + /* Fallback: perf_user_access interface (arm64, riscv, or similar) */ + fd = open("/proc/sys/kernel/perf_user_access", O_RDWR); + if (fd >= 0) + ret = user_read_file_op(fd, enabled); + else if (errno == EACCES || errno == EROFS) + ret = USER_READ_ERR_PERM; + + return ret; +} + + static int test_stat_user_read(u64 event, enum user_read_state enabled) { struct perf_pmu *pmu = NULL; @@ -246,9 +282,6 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) .config = perf_pmus__supports_extended_type() ? event | ((u64)pmu->type << PERF_PMU_TYPE_SHIFT) : event, -#ifdef __aarch64__ - .config1 = 0x2, /* Request user access */ -#endif }; struct perf_evsel *evsel = NULL; int err; @@ -256,8 +289,28 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) bool mapped = false, opened = false, rdpmc_supported; struct perf_counts_values counts = { .val = 0 }; + if (__is_defined(__aarch64__) && enabled == USER_READ_ENABLED) + attr.config1 = 0x2; /* Request user access */ pr_debug("User space counter reading for PMU %s\n", pmu->name); + + if (saved_user_read_state == USER_READ_ERR_PERM) { + pr_debug("User space counter reading for PMU %s [Failed to get user read state]\n", + pmu->name); + ret = TEST_SKIP; + goto cleanup; + } + + /* Check value is in the expected range */ + if (saved_user_read_state != USER_READ_ERR_NO_FILE && + (saved_user_read_state < USER_READ_DISABLED || + saved_user_read_state >= USER_READ_MAX)) { + pr_err("User space counter reading for PMU %s [Unexpected current state %d]\n", + pmu->name, saved_user_read_state); + ret = TEST_FAIL; + goto cleanup; + } + /* * Restrict scheduling to only use the rdpmc on the CPUs the * event can be on. If the test doesn't run on the CPU of the @@ -301,21 +354,18 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) goto cleanup; } - if (saved_user_read_state == USER_READ_UNKNOWN) - rdpmc_supported = pc->cap_user_rdpmc && pc->index; - else - rdpmc_supported = (enabled == USER_READ_ENABLED); + rdpmc_supported = (enabled == USER_READ_ENABLED); - if (rdpmc_supported && (!pc->cap_user_rdpmc || !pc->index)) { - pr_err("User space counter reading for PMU %s [Failed unexpected supported counter access %d %d]\n", - pmu->name, pc->cap_user_rdpmc, pc->index); + if (rdpmc_supported && !pc->index) { + pr_err("User space counter reading for PMU %s [index must be set when enabled %d]\n", + pmu->name, pc->index); ret = TEST_FAIL; goto cleanup; } - if (!rdpmc_supported && pc->cap_user_rdpmc) { - pr_err("User space counter reading for PMU %s [Failed unexpected unsupported counter access %d]\n", - pmu->name, pc->cap_user_rdpmc); + if (rdpmc_supported != pc->cap_user_rdpmc) { + pr_err("User space counter reading for PMU %s [cap should match requested state %d %d]\n", + pmu->name, pc->cap_user_rdpmc, rdpmc_supported); ret = TEST_FAIL; goto cleanup; } @@ -363,7 +413,8 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) perf_evsel__munmap(evsel); if (opened) perf_evsel__close(evsel); - perf_evsel__delete(evsel); + if (evsel) + perf_evsel__delete(evsel); /* If the affinity was changed, then put it back to all CPUs. */ if (pmu->cpus != NULL) { @@ -378,13 +429,13 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) return ret; } -static int test__mmap_user_read_instr(struct test_suite *test __maybe_unused, +static int __maybe_unused test__mmap_user_read_instr(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { return test_stat_user_read(PERF_COUNT_HW_INSTRUCTIONS, USER_READ_ENABLED); } -static int test__mmap_user_read_cycles(struct test_suite *test __maybe_unused, +static int __maybe_unused test__mmap_user_read_cycles(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { return test_stat_user_read(PERF_COUNT_HW_CPU_CYCLES, USER_READ_ENABLED); @@ -406,46 +457,34 @@ static struct test_case tests__basic_mmap[] = { TEST_CASE_REASON("Read samples using the mmap interface", basic_mmap, "permissions"), + +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ + (defined(__riscv) && __riscv_xlen == 64) + /* + * libperf only supports userspace read for these platforms, see + * tools/lib/perf/mmap.c + */ TEST_CASE_REASON_EXCLUSIVE("User space counter reading of instructions", - mmap_user_read_instr, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" -#endif - ), + mmap_user_read_instr, + "permissions"), TEST_CASE_REASON_EXCLUSIVE("User space counter reading of cycles", - mmap_user_read_cycles, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" + mmap_user_read_cycles, + "permissions"), #endif - ), + + /* Counter read via userpage fallback is supported on all platforms */ TEST_CASE_REASON_EXCLUSIVE("User space counter disabling instructions", - mmap_user_read_instr_disabled, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" -#endif - ), + mmap_user_read_instr_disabled, + "permissions"), TEST_CASE_REASON_EXCLUSIVE("User space counter disabling cycles", - mmap_user_read_cycles_disabled, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" -#endif - ), + mmap_user_read_cycles_disabled, + "permissions"), + { .name = NULL, } }; struct test_suite suite__basic_mmap = { .desc = "mmap interface tests", .test_cases = tests__basic_mmap, + .needs_mlock = true, }; diff --git a/tools/perf/tests/openat-syscall-tp-fields.c b/tools/perf/tests/openat-syscall-tp-fields.c index 5365889d326f..1ea336fbc7e7 100644 --- a/tools/perf/tests/openat-syscall-tp-fields.c +++ b/tools/perf/tests/openat-syscall-tp-fields.c @@ -160,4 +160,5 @@ static struct test_case tests__syscall_openat_tp_fields[] = { struct test_suite suite__syscall_openat_tp_fields = { .desc = "syscalls:sys_enter_openat event fields", .test_cases = tests__syscall_openat_tp_fields, + .needs_mlock = true, }; diff --git a/tools/perf/tests/pe-file-parsing.c b/tools/perf/tests/pe-file-parsing.c index 30c7da79e109..190520ffc3ad 100644 --- a/tools/perf/tests/pe-file-parsing.c +++ b/tools/perf/tests/pe-file-parsing.c @@ -59,7 +59,7 @@ static int run_dir(const char *d) TEST_ASSERT_VAL("Failed to get dso", dso); ret = dso__load_bfd_symbols(dso, debugfile); - TEST_ASSERT_VAL("Failed to load symbols", ret == 0); + TEST_ASSERT_VAL("Failed to load symbols", ret > 0); dso__sort_by_name(dso); sym = dso__find_symbol_by_name(dso, "main", &idx); @@ -74,17 +74,20 @@ static int test__pe_file_parsing(struct test_suite *test __maybe_unused, { struct stat st; char path_dir[PATH_MAX]; - - /* First try development tree tests. */ - if (!lstat("./tests", &st)) - return run_dir("./tests"); - - /* Then installed path. */ + const char * const tests_dirs[] = { + "./tools/perf/tests", + "./tests", + "./source/tests", + path_dir, + }; snprintf(path_dir, PATH_MAX, "%s/tests", get_argv_exec_path()); - if (!lstat(path_dir, &st)) - return run_dir(path_dir); + for (size_t i = 0; i < ARRAY_SIZE(tests_dirs); ++i) { + if (!lstat(tests_dirs[i], &st)) + return run_dir(tests_dirs[i]); + } + pr_debug("cannot find the tests directory\n"); return TEST_SKIP; } @@ -93,6 +96,7 @@ static int test__pe_file_parsing(struct test_suite *test __maybe_unused, static int test__pe_file_parsing(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { + pr_debug("libbfd is not available\n"); return TEST_SKIP; } diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c index 0cac6ae1a1fc..370dbde08eac 100644 --- a/tools/perf/tests/perf-record.c +++ b/tools/perf/tests/perf-record.c @@ -372,4 +372,5 @@ static struct test_case tests__PERF_RECORD[] = { struct test_suite suite__PERF_RECORD = { .desc = "PERF_RECORD_* events & perf_sample fields", .test_cases = tests__PERF_RECORD, + .needs_mlock = true, }; diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c index f8f71fdd32b1..0cefa2df2db7 100644 --- a/tools/perf/tests/perf-time-to-tsc.c +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -218,4 +218,5 @@ static struct test_case time_to_tsc_tests[] = { struct test_suite suite__perf_time_to_tsc = { .desc = "Convert perf time to TSC", .test_cases = time_to_tsc_tests, + .needs_mlock = true, }; diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c index f507ce39439a..e6e4f4cf1fa2 100644 --- a/tools/perf/tests/pmu-events.c +++ b/tools/perf/tests/pmu-events.c @@ -906,9 +906,12 @@ static int test__parsing_callback(const struct pmu_metric *pm, struct metric_expr *mexp; list_for_each_entry (mexp, &me->head, nd) { + double result; + if (strcmp(mexp->metric_name, pm->metric_name)) continue; - pr_debug("Result %f\n", test_generic_metric(mexp, 0)); + result = test_generic_metric(mexp, 0); + pr_debug("Result %f\n", result); err = 0; (*failures)--; goto out_err; diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index 32dbc484487a..bd30f6d4c31b 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include @@ -417,6 +418,98 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) return ret; } +static int test_truncated_branch_stack(void) +{ + struct perf_event_attr attr = { + .sample_type = PERF_SAMPLE_BRANCH_STACK, + }; + struct { + struct perf_event_header header; + u64 nr; + struct branch_entry entry; + } input = { + .header = { + .type = PERF_RECORD_SAMPLE, + .size = sizeof(input.header) + sizeof(input.nr), + }, + .nr = 1, + }; + struct perf_sample sample; + struct evsel *evsel; + u64 flags = 1; + int err; + + input.entry.flags.value = flags; + evsel = evsel__new(&attr); + if (!evsel) + return -1; + + evsel->sample_size = __evsel__sample_size(attr.sample_type); + err = __evsel__parse_sample(evsel, (union perf_event *)&input, + &sample, /*needs_swap=*/true); + perf_sample__exit(&sample); + evsel__put(evsel); + + if (err != -EFAULT) { + pr_debug("truncated branch stack returned %d, expected -EFAULT\n", err); + return -1; + } + if (input.entry.flags.value != flags) { + pr_debug("truncated branch stack modified data past the event\n"); + return -1; + } + return 0; +} + +static int test_truncated_swapped_raw(u16 event_size, u32 raw_size) +{ + struct perf_event_attr attr = { + .sample_type = PERF_SAMPLE_RAW, + }; + struct { + struct perf_event_header header; + union { + u64 value; + u32 words[2]; + } raw; + u64 canary; + } input = { + .header = { + .type = PERF_RECORD_SAMPLE, + .size = event_size, + }, + /* Parsing a pre-swapped word exchanges these two u32 values. */ + .raw.words = { 0x12345678, raw_size }, + .canary = 0x8877665544332211ULL, + }; + struct perf_sample sample; + struct evsel *evsel; + u64 raw = input.raw.value; + u64 canary = input.canary; + int err; + + evsel = evsel__new(&attr); + if (!evsel) + return -1; + + evsel->sample_size = __evsel__sample_size(attr.sample_type); + err = __evsel__parse_sample(evsel, (union perf_event *)&input, + &sample, /*needs_swap=*/true); + perf_sample__exit(&sample); + evsel__put(evsel); + + if (err != -EFAULT) { + pr_debug("truncated swapped RAW sample (size %u, raw %u) returned %d, expected -EFAULT\n", + event_size, raw_size, err); + return -1; + } + if (input.raw.value != raw || input.canary != canary) { + pr_debug("truncated swapped RAW sample modified data before validation\n"); + return -1; + } + return 0; +} + /** * test__sample_parsing - test sample parsing. * @@ -433,6 +526,22 @@ static int test__sample_parsing(struct test_suite *test __maybe_unused, int subt size_t i; int err; + err = test_truncated_branch_stack(); + if (err) + return err; + + /* The declared RAW payload extends past an otherwise aligned event. */ + err = test_truncated_swapped_raw(sizeof(struct perf_event_header) + + sizeof(u64), 16); + if (err) + return err; + + /* The final complete word touched by mem_bswap_64() extends past it. */ + err = test_truncated_swapped_raw(sizeof(struct perf_event_header) + + sizeof(u32) + 9, 9); + if (err) + return err; + /* * Fail the test if it has not been updated when new sample format bits * were added. Please actually update the test rather than just change diff --git a/tools/perf/tests/shell/annotate.sh b/tools/perf/tests/shell/annotate.sh index 689de58e9238..26411edf5cb4 100755 --- a/tools/perf/tests/shell/annotate.sh +++ b/tools/perf/tests/shell/annotate.sh @@ -6,7 +6,6 @@ set -e shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh testsym="noploop" diff --git a/tools/perf/tests/shell/annotate_weight.sh b/tools/perf/tests/shell/annotate_weight.sh new file mode 100755 index 000000000000..6b8c105c048b --- /dev/null +++ b/tools/perf/tests/shell/annotate_weight.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# perf annotate weight regression test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=tools/perf/tests/shell/lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +testsym="test_loop" +skip_test_missing_symbol "${testsym}" + +perfdata=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX) +record_log=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX.log) +report_out=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX.report) +annotate_out=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX.annotate) + +cleanup() { + rm -f "${perfdata}" "${record_log}" "${report_out}" "${annotate_out}" + trap - EXIT TERM INT +} + +trap 'cleanup; exit 1' TERM INT +trap cleanup EXIT + +# mem-loads:pu requests a precise user PEBS event whose sample weight should +# be populated by -W. Unsupported PEBS/weight PMUs are skipped below. +if ! perf record -W -e mem-loads:pu -o "${perfdata}" -- perf test -w thloop \ + > /dev/null 2> "${record_log}"; then + echo "[SKIP] precise PEBS weight sampling is unavailable" + exit 2 +fi + +# Confirm the PMU actually produced nonzero weights. A successful record alone +# is insufficient: some PMUs accept the event but provide no weight payload. +if ! perf report --stdio -i "${perfdata}" --fields=weight1,weight2,weight3,symbol --percent-limit 0 \ + > "${report_out}" 2> "${record_log}"; then + echo "[SKIP] weighted samples cannot be decoded" + exit 2 +fi + +if ! awk '$1 ~ /^[0-9]/ && ($1 + 0) > 0 { found = 1 } END { exit !found }' \ + "${report_out}"; then + echo "[SKIP] PEBS weight sampling produced no nonzero weights" + exit 2 +fi + +perf annotate --stdio -i "${perfdata}" --symbol "${testsym}" \ + > "${annotate_out}" 2> "${record_log}" + +grep -q 'Percent Weight' "${annotate_out}" + +# The second numeric column is the rendered average weight. This assertion +# fails on the original regression because calc_percent() left it at zero. +if ! awk '$1 ~ /^[0-9]/ && $2 ~ /^[0-9]/ && ($2 + 0) > 0 { found = 1 } END { exit !found }' \ + "${annotate_out}"; then + echo "Annotation output contained no nonzero weight" + cat "${annotate_out}" + exit 1 +fi + +echo "PEBS annotation weights: PASS" diff --git a/tools/perf/tests/shell/attr.sh b/tools/perf/tests/shell/attr.sh index 5a4e43b2471d..ca3ca4a19397 100755 --- a/tools/perf/tests/shell/attr.sh +++ b/tools/perf/tests/shell/attr.sh @@ -4,6 +4,10 @@ err=0 +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + cleanup() { trap - EXIT TERM INT } @@ -17,6 +21,7 @@ trap trap_cleanup EXIT TERM INT shelldir=$(dirname "$0") perf_path=$(which perf) -python "${shelldir}"/lib/attr.py -d "${shelldir}"/attr -v -p "$perf_path" +$PYTHON "${shelldir}"/lib/attr.py -d "${shelldir}"/attr -v -p "$perf_path" +err=$? cleanup exit $err diff --git a/tools/perf/tests/shell/attr/README b/tools/perf/tests/shell/attr/README index 67c4ca76b85d..c7b49cdf8729 100644 --- a/tools/perf/tests/shell/attr/README +++ b/tools/perf/tests/shell/attr/README @@ -49,10 +49,10 @@ Following tests are defined (with perf commands): perf record --call-graph dwarf kill (test-record-graph-dwarf) perf record --call-graph fp kill (test-record-graph-fp) perf record --call-graph fp kill (test-record-graph-fp-aarch64) - perf record -e '{cycles,instructions}' kill (test-record-group1) - perf record -e '{cycles/period=1/,instructions/period=2/}:S' kill (test-record-group2) - perf record -e '{cycles,cache-misses}:S' kill (test-record-group-sampling1) - perf record -c 10000 -e '{cycles,cache-misses}:S' kill (test-record-group-sampling2) + perf record -e '{cycles,default_core/legacy-hardware-config=1/}' kill (test-record-group1) + perf record -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill (test-record-group2) + perf record -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill (test-record-group-sampling1) + perf record -c 10000 -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill (test-record-group-sampling2) perf record -D kill (test-record-no-delay) perf record -i kill (test-record-no-inherit) perf record -n kill (test-record-no-samples) @@ -67,5 +67,5 @@ Following tests are defined (with perf commands): perf stat -d kill (test-stat-detailed-1) perf stat -dd kill (test-stat-detailed-2) perf stat -ddd kill (test-stat-detailed-3) - perf stat -e '{cycles,instructions}' kill (test-stat-group1) + perf stat -e '{cycles,default_core/legacy-hardware-config=1/}' kill (test-stat-group1) perf stat -i -e cycles kill (test-stat-no-inherit) diff --git a/tools/perf/tests/shell/attr/base-record b/tools/perf/tests/shell/attr/base-record index b44e4e6e4443..a4597d4c0cd1 100644 --- a/tools/perf/tests/shell/attr/base-record +++ b/tools/perf/tests/shell/attr/base-record @@ -5,7 +5,7 @@ group_fd=-1 flags=0|8 cpu=* type=0|1 -size=136 +size=176 config=0|1 sample_period=* sample_type=263 diff --git a/tools/perf/tests/shell/attr/base-stat b/tools/perf/tests/shell/attr/base-stat index fccd8ec4d1b0..67d6eea89470 100644 --- a/tools/perf/tests/shell/attr/base-stat +++ b/tools/perf/tests/shell/attr/base-stat @@ -5,7 +5,7 @@ group_fd=-1 flags=0|8 cpu=* type=0 -size=136 +size=176 config=0 sample_period=0 sample_type=65536 diff --git a/tools/perf/tests/shell/attr/base-stat-group b/tools/perf/tests/shell/attr/base-stat-group new file mode 100644 index 000000000000..9bcb8ab3687f --- /dev/null +++ b/tools/perf/tests/shell/attr/base-stat-group @@ -0,0 +1,45 @@ +[event] +fd=1 +group_fd=-1 +# 0 or PERF_FLAG_FD_CLOEXEC flag +flags=0|8 +cpu=* +type=0 +size=176 +config=0 +sample_period=0 +sample_type=65536 +# The default and -d/-dd/-ddd metrics open part of their events in groups: +# the members get read_format=15 and are enabled right away, the standalone +# ones keep read_format=3 and wait for the exec, and the leaders get 15 when +# their group has members, 3 when alone, as create_perf_stat_counter() goes. +read_format=3|15 +disabled=0|1 +inherit=1 +pinned=0 +exclusive=0 +exclude_user=0 +exclude_kernel=0|1 +exclude_hv=0|1 +exclude_idle=0 +mmap=0 +comm=0 +freq=0 +inherit_stat=0 +enable_on_exec=0|1 +task=0 +watermark=0 +precise_ip=0 +mmap_data=0 +sample_id_all=0 +exclude_host=0|1 +exclude_guest=0|1 +exclude_callchain_kernel=0 +exclude_callchain_user=0 +wakeup_events=0 +bp_type=0 +config1=0 +config2=0 +branch_sample_type=0 +sample_regs_user=0 +sample_stack_user=0 diff --git a/tools/perf/tests/shell/attr/system-wide-dummy b/tools/perf/tests/shell/attr/system-wide-dummy index a1e1d6a263bf..b062ad7f3c1c 100644 --- a/tools/perf/tests/shell/attr/system-wide-dummy +++ b/tools/perf/tests/shell/attr/system-wide-dummy @@ -7,7 +7,7 @@ cpu=* pid=-1 flags=8 type=1 -size=136 +size=176 config=9 sample_period=1 # PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | diff --git a/tools/perf/tests/shell/attr/test-record-dummy-C0 b/tools/perf/tests/shell/attr/test-record-dummy-C0 index 91499405fff4..54adb835d203 100644 --- a/tools/perf/tests/shell/attr/test-record-dummy-C0 +++ b/tools/perf/tests/shell/attr/test-record-dummy-C0 @@ -10,7 +10,7 @@ cpu=0 pid=-1 flags=8 type=1 -size=136 +size=176 config=9 sample_period=4000 # PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | diff --git a/tools/perf/tests/shell/attr/test-record-graph-dwarf b/tools/perf/tests/shell/attr/test-record-graph-dwarf index ae92061d611d..fbe0faa8cdb6 100644 --- a/tools/perf/tests/shell/attr/test-record-graph-dwarf +++ b/tools/perf/tests/shell/attr/test-record-graph-dwarf @@ -4,7 +4,7 @@ args = --no-bpf-event --call-graph dwarf -- kill >/dev/null 2>&1 ret = 1 [event:base-record] -sample_type=45359 +sample_type=12583 exclude_callchain_user=1 sample_stack_user=8192 # TODO different for each arch, no support for that now diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling b/tools/perf/tests/shell/attr/test-record-group-sampling index 86a940d7895d..ed47dd1e5984 100644 --- a/tools/perf/tests/shell/attr/test-record-group-sampling +++ b/tools/perf/tests/shell/attr/test-record-group-sampling @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1 ret = 1 kernel_until = 6.12 +requires = core-pmu [event-1:base-record] fd=1 @@ -15,7 +16,7 @@ inherit=0 fd=2 group_fd=1 -# cache-misses +# cache-misses, explicitly requested with the legacy hardware encoding type=0 config=3 diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling1 b/tools/perf/tests/shell/attr/test-record-group-sampling1 index 4748ab7bf684..9cf013dc03dd 100644 --- a/tools/perf/tests/shell/attr/test-record-group-sampling1 +++ b/tools/perf/tests/shell/attr/test-record-group-sampling1 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1 ret = 1 kernel_since = 6.12 +requires = core-pmu [event-1:base-record] fd=1 @@ -30,7 +31,7 @@ inherit=1 fd=2 group_fd=1 -# cache-misses +# cache-misses, explicitly requested with the legacy hardware encoding type=0 config=3 diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling2 b/tools/perf/tests/shell/attr/test-record-group-sampling2 index e0432244a0eb..bd1eb4161e4a 100644 --- a/tools/perf/tests/shell/attr/test-record-group-sampling2 +++ b/tools/perf/tests/shell/attr/test-record-group-sampling2 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -c 10000 -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1 ret = 1 kernel_since = 6.12 +requires = core-pmu [event-1:base-record] fd=1 @@ -36,7 +37,7 @@ write_backward=0 fd=2 group_fd=1 -# cache-misses +# cache-misses, explicitly requested with the legacy hardware encoding type=0 config=3 diff --git a/tools/perf/tests/shell/attr/test-record-group1 b/tools/perf/tests/shell/attr/test-record-group1 index eeb1db392bc9..5fdd5288555f 100644 --- a/tools/perf/tests/shell/attr/test-record-group1 +++ b/tools/perf/tests/shell/attr/test-record-group1 @@ -1,7 +1,8 @@ [config] command = record -args = --no-bpf-event -e '{cycles,instructions}' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=1/}' kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event-1:base-record] fd=1 diff --git a/tools/perf/tests/shell/attr/test-record-group2 b/tools/perf/tests/shell/attr/test-record-group2 index 891d41a7bddf..e7274108f5fd 100644 --- a/tools/perf/tests/shell/attr/test-record-group2 +++ b/tools/perf/tests/shell/attr/test-record-group2 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill >/dev/null 2>&1 ret = 1 kernel_until = 6.12 +requires = core-pmu [event-1:base-record] fd=1 diff --git a/tools/perf/tests/shell/attr/test-record-group3 b/tools/perf/tests/shell/attr/test-record-group3 index 249be884959e..e74fd3a48ac9 100644 --- a/tools/perf/tests/shell/attr/test-record-group3 +++ b/tools/perf/tests/shell/attr/test-record-group3 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill >/dev/null 2>&1 ret = 1 kernel_since = 6.12 +requires = core-pmu [event-1:base-record] fd=1 diff --git a/tools/perf/tests/shell/attr/test-stat-C0 b/tools/perf/tests/shell/attr/test-stat-C0 index a2c76d10b2bb..9d242e93e3a9 100644 --- a/tools/perf/tests/shell/attr/test-stat-C0 +++ b/tools/perf/tests/shell/attr/test-stat-C0 @@ -2,6 +2,7 @@ command = stat args = -e cycles -C 0 kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] # events are disabled by default when attached to cpu diff --git a/tools/perf/tests/shell/attr/test-stat-basic b/tools/perf/tests/shell/attr/test-stat-basic index 69867d049fda..277897bd2626 100644 --- a/tools/perf/tests/shell/attr/test-stat-basic +++ b/tools/perf/tests/shell/attr/test-stat-basic @@ -2,6 +2,7 @@ command = stat args = -e cycles kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] optional=1 diff --git a/tools/perf/tests/shell/attr/test-stat-default b/tools/perf/tests/shell/attr/test-stat-default index 8dd27c1fb661..6a0c0c43f233 100644 --- a/tools/perf/tests/shell/attr/test-stat-default +++ b/tools/perf/tests/shell/attr/test-stat-default @@ -2,74 +2,75 @@ command = stat args = kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -78,7 +79,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -89,7 +90,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -100,7 +101,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -111,7 +112,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -122,7 +123,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -133,7 +134,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -144,7 +145,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -155,7 +156,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -166,71 +167,99 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event29:base-stat] +[event29:base-stat-group] fd=29 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event30:base-stat-group] +fd=30 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event31:base-stat-group] +fd=31 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event32:base-stat-group] +fd=32 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event33:base-stat-group] +fd=33 +type=4 +config=25166257|25182129|209|391 optional=1 \ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-1 b/tools/perf/tests/shell/attr/test-stat-detailed-1 index 12a2ebf4e64a..3d47dc6f815f 100644 --- a/tools/perf/tests/shell/attr/test-stat-detailed-1 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-1 @@ -2,76 +2,77 @@ command = stat args = -d kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -80,7 +81,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -91,7 +92,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -102,7 +103,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -113,7 +114,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -124,7 +125,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -135,7 +136,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -146,7 +147,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -157,7 +158,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -168,63 +169,63 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 @@ -234,7 +235,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event29:base-stat] +[event29:base-stat-group] fd=29 type=3 config=0 @@ -244,7 +245,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event30:base-stat] +[event30:base-stat-group] fd=30 type=3 config=65536 @@ -254,7 +255,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event31:base-stat] +[event31:base-stat-group] fd=31 type=3 config=2 @@ -264,15 +265,43 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event32:base-stat] +[event32:base-stat-group] fd=32 type=3 config=65538 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event33:base-stat] +[event33:base-stat-group] fd=33 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event34:base-stat-group] +fd=34 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event35:base-stat-group] +fd=35 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event36:base-stat-group] +fd=36 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event37:base-stat-group] +fd=37 +type=4 +config=25166257|25182129|209|391 optional=1 \ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-2 b/tools/perf/tests/shell/attr/test-stat-detailed-2 index 66ea25b7d38f..ebe1f9e2736d 100644 --- a/tools/perf/tests/shell/attr/test-stat-detailed-2 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-2 @@ -2,76 +2,77 @@ command = stat args = -dd kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -80,7 +81,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -91,7 +92,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -102,7 +103,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -113,7 +114,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -124,7 +125,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -135,7 +136,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -146,7 +147,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -157,7 +158,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -168,63 +169,63 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 @@ -234,7 +235,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event29:base-stat] +[event29:base-stat-group] fd=29 type=3 config=0 @@ -244,7 +245,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event30:base-stat] +[event30:base-stat-group] fd=30 type=3 config=65536 @@ -254,7 +255,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event31:base-stat] +[event31:base-stat-group] fd=31 type=3 config=2 @@ -264,7 +265,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event32:base-stat] +[event32:base-stat-group] fd=32 type=3 config=65538 @@ -274,7 +275,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event33:base-stat] +[event33:base-stat-group] fd=33 type=3 config=1 @@ -284,7 +285,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event34:base-stat] +[event34:base-stat-group] fd=34 type=3 config=65537 @@ -294,7 +295,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event35:base-stat] +[event35:base-stat-group] fd=35 type=3 config=3 @@ -304,7 +305,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event36:base-stat] +[event36:base-stat-group] fd=36 type=3 config=65539 @@ -314,7 +315,7 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event37:base-stat] +[event37:base-stat-group] fd=37 type=3 config=4 @@ -324,15 +325,43 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event38:base-stat] +[event38:base-stat-group] fd=38 type=3 config=65540 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event39:base-stat] +[event39:base-stat-group] fd=39 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event40:base-stat-group] +fd=40 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event41:base-stat-group] +fd=41 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event42:base-stat-group] +fd=42 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event43:base-stat-group] +fd=43 +type=4 +config=25166257|25182129|209|391 optional=1 \ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-3 b/tools/perf/tests/shell/attr/test-stat-detailed-3 index 4a27bbfb9f87..e7eef828245c 100644 --- a/tools/perf/tests/shell/attr/test-stat-detailed-3 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-3 @@ -2,76 +2,77 @@ command = stat args = -ddd kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -80,7 +81,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -91,7 +92,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -102,7 +103,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -113,7 +114,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -124,7 +125,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -135,7 +136,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -146,7 +147,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -157,7 +158,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -168,63 +169,63 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 @@ -234,7 +235,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event29:base-stat] +[event29:base-stat-group] fd=29 type=3 config=0 @@ -244,7 +245,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event30:base-stat] +[event30:base-stat-group] fd=30 type=3 config=65536 @@ -254,7 +255,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event31:base-stat] +[event31:base-stat-group] fd=31 type=3 config=2 @@ -264,7 +265,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event32:base-stat] +[event32:base-stat-group] fd=32 type=3 config=65538 @@ -274,7 +275,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event33:base-stat] +[event33:base-stat-group] fd=33 type=3 config=1 @@ -284,7 +285,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event34:base-stat] +[event34:base-stat-group] fd=34 type=3 config=65537 @@ -294,7 +295,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event35:base-stat] +[event35:base-stat-group] fd=35 type=3 config=3 @@ -304,7 +305,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event36:base-stat] +[event36:base-stat-group] fd=36 type=3 config=65539 @@ -314,7 +315,7 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event37:base-stat] +[event37:base-stat-group] fd=37 type=3 config=4 @@ -324,7 +325,7 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event38:base-stat] +[event38:base-stat-group] fd=38 type=3 config=65540 @@ -334,7 +335,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event39:base-stat] +[event39:base-stat-group] fd=39 type=3 config=512 @@ -344,15 +345,43 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event40:base-stat] +[event40:base-stat-group] fd=40 type=3 config=66048 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event41:base-stat] +[event41:base-stat-group] fd=41 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event42:base-stat-group] +fd=42 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event43:base-stat-group] +fd=43 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event44:base-stat-group] +fd=44 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event45:base-stat-group] +fd=45 +type=4 +config=25166257|25182129|209|391 optional=1 \ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-group1 b/tools/perf/tests/shell/attr/test-stat-group1 index 1746751123dc..7a31b27933f1 100644 --- a/tools/perf/tests/shell/attr/test-stat-group1 +++ b/tools/perf/tests/shell/attr/test-stat-group1 @@ -1,7 +1,8 @@ [config] command = stat -args = -e '{cycles,instructions}' kill >/dev/null 2>&1 +args = -e '{cycles,default_core/legacy-hardware-config=1/}' kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event-1:base-stat] fd=1 diff --git a/tools/perf/tests/shell/attr/test-stat-no-inherit b/tools/perf/tests/shell/attr/test-stat-no-inherit index 924fbb9300d1..38f746855b1b 100644 --- a/tools/perf/tests/shell/attr/test-stat-no-inherit +++ b/tools/perf/tests/shell/attr/test-stat-no-inherit @@ -2,6 +2,7 @@ command = stat args = -i -e cycles kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] inherit=0 diff --git a/tools/perf/tests/shell/base_probe/test_adding_kernel.sh b/tools/perf/tests/shell/base_probe/test_adding_kernel.sh index 555a825d55f2..eadac837f108 100755 --- a/tools/perf/tests/shell/base_probe/test_adding_kernel.sh +++ b/tools/perf/tests/shell/base_probe/test_adding_kernel.sh @@ -19,7 +19,6 @@ TEST_RESULT=0 # include working environment . "$DIR_PATH/../common/init.sh" -# shellcheck source=lib/probe_vfs_getname.sh . "$DIR_PATH/../lib/probe_vfs_getname.sh" TEST_PROBE=${TEST_PROBE:-"inode_permission"} diff --git a/tools/perf/tests/shell/c2c.sh b/tools/perf/tests/shell/c2c.sh index 2471d44595c3..cf76359d19cc 100755 --- a/tools/perf/tests/shell/c2c.sh +++ b/tools/perf/tests/shell/c2c.sh @@ -6,10 +6,15 @@ set -e err=0 perfdata=$(mktemp /tmp/__perf_c2c_test.perf.data.XXXXX) +funcdata=$(mktemp /tmp/__perf_c2c_function_test.perf.data.XXXXX) +perfout=$(mktemp /tmp/__perf_c2c_test.output.XXXXX) cleanup() { rm -f "${perfdata}" rm -f "${perfdata}".old + rm -f "${funcdata}" + rm -f "${funcdata}".old + rm -f "${perfout}" trap - EXIT TERM INT } @@ -39,6 +44,7 @@ test_c2c_record_report() { # Run a workload that does some memory operations. if ! perf c2c record -o "${perfdata}" -- perf test -w datasym 1 > /dev/null 2>&1 ; then echo "c2c record and report test [Skipped: perf c2c record failed during workload]" + err=2 return fi @@ -57,6 +63,114 @@ test_c2c_record_report() { echo "c2c record and report test [Success]" } +test_c2c_function_report() { + echo "c2c function stdio report test" + + if perf c2c report -i "${perfdata}" --function -c pid > "${perfout}" 2>&1 ; then + echo "c2c function stdio report test [Failed: report accepted missing iaddr]" + err=1 + return + fi + if ! grep -Fq "The function view requires iaddr in --coalesce." "${perfout}" ; then + echo "c2c function stdio report test [Failed: missing iaddr diagnostic]" + cat "${perfout}" + err=1 + return + fi + if grep -Fq "Shared Data Functions Table" "${perfout}" ; then + echo "c2c function stdio report test [Failed: partial report on missing iaddr]" + cat "${perfout}" + err=1 + return + fi + + if perf c2c report -i "${perfdata}" --function --stats > "${perfout}" 2>&1 ; then + echo "c2c function stdio report test [Failed: accepted conflicting options]" + err=1 + return + fi + if ! grep -Fq -- "--stats and --function cannot be used together." "${perfout}" ; then + echo "c2c function stdio report test [Failed: missing conflict diagnostic]" + cat "${perfout}" + err=1 + return + fi + + # Exercise contended futex hash-bucket locks so the function view can get + # reader, writer, and cacheline rows without changing the original test. + if ! perf c2c record -o "${funcdata}" -- \ + perf bench futex hash -t 4 -r 1 -s > /dev/null 2>&1 ; then + echo "c2c function stdio report test [Skipped: recording failed]" + err=2 + return + fi + + if ! perf c2c report -i "${funcdata}" --function > "${perfout}" 2>&1 ; then + echo "c2c function stdio report test [Failed: report failed]" + cat "${perfout}" + err=1 + return + fi + + for expected in "Shared Data Functions Table" \ + "# Cycles Store" \ + "# % count Function / Contending function / Cacheline" \ + "# ......... ......." ; do + if ! grep -Fq "${expected}" "${perfout}" ; then + echo "c2c function stdio report test [Failed: missing '${expected}']" + cat "${perfout}" + err=1 + return + fi + done + for unexpected in "Shared Data Cache Line Table" \ + "Shared Cache Line Distribution Pareto" ; do + if grep -Fq "${unexpected}" "${perfout}" ; then + echo "c2c function stdio report test [Failed: found '${unexpected}']" + cat "${perfout}" + err=1 + return + fi + done + + if ! awk '/^# \.+/ { body = 1; next } + body && !/^#/ && NF { found = 1 } + END { exit !found }' "${perfout}" ; then + echo "c2c function stdio report test [Skipped: no contended samples]" + err=2 + return + fi + + # The spaces intentionally verify per-level indentation and expanded + # fold-sign placement without depending on symbol names. + for expected in \ + '^ - +[0-9]+\.[0-9]+% +[0-9]+ - ' \ + '^ +[0-9]+ - ' \ + '^ +[0-9]+ 0x[[:xdigit:]]+$' ; do + if ! grep -Eq "${expected}" "${perfout}" ; then + echo "c2c function stdio report test [Failed: missing hierarchy row]" + echo " ${expected}" + cat "${perfout}" + err=1 + return + fi + done + + if awk '/^# \.+/ { body = 1; next } + body && /[[:blank:]]$/ { found = 1 } + END { exit !found }' "${perfout}" ; then + echo "c2c function stdio report test [Failed: trailing whitespace in table body]" + cat "${perfout}" + err=1 + return + fi + + echo "c2c function stdio report test [Success]" +} + test_c2c_record_report +if [ "${err}" -eq 0 ]; then + test_c2c_function_report +fi cleanup exit $err diff --git a/tools/perf/tests/shell/common/init.sh b/tools/perf/tests/shell/common/init.sh index cbfc78bec974..db89a5be95dc 100644 --- a/tools/perf/tests/shell/common/init.sh +++ b/tools/perf/tests/shell/common/init.sh @@ -130,9 +130,28 @@ check_uprobes_available() test -e /sys/kernel/debug/tracing/uprobe_events } +# Disable every kprobe and uprobe, listed as "TYPE:GROUP/EVENT ARGS...". +disable_all_probes() +{ + PROBE_SPECS=`cat /sys/kernel/debug/tracing/kprobe_events \ + /sys/kernel/debug/tracing/uprobe_events 2> /dev/null | + cut -d ' ' -f 1` + for PROBE_SPEC in $PROBE_SPECS + do + case "$PROBE_SPEC" in + *:*/*) ;; + *) continue ;; + esac + PROBE_ENABLE="/sys/kernel/debug/tracing/events/${PROBE_SPEC#*:}/enable" + test -e "$PROBE_ENABLE" && echo 0 > "$PROBE_ENABLE" + done +} + clear_all_probes() { - echo 0 > /sys/kernel/debug/tracing/events/enable + # Any enabled probe makes the clearing below fail with EBUSY. Unlike + # events/enable, this leaves other sessions' tracepoints alone. + disable_all_probes check_kprobes_available && echo > /sys/kernel/debug/tracing/kprobe_events check_uprobes_available && echo > /sys/kernel/debug/tracing/uprobe_events } diff --git a/tools/perf/tests/shell/coresight/callchain.sh b/tools/perf/tests/shell/coresight/callchain.sh index 13cca7dc1118..43ad90bc3034 100755 --- a/tools/perf/tests/shell/coresight/callchain.sh +++ b/tools/perf/tests/shell/coresight/callchain.sh @@ -30,6 +30,11 @@ skip_if_system_is_not_ready() return 2 } + [ -e /proc/kcore ] || { + echo "[Skip] /proc/kcore is not available" >&2 + return 2 + } + return 0 } @@ -43,7 +48,7 @@ record_trace() mkfifo "$cf" "$af" - perf record -o "$data" -e cs_etm// --per-thread -D -1 --control fifo:"$cf","$af" -- \ + perf record --kcore -o "$data" -e cs_etm// --per-thread -D -1 --control fifo:"$cf","$af" -- \ perf test --record-ctl fifo:"$cf","$af" -w callchain >/dev/null 2>&1 && # It is safe to use 'i3i' with a three-instruction interval, since the diff --git a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh index f3ebad596378..d298b18154b2 100755 --- a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh +++ b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Check Arm CoreSight disassembly script completes without errors (exclusive) # SPDX-License-Identifier: GPL-2.0 +# Check Arm CoreSight disassembly script completes without errors (exclusive) # The disassembly script reconstructs ranges of instructions and gives these to objdump to # decode. objdump doesn't like ranges that go backwards, but these are a good indication @@ -20,11 +20,16 @@ skip_if_no_cs_etm_event || exit 2 set -e glb_err=1 +# shellcheck source=lib/setup_python.sh +. "$(dirname "$0")"/../lib/setup_python.sh +$PYTHON -c "import perf" 2>/dev/null || { + echo "Skipping test, perf python module not found" + exit 2 +} + perfdata_dir=$(mktemp -d /tmp/__perf_test.perf.data.XXXXX) perfdata=${perfdata_dir}/perf.data file=$(mktemp /tmp/temporary_file.XXXXX) -# Relative path works whether it's installed or running from repo -script_path=$(dirname "$0")/../../../scripts/python/arm-cs-trace-disasm.py cleanup_files() { @@ -44,7 +49,7 @@ branch_search='[[:space:]](bl|b(\.(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al) if [ "$(id -u)" == 0 ] && [ -e /proc/kcore ]; then echo "Testing kernel disassembly" perf record -o ${perfdata} -e cs_etm//k --kcore -Se -m,64K -- touch $file > /dev/null 2>&1 - perf script -i ${perfdata} --itrace=b -s python:${script_path} -- \ + perf script -i ${perfdata} arm-cs-trace-disasm -- \ -d --stop-sample=2 -k ${perfdata}/kcore_dir/kcore 2> /dev/null > ${file} grep -q -E ${branch_search} ${file} echo "Found kernel branches" @@ -56,7 +61,7 @@ fi ## Test user ## echo "Testing userspace disassembly" perf record -o ${perfdata} -e cs_etm//u -Se -m,64K -- touch $file > /dev/null 2>&1 -perf script -i ${perfdata} --itrace=b -s python:${script_path} -- \ +perf script -i ${perfdata} arm-cs-trace-disasm -- \ -d --stop-sample=2 2> /dev/null > ${file} grep -q -E ${branch_search} ${file} echo "Found userspace branches" diff --git a/tools/perf/tests/shell/data_type_profiling.sh b/tools/perf/tests/shell/data_type_profiling.sh index eca694600a04..57203e0e5f85 100755 --- a/tools/perf/tests/shell/data_type_profiling.sh +++ b/tools/perf/tests/shell/data_type_profiling.sh @@ -8,8 +8,8 @@ set -e # data type profiling manifestation # Values in testtypes and testprogs should match -testtypes=("# data-type: struct Buf" "# data-type: struct buf") -testprogs=("perf test -w code_with_type" "perf test -w datasym") +testtypes=("# data-type: struct Buf" "# data-type: struct buf" "# data-type: struct net_conn") +testprogs=("perf test -w code_with_type" "perf test -w datasym" "perf test -w false_sharing") err=0 perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) @@ -19,6 +19,15 @@ perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX) perf mem record -o /dev/null -- true 2>&1 | \ grep -q "failed: no PMU supports the memory events" && exit 2 +# Skip if per-thread mem record is not supported on this PMU (e.g. AMD IBS +# needs system-wide '-a'): it is what the test records with below, and a +# failing record must not be reported as a test failure. +if ! perf mem record -o /dev/null -- true 2>/dev/null +then + echo "Skip: cannot record memory events on this PMU" + exit 2 +fi + cleanup() { rm -rf "${perfdata}" "${perfout}" rm -rf "${perfdata}".old @@ -50,27 +59,47 @@ test_basic_annotate() { "xC") index=1 ;; + + "xFS") + index=2 ;; esac + # Under 'set -e' a bare failing command aborts the script through the EXIT + # trap, so the commands that report a failure have to be the condition of + # an 'if' for that reporting to ever happen. if [ "x${mode}" == "xBasic" ] then - perf mem record -o "${perfdata}" ${testprogs[$index]} 2> /dev/null + if ! perf mem record -o "${perfdata}" ${testprogs[$index]} 2> /dev/null + then + echo "${mode} annotate [Failed: perf record]" + err=1 + return + fi else - perf mem record -o - ${testprogs[$index]} 2> /dev/null > "${perfdata}" - fi - if [ "x$?" != "x0" ] - then - echo "${mode} annotate [Failed: perf record]" - err=1 - return + if ! perf mem record -o - ${testprogs[$index]} 2> /dev/null > "${perfdata}" + then + echo "${mode} annotate [Failed: perf record]" + err=1 + return + fi fi # Generate the annotated output file if [ "x${mode}" == "xBasic" ] then - perf annotate --code-with-type -i "${perfdata}" --stdio --percent-limit 1 2> /dev/null > "${perfout}" + if ! perf annotate --code-with-type -i "${perfdata}" --stdio --percent-limit 1 2> /dev/null > "${perfout}" + then + echo "${mode} annotate [Failed: perf annotate]" + err=1 + return + fi else - perf annotate --code-with-type -i - --stdio 2> /dev/null --percent-limit 1 < "${perfdata}" > "${perfout}" + if ! perf annotate --code-with-type -i - --stdio 2> /dev/null --percent-limit 1 < "${perfdata}" > "${perfout}" + then + echo "${mode} annotate [Failed: perf annotate]" + err=1 + return + fi fi # check if it has the target data type @@ -88,6 +117,8 @@ test_basic_annotate Basic Rust test_basic_annotate Pipe Rust test_basic_annotate Basic C test_basic_annotate Pipe C +test_basic_annotate Basic FS +test_basic_annotate Pipe FS cleanup exit $err diff --git a/tools/perf/tests/shell/diff.sh b/tools/perf/tests/shell/diff.sh index fe05fdebcab5..88f37b8ef893 100755 --- a/tools/perf/tests/shell/diff.sh +++ b/tools/perf/tests/shell/diff.sh @@ -11,7 +11,6 @@ perfdata3=$(mktemp /tmp/__perf_test.perf.data.XXXXX) testprog="perf test -w thloop" shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh testsym="test_loop" diff --git a/tools/perf/tests/shell/drm_pmu.sh b/tools/perf/tests/shell/drm_pmu.sh index e629fe0e8463..c827e613fc66 100755 --- a/tools/perf/tests/shell/drm_pmu.sh +++ b/tools/perf/tests/shell/drm_pmu.sh @@ -1,5 +1,5 @@ #!/bin/bash -# DRM PMU +# DRM PMU (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/ftrace.sh b/tools/perf/tests/shell/ftrace.sh index 9f6e590f6437..84d6126bf94d 100755 --- a/tools/perf/tests/shell/ftrace.sh +++ b/tools/perf/tests/shell/ftrace.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf ftrace tests +# perf ftrace tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh index d83c2591db8f..6ee4c8b5c850 100755 --- a/tools/perf/tests/shell/inject_aslr.sh +++ b/tools/perf/tests/shell/inject_aslr.sh @@ -6,7 +6,6 @@ set -e set -o pipefail shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh sym="noploop" diff --git a/tools/perf/tests/shell/jitdump-python.sh b/tools/perf/tests/shell/jitdump-python.sh index 05aaa3bd900b..ad345a35c16d 100755 --- a/tools/perf/tests/shell/jitdump-python.sh +++ b/tools/perf/tests/shell/jitdump-python.sh @@ -3,7 +3,6 @@ # SPDX-License-Identifier: GPL-2.0 SHELLDIR=$(dirname $0) -# shellcheck source=lib/setup_python.sh . "${SHELLDIR}"/lib/setup_python.sh OUTPUT=$(${PYTHON} -Xperf_jit -c 'import os, sys; print(os.getpid(), sys.is_stack_trampoline_active())' 2> /dev/null) diff --git a/tools/perf/tests/shell/kallsyms.sh b/tools/perf/tests/shell/kallsyms.sh index d0eb99753f47..e3674cc52b30 100755 --- a/tools/perf/tests/shell/kallsyms.sh +++ b/tools/perf/tests/shell/kallsyms.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf kallsyms tests +# perf kallsyms tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 err=0 diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py index bfccc727d9b2..df1993883199 100644 --- a/tools/perf/tests/shell/lib/attr.py +++ b/tools/perf/tests/shell/lib/attr.py @@ -1,10 +1,11 @@ # SPDX-License-Identifier: GPL-2.0 import configparser +import errno import os import sys import glob -import optparse +import argparse import platform import tempfile import logging @@ -12,6 +13,8 @@ import re import shutil import subprocess +log = logging.getLogger('test') + def data_equal(a, b): # Allow multiple values in assignment separated by '|' a_list = a.split('|') @@ -26,6 +29,192 @@ def data_equal(a, b): return False +CORE_PMU_AVAILABLE = None +CORE_PMU_COUNT = None + +def core_pmu_available(): + """ + Is there a core PMU on this machine? Without one hardware events aren't + supported and get replaced by the software fallback, so there's no way to + check the expectations that pin their legacy encodings. The answer can't + change while the tests run, so probe the binding only once. + """ + core_pmu_probe() + return CORE_PMU_AVAILABLE + +def core_pmu_probe(): + """ + Ask perf itself, through its python binding, to parse and open 'cycles' + once: a parse failure or an ENOENT means no PMU can count it, any other + error means we can't tell whether there is one, and the number of events + it expanded to is the number of core PMUs, see + parse_events_multi_pmu_add(), which is what core_pmu_count() asks for. + """ + global CORE_PMU_AVAILABLE, CORE_PMU_COUNT + + if CORE_PMU_AVAILABLE is not None: + return + + try: + import perf + parse_events = perf.parse_events + except (ImportError, AttributeError): + # Nothing to ask about it, let the expectations speak. + CORE_PMU_AVAILABLE = True + return + + try: + evlist = parse_events('cycles') + except OSError: + # No PMU that can count it, the same as the ENOENT below. + CORE_PMU_AVAILABLE = False + return + + CORE_PMU_COUNT = len(evlist) + try: + evlist.open() + except OSError as err: + # Anything else is a permission problem or alike, not our business here. + CORE_PMU_AVAILABLE = err.errno != errno.ENOENT + return + evlist.close() + + CORE_PMU_AVAILABLE = True + +PERF_TYPE_HARDWARE = 0 +PERF_TYPE_HW_CACHE = 3 +PERF_SAMPLE_ID = 1 << 6 +PERF_SAMPLE_IDENTIFIER = 1 << 16 +PERF_TYPE_SOFTWARE = 1 +PERF_COUNT_SW_DUMMY = 9 + +def is_dummy_tracking_event(event): + """ + Is this the dummy event perf opens to carry the tracking events (mmap, + comm, task) when the requested ones can't, like when there is more than one + core PMU and each core PMU's events run on only part of the CPUs? + """ + return (event.get('type') == str(PERF_TYPE_SOFTWARE) and + event.get('config') == str(PERF_COUNT_SW_DUMMY)) + +# With more than one core PMU the tracking terms go to the dummy tracking event, +# leaving the requested events without them, so they can't be matched there. +TRACKING_TERMS = ['mmap', 'comm', 'task', 'mmap_data'] + +# The legacy event types, where perf may qualify 'config' with the core PMU +# type in its top 32 bits when there is more than one core PMU. +LEGACY_TYPES = (str(PERF_TYPE_HARDWARE), str(PERF_TYPE_HW_CACHE)) + +def mask_bits(value, bits): + """ + Take the bits in 'bits' out of each of the '|'-separated alternatives in + 'value', keeping the rest of the expression, wildcards included. + """ + masked = [] + + for alternative in value.split('|'): + try: + alternative = str(int(alternative, 0) & ~bits) + except ValueError: + pass + masked.append(alternative) + + return '|'.join(masked) + +def hide_core_pmu_type(value): + """ + The kernel can be told which core PMU a legacy type event is meant for by + putting its type in the top 32 bits of 'config', which perf only does when + there is more than one core PMU, so take those bits out to keep the + expectations written for the single core PMU case matching on + heterogeneous machines. + """ + return mask_bits(value, 0xffffffff00000000) + +# The two sample id bits mean the same thing here: how the samples of the +# events in the evlist get associated with the event that produced them. +SAMPLE_ID_BITS = PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER + +# No real sample_type bit is this high, so it can stand for "either of the +# sample id bits" when comparing the two sides. +SAMPLE_ID_EITHER = 1 << 40 + +PERF_FORMAT_GROUP = 1 << 3 + +def core_pmu_count(): + """ + How many core PMUs does perf see? An event with no PMU named in it + becomes one event per core PMU, see parse_events_multi_pmu_add(), and + that is the expansion that leaves the clones for the other core PMUs + without any group members and that makes perf qualify 'config' with the + type of the PMU it is for, so this is the number of events perf makes of + 'cycles', from the probe core_pmu_available() shares. + """ + core_pmu_probe() + return CORE_PMU_COUNT + +def single_core_pmu(): + """ + Does perf see only one core PMU? Asked for by the comparisons that need + it, not at import time, as a run may never get to one. + """ + return core_pmu_count() == 1 + +def hide_group_format(expected, recorded): + """ + read_format's PERF_FORMAT_GROUP bit asks for the group members to be read + along with the leader, which aside from this bit is verified with the + fd/group_fd based group checking, and with more than one core PMU a group + whose members are pinned to one of them, say 'default_core', leaves the + clones for the other core PMUs without any members, so that bit isn't + there: whether it is there is a consequence of how the evlist got + expanded to more than one core PMU, not of what the expectations are + about, so take it out on both sides. On a machine with a single core PMU + there is nothing to expand, so the expectations keep verifying it. + """ + if single_core_pmu(): + return expected, recorded + + return (mask_bits(expected, PERF_FORMAT_GROUP), + mask_bits(recorded, PERF_FORMAT_GROUP)) + +def hide_sample_id(expected, recorded): + """ + perf asks for a way to tell the samples apart, PERF_SAMPLE_IDENTIFIER where + available, PERF_SAMPLE_ID otherwise, see evlist__config(): whenever the + evlist has more than one event, and when asked to outright, with + --sample-identifier, by an auxtrace session, or by record__config() for a + pipe run, none of which depends on the sample types, as those only choose + between the two bits: + with more than one core PMU the dummy tracking events make the sample types + differ, so IDENTIFIER is asked for even for events whose expectations ask + for ID, written on a machine with a single core PMU. + + So treat the two as the same thing: when the expectation asks for either + of them, ask for "an id of some sort" on both sides, and when the + expectation doesn't ask for any, take the bits out of what got recorded. + """ + try: + asked = int(expected, 0) & SAMPLE_ID_BITS + except ValueError: + # The expectation is a wildcard or a set of alternatives. + return expected, recorded + + try: + recorded_id_bits = int(recorded, 0) & SAMPLE_ID_BITS + except ValueError: + # The recorded side is a wildcard or a set of alternatives. + return expected, mask_bits(recorded, SAMPLE_ID_BITS) + + if not asked: + if single_core_pmu(): + return expected, recorded + return expected, mask_bits(recorded, SAMPLE_ID_BITS) + + return (str(int(expected, 0) & ~SAMPLE_ID_BITS | SAMPLE_ID_EITHER), + str(int(recorded, 0) & ~SAMPLE_ID_BITS | + (SAMPLE_ID_EITHER if recorded_id_bits else 0))) + class Fail(Exception): def __init__(self, test, msg): self.msg = msg @@ -34,17 +223,20 @@ class Fail(Exception): return '\'%s\' - %s' % (self.test.path, self.msg) class Notest(Exception): - def __init__(self, test, arch): - self.arch = arch + def __init__(self, test, reason): + self.reason = reason self.test = test def getMsg(self): - return '[%s] \'%s\'' % (self.arch, self.test.path) + return '[%s] \'%s\'' % (self.reason, self.test.path) class Unsup(Exception): - def __init__(self, test): + def __init__(self, test, ret, expected): self.test = test + self.ret = ret + self.expected = expected def getMsg(self): - return '\'%s\'' % self.test.path + return '\'%s\' exit %d, expected %s, no attrs recorded to verify' % ( + self.test.path, self.ret, self.expected) class Event(dict): terms = [ @@ -89,22 +281,86 @@ class Event(dict): def add(self, data): for key, val in data: - log.debug(" %s = %s" % (key, val)) + log.debug(" %s = %s", key, val) self[key] = val def __init__(self, name, data, base): - log.debug(" Event %s" % name); - self.name = name; + log.debug(" Event %s", name) + self.name = name self.group = '' self.add(base) self.add(data) + def hide_core_pmu_type_terms(self, other): + """ + Is this a legacy type event? Then the core PMU type that perf may have + put in the top 32 bits of 'config' to tell the kernel which core PMU it + wants isn't part of what we asked for, see hide_core_pmu_type(). + """ + for event in (self, other): + for type in event.get('type', '').split('|'): + if type in LEGACY_TYPES: + return True + + return False + + def hide_core_pmu_type(self, term, value, other): + if term == 'config' and self.hide_core_pmu_type_terms(other): + return hide_core_pmu_type(value) + + return value + + def hide_event_count_dependent_terms(self, term, expected, recorded): + if term == 'sample_type': + return hide_sample_id(expected, recorded) + if term == 'read_format': + return hide_group_format(expected, recorded) + + return expected, recorded + + def recorded_side(self, other): + """ + Which one is what perf recorded? Both directions get compared, see + Test.compare(), so this can't be taken from the arguments' order. + """ + if getattr(other, 'recorded', False): + return other + + return self if getattr(self, 'recorded', False) else None + + def hide_tracking_via_dummy(self, term, other): + """ + The tracking terms are set on the tracking event, which is the dummy + event when there is more than one core PMU, leaving the requested + events without them, nothing to match on then. + """ + if term not in TRACKING_TERMS: + return False + + for event in (self, other): + if term in getattr(event, 'tracking_moved_to_dummy', ()): + return True + + return False + def equal(self, other): for t in Event.terms: - log.debug(" [%s] %s %s" % (t, self[t], other[t])); + log.debug(" [%s] %s %s", t, self[t], other[t]) if t not in self or t not in other: return False - if not data_equal(self[t], other[t]): + if self.hide_tracking_via_dummy(t, other): + continue + + recorded = self.recorded_side(other) + if recorded is self: + expected, got_event = other, self + else: + expected, got_event = self, other + exp = self.hide_core_pmu_type(t, expected[t], other) + got = self.hide_core_pmu_type(t, got_event[t], other) + if recorded is not None: + exp, got = self.hide_event_count_dependent_terms(t, exp, got) + if not data_equal(exp, got): return False return True @@ -117,14 +373,30 @@ class Event(dict): for t in Event.terms: if t not in self or t not in other: continue - if not data_equal(self[t], other[t]): - log.warning("expected %s=%s, got %s" % (t, self[t], other[t])) + if self.hide_tracking_via_dummy(t, other): + continue + + recorded = self.recorded_side(other) + if recorded is self: + expected, got_event = other, self + else: + expected, got_event = self, other + exp = self.hide_core_pmu_type(t, expected[t], other) + got = self.hide_core_pmu_type(t, got_event[t], other) + if recorded is not None: + exp, got = self.hide_event_count_dependent_terms(t, exp, got) + if not data_equal(exp, got): + log.warning("expected %s=%s, got %s", t, expected[t], + got_event[t]) def parse_version(version): if not version: return None return [int(v) for v in version.split(".")[0:2]] +def format_version(version): + return ".".join(str(v) for v in version) + # Test file description needs to have following sections: # [config] # - just single instance in file @@ -136,6 +408,9 @@ def parse_version(version): # 'arch' - architecture specific test (optional) # comma separated list, ! at the beginning # negates it. +# 'requires' - Comma separated list of things the test needs, it is +# skipped when any of them isn't there, currently: +# core-pmu - the machine can count hardware events. # 'auxv' - Truthy statement that is evaled in the scope of the auxv map. When false, # the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional) # 'kernel_since' - Inclusive kernel version from which the test will start running. Only the @@ -149,7 +424,7 @@ class Test(object): parser = configparser.ConfigParser() parser.read(path) - log.warning("running '%s'" % path) + log.warning("running '%s'", path) self.path = path self.test_dir = options.test_dir @@ -159,23 +434,24 @@ class Test(object): try: self.ret = parser.get('config', 'ret') - except: + except Exception: self.ret = 0 self.test_ret = parser.getboolean('config', 'test_ret', fallback=False) try: self.arch = parser.get('config', 'arch') - log.warning("test limitation '%s'" % self.arch) - except: + log.warning("test limitation '%s'", self.arch) + except Exception: self.arch = '' self.auxv = parser.get('config', 'auxv', fallback=None) + self.requires = parser.get('config', 'requires', fallback='') self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None)) self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None)) self.expect = {} self.result = {} - log.debug(" loading expected events"); + log.debug(" loading expected events") self.load_events(path, self.expect) def is_event(self, name): @@ -186,13 +462,17 @@ class Test(object): def skip_test_kernel_since(self): if not self.kernel_since: - return False - return not self.kernel_since <= parse_version(platform.release()) + return None + if self.kernel_since <= parse_version(platform.release()): + return None + return "kernel older than %s" % format_version(self.kernel_since) def skip_test_kernel_until(self): if not self.kernel_until: - return False - return not parse_version(platform.release()) < self.kernel_until + return None + if parse_version(platform.release()) < self.kernel_until: + return None + return "kernel newer than %s" % format_version(self.kernel_until) def skip_test_auxv(self): def new_auxv(a, pattern): @@ -203,23 +483,25 @@ class Test(object): else: try: value = int(items[-1], 0) - except: + except ValueError: value = items[-1] return (items[0], value) if not self.auxv: - return False + return None auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \ .decode(sys.stdout.encoding) pattern = re.compile(r"[: ]+") auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()]) - return not eval(self.auxv) + if eval(self.auxv): + return None + + return "the auxv condition doesn't hold here" def skip_test_arch(self, myarch): # If architecture not set always run test if self.arch == '': - # log.warning("test for arch %s is ok" % myarch) - return False + return None # Allow multiple values in assignment separated by ',' arch_list = self.arch.split(',') @@ -227,35 +509,45 @@ class Test(object): # Handle negated list such as !s390x,ppc if arch_list[0][0] == '!': arch_list[0] = arch_list[0][1:] - log.warning("excluded architecture list %s" % arch_list) + log.warning("excluded architecture list %s", arch_list) for arch_item in arch_list: - # log.warning("test for %s arch is %s" % (arch_item, myarch)) if arch_item == myarch: - return True - return False + return "arch = %s, machine = %s" % (self.arch, myarch) + return None for arch_item in arch_list: - # log.warning("test for architecture '%s' current '%s'" % (arch_item, myarch)) if arch_item == myarch: - return False - return True + return None + + return "arch = %s, machine = %s" % (self.arch, myarch) + + def skip_test_requires(self): + for requirement in filter(None, map(str.strip, self.requires.split(','))): + if requirement == 'core-pmu': + if not core_pmu_available(): + log.warning("test limitation 'requires %s'", requirement) + return "needs a core PMU, none here" + else: + log.warning("unknown requirement '%s', ignored", requirement) + + return None def restore_sample_rate(self, value=10000): try: # Check value of sample_rate - with open("/proc/sys/kernel/perf_event_max_sample_rate", "r") as fIn: + with open("/proc/sys/kernel/perf_event_max_sample_rate", "r", encoding="utf-8") as fIn: curr_value = fIn.readline() # If too low restore to reasonable value if not curr_value or int(curr_value) < int(value): - with open("/proc/sys/kernel/perf_event_max_sample_rate", "w") as fOut: + with open("/proc/sys/kernel/perf_event_max_sample_rate", "w", encoding="utf-8") as fOut: fOut.write(str(value)) except IOError as e: - log.warning("couldn't restore sample_rate value: I/O error %s" % e) + log.warning("couldn't restore sample_rate value: I/O error %s", e) except ValueError as e: - log.warning("couldn't restore sample_rate value: Value error %s" % e) + log.warning("couldn't restore sample_rate value: Value error %s", e) except TypeError as e: - log.warning("couldn't restore sample_rate value: Type error %s" % e) + log.warning("couldn't restore sample_rate value: Type error %s", e) def load_events(self, path, events): parser_event = configparser.ConfigParser() @@ -266,7 +558,7 @@ class Test(object): # event' first as a base for section in filter(self.is_event, parser_event.sections()): - parser_items = parser_event.items(section); + parser_items = parser_event.items(section) base_items = {} # Read parent event if there's any @@ -280,69 +572,157 @@ class Test(object): events[section] = e def run_cmd(self, tempdir): - junk1, junk2, junk3, junk4, myarch = (os.uname()) + _junk1, _junk2, _junk3, _junk4, myarch = (os.uname()) - if self.skip_test_arch(myarch): - raise Notest(self, myarch) + arch = self.skip_test_arch(myarch) + if arch: + log.debug(" detail: the test file has arch = %s, the machine reports %s", + self.arch, myarch) + raise Notest(self, arch) - if self.skip_test_auxv(): - raise Notest(self, "auxv skip") + auxv = self.skip_test_auxv() + if auxv: + log.debug(" detail: the auxv condition %s doesn't hold, the bits come " + "from the LD_SHOW_AUXV=1 output", self.auxv) + raise Notest(self, auxv) - if self.skip_test_kernel_since(): - raise Notest(self, "old kernel skip") + kernel = self.skip_test_kernel_since() + if kernel: + log.debug(" detail: the test file's kernel_since = %s, this kernel is %s", + format_version(self.kernel_since), platform.release()) + raise Notest(self, kernel) - if self.skip_test_kernel_until(): - raise Notest(self, "new kernel skip") + kernel = self.skip_test_kernel_until() + if kernel: + log.debug(" detail: the test file's kernel_until = %s, this kernel is %s", + format_version(self.kernel_until), platform.release()) + raise Notest(self, kernel) + + requirement = self.skip_test_requires() + if requirement: + log.debug(" detail: requires core-pmu, nothing countable here, check " + "kernel.perf_event_paranoid and the PMU nodes under " + "/sys/bus/event_source/") + raise Notest(self, requirement) self.restore_sample_rate() cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir, self.perf, self.command, tempdir, self.args) ret = os.WEXITSTATUS(os.system(cmd)) - log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret))) + log.info(" '%s' ret '%s', expected '%s'", cmd, str(ret), str(self.ret)) if not data_equal(str(ret), str(self.ret)): + plain_cmd = "%s %s -o /dev/null %s" % ( + self.perf, self.command, + self.args.split('>/dev/null')[0].rstrip()) if self.test_ret: raise Fail(self, "Perf exit code failure") else: - raise Unsup(self) + log.debug(" detail: '%s' exited with %d, the expectations want the " + "workload's %s exit, so there are no attrs recorded to " + "verify; this is normally a capability problem, run '%s' " + "to see why", plain_cmd, ret, self.ret, plain_cmd) + raise Unsup(self, ret, str(self.ret)) - def compare(self, expect, result): + def drop_dummy_tracking_events(self): + """ + Leave out the dummy event perf itself opened, see + is_dummy_tracking_event(), unless the expectations ask for one. + """ + if any(is_dummy_tracking_event(e) for e in self.expect.values()): + return + + if not any(is_dummy_tracking_event(e) for e in self.result.values()): + return + + moved = set() + for name, event in list(self.result.items()): + if is_dummy_tracking_event(event): + log.debug(" dropping dummy tracking event [%s]", name) + moved.update(t for t in TRACKING_TERMS if event.get(t) == '1') + del self.result[name] + + # The tracking terms the dropped dummies carried, flag the remaining + # events so that the match leaves only those out. + for event in self.result.values(): + event.tracking_moved_to_dummy = moved + + def drop_other_core_pmu_events(self, devices='/sys/bus/event_source/devices'): + """ + Leave out the events of the other core PMUs, which have their own + encodings, when the expectations name one of the core PMUs. + """ + asked = set() + for event in self.expect.values(): + types = event.get('type', '').split('|') + if '*' in types: + return + asked.update(types) + + try: + names = os.listdir(devices) + except OSError: + return + + core = set() + for name in names: + path = os.path.join(devices, name) + has_cpus = os.path.exists(os.path.join(path, 'cpus')) + if name not in ('cpu', 'cpum_cf') and not has_cpus: + continue + try: + with open(os.path.join(path, 'type'), encoding='utf-8') as f: + core.add(f.read().strip()) + except OSError: + pass + + if not core & asked: + return + + others = core - asked + for name, event in list(self.result.items()): + if event.get('type') in others: + log.debug(" dropping other core PMU event [%s]", name) + del self.result[name] + + def compare(self, expect, result, strict_groups=True): match = {} - log.debug(" compare"); + log.debug(" compare") # For each expected event find all matching # events in result. Fail if there's not any. for exp_name, exp_event in expect.items(): exp_list = [] res_event = {} - log.debug(" matching [%s]" % exp_name) + log.debug(" matching [%s]", exp_name) for res_name, res_event in result.items(): - log.debug(" to [%s]" % res_name) + log.debug(" to [%s]", res_name) if (exp_event.equal(res_event)): exp_list.append(res_name) log.debug(" ->OK") else: - log.debug(" ->FAIL"); + log.debug(" ->FAIL") - log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list))) + log.debug(" match: [%s] matches %s", exp_name, str(exp_list)) # we did not any matching event - fail if not exp_list: if exp_event.optional(): - log.debug(" %s does not match, but is optional" % exp_name) + log.debug(" %s does not match, but is optional", exp_name) else: if not res_event: - log.debug(" res_event is empty"); + log.debug(" res_event is empty") else: exp_event.diff(res_event) - raise Fail(self, 'match failure'); + raise Fail(self, 'match failure') match[exp_name] = exp_list - # For each defined group in the expected events - # check we match the same group in the result. + # Check that each expected group is matched in the result; an event + # opened in a group may match an expectation that doesn't declare + # one, as those only verify the event attributes. for exp_name, exp_event in expect.items(): group = exp_event.group @@ -351,45 +731,52 @@ class Test(object): for res_name in match[exp_name]: res_group = result[res_name].group + if res_group == '' and not strict_groups: + continue if res_group not in match[group]: raise Fail(self, 'group failure') - log.debug(" group: [%s] matches group leader %s" % - (exp_name, str(match[group]))) + log.debug(" group: [%s] matches group leader %s", + exp_name, str(match[group])) log.debug(" matched") def resolve_groups(self, events): for name, event in events.items(): - group_fd = event['group_fd']; + group_fd = event['group_fd'] if group_fd == '-1': - continue; + continue for iname, ievent in events.items(): if (ievent['fd'] == group_fd): event.group = iname - log.debug('[%s] has group leader [%s]' % (name, iname)) - break; + log.debug('[%s] has group leader [%s]', name, iname) + break def run(self): - tempdir = tempfile.mkdtemp(); + tempdir = tempfile.mkdtemp() try: # run the test script - self.run_cmd(tempdir); + self.run_cmd(tempdir) # load events expectation for the test - log.debug(" loading result events"); + log.debug(" loading result events") for f in glob.glob(tempdir + '/event*'): - self.load_events(f, self.result); + self.load_events(f, self.result) + for event in self.result.values(): + event.recorded = True + + self.drop_dummy_tracking_events() + self.drop_other_core_pmu_events() # resolve group_fd to event names - self.resolve_groups(self.expect); - self.resolve_groups(self.result); + self.resolve_groups(self.expect) + self.resolve_groups(self.result) # do the expectation - results matching - both ways self.compare(self.expect, self.result) - self.compare(self.result, self.expect) + self.compare(self.result, self.expect, strict_groups=False) finally: # cleanup @@ -401,9 +788,9 @@ def run_tests(options): try: Test(f, options).run() except Unsup as obj: - log.warning("unsupp %s" % obj.getMsg()) + log.warning("unsupp %s", obj.getMsg()) except Notest as obj: - log.warning("skipped %s" % obj.getMsg()) + log.warning("skipped %s", obj.getMsg()) def setup_log(verbose): global log @@ -424,29 +811,20 @@ def setup_log(verbose): ch.setFormatter(formatter) log.addHandler(ch) -USAGE = '''%s [OPTIONS] - -d dir # tests dir - -p path # perf binary - -t test # single test - -v # verbose level -''' % sys.argv[0] - def main(): - parser = optparse.OptionParser(usage=USAGE) + parser = argparse.ArgumentParser() - parser.add_option("-t", "--test", - action="store", type="string", dest="test") - parser.add_option("-d", "--test-dir", - action="store", type="string", dest="test_dir") - parser.add_option("-p", "--perf", - action="store", type="string", dest="perf") - parser.add_option("-v", "--verbose", - default=0, action="count", dest="verbose") + parser.add_argument("-t", "--test", + action="store", dest="test", help="single test") + parser.add_argument("-d", "--test-dir", + action="store", dest="test_dir", help="tests dir") + parser.add_argument("-p", "--perf", + action="store", dest="perf", help="perf binary") + parser.add_argument("-v", "--verbose", + default=0, action="count", dest="verbose", + help="verbose level") - options, args = parser.parse_args() - if args: - parser.error('FAILED wrong arguments %s' % ' '.join(args)) - return -1 + options = parser.parse_args() setup_log(options.verbose) diff --git a/tools/perf/tests/shell/lib/perf_brstack_max.py b/tools/perf/tests/shell/lib/perf_brstack_max.py new file mode 100644 index 000000000000..cc065f07cfed --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_brstack_max.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# SPDX-License-Identifier: GPL-2.0 +# Determine the maximum size of branch stacks in a perf.data file. + +import argparse +import sys + +import perf + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("-i", "--input", default="perf.data", help="Input file name") + args = ap.parse_args() + + bmax = 0 + + def process_event(sample): + nonlocal bmax + try: + brstack = sample.brstack + if brstack: + n = len(list(brstack)) + if n > bmax: + bmax = n + except AttributeError: + pass + + session = None + try: + session = perf.session(perf.data(args.input), sample=process_event) + session.process_events() + print("max brstack", bmax) + except Exception as e: + print(f"Error processing events: {e}", file=sys.stderr) + sys.exit(1) + finally: + session = None + +if __name__ == "__main__": + main() 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 dafbde56cc76..8cde22ac9d97 100644 --- a/tools/perf/tests/shell/lib/perf_json_output_lint.py +++ b/tools/perf/tests/shell/lib/perf_json_output_lint.py @@ -46,46 +46,46 @@ def is_counter_value(num): def is_metric_value(num): return isfloat(num) or num == 'none' -def check_json_output(expected_items): +def check_json_output(expected_count): checks = { - 'counters': lambda x: isfloat(x), + 'counters': isfloat, 'core': lambda x: True, - 'counter-value': lambda x: is_counter_value(x), + 'counter-value': is_counter_value, 'cgroup': lambda x: True, - 'cpu': lambda x: isint(x), + 'cpu': isint, 'cache': lambda x: True, 'cluster': lambda x: True, 'die': lambda x: True, 'event': lambda x: True, - 'event-runtime': lambda x: isfloat(x), - 'interval': lambda x: isfloat(x), + 'event-runtime': isfloat, + 'interval': isfloat, 'metric-unit': lambda x: True, - 'metric-value': lambda x: is_metric_value(x), + 'metric-value': is_metric_value, 'metric-threshold': lambda x: x in ['unknown', 'good', 'less good', 'nearly bad', 'bad'], 'metricgroup': lambda x: True, 'node': lambda x: True, - 'pcnt-running': lambda x: isfloat(x), + 'pcnt-running': isfloat, 'socket': lambda x: True, 'thread': lambda x: True, 'unit': lambda x: True, } - input = '[\n' + ','.join(Lines) + '\n]' - for item in json.loads(input): - if expected_items != -1: + json_input = '[\n' + ','.join(Lines) + '\n]' + for item in json.loads(json_input): + if expected_count: count = len(item) - if count not in expected_items and count >= 1 and count <= 7 and 'metric-value' in item: + if count not in expected_count and count >= 1 and count <= 7 and 'metric-value' in item: # Events that generate >1 metric may have isolated metric # values and possibly other prefixes like interval, core, # counters, or event-runtime/pcnt-running from multiplexing. pass - elif count not in expected_items and count >= 1 and count <= 5 and 'metricgroup' in item: + elif count not in expected_count and count >= 1 and count <= 5 and 'metricgroup' in item: pass - elif count - 1 in expected_items and 'metric-threshold' in item: + elif count - 1 in expected_count and 'metric-threshold' in item: pass - elif count in expected_items and 'insn per cycle' in item: + elif count in expected_count and 'insn per cycle' in item: pass - elif count not in expected_items: - raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}' + elif count not in expected_count: + raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_count}' f' in \'{item}\'') for key, value in item.items(): if key not in checks: @@ -107,7 +107,7 @@ try: expected_items = [1, 2] else: # If no option is specified, don't check the number of items. - expected_items = -1 + expected_items = [] check_json_output(expected_items) except: print('Test failed for input:\n' + '\n'.join(Lines)) diff --git a/tools/perf/tests/shell/lib/perf_metric_validation.py b/tools/perf/tests/shell/lib/perf_metric_validation.py index 3d52f94f22b9..dff60337c030 100644 --- a/tools/perf/tests/shell/lib/perf_metric_validation.py +++ b/tools/perf/tests/shell/lib/perf_metric_validation.py @@ -1,10 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 -import re -import csv -import json import argparse +import json from pathlib import Path import subprocess +import sys +from typing import Any class TestError: @@ -77,11 +77,11 @@ class Validator: def read_json(self, filename: str) -> dict: try: - with open(Path(filename).resolve(), "r") as f: + with open(Path(filename).resolve(), "r", encoding="utf-8") as f: data = json.loads(f.read()) except OSError as e: print(f"Error when reading file {e}") - sys.exit() + sys.exit(1) return data @@ -90,16 +90,16 @@ class Validator: if not parent.exists(): parent.mkdir(parents=True) - with open(output_file, "w+") as output_file: + with open(output_file, "w+", encoding="utf-8") as out_f: json.dump(data, - output_file, + out_f, ensure_ascii=True, indent=4) def get_results(self, idx: int = 0): return self.results.get(idx) - def get_bounds(self, lb, ub, error, alias={}, ridx: int = 0) -> list: + def get_bounds(self, lb, ub, error, alias=None, ridx: int = 0) -> tuple: """ Get bounds and tolerance from lb, ub, and error. If missing lb, use 0.0; missing ub, use float('inf); missing error, use self.tolerance. @@ -111,6 +111,9 @@ class Validator: upper bound, return -1 if the upper bound is a metric value and is not collected tolerance, denormalized base on upper bound value """ + if alias is None: + alias = {} + # init ubv and lbv to invalid values def get_bound_value(bound, initval, ridx): val = initval @@ -120,7 +123,7 @@ class Validator: if bound == '': val = float("inf") elif bound in alias: - vall = self.get_value(alias[ub], ridx) + vall = self.get_value(alias[bound], ridx) if vall: val = vall[0] elif bound.replace('.', '1').isdigit(): @@ -213,7 +216,7 @@ class Validator: @param alias: the dict has alias to metric name mapping @returns: value of the formula is success; -1 if the one or more metric value not provided """ - stack = [] + stack: list[Any] = [] b = 0 errs = [] sign = "+" @@ -230,9 +233,9 @@ class Validator: else: f = f + "{0}(={1:.4f})".format(s, v[0]) if sign == "*": - stack[-1] = stack[-1] * v + stack[-1] = stack[-1] * v[0] elif sign == "/": - stack[-1] = stack[-1] / v + stack[-1] = stack[-1] / v[0] elif sign == '-': stack.append(-v[0]) else: @@ -263,7 +266,7 @@ class Validator: alias[m['Alias']] = m['Name'] lbv, ubv, t = self.get_bounds( rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold'], alias, ridx=rule['RuleIndex']) - val, f = self.evaluate_formula( + val, _ = self.evaluate_formula( rule['Formula'], alias, ridx=rule['RuleIndex']) lb = rule['RangeLower'] @@ -316,7 +319,7 @@ class Validator: rerun.append(m['Name']) if len(rerun) > 0 and len(rerun) < 20: - second_results = dict() + second_results: dict[Any, Any] = dict() self.second_test(rerun, second_results) for name, val in second_results.items(): if name not in failures: @@ -373,7 +376,7 @@ class Validator: name = result["metric-unit"].split(" ")[1] if len(result["metric-unit"].split(" ")) > 1 \ else result["metric-unit"] metricvalues[name.lower()] = float(result["metric-value"]) - except ValueError as error: + except ValueError: continue return @@ -383,7 +386,7 @@ class Validator: wl = workload.split() command.extend(wl) print(" ".join(command)) - cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') + cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', check=False) lines = cmd.stderr.splitlines() + cmd.stdout.splitlines() data = [] for line in lines: @@ -397,9 +400,9 @@ class Validator: Collect metric data with "perf stat -M" on given workload with -a and -j. """ self.results = dict() - print(f"Starting perf collection") + print("Starting perf collection") print(f"Long workload: {workload}") - collectlist = dict() + collectlist: dict[int, Any] = dict() if self.collectlist != "": collectlist[0] = {x for x in self.collectlist.split(",")} else: @@ -441,7 +444,7 @@ class Validator: """ command = ['perf', 'list', '-j', '--details', 'metrics'] cmd = subprocess.run(command, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, encoding='utf-8') + stderr=subprocess.PIPE, encoding='utf-8', check=False) try: data = json.loads(cmd.stdout) for m in data: @@ -454,9 +457,9 @@ class Validator: self.metrics.add(name) if 'ScaleUnit' in m and (m['ScaleUnit'] == '1%' or m['ScaleUnit'] == '100%'): self.pctgmetrics.add(name.lower()) - except ValueError as error: - print(f"Error when parsing metric data") - sys.exit() + except ValueError: + print("Error when parsing metric data") + sys.exit(1) return @@ -519,9 +522,6 @@ class Validator: # Initialize data structures before data validation of each workload def _init_data(self): - - testtypes = ['PositiveValueTest', - 'RelationshipTest', 'SingleMetricTest'] self.results = dict() self.ignoremetrics = set() self.errlist = list() @@ -572,7 +572,7 @@ class Validator: # End of Class Validator -def main() -> None: +def main() -> int: parser = argparse.ArgumentParser( description="Launch metric value validation") @@ -602,5 +602,4 @@ def main() -> None: if __name__ == "__main__": - import sys sys.exit(main()) diff --git a/tools/perf/tests/shell/lib/perf_record.sh b/tools/perf/tests/shell/lib/perf_record.sh index 2b9e11b66dc7..a95fafb8b6fa 100644 --- a/tools/perf/tests/shell/lib/perf_record.sh +++ b/tools/perf/tests/shell/lib/perf_record.sh @@ -30,7 +30,7 @@ perf_record_with_retry() { fi for duration in 0.01 0.1 0.3 1.0 2.0; do - rm -f "${perfdata}".old + rm -rf "${perfdata}".old ${cmd_prefix} "$@" -o "${perfdata}" ${testprog_base} ${duration} > "$logfile" 2>&1 local record_exit=$? diff --git a/tools/perf/tests/shell/lib/probe_vfs_getname.sh b/tools/perf/tests/shell/lib/probe_vfs_getname.sh index 88cd0e26d5f6..57a59ae7d4ae 100644 --- a/tools/perf/tests/shell/lib/probe_vfs_getname.sh +++ b/tools/perf/tests/shell/lib/probe_vfs_getname.sh @@ -1,18 +1,36 @@ #!/bin/bash # Arnaldo Carvalho de Melo , 2017 -perf probe -l 2>&1 | grep -q probe:vfs_getname +# Scoped to the pid for parallel runs, and not "vfs_getname*" so that perf +# trace's probe:vfs_getname* wildcard doesn't open, and so pin, the probe. +: "${vfs_getname:=getname_flags_$$}" + +# The probes added, including _1, _2... for inlined copies of getname_flags. +probes_vfs_getname() { + perf probe -l 2>/dev/null | awk '{print $1}' | + grep -E "^probe:${vfs_getname}(_[[:digit:]]+)?$" +} + +probes_vfs_getname > /dev/null had_vfs_getname=$? cleanup_probe_vfs_getname() { if [ $had_vfs_getname -eq 1 ] ; then - perf probe -q -d probe:vfs_getname* + local probe + for probe in $(probes_vfs_getname); do + perf probe -q -d "$probe" + done fi } +# A pid scoped probe is never reused, so remove it however the test exits. +trap cleanup_probe_vfs_getname exit +trap 'exit 1' term int + add_probe_vfs_getname() { add_probe_verbose=$1 if [ $had_vfs_getname -eq 1 ] ; then + local func=getname_flags result_initname_re="[[:space:]]+([[:digit:]]+)[[:space:]]+initname.*" line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_initname_re" | sed -r "s/$result_initname_re/\1/") @@ -28,13 +46,20 @@ add_probe_vfs_getname() { line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_aname_re" | sed -r "s/$result_aname_re/\1/") fi + # Since v7.0 getname_flags() is a wrapper around do_getname(). + if [ -z "$line" ] ; then + func=do_getname + line=$(perf probe -L $func 2>&1 | grep -E "$result_initname_re" | + sed -r "s/$result_initname_re/\1/") + fi + if [ -z "$line" ] ; then echo "Could not find probeable line" return 2 fi - perf probe -q "vfs_getname=getname_flags:${line} pathname=result->name:string" || \ - perf probe $add_probe_verbose "vfs_getname=getname_flags:${line} pathname=filename:ustring" || return 1 + perf probe -q "${vfs_getname}=${func}:${line} pathname=result->name:string" || \ + perf probe $add_probe_verbose "${vfs_getname}=${func}:${line} pathname=filename:ustring" || return 1 fi } diff --git a/tools/perf/tests/shell/lib/setup_python.sh b/tools/perf/tests/shell/lib/setup_python.sh index 2173215a0517..758834f8dcf8 100644 --- a/tools/perf/tests/shell/lib/setup_python.sh +++ b/tools/perf/tests/shell/lib/setup_python.sh @@ -1,29 +1,50 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -if [ "x$PYTHON" = "x" ] +if [ -z "$PYTHON" ] then python3 --version >/dev/null 2>&1 && PYTHON=python3 fi -if [ "x$PYTHON" = "x" ] +if [ -z "$PYTHON" ] then python --version >/dev/null 2>&1 && PYTHON=python fi -if [ "x$PYTHON" = "x" ] +if [ -z "$PYTHON" ] then echo Skipping test, python not detected please set environment variable PYTHON. exit 2 fi +export PYTHON -# Set PYTHONPATH to find the in-tree built perf.so first, avoiding system-wide perf.so +# Set PYTHONPATH to find the built perf.so and standalone scripts first, +# avoiding system-wide perf.so if [ -n "$PERF_EXEC_PATH" ] && [ -d "$PERF_EXEC_PATH/python" ]; then PYTHONPATH_DIR="$PERF_EXEC_PATH/python" +elif [ -n "${BASH_SOURCE[0]}" ] && [ -d "$(dirname "${BASH_SOURCE[0]}")/../../../python" ]; then + PYTHONPATH_DIR="$(dirname "${BASH_SOURCE[0]}")/../../../python" +elif [ -d "$(dirname "$0")/../../../python" ]; then + PYTHONPATH_DIR="$(dirname "$0")/../../../python" elif [ -d "$(dirname "$0")/../../python" ]; then PYTHONPATH_DIR="$(dirname "$0")/../../python" elif [ -d "$(dirname "$0")/../python" ]; then PYTHONPATH_DIR="$(dirname "$0")/../python" fi +if [ -n "${BASH_SOURCE[0]}" ] && [ -d "$(dirname "${BASH_SOURCE[0]}")/../../../python" ]; then + SRC_PYTHON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../python" && pwd)" + export PYTHONPATH="$SRC_PYTHON_DIR${PYTHONPATH:+:$PYTHONPATH}" + if [ -z "$PERF_EXEC_PATH" ] || [ ! -f "$PERF_EXEC_PATH/python/perf_live.py" ]; then + PERF_EXEC_PATH="$(dirname "$SRC_PYTHON_DIR")" + export PERF_EXEC_PATH + fi +fi + if [ -n "$PYTHONPATH_DIR" ]; then export PYTHONPATH="$PYTHONPATH_DIR${PYTHONPATH:+:$PYTHONPATH}" fi + +PERF_BIN=$(which perf 2>/dev/null || true) +if [ -n "$PERF_BIN" ] && [ -d "$(dirname "$PERF_BIN")/python" ]; then + PERF_BIN_PYTHON="$(dirname "$PERF_BIN")/python" + export PYTHONPATH="$PERF_BIN_PYTHON${PYTHONPATH:+:$PYTHONPATH}" +fi diff --git a/tools/perf/tests/shell/lib/waiting.sh b/tools/perf/tests/shell/lib/waiting.sh index 3a152892e077..43f4322dbe9e 100644 --- a/tools/perf/tests/shell/lib/waiting.sh +++ b/tools/perf/tests/shell/lib/waiting.sh @@ -1,77 +1,75 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -tenths=date\ +%s%1N - # Wait for PID $1 to have $2 number of threads started -# Time out after $3 tenths of a second or 5 seconds if $3 is "" +# Time out after approx. $3 tenths of a second or 5 seconds if $3 is "" wait_for_threads() { tm_out=$3 ; [ -n "${tm_out}" ] || tm_out=50 - start_time=$($tenths) while [ -e "/proc/$1/task" ] ; do th_cnt=$(find "/proc/$1/task" -mindepth 1 -maxdepth 1 -printf x | wc -c) if [ "${th_cnt}" -ge "$2" ] ; then return 0 fi - # Wait at most tm_out tenths of a second - if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then + if [ "${tm_out}" -le 0 ] ; then echo "PID $1 does not have $2 threads" return 1 fi + sleep 0.1 + tm_out=$((tm_out - 1)) done return 1 } # Wait for perf record -vvv 2>$2 with PID $1 to start by looking at file $2 # It depends on capturing perf record debug message "perf record has started" -# Time out after $3 tenths of a second or 5 seconds if $3 is "" +# Time out after approx. $3 tenths of a second or 5 seconds if $3 is "" wait_for_perf_to_start() { tm_out=$3 ; [ -n "${tm_out}" ] || tm_out=50 echo "Waiting for \"perf record has started\" message" - start_time=$($tenths) while [ -e "/proc/$1" ] ; do if grep -q "perf record has started" "$2" ; then echo OK break fi - # Wait at most tm_out tenths of a second - if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then + if [ "${tm_out}" -le 0 ] ; then echo "perf recording did not start" return 1 fi + sleep 0.1 + tm_out=$((tm_out - 1)) done return 0 } # Wait for process PID %1 to exit -# Time out after $2 tenths of a second or 5 seconds if $2 is "" +# Time out after approx. $2 tenths of a second or 5 seconds if $2 is "" wait_for_process_to_exit() { tm_out=$2 ; [ -n "${tm_out}" ] || tm_out=50 - start_time=$($tenths) while [ -e "/proc/$1" ] ; do - # Wait at most tm_out tenths of a second - if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then + if [ "${tm_out}" -le 0 ] ; then echo "PID $1 did not exit as expected" return 1 fi + sleep 0.1 + tm_out=$((tm_out - 1)) done return 0 } -# Check if PID $1 is still running after $2 tenths of a second +# Check if PID $1 is still running after approx. $2 tenths of a second # or 0.3 seconds if $2 is "" is_running() { tm_out=$2 ; [ -n "${tm_out}" ] || tm_out=3 - start_time=$($tenths) while [ -e "/proc/$1" ] ; do - # Check for at least tm_out tenths of a second - if [ $(($($tenths) - start_time)) -gt $tm_out ] ; then + if [ "${tm_out}" -le 0 ] ; then return 0 fi + sleep 0.1 + tm_out=$((tm_out - 1)) done echo "PID $1 exited prematurely" return 1 diff --git a/tools/perf/tests/shell/list.sh b/tools/perf/tests/shell/list.sh index 0c04b3159cef..d4a0b04154d6 100755 --- a/tools/perf/tests/shell/list.sh +++ b/tools/perf/tests/shell/list.sh @@ -1,11 +1,10 @@ #!/bin/bash -# perf list tests +# perf list tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh list_output=$(mktemp /tmp/__perf_test.list_output.json.XXXXX) diff --git a/tools/perf/tests/shell/perf_sched_stats.sh b/tools/perf/tests/shell/perf_sched_stats.sh index f13eb0a75b76..4ede9d701630 100755 --- a/tools/perf/tests/shell/perf_sched_stats.sh +++ b/tools/perf/tests/shell/perf_sched_stats.sh @@ -1,5 +1,5 @@ #!/bin/sh -# perf sched stats tests +# perf sched stats tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/pipe_test.sh b/tools/perf/tests/shell/pipe_test.sh index ce68d850c983..f4eb129afda2 100755 --- a/tools/perf/tests/shell/pipe_test.sh +++ b/tools/perf/tests/shell/pipe_test.sh @@ -3,7 +3,6 @@ # SPDX-License-Identifier: GPL-2.0 shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh sym="noploop" diff --git a/tools/perf/tests/shell/probe_vfs_getname.sh b/tools/perf/tests/shell/probe_vfs_getname.sh index 5fe5682c28ce..43bdd693076b 100755 --- a/tools/perf/tests/shell/probe_vfs_getname.sh +++ b/tools/perf/tests/shell/probe_vfs_getname.sh @@ -1,16 +1,13 @@ #!/bin/bash -# Add vfs_getname probe to get syscall args filenames (exclusive) - +# Add vfs_getname probe to get syscall args filenames (no_mlock) # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo , 2017 -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh skip_if_no_perf_probe || exit 2 [ "$(id -u)" = 0 ] || exit 2 -# shellcheck source=lib/probe_vfs_getname.sh . "$(dirname $0)"/lib/probe_vfs_getname.sh add_probe_vfs_getname diff --git a/tools/perf/tests/shell/python-use.sh b/tools/perf/tests/shell/python-use.sh index fd2ee5390060..ece4d306869a 100755 --- a/tools/perf/tests/shell/python-use.sh +++ b/tools/perf/tests/shell/python-use.sh @@ -1,11 +1,10 @@ #!/bin/bash -# 'import perf' in python +# 'import perf' in python (no_mlock) # SPDX-License-Identifier: GPL-2.0 # Just test if we can load the python binding. set -e shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh MODULE_DIR=$(dirname "$(which perf)")/python 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 eca629ee83f0..3a363a99ef41 100755 --- a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh +++ b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh @@ -1,5 +1,5 @@ #!/bin/bash -# probe libc's inet_pton & backtrace it with ping (exclusive) +# probe libc's inet_pton & backtrace it with ping # Installs a probe on libc's inet_pton function, that will use uprobes, # then use 'perf trace' on a ping to localhost asking for just one packet @@ -10,9 +10,7 @@ # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo , 2017 -# shellcheck source=lib/probe.sh . "$(dirname "$0")/lib/probe.sh" -# shellcheck source=lib/probe_vfs_getname.sh . "$(dirname "$0")/lib/probe_vfs_getname.sh" libc=$(grep -w libc /proc/self/maps | head -1 | sed -r 's/.*[[:space:]](\/.*)/\1/g') @@ -21,20 +19,27 @@ nm -Dg $libc 2>/dev/null | grep -F -q inet_pton || exit 254 event_pattern='probe_libc:inet_pton(_[[:digit:]]+)?' add_libc_inet_pton_event() { + local attempts=0 + while [ $attempts -lt 3 ]; do + event_name=$(perf probe -f -x $libc -a "inet_pton_$$=inet_pton" 2>&1 | \ + awk -v ep="$event_pattern" -v l="$libc" '$0 ~ ep && $0 ~ \ + ("\\(on inet_pton in " l "\\)") {print $1}' | head -n 1) - event_name=$(perf probe -f -x $libc -a inet_pton 2>&1 | \ - awk -v ep="$event_pattern" -v l="$libc" '$0 ~ ep && $0 ~ \ - ("\\(on inet_pton in " l "\\)") {print $1}' | head -n 1) + if [ -n "$event_name" ]; then + return 0 + fi + attempts=$((attempts + 1)) + sleep 0.1 + done - if [ $? -ne 0 ] || [ -z "$event_name" ] ; then - printf "FAIL: could not add event\n" - return 1 - fi + printf "FAIL: could not add event\n" + return 1 } trace_libc_inet_pton_backtrace() { - expected=`mktemp -u /tmp/expected.XXX` + # Create the files, as root a reserved /tmp name could be a planted symlink. + expected=$(mktemp /tmp/expected.XXX) || return 1 echo "ping[][0-9 \.:]+$event_name: \([[:xdigit:]]+\)" > $expected echo ".*inet_pton\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected @@ -50,8 +55,8 @@ trace_libc_inet_pton_backtrace() { ;; esac - perf_data=`mktemp -u /tmp/perf.data.XXX` - perf_script=`mktemp -u /tmp/perf.script.XXX` + perf_data=$(mktemp /tmp/perf.data.XXX) || return 1 + perf_script=$(mktemp /tmp/perf.script.XXX) || return 1 # Check presence of libtraceevent support to run perf record skip_no_probe_record_support "$event_name/$eventattr/" @@ -61,9 +66,9 @@ trace_libc_inet_pton_backtrace() { fi 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" + # mktemp created the file, so check that perf record wrote to it. + if [ ! -s "$perf_data" ]; then + printf "FAIL: perf record failed to write \"%s\" \n" "$perf_data" return 1 fi perf script -i $perf_data | tac | grep -m1 ^ping -B9 | tac > $perf_script @@ -97,21 +102,59 @@ trace_libc_inet_pton_backtrace() { # even if the perf script output does not match. } -delete_libc_inet_pton_event() { +# The probes added, including any _1, _2... suffix perf probe appended. +libc_inet_pton_events() { + perf probe -l 2>/dev/null | awk '{print $1}' | + grep -E "^probe_libc:inet_pton_$$(_[[:digit:]]+)?$" +} - if [ -n "$event_name" ] ; then - perf probe -q -d $event_name - fi +delete_libc_inet_pton_event() { + # List rather than use event_name, which a signal may have left unset. + # Retry as, like adding, deleting can fail with EBUSY. + local attempts=0 + local probe + + while [ $attempts -lt 3 ]; do + for probe in $(libc_inet_pton_events); do + perf probe -q -d "$probe" + done + + if [ -z "$(libc_inet_pton_events)" ]; then + return 0 + fi + + attempts=$((attempts + 1)) + sleep 0.1 + done + + printf "WARN: could not delete event(s): %s\n" \ + "$(libc_inet_pton_events | tr '\n' ' ')" + return 1 +} + +cleanup() { + rm -f ${perf_data} ${perf_script} ${expected} + delete_libc_inet_pton_event + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 } # Check for IPv6 interface existence ip a sh lo | grep -F -q inet6 || exit 2 [ "$(id -u)" = 0 ] || exit 2 +# After the skips, as trap_cleanup exits 1. A pid scoped probe is never reused, +# so always remove it. +trap trap_cleanup EXIT TERM INT + skip_if_no_perf_probe && \ add_libc_inet_pton_event && \ trace_libc_inet_pton_backtrace err=$? -rm -f ${perf_data} ${perf_script} ${expected} -delete_libc_inet_pton_event +cleanup exit $err 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 002f7037f182..16dbbcfc0e40 100755 --- a/tools/perf/tests/shell/record+script_probe_vfs_getname.sh +++ b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Use vfs_getname probe to get syscall args filenames (exclusive) +# Use vfs_getname probe to get syscall args filenames # Uses the 'perf test shell' library to add probe:vfs_getname to the system # then use it with 'perf record' using 'touch' to write to a temp file, then @@ -9,30 +9,36 @@ # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo , 2017 -# shellcheck source=lib/probe.sh . "$(dirname "$0")/lib/probe.sh" skip_if_no_perf_probe || exit 2 [ "$(id -u)" = 0 ] || exit 2 -# shellcheck source=lib/probe_vfs_getname.sh . "$(dirname "$0")/lib/probe_vfs_getname.sh" +# shellcheck disable=SC2154 # vfs_getname is assigned in lib/probe_vfs_getname.sh record_open_file() { echo "Recording open file:" # Check presence of libtraceevent support to run perf record - skip_no_probe_record_support "probe:vfs_getname*" + skip_no_probe_record_support if [ $? -eq 2 ]; then echo "WARN: Skipping test record_open_file. No libtraceevent support" return 2 fi - perf record -o ${perfdata} -e probe:vfs_getname\* touch $file + # Name the probes, a "${vfs_getname}*" glob would match longer pids. + local events + events=$(probes_vfs_getname | paste -sd, -) + if [ -z "${events}" ] ; then + echo "FAIL: no ${vfs_getname} probe to record" + return 1 + fi + perf record -o ${perfdata} -e "${events}" touch $file } perf_script_filenames() { echo "Looking at perf.data file for vfs_getname records for the file we touched:" perf script -i ${perfdata} | \ - grep -E " +touch +[0-9]+ +\[[0-9]+\] +[0-9]+\.[0-9]+: +probe:vfs_getname[_0-9]*: +\([[:xdigit:]]+\) +pathname=\"${file}\"" + grep -E " +touch +[0-9]+ +\[[0-9]+\] +[0-9]+\.[0-9]+: +probe:${vfs_getname}(_[0-9]+)?: +\([[:xdigit:]]+\) +pathname=\"${file}\"" } add_probe_vfs_getname diff --git a/tools/perf/tests/shell/record.sh b/tools/perf/tests/shell/record.sh index 625240809fce..01dc3e22fa73 100755 --- a/tools/perf/tests/shell/record.sh +++ b/tools/perf/tests/shell/record.sh @@ -8,10 +8,8 @@ shelldir=$(dirname "$0") . "${shelldir}"/lib/perf_record.sh -# shellcheck source=lib/waiting.sh . "${shelldir}"/lib/waiting.sh -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh testsym="test_loop" @@ -39,8 +37,8 @@ default_fd_limit=$(ulimit -Sn) min_fd_limit=$(($(getconf _NPROCESSORS_ONLN) * 16)) cleanup() { - rm -f "${perfdata}" - rm -f "${perfdata}".old + rm -rf "${perfdata}" + rm -rf "${perfdata}".old rm -f "${script_output}" perf_record_cleanup @@ -131,6 +129,261 @@ check_system_wide() { perf report -i "${perfdata}" -q | grep -q "${testsym}" } +check_ext_reg_capture() { + local script_field="$1" + local ext_reg="$2" + + perf script -F ip,sym,"${script_field}" -i "${perfdata}" 2>/dev/null | \ + grep -q -i "${ext_reg}:" +} + +get_x86_extended_regs() { + local advertised_ext_regs="$1" + local ext_regs="" + + if echo "${advertised_ext_regs}" | grep -q -i R16 + then + ext_regs="${ext_regs} R16" + fi + if echo "${advertised_ext_regs}" | grep -q -i R31 + then + ext_regs="${ext_regs} R31" + fi + if echo "${advertised_ext_regs}" | grep -q -i SSP + then + ext_regs="${ext_regs} SSP" + fi + + echo "${ext_regs}" | xargs +} + +validate_extd_regs_sampling() { + local regs_opt="$1" + local extd_regs="$2" + local script_field="$3" + local ret=0 + local reg + + for reg in ${extd_regs} + do + perf_record_with_retry "${perfdata}" \ + "check_ext_reg_capture ${script_field} ${reg}" "perf test -w thloop" \ + -e br_inst_retired.near_call ${regs_opt}=${reg} -c 1000 \ + --per-thread || ret=$? + + if [ $ret -ne 0 ] + then + echo "Extended register capture test [Failed record ${regs_opt}=${reg}]" + return 1 + fi + done + + return 0 +} + +test_extd_register_capture() { + local arch + local intr_regs + local user_regs + local intr_ext_regs + local user_ext_regs + local tested=0 + + echo "Extended register capture test" + if ! perf list pmu | grep -q 'br_inst_retired.near_call' + then + echo "Extended register capture test [Skipped missing event]" + return + fi + + intr_regs=$(perf record --intr-regs=\? 2>&1 || true) + user_regs=$(perf record --user-regs=\? 2>&1 || true) + + intr_ext_regs="" + user_ext_regs="" + + arch=$(uname -m) + case ${arch} in + x86_64|i386) + intr_ext_regs=$(get_x86_extended_regs "${intr_regs}") + user_ext_regs=$(get_x86_extended_regs "${user_regs}") + ;; + *) + echo "Extended register capture test [Skipped non-x86 platform]" + return + ;; + esac + + if [ -z "${intr_ext_regs}" ] + then + echo "Extended register capture test [Skipped missing intr extended registers]" + elif ! validate_extd_regs_sampling "--intr-regs" "${intr_ext_regs}" "iregs" + then + echo "Extended register capture test [Failed intr extended register sampling]" + err=1 + return + else + tested=1 + fi + + if [ -z "${user_ext_regs}" ] + then + echo "Extended register capture test [Skipped missing user extended registers]" + elif ! validate_extd_regs_sampling "--user-regs" "${user_ext_regs}" "uregs" + then + echo "Extended register capture test [Failed user extended register sampling]" + err=1 + return + else + tested=1 + fi + + if [ ${tested} -eq 0 ] + then + echo "Extended register capture test [Skipped missing extended registers]" + return + fi + + echo "Extended register capture test [Success]" +} + +extract_x86_advertised_simd_classes() { + local regs_output="$1" + + echo "${regs_output}" \ + | grep -oE '(ZMM|YMM|XMM|OPMASK)[0-9]+-[0-9]+' \ + | sed -E 's/[0-9]+-[0-9]+$//' \ + | tr '[:upper:]' '[:lower:]' \ + | sort -u +} + +ordered_x86_simd_classes() { + local advertised_classes="$1" + local simd_classes="" + + if echo "${advertised_classes}" | grep -qw zmm + then + simd_classes="${simd_classes} zmm" + fi + if echo "${advertised_classes}" | grep -qw ymm + then + simd_classes="${simd_classes} ymm" + fi + if echo "${advertised_classes}" | grep -qw xmm + then + simd_classes="${simd_classes} xmm" + fi + if echo "${advertised_classes}" | grep -qw opmask + then + simd_classes="${simd_classes} opmask" + fi + + echo "${simd_classes}" | xargs +} + +check_simd_reg_capture() { + local script_field="$1" + local simd_reg="$2" + + perf script -F ip,sym,"${script_field}" -i "${perfdata}" 2>/dev/null | \ + grep -q -i "${simd_reg}\[" +} + +validate_simd_regs_sampling() { + local regs_opt="$1" + local simd_classes="$2" + local script_field="$3" + local simd_class + local ret=0 + + for simd_class in ${simd_classes} + do + perf_record_with_retry "${perfdata}" \ + "check_simd_reg_capture ${script_field} ${simd_class}" "perf test -w thloop" \ + -e br_inst_retired.near_call ${regs_opt}=${simd_class} -c 1000 \ + --per-thread || ret=$? + + if [ $ret -ne 0 ] + then + echo "SIMD register capture test [Failed record ${regs_opt}=${simd_class}]" + return 1 + fi + done + + return 0 +} + +test_simd_register_capture() { + local arch + local intr_regs + local user_regs + local simd_classes + local user_simd_classes + local advertised_intr_classes + local advertised_user_classes + local tested=0 + + echo "SIMD register capture test" + if ! perf list pmu | grep -q 'br_inst_retired.near_call' + then + echo "SIMD register capture test [Skipped missing event]" + return + fi + + intr_regs=$(perf record --intr-regs=\? 2>&1 || true) + user_regs=$(perf record --user-regs=\? 2>&1 || true) + + simd_classes="" + user_simd_classes="" + + arch=$(uname -m) + case ${arch} in + x86_64|i386) + advertised_intr_classes=$(extract_x86_advertised_simd_classes "${intr_regs}") + advertised_user_classes=$(extract_x86_advertised_simd_classes "${user_regs}") + + simd_classes=$(ordered_x86_simd_classes "${advertised_intr_classes}") + user_simd_classes=$(ordered_x86_simd_classes "${advertised_user_classes}") + ;; + *) + echo "SIMD register capture test [Skipped non-x86 platform]" + return + ;; + esac + + if [ -z "${simd_classes}" ] + then + echo "SIMD register capture test [Skipped missing intr SIMD registers]" + elif ! validate_simd_regs_sampling "--intr-regs" "${simd_classes}" "iregs" + then + echo "SIMD register capture test [Failed intr SIMD register sampling]" + err=1 + return + else + tested=1 + fi + + if [ -z "${user_simd_classes}" ] + then + echo "SIMD register capture test [Skipped missing user SIMD registers]" + elif ! validate_simd_regs_sampling "--user-regs" "${user_simd_classes}" "uregs" + then + echo "SIMD register capture test [Failed user SIMD register sampling]" + err=1 + return + else + tested=1 + fi + + if [ ${tested} -eq 0 ] + then + echo "SIMD register capture test [Skipped missing SIMD registers]" + return + fi + + echo "SIMD register capture test [Success]" +} + test_system_wide() { echo "Basic --system-wide mode test" local ret=0 @@ -282,7 +535,7 @@ test_leader_sampling() { err=1 return fi - perf script -i "${perfdata}" | grep brstack > $script_output + perf script -i "${perfdata}" -F period,ip,sym | grep brstack > $script_output # Check if the two instruction counts are equal in each record. # However, the throttling code doesn't consider event grouping. During throttling, only the # leader is stopped, causing the slave's counts significantly higher. To temporarily solve this, @@ -294,12 +547,15 @@ test_leader_sampling() { tolerance_rate=0.8 while IFS= read -r line do - cycles=$(echo $line | awk '{for(i=1;i<=NF;i++) if($i=="cycles:") print $(i-1)}') - if [ $(($index%2)) -ne 0 ] && [ ${cycles}x != ${prev_cycles}x ] + cycles=$(echo $line | awk '{ print $1 }') + if [ $(($index%2)) -ne 0 ] then - invalid_counts=$(($invalid_counts+1)) - else - valid_counts=$(($valid_counts+1)) + if (( $(bc <<< "scale=4; r = ${cycles} / ${prev_cycles}; r >= 0.99 && r <= 1.01") )) + then + valid_counts=$(($valid_counts+1)) + else + invalid_counts=$(($invalid_counts+1)) + fi fi index=$(($index+1)) prev_cycles=$cycles @@ -491,6 +747,8 @@ fi test_per_thread test_register_capture +test_extd_register_capture +test_simd_register_capture test_system_wide test_workload test_branch_counter diff --git a/tools/perf/tests/shell/record_weak_term.sh b/tools/perf/tests/shell/record_weak_term.sh index 811b00ffb47a..da418669ab52 100755 --- a/tools/perf/tests/shell/record_weak_term.sh +++ b/tools/perf/tests/shell/record_weak_term.sh @@ -5,7 +5,6 @@ set -e shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh # Find the first event with a specified period, such as diff --git a/tools/perf/tests/shell/report_hybrid_merge.sh b/tools/perf/tests/shell/report_hybrid_merge.sh new file mode 100755 index 000000000000..aca5d16c6c9d --- /dev/null +++ b/tools/perf/tests/shell/report_hybrid_merge.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# perf report hybrid merge tests + +set -e + +err=0 +log_dir=$(mktemp -d /tmp/__perf_test.report_hybrid_merge.XXXXXX) +perf_data="${log_dir}/perf.data" +perf_out="${log_dir}/perf.out" +perf_config="${log_dir}/perfconfig" + +cleanup() { + rm -rf "${log_dir}" + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +# Record 2 events so that all the events of a hybrid machine must be merged, +# not just the first pair found. +events="" +record_events() { + for try in "cycles,instructions" "cpu-clock,task-clock"; do + if perf record -o "${perf_data}" -e "${try}" -- \ + perf test -w thloop 1 >/dev/null 2>&1; then + events="${try//,/ }" + return 0 + fi + done + return 1 +} + +test_hybrid_merge_report() { + echo "Perf report hybrid merge test" + + # Test with multiple fields to ensure formatting alignment doesn't hide members + if ! perf report -i "${perf_data}" --hybrid-merge \ + -F comm,overhead --stdio > "${perf_out}" 2>&1; then + echo "Perf report hybrid merge test [Failed: run err]" + err=1 + return + fi + + # Check if the output actually contains the 'comm' and 'overhead' headers correctly + if ! grep -qi "Overhead" "${perf_out}"; then + echo "Perf report hybrid merge test [Failed: missing overhead header]" + err=1 + return + fi + + if ! grep -qi "Command" "${perf_out}"; then + echo "Perf report hybrid merge test [Failed: missing comm header]" + err=1 + return + fi + + # Every recorded event must be displayed, merged into a group on a + # hybrid machine and separately elsewhere. + if ! perf report -i "${perf_data}" --hybrid-merge --stdio \ + > "${perf_out}" 2>&1; then + echo "Perf report hybrid merge test [Failed: run err]" + err=1 + return + fi + for event in ${events}; do + if ! grep -q -- "${event}" "${perf_out}"; then + echo "Perf report hybrid merge test [Failed: missing event ${event}]" + cat "${perf_out}" + err=1 + return + fi + done + + echo "Perf report hybrid merge test [Success]" +} + +test_hybrid_merge_config() { + echo "Perf report hybrid merge config test" + + cat < "${perf_config}" +[core] + hybrid-merge = true +EOF + + # Merging from the config file is a default, it must give way to an + # explicitly requested --hierarchy rather than failing. + if ! PERF_CONFIG="${perf_config}" perf report -i "${perf_data}" \ + --hierarchy --stdio > "${perf_out}" 2>&1; then + echo "Perf report hybrid merge config test [Failed: --hierarchy]" + cat "${perf_out}" + err=1 + return + fi + + # Asking for both on the command line remains an error. + if PERF_CONFIG="${perf_config}" perf report -i "${perf_data}" \ + --hierarchy --hybrid-merge --stdio > "${perf_out}" 2>&1; then + echo "Perf report hybrid merge config test [Failed: no error for both]" + err=1 + return + fi + + echo "Perf report hybrid merge config test [Success]" +} + +if ! record_events; then + echo "Perf report hybrid merge test [Skipped: perf record failed]" + cleanup + exit 2 +fi + +test_hybrid_merge_report +test_hybrid_merge_config +cleanup +exit $err diff --git a/tools/perf/tests/shell/schedstat_snapshots.sh b/tools/perf/tests/shell/schedstat_snapshots.sh new file mode 100755 index 000000000000..2050478e3f1a --- /dev/null +++ b/tools/perf/tests/shell/schedstat_snapshots.sh @@ -0,0 +1,155 @@ +#!/bin/sh +# Validate CPU and domain pairing in perf sched stats snapshots (no_mlock) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +# shellcheck source=lib/setup_python.sh +. "$(dirname "$0")/lib/setup_python.sh" + +if ! perf version --build-options | grep -q 'libtraceevent:.*on'; then + echo "[Skip] perf sched requires libtraceevent" + exit 2 +fi + +$PYTHON - <<'PY' +import os +import re +import struct +import subprocess +import sys +import tempfile + +# Native-endian perf.data with only NRCPUS and CPU_DOMAIN_INFO features. +endian = '<' if sys.byteorder == 'little' else '>' + + +def pack(fmt, *values): + return struct.pack(endian + fmt, *values) + + +def string(value): + data = value.encode() + b'\0' + return pack('I', len(data)) + data + + +def cpu(cpu_id, timestamp, value, version): + return pack('IHHQIHH6I3Q', 85, 0, 72, timestamp, cpu_id, version, 0, + *([value] * 9)) + + +def domain(cpu_id, domain_id, timestamp, value, version): + # All supported versions use the largest union member's record size. + return pack('IHHQIHH45I4x', 86, 0, 208, timestamp, cpu_id, version, + domain_id, *([value] * 45)) + + +def snapshot(timestamp, value, version=17, cpus=(0, 1, 2), domains=(0, 1)): + records = [] + for cpu_id in cpus: + records.append(cpu(cpu_id, timestamp, value + cpu_id * 100, version)) + for domain_id in domains: + records.append(domain(cpu_id, domain_id, timestamp, + value + cpu_id * 100, version)) + return records + + +def write_file(path, records, version=17): + metadata = pack('II', version, 2) + for cpu_id in range(3): + metadata += pack('II', cpu_id, 2) + for domain_id in range(2): + metadata += pack('I', domain_id) + if version >= 17: + metadata += string('SMT' if domain_id == 0 else 'MC') + metadata += string('7') + string('0-2') + features = [pack('II', 3, 3), metadata] + data = b''.join(records) + offset = 104 + len(data) + 16 * len(features) + sections = b'' + for feature in features: + sections += pack('QQ', offset, len(feature)) + offset += len(feature) + header = pack('13Q', 0x32454c4946524550, 104, 144, 104, 0, + 104, len(data), 0, 0, (1 << 7) | (1 << 32), 0, 0, 0) + with open(path, 'wb') as output: + output.write(header + data + sections + b''.join(features)) + + +def run(args, valid, domains=True): + result = subprocess.run(['perf', 'sched', 'stats'] + args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, timeout=10) + if valid: + assert result.returncode == 0, result.stderr + assert re.search(r'^yld_count\s+:\s+10\b', result.stdout, re.M), result.stdout + if domains: + assert re.search(r'^busy_lb_count\s+:\s+10\b', result.stdout, re.M), result.stdout + else: + assert 'busy_lb_count' not in result.stdout, result.stdout + else: + assert result.returncode > 0, (args, result.returncode, result.stdout) + assert 'Incompatible or incomplete schedstat snapshots' in result.stderr + assert not result.stdout, result.stdout + assert 'Sanitizer' not in result.stderr, result.stderr + + +with tempfile.TemporaryDirectory(prefix='perf-schedstat-') as directory: + good = os.path.join(directory, 'good.data') + test = os.path.join(directory, 'test.data') + before = snapshot(100, 1000) + after = snapshot(200, 1010) + write_file(good, before + after) + + for version in (15, 16, 17): + for timestamp in (100, 200): + write_file(test, snapshot(100, 1000, version) + + snapshot(timestamp, 1010, version), version) + run(['report', '-C', '0,1,2', '-i', test], True) + run(['diff', test, test], True) + print('Matching snapshots, including equal timestamps: [Success]') + + write_file(test, snapshot(100, 1000, domains=()) + + snapshot(200, 1010, domains=())) + run(['report', '-C', '0,1,2', '-i', test], True, domains=False) + run(['diff', test, test], True, domains=False) + print('CPUs without domains: [Success]') + + write_file(test, before + snapshot(200, 1010, cpus=(0, 1))) + run(['report', '-C', '0,1', '-i', test], True) + run(['report', '-C', '1', '-i', good], True) + write_file(test, snapshot(100, 1000, cpus=(0, 1)) + + snapshot(200, 1010, cpus=(0, 1))) + run(['diff', good, test], True) + print('CPU filtering and different CPU sets across files: [Success]') + + write_file(test, snapshot(100, 0xfffffffa, cpus=(0,)) + + snapshot(200, 4, cpus=(0,))) + run(['report', '-C', '0', '-i', test], True) + print('Wrapping 32-bit counters: [Success]') + + cases = { + 'missing first CPU': before + snapshot(200, 1010, cpus=(1, 2)), + 'missing middle CPU': before + snapshot(200, 1010, cpus=(0, 2)), + 'missing last CPU': before + snapshot(200, 1010, cpus=(0, 1)), + 'added CPU': snapshot(100, 1000, cpus=(0, 1)) + after, + 'reordered CPUs': before + snapshot(200, 1010, cpus=(0, 2, 1)), + 'equal timestamp, missing first CPU': before + snapshot(100, 1010, cpus=(1, 2)), + 'missing first domain': before + snapshot(200, 1010, domains=(1,)), + 'missing last domain': before + after[:-1], + 'added domain': snapshot(100, 1000, domains=(0,)) + after, + 'reordered domains': before + snapshot(200, 1010, domains=(1, 0)), + 'domain without CPU': before[1:] + after, + 'wrong domain CPU': before + [after[0], domain(1, 0, 200, 1010, 17)] + after[2:], + 'changed version': before + snapshot(200, 1010, version=16), + 'backwards timestamp': before + snapshot(50, 1010), + 'third snapshot': before + after + snapshot(300, 1020), + 'missing second snapshot': before, + } + for name, records in cases.items(): + write_file(test, records) + run(['report', '-C', '0,1,2', '-i', test], False) + run(['diff', test, good], False) + run(['diff', good, test], False) + print(name + ': [Success]') +PY diff --git a/tools/perf/tests/shell/script.sh b/tools/perf/tests/shell/script.sh index 7007f1cdf761..af687c7cfe44 100755 --- a/tools/perf/tests/shell/script.sh +++ b/tools/perf/tests/shell/script.sh @@ -1,14 +1,11 @@ #!/bin/bash -# perf script tests # SPDX-License-Identifier: GPL-2.0 +# perf script tests set -e temp_dir=$(mktemp -d /tmp/perf-test-script.XXXXXXXXXX) -perfdatafile="${temp_dir}/perf.data" -db_test="${temp_dir}/db_test.py" - err=0 cleanup() @@ -30,44 +27,6 @@ trap_cleanup() trap trap_cleanup EXIT TERM INT - -test_db() -{ - echo "DB test" - - # Check if python script is supported - if perf version --build-options | grep python | grep -q OFF ; then - echo "SKIP: python scripting is not supported" - err=2 - return - fi - - cat << "_end_of_file_" > "${db_test}" -perf_db_export_mode = True -perf_db_export_calls = False -perf_db_export_callchains = True - -def sample_table(*args): - print(f'sample_table({args})') - -def call_path_table(*args): - print(f'call_path_table({args}') -_end_of_file_ - case $(uname -m) - in s390x) - cmd_flags="--call-graph dwarf -e cpu-clock";; - *) - cmd_flags="-g";; - esac - - perf record $cmd_flags -o "${perfdatafile}" true - # Disable lsan to avoid warnings about python memory leaks. - export ASAN_OPTIONS=detect_leaks=0 - perf script -i "${perfdatafile}" -s "${db_test}" - export ASAN_OPTIONS= - echo "DB test [Success]" -} - test_parallel_perf() { echo "parallel-perf test" @@ -76,7 +35,7 @@ test_parallel_perf() err=2 return fi - pp=$(dirname "$0")/../../scripts/python/parallel-perf.py + pp=$(dirname "$0")/../../python/parallel-perf.py if [ ! -f "${pp}" ] ; then echo "SKIP: parallel-perf.py script not found " err=2 @@ -91,7 +50,6 @@ test_parallel_perf() echo "parallel-perf test [Success]" } -test_db test_parallel_perf cleanup diff --git a/tools/perf/tests/shell/script_dlfilter.sh b/tools/perf/tests/shell/script_dlfilter.sh index aaed92bb7828..88222280f609 100755 --- a/tools/perf/tests/shell/script_dlfilter.sh +++ b/tools/perf/tests/shell/script_dlfilter.sh @@ -5,7 +5,6 @@ set -e shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh # skip if there's no compiler diff --git a/tools/perf/tests/shell/script_perl.sh b/tools/perf/tests/shell/script_perl.sh deleted file mode 100755 index b6d65b6fbda1..000000000000 --- a/tools/perf/tests/shell/script_perl.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash -# perf script perl tests -# SPDX-License-Identifier: GPL-2.0 - -set -e - -# set PERF_EXEC_PATH to find scripts in the source directory -perfdir=$(dirname "$0")/../.. -if [ -e "$perfdir/scripts/perl/Perf-Trace-Util" ]; then - export PERF_EXEC_PATH=$perfdir -fi - - -perfdata=$(mktemp /tmp/__perf_test_script_perl.perf.data.XXXXX) -generated_script=$(mktemp /tmp/__perf_test_script.XXXXX.pl) - -cleanup() { - rm -f "${perfdata}" - rm -f "${generated_script}" - trap - EXIT TERM INT -} - -trap_cleanup() { - echo "Unexpected signal in ${FUNCNAME[1]}" - cleanup - exit 1 -} -trap trap_cleanup TERM INT -trap cleanup EXIT - -check_perl_support() { - if perf check feature -q libperl; then - return 0 - fi - echo "perf script perl test [Skipped: no libperl support]" - return 2 -} - -test_script() { - local event_name=$1 - local expected_output=$2 - local record_opts=$3 - - echo "Testing event: $event_name" - - # Try to record. If this fails, it might be permissions or lack of support. - # We return 2 to indicate "skip this event" rather than "fail test". - if ! perf record -o "${perfdata}" -e "$event_name" $record_opts -- perf test -w thloop > /dev/null 2>&1; then - echo "perf script perl test [Skipped: failed to record $event_name]" - return 2 - fi - - echo "Generating perl script..." - if ! perf script -i "${perfdata}" -g "${generated_script}"; then - echo "perf script perl test [Failed: script generation for $event_name]" - return 1 - fi - - if [ ! -f "${generated_script}" ]; then - echo "perf script perl test [Failed: script not generated for $event_name]" - return 1 - fi - - echo "Executing perl script..." - output=$(perf script -i "${perfdata}" -s "${generated_script}" 2>&1) - - if echo "$output" | grep -q "$expected_output"; then - echo "perf script perl test [Success: $event_name triggered $expected_output]" - return 0 - else - echo "perf script perl test [Failed: $event_name did not trigger $expected_output]" - echo "Output was:" - echo "$output" | head -n 20 - return 1 - fi -} - -check_perl_support || exit 2 - -# Try tracepoint first -test_script "sched:sched_switch" "sched::sched_switch" "-c 1" && res=0 || res=$? - -if [ $res -eq 0 ]; then - exit 0 -elif [ $res -eq 1 ]; then - exit 1 -fi - -# If tracepoint skipped (res=2), try task-clock -# For generic events like task-clock, the generated script uses process_event() -# which dumps data using Data::Dumper. We check for "$VAR1" which is standard Dumper output. -test_script "task-clock" "\$VAR1" "-c 100" && res=0 || res=$? - -if [ $res -eq 0 ]; then - exit 0 -elif [ $res -eq 1 ]; then - exit 1 -fi - -# If both skipped -echo "perf script perl test [Skipped: Could not record tracepoint or task-clock]" -exit 2 diff --git a/tools/perf/tests/shell/script_python.sh b/tools/perf/tests/shell/script_python.sh deleted file mode 100755 index 6bc66074a31f..000000000000 --- a/tools/perf/tests/shell/script_python.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -# perf script python tests -# SPDX-License-Identifier: GPL-2.0 - -set -e - -# set PERF_EXEC_PATH to find scripts in the source directory -perfdir=$(dirname "$0")/../.. -if [ -e "$perfdir/scripts/python/Perf-Trace-Util" ]; then - export PERF_EXEC_PATH=$perfdir -fi - - -perfdata=$(mktemp /tmp/__perf_test_script_python.perf.data.XXXXX) -generated_script=$(mktemp /tmp/__perf_test_script.XXXXX.py) - -cleanup() { - rm -f "${perfdata}" - rm -f "${generated_script}" - trap - EXIT TERM INT -} - -trap_cleanup() { - echo "Unexpected signal in ${FUNCNAME[1]}" - cleanup - exit 1 -} -trap trap_cleanup TERM INT -trap cleanup EXIT - -check_python_support() { - if perf check feature -q libpython; then - return 0 - fi - echo "perf script python test [Skipped: no libpython support]" - return 2 -} - -test_script() { - local event_name=$1 - local expected_output=$2 - local record_opts=$3 - - echo "Testing event: $event_name" - - # Try to record. If this fails, it might be permissions or lack of - # support. Return 2 to indicate "skip this event" rather than "fail - # test". - if ! perf record -o "${perfdata}" -e "$event_name" $record_opts -- perf test -w thloop > /dev/null 2>&1; then - echo "perf script python test [Skipped: failed to record $event_name]" - return 2 - fi - - echo "Generating python script..." - if ! perf script -i "${perfdata}" -g "${generated_script}"; then - echo "perf script python test [Failed: script generation for $event_name]" - return 1 - fi - - if [ ! -f "${generated_script}" ]; then - echo "perf script python test [Failed: script not generated for $event_name]" - return 1 - fi - - # Perf script -g python doesn't generate process_event for generic - # events so append it manually to test that the callback works. - if ! grep -q "def process_event" "${generated_script}"; then - cat <> "${generated_script}" - -def process_event(param_dict): - print("param_dict: %s" % param_dict) -EOF - fi - - echo "Executing python script..." - output=$(perf script -i "${perfdata}" -s "${generated_script}" 2>&1) - - if echo "$output" | grep -q "$expected_output"; then - echo "perf script python test [Success: $event_name triggered $expected_output]" - return 0 - else - echo "perf script python test [Failed: $event_name did not trigger $expected_output]" - echo "Output was:" - echo "$output" | head -n 20 - return 1 - fi -} - -check_python_support || exit 2 - -# Try tracepoint first -test_script "sched:sched_switch" "sched__sched_switch" "-c 1" && res=0 || res=$? - -if [ $res -eq 0 ]; then - exit 0 -elif [ $res -eq 1 ]; then - exit 1 -fi - -# If tracepoint skipped (res=2), try task-clock -# For generic events like task-clock, the generated script uses process_event() -# which prints the param_dict. -test_script "task-clock" "param_dict" "-c 100" && res=0 || res=$? - -if [ $res -eq 0 ]; then - exit 0 -elif [ $res -eq 1 ]; then - exit 1 -fi - -# If both skipped -echo "perf script python test [Skipped: Could not record tracepoint or task-clock]" -exit 2 diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/shell/stat+csv_output.sh index cd6fff597091..8beb6c674d05 100755 --- a/tools/perf/tests/shell/stat+csv_output.sh +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -1,12 +1,11 @@ #!/bin/bash -# perf stat CSV output linter +# perf stat CSV output linter (no_mlock) # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) # Tests various perf stat CSV output commands for the # correct number of fields and the CSV separator set to ','. set -e -# shellcheck source=lib/stat_output.sh . "$(dirname $0)"/lib/stat_output.sh csv_sep=@ diff --git a/tools/perf/tests/shell/stat+csv_summary.sh b/tools/perf/tests/shell/stat+csv_summary.sh index 9a4353db3825..6d8beb88fb6f 100755 --- a/tools/perf/tests/shell/stat+csv_summary.sh +++ b/tools/perf/tests/shell/stat+csv_summary.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat csv summary test +# perf stat csv summary test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+event_uniquifying.sh b/tools/perf/tests/shell/stat+event_uniquifying.sh index b5dec6b6da36..f5c6fa30d8db 100755 --- a/tools/perf/tests/shell/stat+event_uniquifying.sh +++ b/tools/perf/tests/shell/stat+event_uniquifying.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat events uniquifying +# perf stat events uniquifying (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/shell/stat+json_output.sh index 85d1ad7186c6..cbf7a4ffd7e8 100755 --- a/tools/perf/tests/shell/stat+json_output.sh +++ b/tools/perf/tests/shell/stat+json_output.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat JSON output linter +# perf stat JSON output linter (no_mlock) # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) # Checks various perf stat JSON output commands for the # correct number of fields. @@ -9,7 +9,6 @@ set -e skip_test=0 shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh pythonchecker=$(dirname $0)/lib/perf_json_output_lint.py diff --git a/tools/perf/tests/shell/stat+shadow_stat.sh b/tools/perf/tests/shell/stat+shadow_stat.sh index cabbbf17c662..7a58e1507ce2 100755 --- a/tools/perf/tests/shell/stat+shadow_stat.sh +++ b/tools/perf/tests/shell/stat+shadow_stat.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat metrics (shadow stat) test +# perf stat metrics (shadow stat) test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/shell/stat+std_output.sh index 9c4b92ecf448..b915656a20f6 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -1,12 +1,11 @@ #!/bin/bash -# perf stat STD output linter +# perf stat STD output linter (no_mlock) # SPDX-License-Identifier: GPL-2.0 # Tests various perf stat STD output commands for # default event and metricgroup set -e -# shellcheck source=lib/stat_output.sh . "$(dirname $0)"/lib/stat_output.sh stat_output=$(mktemp /tmp/__perf_test.stat_output.std.XXXXX) diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh index 298ce454b45a..ac9e031831da 100755 --- a/tools/perf/tests/shell/stat.sh +++ b/tools/perf/tests/shell/stat.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat tests +# perf stat tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e @@ -583,6 +583,11 @@ test_hide_zero_events_stat() { # Check that --metric-only works with --hide-zero-events if ! perf stat --hide-zero-events --metric-only -e instructions,cycles true > "${stat_output}" 2>&1 then + if grep -sq 'No supported events found' "${stat_output}" + then + echo "Hide zero events stat test [Skipped - events not supported]" + return + fi echo "Hide zero events stat test [Failed - metric-only command failed]" err=1 return diff --git a/tools/perf/tests/shell/stat_all_metricgroups.sh b/tools/perf/tests/shell/stat_all_metricgroups.sh index 81bc7070b5ab..074a437da0ae 100755 --- a/tools/perf/tests/shell/stat_all_metricgroups.sh +++ b/tools/perf/tests/shell/stat_all_metricgroups.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf all metricgroups test +# perf all metricgroups test (no_mlock) # SPDX-License-Identifier: GPL-2.0 ParanoidAndNotRoot() diff --git a/tools/perf/tests/shell/stat_all_metrics.sh b/tools/perf/tests/shell/stat_all_metrics.sh index df5fa27b21ae..9d0d4b8a217e 100755 --- a/tools/perf/tests/shell/stat_all_metrics.sh +++ b/tools/perf/tests/shell/stat_all_metrics.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf all metrics test +# perf all metrics test (no_mlock) # SPDX-License-Identifier: GPL-2.0 ParanoidAndNotRoot() diff --git a/tools/perf/tests/shell/stat_all_pfm.sh b/tools/perf/tests/shell/stat_all_pfm.sh index c08c186af2c4..78d8006fb729 100755 --- a/tools/perf/tests/shell/stat_all_pfm.sh +++ b/tools/perf/tests/shell/stat_all_pfm.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf all libpfm4 events test +# perf all libpfm4 events test (no_mlock) # SPDX-License-Identifier: GPL-2.0 if perf version --build-options | grep HAVE_LIBPFM | grep -q OFF diff --git a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh index febe3af06441..d1b4632c17a3 100755 --- a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh +++ b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat --bpf-counters --for-each-cgroup test +# perf stat --bpf-counters --for-each-cgroup test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat_metrics_cgrp.sh b/tools/perf/tests/shell/stat_metrics_cgrp.sh index d4226ee0ae98..75b8e01d11cc 100755 --- a/tools/perf/tests/shell/stat_metrics_cgrp.sh +++ b/tools/perf/tests/shell/stat_metrics_cgrp.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat metrics --for-each-cgroup test +# perf stat metrics --for-each-cgroup test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e @@ -86,6 +86,13 @@ check_metric_reported() for cgrp in "${cgrps[@]}"; do # Find metric lines for this cgroup + case "$cgrp" in + /) # The root cgroup, leave unchanged. + ;; + /*) # Strip any starting slashes. + cgrp="${cgrp#/}" + ;; + esac # We use exact cgroup match with surrounding commas local cgrp_lines cgrp_lines=$(echo "${output}" | grep -F ",${cgrp}," | grep "insn_per_cycle" || true) diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tests/shell/stat_metrics_values.sh index 86c5c7f70933..3e073a0e5b9f 100755 --- a/tools/perf/tests/shell/stat_metrics_values.sh +++ b/tools/perf/tests/shell/stat_metrics_values.sh @@ -1,9 +1,8 @@ #!/bin/bash -# perf metrics value validation +# perf metrics value validation (no_mlock) # SPDX-License-Identifier: GPL-2.0 shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh grep -q GenuineIntel /proc/cpuinfo || { echo Skipping non-Intel; exit 2; } diff --git a/tools/perf/tests/shell/test_arm_callgraph_fp.sh b/tools/perf/tests/shell/test_arm_callgraph_fp.sh index 9172dd68a81d..8adfe81fa8dc 100755 --- a/tools/perf/tests/shell/test_arm_callgraph_fp.sh +++ b/tools/perf/tests/shell/test_arm_callgraph_fp.sh @@ -3,7 +3,6 @@ # SPDX-License-Identifier: GPL-2.0 shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh if [ "$(uname -m)" != "aarch64" ]; then diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh index 71550e0b37ba..fd05ebb5ae74 100755 --- a/tools/perf/tests/shell/test_brstack.sh +++ b/tools/perf/tests/shell/test_brstack.sh @@ -5,7 +5,6 @@ # German Gomez , 2022 shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh # skip the test if the hardware doesn't support branch stack sampling diff --git a/tools/perf/tests/shell/test_check_perf_trace_python.sh b/tools/perf/tests/shell/test_check_perf_trace_python.sh new file mode 100755 index 000000000000..baf55945d429 --- /dev/null +++ b/tools/perf/tests/shell/test_check_perf_trace_python.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# check-perf-trace python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +if ! perf check feature -q libtraceevent; then + echo "Skipping test, perf built without libtraceevent" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/check-perf-trace.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, check-perf-trace.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} + +trap 'cleanup' EXIT +trap 'cleanup; exit 1' TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +test_file_mode() { + echo "Testing check-perf-trace.py..." + + events="" + tp_list=$(perf list tracepoint) + if echo "$tp_list" | grep -q "irq:softirq_entry"; then + events="irq:softirq_entry" + fi + if echo "$tp_list" | grep -q "kmem:kmalloc"; then + if [ -n "$events" ]; then + events="$events,kmem:kmalloc,kmem:kfree" + else + events="kmem:kmalloc,kmem:kfree" + fi + fi + + if [ -z "$events" ]; then + echo "Skipping test, no required tracepoints found" + exit 2 + fi + + # Generate events + if ! perf record -B -N --no-bpf-event -e "$events" -o "${temp_data}" \ + -- sh -c "sleep 0.1" >/dev/null 2>&1; then + echo "Skipping test, perf record failed" + exit 2 + fi + + # Run the script + if ! perf script check-perf-trace -i "${temp_data}" > "${temp_out}"; then + echo "File mode test failed." + err=1 + elif ! grep -q "in trace_begin" "${temp_out}" || \ + ! grep -q "in trace_end" "${temp_out}"; then + echo "File mode test missing trace_begin/trace_end markers." + err=1 + else + echo "File mode test passed." + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_compaction_times_python.sh b/tools/perf/tests/shell/test_compaction_times_python.sh new file mode 100755 index 000000000000..3f5a71e52388 --- /dev/null +++ b/tools/perf/tests/shell/test_compaction_times_python.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# compaction-times python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +if ! perf check feature -q libtraceevent; then + echo "Skipping test, perf built without libtraceevent" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/compaction-times.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, compaction-times.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +test_file_mode() { + echo "Testing compaction-times.py..." + + # Check for any compaction events to see if kernel supports it + if ! perf list tracepoint | grep -q "compaction:mm_compaction_begin"; then + echo "Skipping test, compaction tracepoints not found" + exit 2 + fi + + # Generate some events + # We might not naturally trigger compaction in 0.1s sleep, but the script + # should parse the empty or sparse file correctly without crashing. + if ! perf record -B -N --no-bpf-event \ + -e "compaction:mm_compaction_begin,compaction:mm_compaction_end" \ + -a -o "${temp_data}" -- sleep 0.1 >/dev/null 2>&1; then + echo "Skipping test, perf record failed" + exit 2 + fi + + # Run the script with some filters to validate filtering logic + if ! perf script compaction-times -i "${temp_data}" > "${temp_out}"; then + echo "File mode default test failed." + err=1 + elif ! grep -q "total:" "${temp_out}"; then + echo "File mode default test missing 'total:' output." + err=1 + fi + + if ! perf script compaction-times -i "${temp_data}" "0-0" > "${temp_out}"; then + echo "File mode strict PID filter test failed." + err=1 + elif ! grep -q "total:" "${temp_out}"; then + echo "File mode strict PID filter test missing 'total:' output." + err=1 + fi + + if ! perf script compaction-times -i "${temp_data}" "sleep" > "${temp_out}"; then + echo "File mode comm filter test failed." + err=1 + elif ! grep -q "total:" "${temp_out}"; then + echo "File mode comm filter test missing 'total:' output." + err=1 + fi + + # Deterministic unit test of comm filtering and non-zero duration aggregation + if ! "$PYTHON" -c " +import re, sys, importlib.util +spec = importlib.util.spec_from_file_location('compaction_times', sys.argv[1]) +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) + +class DummyThread: + def comm(self): return 'sleep' + +class DummySession: + def find_thread(self, pid, tid): return DummyThread() + +class DummySample: + def __init__(self, evsel, time, **kw): + self.evsel = evsel + self.sample_time = time + self.sample_pid = 100 + self.sample_tid = 100 + for k, v in kw.items(): + setattr(self, k, v) + +mod.Chead.add_filter(mod.get_comm_filter(re.compile('sleep'))) +mod.session = DummySession() +mod.process_event(DummySample('evsel(compaction:mm_compaction_begin)', 1000)) +mod.process_event(DummySample('evsel(compaction:mm_compaction_end)', 2500)) +mod.trace_end() +" "$script_path" > "${temp_out}"; then + echo "Deterministic comm filter unit test failed." + err=1 + elif ! grep -q "total: 1500ns" "${temp_out}"; then + echo "Deterministic comm filter unit test did not report expected 1500ns total." + err=1 + fi + + if [ $err -eq 0 ]; then + echo "File mode test passed." + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_data_symbol.sh b/tools/perf/tests/shell/test_data_symbol.sh index d61b5659a46d..8d47f14a8802 100755 --- a/tools/perf/tests/shell/test_data_symbol.sh +++ b/tools/perf/tests/shell/test_data_symbol.sh @@ -6,7 +6,6 @@ shelldir=$(dirname "$0") -# shellcheck source=lib/perf_has_symbol.sh . "${shelldir}"/lib/perf_has_symbol.sh skip_if_no_mem_event() { @@ -65,15 +64,17 @@ if (($is_amd >= 1)); then # --ldlat on AMD: # o Zen4 and earlier uarch does not support ldlat # o Even on supported platforms, it's disabled (--ldlat=0) by default. + # o Kernels with the swfilt term add it even when ldlat is not + # supported, so only check ldlat when the term is present. ldlat=${BASH_REMATCH[1]} - if [[ -n $ldlat ]]; then + if [[ $ldlat == *ldlat=* ]]; then if ! [[ "$ldlat" =~ ldlat=0 ]]; then echo "ERROR: ldlat not initialized to 0?" exit 1 fi mem_events="$(perf mem record -v --ldlat=150 -e list 2>&1)" - if ! [[ "$mem_events" =~ ^mem-ldst.*ibs_op/ldlat=150/.*available ]]; then + if ! [[ "$mem_events" =~ ^mem-ldst.*ibs_op/ldlat=150[,/].*available ]]; then echo "ERROR: --ldlat not honored?" exit 1 fi diff --git a/tools/perf/tests/shell/test_event_analyzing_sample_python.sh b/tools/perf/tests/shell/test_event_analyzing_sample_python.sh new file mode 100755 index 000000000000..cddb12b67698 --- /dev/null +++ b/tools/perf/tests/shell/test_event_analyzing_sample_python.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# event_analyzing_sample python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/event_analyzing_sample.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, event_analyzing_sample.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir="" + +cleanup() { + rm -rf "${temp_dir}" +} + +trap 'cleanup' EXIT TERM INT + +temp_dir=$(mktemp -d /tmp/perf.event_analyzing.XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_db="${temp_dir}/perf.db" + +test_file_mode() { + echo "Testing event_analyzing_sample.py..." + + # Generate some events + if ! perf record -B -N --no-bpf-event -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1; then + echo "Skipping test, perf record failed" + exit 2 + fi + + # Run the script with default (:memory:) database and with explicit -d path + if ! perf script event_analyzing_sample -i "${temp_data}" \ + > "${temp_dir}/perf.mem.out" 2>&1 || \ + ! grep -q "Statistics about the general events" "${temp_dir}/perf.mem.out" || \ + grep -q "Error creating/inserting event" "${temp_dir}/perf.mem.out"; then + echo "Default in-memory database mode test failed." + err=1 + elif ! perf script event_analyzing_sample -i "${temp_data}" \ + -d "${temp_db}" > "${temp_dir}/perf.out" 2>&1; then + echo "File mode test failed." + err=1 + elif ! grep -q "Statistics about the general events" "${temp_dir}/perf.out" || \ + grep -q "Error creating/inserting event" "${temp_dir}/perf.out"; then + echo "Event analysis output validation failed." + err=1 + else + echo "File mode test passed." + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_event_open_fallback.sh b/tools/perf/tests/shell/test_event_open_fallback.sh index 9420a7557c13..27d440a5355d 100755 --- a/tools/perf/tests/shell/test_event_open_fallback.sh +++ b/tools/perf/tests/shell/test_event_open_fallback.sh @@ -57,8 +57,6 @@ ret=0 test_decrease_precise_ip || ret=$? ; count_result $ret ; ret=0 test_decrease_precise_ip_complicated || ret=$? ; count_result $ret ; ret=0 -cleanup - if [ ${err_cnt} -gt 0 ] ; then exit 1 fi diff --git a/tools/perf/tests/shell/test_export_to_postgresql_python.sh b/tools/perf/tests/shell/test_export_to_postgresql_python.sh new file mode 100755 index 000000000000..835f48f002e9 --- /dev/null +++ b/tools/perf/tests/shell/test_export_to_postgresql_python.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# export-to-postgresql python test (exclusive) + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +# If we don't have psql, we can't test +if ! command -v psql >/dev/null 2>&1; then + echo "Skipping test, psql not found" + exit 2 +fi + +# Check if we can connect to postgres and create a database +if ! psql -c "SELECT 1" postgres >/dev/null 2>&1; then + echo "Skipping test, cannot connect to postgres" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/export-to-postgresql.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, export-to-postgresql.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir="" +temp_data="" +temp_db="" + +cleanup() { + [ -n "${temp_dir}" ] && rm -rf "${temp_dir}" + psql -c "DROP DATABASE IF EXISTS ${temp_db}" postgres >/dev/null 2>&1 || true +} + +trap 'cleanup' EXIT +trap 'cleanup; exit 1' TERM INT + +temp_dir=$(mktemp -d /tmp/perf.pg.XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_db="perf_test_db_$$" + +test_file_mode() { + echo "Testing export-to-postgresql.py..." + + # Verify that invalid URI / key-value database names are rejected + if perf script export-to-postgresql -i /dev/null \ + -o "dbname=foo host=evil" >/dev/null 2>&1; then + echo "Connection parameter injection check failed." + err=1 + fi + + # Generate events with callchains and context switches + if ! perf record -B -N --no-bpf-event -g --switch-events -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1 && \ + ! perf record -B -N --no-bpf-event -g -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1; then + echo "Skipping test, perf record failed" + exit 2 + fi + + # Ensure clean db start + psql -c "DROP DATABASE IF EXISTS ${temp_db}" postgres >/dev/null 2>&1 || true + + # Run the script + if ! perf script export-to-postgresql -i "${temp_data}" -o "${temp_db}" >/dev/null; then + echo "File mode test failed." + err=1 + else + # Check DB + if ! psql -d "${temp_db}" -t -c 'SELECT COUNT(*) FROM samples WHERE id > 0;' | \ + grep -q '[1-9]' || \ + ! psql -d "${temp_db}" -t -c 'SELECT COUNT(*) FROM call_paths WHERE id > 0;' | \ + grep -q '[1-9]'; then + echo "PostgreSQL validation failed." + err=1 + else + echo "File mode test passed." + fi + fi +} + +test_intel_pt() { + echo "Testing export-to-postgresql.py with intel_pt..." + + # Generate some intel_pt events; sleep briefly after uname in the subshell + # so uname's AUX buffer is flushed before SIGCHLD stops perf record. + passed=0 + for _ in 1 2 3 4 5; do + psql -c "DROP DATABASE IF EXISTS ${temp_db}" postgres >/dev/null 2>&1 || true + rm -f "${temp_data}" + if ! perf record -B -N --no-bpf-event -e intel_pt//u -o "${temp_data}" \ + -- sh -c "uname; sleep 0.05" >/dev/null 2>&1; then + echo "Skipping intel_pt test, intel_pt not available." + return 0 + fi + + # Run the script with --itrace cr to synthesize call_returns + if perf script export-to-postgresql -i "${temp_data}" \ + -o "${temp_db}" --itrace cr >/dev/null && \ + psql -d "${temp_db}" -t -c 'SELECT COUNT(*) FROM calls WHERE id > 0;' | \ + grep -q '[1-9]'; then + passed=1 + break + fi + done + + if [ "$passed" -eq 0 ]; then + echo "PostgreSQL intel_pt validation failed (no calls found)." + err=1 + else + echo "intel_pt test passed (cr validated)." + fi +} + +test_file_mode +test_intel_pt + +exit $err diff --git a/tools/perf/tests/shell/test_export_to_sqlite_python.sh b/tools/perf/tests/shell/test_export_to_sqlite_python.sh new file mode 100755 index 000000000000..19ca7c539cf7 --- /dev/null +++ b/tools/perf/tests/shell/test_export_to_sqlite_python.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# export-to-sqlite python test (exclusive) + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +# If we don't have sqlite3, we can't test +if ! "$PYTHON" -c 'import sqlite3' > /dev/null 2>&1; then + echo "Skipping test, sqlite3 module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/export-to-sqlite.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, export-to-sqlite.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir="" +temp_data="" +temp_db="" + +cleanup() { + [ -n "${temp_dir}" ] && rm -rf "${temp_dir}" +} + +trap 'cleanup' EXIT +trap 'cleanup; exit 1' TERM INT + +temp_dir=$(mktemp -d /tmp/perf.sqlite.XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_db="${temp_dir}/perf.export.db" + +test_file_mode() { + echo "Testing export-to-sqlite.py..." + + # Generate events with callchains and context switches if supported + if ! perf record -B -N --no-bpf-event -g --switch-events -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1 && \ + ! perf record -B -N --no-bpf-event -g -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1; then + echo "Skipping test, perf record failed" + exit 2 + fi + + # Run the script + if ! perf script export-to-sqlite -i "${temp_data}" -o "${temp_db}" >/dev/null; then + echo "File mode test failed." + err=1 + else + # Check DB tables (samples, call_paths, threads, comms) + query="import sqlite3; c = sqlite3.connect('${temp_db}'); " + query="${query}s = c.execute('SELECT COUNT(*) FROM samples').fetchone()[0]; " + query="${query}q = 'SELECT COUNT(*) FROM call_paths WHERE id > 0'; " + query="${query}cp = c.execute(q).fetchone()[0]; " + query="${query}exit(0 if s > 0 and cp > 0 else 1)" + if ! "$PYTHON" -c "$query" >/dev/null 2>&1; then + echo "SQLite validation failed." + err=1 + else + echo "File mode test passed." + fi + fi +} + +test_intel_pt() { + echo "Testing export-to-sqlite.py with intel_pt..." + + query="import sqlite3; c = sqlite3.connect('${temp_db}'); " + query="${query}r = c.execute('SELECT COUNT(*) FROM calls').fetchone()[0]; " + query="${query}exit(1 if r == 0 else 0)" + + # Generate some intel_pt events; sleep briefly after uname in the subshell + # so uname's AUX buffer is flushed before SIGCHLD stops perf record. + passed=0 + for _ in 1 2 3 4 5; do + rm -f "${temp_db}" "${temp_data}" + if ! perf record -B -N --no-bpf-event -e intel_pt//u -o "${temp_data}" \ + -- sh -c "uname; sleep 0.05" >/dev/null 2>&1; then + echo "Skipping intel_pt test, intel_pt not available." + return 0 + fi + + # Run the script with --itrace cr to synthesize call_returns + if perf script export-to-sqlite -i "${temp_data}" -o "${temp_db}" --itrace cr && \ + "$PYTHON" -c "$query" >/dev/null 2>&1; then + passed=1 + break + fi + done + + if [ "$passed" -eq 0 ]; then + echo "SQLite intel_pt validation failed (no calls found)." + err=1 + else + echo "intel_pt test passed (cr validated)." + fi +} + +test_file_mode +test_intel_pt + +exit $err diff --git a/tools/perf/tests/shell/test_failed_syscalls_by_pid_python.sh b/tools/perf/tests/shell/test_failed_syscalls_by_pid_python.sh new file mode 100755 index 000000000000..070a7ef6b8ad --- /dev/null +++ b/tools/perf/tests/shell/test_failed_syscalls_by_pid_python.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# failed-syscalls-by-pid python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/failed-syscalls-by-pid.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, failed-syscalls-by-pid.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir=$(mktemp -d /tmp/perf-failed-syscalls-by-pid-XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_out="${temp_dir}/perf.out" + +cleanup() { + rm -rf "${temp_dir}" +} + +trap 'cleanup' EXIT TERM INT + +test_file_mode() { + echo "Testing failed-syscalls-by-pid.py..." + + # Check if syscalls:sys_exit is supported/readable + if ! perf record -B -N --no-bpf-event -e syscalls:sys_exit \ + -o /dev/null -- true >/dev/null 2>&1; then + if ! perf record -B -N --no-bpf-event -e raw_syscalls:sys_exit \ + -o /dev/null -- true >/dev/null 2>&1; then + echo "Skipping test, no syscalls:sys_exit or raw_syscalls:sys_exit event" + exit 2 + else + EVENT="raw_syscalls:sys_exit" + fi + else + EVENT="syscalls:sys_exit" + fi + + # Generate some events by running a command that fails a syscall + # (e.g. failing stat on non-existent file), sleeping briefly in the + # subshell so ls's PERF_RECORD_COMM and sys_exit events are flushed. + passed=0 + for _ in 1 2 3 4 5; do + rm -f "${temp_data}" "${temp_out}" "${temp_out}.comm" "${temp_out}.pid" + perf record -B -N --no-bpf-event -e "${EVENT}" -o "${temp_data}" \ + -- sh -c "ls /does_not_exist 2>/dev/null; sleep 0.05 || true" \ + >/dev/null 2>&1 || true + if [ ! -s "${temp_data}" ]; then + continue + fi + + if perf script failed-syscalls-by-pid -i "${temp_data}" > "${temp_out}" && \ + grep -n -q "err = ENOENT" "${temp_out}" && \ + perf script failed-syscalls-by-pid -i "${temp_data}" \ + "ls" > "${temp_out}.comm" && \ + grep -q "err = ENOENT" "${temp_out}.comm"; then + ls_pid=$(sed -n 's/^ls \[\([0-9][0-9]*\)\].*/\1/p' \ + "${temp_out}.comm" | head -n 1) + if [ -n "${ls_pid}" ] && \ + perf script failed-syscalls-by-pid -i "${temp_data}" \ + "${ls_pid}" > "${temp_out}.pid" && \ + grep -q "err = ENOENT" "${temp_out}.pid"; then + passed=1 + break + fi + fi + done + + if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 + fi + + if [ "$passed" -eq 0 ]; then + echo "failed-syscalls-by-pid test failed." + cat "${temp_out}" 2>/dev/null || true + cat "${temp_out}.comm" 2>/dev/null || true + cat "${temp_out}.pid" 2>/dev/null || true + err=1 + else + echo "failed-syscalls-by-pid test passed." + fi + rm -f "${temp_out}" "${temp_out}.comm" "${temp_out}.pid" +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_failed_syscalls_python.sh b/tools/perf/tests/shell/test_failed_syscalls_python.sh new file mode 100755 index 000000000000..9aaf05883c50 --- /dev/null +++ b/tools/perf/tests/shell/test_failed_syscalls_python.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# failed-syscalls python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/failed-syscalls.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, failed-syscalls.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir=$(mktemp -d /tmp/perf-failed-syscalls-XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_out="${temp_dir}/perf.out" + +cleanup() { + rm -rf "${temp_dir}" +} +trap 'cleanup' EXIT TERM INT + +echo "Testing failed-syscalls.py..." + +# Check if sys_exit event can be recorded +if ! perf record -B -N --no-bpf-event -e raw_syscalls:sys_exit \ + -o /dev/null -- true >/dev/null 2>&1; then + if ! perf record -B -N --no-bpf-event -e syscalls:sys_exit \ + -o /dev/null -- true >/dev/null 2>&1; then + echo "Skipping test, no permission or support for sys_exit event" + exit 2 + else + EVENT="syscalls:sys_exit" + fi +else + EVENT="raw_syscalls:sys_exit" +fi + +# Run perf record with a command that fails a syscall (ls non-existent file), +# sleeping briefly in the subshell so ls's PERF_RECORD_COMM and sys_exit events are flushed. +passed=0 +for _ in 1 2 3 4 5; do + rm -f "${temp_data}" "${temp_out}" + perf record -B -N --no-bpf-event -e "${EVENT}" -o "${temp_data}" \ + -- sh -c "ls /nonexistent_file_for_test 2>/dev/null; sleep 0.05 || true" \ + >/dev/null 2>&1 || true + + if [ ! -s "${temp_data}" ]; then + continue + fi + + # Check that the script executes + if perf script failed-syscalls -i "${temp_data}" > "${temp_out}" && \ + grep -q "failed syscalls by comm" "${temp_out}" && \ + grep -Eq '^ls[[:space:]]+[0-9]+' "${temp_out}"; then + passed=1 + break + fi +done + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +if [ "$passed" -eq 0 ]; then + echo "Failed to find the metrics table header or expected error" + err=1 +else + echo "failed-syscalls test passed." +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_flamegraph_python.sh b/tools/perf/tests/shell/test_flamegraph_python.sh new file mode 100755 index 000000000000..787d6bbdf04a --- /dev/null +++ b/tools/perf/tests/shell/test_flamegraph_python.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# flamegraph python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/flamegraph.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, flamegraph.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_json="" +temp_html="" +temp_tpl="" + +cleanup() { + rm -f "${temp_data}" "${temp_json}" "${temp_html}" "${temp_tpl}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_json=$(mktemp /tmp/perf.flamegraph.json.XXXXXX) +temp_html=$(mktemp /tmp/perf.flamegraph.html.XXXXXX) +temp_tpl=$(mktemp /tmp/perf.flamegraph.tpl.XXXXXX) + +validate_json() { + local json_file="$1" + local label="$2" + + if ! "$PYTHON" -m json.tool "${json_file}" /dev/null >/dev/null 2>&1; then + echo "${label} JSON validation failed." + err=1 + elif ! "$PYTHON" -c "import json, sys +d = json.load(open(sys.argv[1])) +assert d.get('v', 0) > 0 or len(d.get('c', [])) > 0" "${json_file}" >/dev/null 2>&1; then + echo "${label} JSON flamegraph structure is empty." + err=1 + else + echo "${label} JSON test passed." + fi +} + +test_file_mode() { + echo "Testing flamegraph.py..." + + # Generate some events with callchains + if ! perf record -B -N --no-bpf-event -g -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1; then + echo "Skipping test, perf record -g failed (permissions or lack of support)" + exit 2 + fi + + # Run the script in file mode and validate JSON output + if ! perf script flamegraph -i "${temp_data}" -f json -o "${temp_json}" >/dev/null; then + echo "File mode JSON test failed." + err=1 + else + validate_json "${temp_json}" "File mode" + fi + + # Run the script in pipe ('-') mode and validate JSON output + rm -f "${temp_json}" + if ! perf record -B -N --no-bpf-event -g -o - -- perf test -w noploop 2>/dev/null | \ + perf script flamegraph -i - -f json -o "${temp_json}" >/dev/null; then + echo "Pipe stdin JSON mode test failed." + err=1 + else + validate_json "${temp_json}" "Pipe mode" + fi + + # Run the script, dump as html to temp_html using MINIMAL_HTML fallback + if ! perf script flamegraph -i "${temp_data}" -f html \ + --template /nonexistent/template.html -o "${temp_html}" >/dev/null 2>&1; then + echo "File mode HTML test failed." + err=1 + else + if ! grep -q "" "${temp_html}" || \ + ! grep -q 'crossorigin="anonymous"' "${temp_html}"; then + echo "HTML validation failed." + err=1 + else + echo "File mode HTML test passed." + fi + fi + + # Test custom local HTML template and --colorscheme option + cat << 'EOF' > "${temp_tpl}" + +EOF + if ! perf script flamegraph -i "${temp_data}" -f html --template "${temp_tpl}" \ + --colorscheme blue-green -o "${temp_html}" >/dev/null 2>&1; then + echo "Custom template HTML test failed." + err=1 + elif ! grep -q "blue-green" "${temp_html}"; then + echo "Custom template colorscheme substitution failed." + err=1 + else + echo "Custom template HTML test passed." + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_futex_contention_python.sh b/tools/perf/tests/shell/test_futex_contention_python.sh new file mode 100755 index 000000000000..41ce8470a75f --- /dev/null +++ b/tools/perf/tests/shell/test_futex_contention_python.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# futex-contention python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/futex-contention.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, futex-contention.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +test_synthetic() { + echo "Testing futex-contention.py synthetic events..." + if ! "$PYTHON" - "$script_path" << 'EOF' +import importlib.util +import sys +from types import SimpleNamespace + +spec = importlib.util.spec_from_file_location("futex_contention", sys.argv[1]) +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) + +enter_sample = SimpleNamespace( + evsel="evsel(syscalls:sys_enter_futex:k)", + sample_pid=1234, + sample_tid=1234, + uaddr=0xdeadbeef, + op=mod.FUTEX_WAIT | mod.FUTEX_PRIVATE_FLAG, + sample_time=1000, +) +exit_sample = SimpleNamespace( + evsel="evsel(syscalls:sys_exit_futex:k)", + sample_pid=1234, + sample_tid=1234, + sample_time=2500, +) + +mod.process_event(enter_sample) +mod.process_event(exit_sample) + +stats = mod.durations.get((1234, 0xdeadbeef)) +assert stats is not None and stats.count == 1, f"Unexpected stats: {stats}" +assert stats.min_time == 1500 and stats.max_time == 1500 and stats.avg() == 1500.0 +line = (f"{mod.process_names.get(1234, 'unknown')}[1234] lock {0xdeadbeef:x} " + f"contended {stats.count} times, {stats.avg():.0f} avg ns " + f"[max: {stats.max_time} ns, min {stats.min_time} ns]") +expected = "unknown[1234] lock deadbeef contended 1 times, 1500 avg ns [max: 1500 ns, min 1500 ns]" +assert line == expected, line +EOF + then + echo "Synthetic test failed." + err=1 + else + echo "Synthetic test passed." + fi +} + +test_file_mode() { + echo "Testing futex-contention.py..." + # Some systems might not have syscalls:sys_enter_futex + if ! perf list tracepoint | grep -q syscalls:sys_enter_futex; then + echo "Skipping file mode test, syscalls:sys_enter_futex not found" + return + fi + + # Generate some futex events + if ! perf record -B -N --no-bpf-event \ + -e syscalls:sys_enter_futex,syscalls:sys_exit_futex -a -o "${temp_data}" \ + -- sleep 0.1 2>/dev/null; then + echo "Skipping file mode test (record failed)" + return + fi + + if ! perf script futex-contention -i "${temp_data}" > "${temp_out}"; then + echo "File mode test failed." + err=1 + else + echo "File mode test passed." + fi +} + +test_synthetic +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_gecko_python.sh b/tools/perf/tests/shell/test_gecko_python.sh new file mode 100755 index 000000000000..18e9f5cd2ab0 --- /dev/null +++ b/tools/perf/tests/shell/test_gecko_python.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# gecko python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/gecko.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, gecko.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_json="" + +cleanup() { + rm -f "${temp_data}" "${temp_json}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_json=$(mktemp /tmp/perf.gecko.json.XXXXXX) + +test_file_mode() { + echo "Testing gecko.py..." + + # Generate some events with callchains + if ! perf record -B -N --no-bpf-event -g -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1; then + echo "Skipping test, perf record -g failed (permissions or lack of support)" + exit 2 + fi + + # Run the script in save-only mode with custom product and category colors + if ! perf script gecko -i "${temp_data}" \ + --product "perf-test-product" --user-color blue --kernel-color red \ + --save-only "${temp_json}" >/dev/null; then + echo "File mode test failed." + err=1 + else + # Validate JSON schema and custom CLI option values + if ! "$PYTHON" - "$script_path" "${temp_json}" << 'PYEOF' +import importlib.util +import json +import sys + +with open(sys.argv[2], encoding="utf-8") as f: + data = json.load(f) + +assert data["meta"]["product"] == "perf-test-product" +assert data["meta"]["version"] == 24 +assert isinstance(data["threads"], list) and len(data["threads"]) > 0 + +spec = importlib.util.spec_from_file_location("gecko", sys.argv[1]) +mod = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = mod +spec.loader.exec_module(mod) + +class DummyHandler: + headers = [] + error_code = None + def send_header(self, k, v): + self.headers.append((k, v)) + def send_error(self, code, _msg): + self.error_code = code + +dummy = DummyHandler() +mod.CORSRequestHandler.list_directory(dummy, "/tmp") +assert dummy.error_code == 403 +PYEOF + then + echo "Gecko JSON and CORSRequestHandler validation failed." + err=1 + else + echo "File mode JSON and CORSRequestHandler test passed." + fi + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_ilist_python.sh b/tools/perf/tests/shell/test_ilist_python.sh new file mode 100755 index 000000000000..a235539c0176 --- /dev/null +++ b/tools/perf/tests/shell/test_ilist_python.sh @@ -0,0 +1,181 @@ +#!/bin/bash +# perf script ilist and perf list --tui test (no_mlock) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +if ! "$PYTHON" -c 'import textual' > /dev/null 2>&1; then + echo "Skipping test, python textual library not found" + exit 2 +fi + +err=0 +output=$(mktemp /tmp/__perf_ilist_test.output.XXXXX) + +cleanup() { + rm -f "${output}" + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_plumbing() { + echo "ilist plumbing test" + # The script should be found and run by perf script, --help doesn't + # need a terminal. + if ! perf script ilist --help > "${output}" 2>&1; then + echo "ilist plumbing test [Failed: script failed]" + cat "${output}" + err=1 + return + fi + if ! grep -q -- "--interval" "${output}"; then + echo "ilist plumbing test [Failed: script not launched]" + cat "${output}" + err=1 + return + fi + echo "ilist plumbing test [Success]" +} + +test_list_tui() { + echo "perf list --tui plumbing test" + # Arguments after -- are passed to the script launched by perf list. + if ! perf list --tui -- --help > "${output}" 2>&1; then + echo "perf list --tui plumbing test [Failed: script failed]" + cat "${output}" + err=1 + return + fi + if ! grep -q -- "--interval" "${output}"; then + echo "perf list --tui plumbing test [Failed: script not launched]" + cat "${output}" + err=1 + return + fi + echo "perf list --tui plumbing test [Success]" +} + +test_headless_ui() { + echo "ilist headless UI test" + # Drive the textual app without a terminal. Opening counters may fail + # for want of permissions, in which case an error dialog is shown. + if ! "$PYTHON" > "${output}" 2>&1 <<'EOF' +import asyncio +import ilist +from textual.widgets import Label, Tree + +async def dismiss_error(app, pilot) -> bool: + """Close the error dialog if shown, returning whether it was.""" + if not isinstance(app.screen, ilist.ErrorScreen): + return False + await pilot.click("#error") + await pilot.pause() + if isinstance(app.screen, ilist.ErrorScreen): + raise RuntimeError("Error dialog not dismissed") + return True + +async def search(pilot, text: str) -> None: + await pilot.press("s") + await pilot.pause() + await pilot.press(*text, "enter") + await pilot.pause() + +def find_leaf(node, cls): + """Find the first node whose data is of type cls.""" + if isinstance(node.data, cls): + return node + for child in node.children: + found = find_leaf(child, cls) + if found: + return found + return None + +async def check_counting(app, pilot, name: str) -> None: + """Check the selected event is shown and counting, or that it failed to open.""" + await pilot.pause(0.5) + if await dismiss_error(app, pilot): + print(f"{name}: failed to open") + return + shown = str(app.query_one("#event_name", Label).render()) + if shown != name: + raise RuntimeError(f"Selected {shown} rather than {name}") + if not app.query(ilist.CounterSparkline) or not app.query("#counter_total"): + raise RuntimeError(f"No counters shown for {name}") + print(f"{name}: total {app.query_one('#counter_total', Label).render()}") + +async def run() -> None: + app = ilist.IListApp(0.1) + async with app.run_test(size=(120, 40)) as pilot: + await pilot.pause() + tree = app.query_one("#root", Tree) + top = [str(node.label) for node in tree.root.children] + if top != ["PMUs", "Metrics"]: + raise RuntimeError(f"Unexpected tree: {top}") + pmus = [str(node.label) for node in tree.root.children[0].children] + if "software" not in pmus: + raise RuntimeError(f"No software PMU in: {pmus}") + + # Search for a software event, which exists everywhere. The search + # result is selected which opens the event. + await search(pilot, "task-clock") + found = app.cur_search_result + if not found or not isinstance(found.data, ilist.PmuEvent) or \ + "task-clock" not in str(found.label): + raise RuntimeError(f"Search didn't find task-clock: {found}") + await check_counting(app, pilot, found.data.name()) + if not app.query_one("#active_search", Label).display: + raise RuntimeError("Active search not shown") + await pilot.press("n", "p", "down", "up", "c") + await pilot.pause(0.2) + await dismiss_error(app, pilot) + + # Searching for something that doesn't exist shows an error. + await search(pilot, "no-such-event-xyzzy") + if not await dismiss_error(app, pilot): + raise RuntimeError("No error for a failed search") + + # Select a metric, if there are any. + metric = find_leaf(tree.root, ilist.Metric) + if metric: + app.expand_and_select(metric) + await check_counting(app, pilot, metric.data.name()) + print("headless UI ok") + +asyncio.run(run()) +EOF + then + echo "ilist headless UI test [Failed: exception]" + cat "${output}" + err=1 + return + fi + if ! grep -q "headless UI ok" "${output}"; then + echo "ilist headless UI test [Failed: unexpected output]" + cat "${output}" + err=1 + return + fi + echo "ilist headless UI test [Success]" +} + +test_plumbing +test_list_tui +test_headless_ui + +cleanup +exit $err diff --git a/tools/perf/tests/shell/test_intel_pt.sh b/tools/perf/tests/shell/test_intel_pt.sh index 26243ff760ec..c0ecece0a825 100755 --- a/tools/perf/tests/shell/test_intel_pt.sh +++ b/tools/perf/tests/shell/test_intel_pt.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Miscellaneous Intel PT testing (exclusive) # SPDX-License-Identifier: GPL-2.0 +# Miscellaneous Intel PT testing (exclusive) set -e @@ -8,7 +8,6 @@ set -e perf list pmu | grep -q 'intel_pt//' || exit 2 shelldir=$(dirname "$0") -# shellcheck source=lib/waiting.sh . "${shelldir}"/lib/waiting.sh skip_cnt=0 @@ -22,7 +21,6 @@ perfdatafile="${temp_dir}/test-perf.data" outfile="${temp_dir}/test-out.txt" errfile="${temp_dir}/test-err.txt" awkscript="${temp_dir}/awkscript" -maxbrstack="${temp_dir}/maxbrstack.py" cleanup() { @@ -376,34 +374,28 @@ test_kernel_trace() test_virtual_lbr() { echo "--- Test virtual LBR ---" - # Check if python script is supported - libpython=$(perf version --build-options | grep python | grep -cv OFF) - if [ "${libpython}" != "1" ] ; then - echo "SKIP: python scripting is not supported" + # Run setup_python.sh in a subshell first so its 'exit 2' when Python is + # not detected skips only this subtest rather than exiting test_intel_pt.sh + # (which would trigger trap_cleanup and fail the suite). + # shellcheck source=lib/setup_python.sh + if ! ( . "${shelldir}"/lib/setup_python.sh ) ; then + return 2 + fi + # shellcheck source=lib/setup_python.sh + . "${shelldir}"/lib/setup_python.sh + + if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "SKIP: Python perf module not found" return 2 fi - # Python script to determine the maximum size of branch stacks - cat << "_end_of_file_" > "${maxbrstack}" -from __future__ import print_function - -bmax = 0 - -def process_event(param_dict): - if "brstack" in param_dict: - brstack = param_dict["brstack"] - n = len(brstack) - global bmax - if n > bmax: - bmax = n - -def trace_end(): - print("max brstack", bmax) -_end_of_file_ - # Check if virtual lbr is working - perf_record_no_bpf -o "${perfdatafile}" --aux-sample -e '{intel_pt//,cycles}:u' uname - times_val=$(perf script -i "${perfdatafile}" --itrace=L -s "${maxbrstack}" 2>/dev/null | grep "max brstack " | cut -d " " -f 3) + perf_record_no_bpf -o "${tmpfile}" --aux-sample \ + -e '{intel_pt//,cycles}:u' perf test -w brstack || return 1 + perf inject --itrace=L -i "${tmpfile}" -o "${perfdatafile}" || return 1 + output=$("$PYTHON" "${shelldir}/lib/perf_brstack_max.py" -i "${perfdatafile}") || return 1 + echo "Debug: perf_brstack_max.py output: $output" + times_val=$(echo "$output" | grep "max brstack " | cut -d " " -f 3) case "${times_val}" in [0-9]*) ;; *) times_val=0;; diff --git a/tools/perf/tests/shell/test_intel_pt_events_python.sh b/tools/perf/tests/shell/test_intel_pt_events_python.sh new file mode 100755 index 000000000000..9754b9125da1 --- /dev/null +++ b/tools/perf/tests/shell/test_intel_pt_events_python.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# intel-pt-events python test (exclusive) + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/intel-pt-events.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, intel-pt-events.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir="" +temp_data="" +temp_out="" + +cleanup() { + [ -n "${temp_dir}" ] && rm -rf "${temp_dir}" +} + +trap 'cleanup' EXIT +trap 'cleanup; exit 1' TERM INT + +temp_dir=$(mktemp -d /tmp/perf.ipt.XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_out="${temp_dir}/perf.out" + +test_intel_pt() { + echo "Testing intel-pt-events.py with intel_pt..." + + # Generate some intel_pt events; sleep briefly after uname in the subshell + # so uname's AUX buffer is flushed before SIGCHLD stops perf record. + passed=0 + for _ in 1 2 3 4 5; do + rm -f "${temp_data}" "${temp_out}" + if ! perf record -B -N --no-bpf-event -e intel_pt//u -o "${temp_data}" \ + -- sh -c "uname; sleep 0.05" >/dev/null 2>&1; then + echo "Skipping intel_pt test, intel_pt not available." + exit 2 + fi + + # Run the script and check output + if perf script intel-pt-events -i "${temp_data}" > "${temp_out}" && \ + grep -q "Intel PT Branch Trace" "${temp_out}" && \ + grep -q "uname" "${temp_out}"; then + passed=1 + break + fi + done + + if [ "$passed" -eq 0 ]; then + echo "Failed to find expected output: $(cat "${temp_out}" 2>/dev/null)" + err=1 + else + echo "intel-pt-events test passed." + fi + rm -f "${temp_out}" +} + +test_intel_pt + +exit $err diff --git a/tools/perf/tests/shell/test_mem_phys_addr_python.sh b/tools/perf/tests/shell/test_mem_phys_addr_python.sh new file mode 100755 index 000000000000..cbb9f572be9d --- /dev/null +++ b/tools/perf/tests/shell/test_mem_phys_addr_python.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# mem-phys-addr python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/mem-phys-addr.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, mem-phys-addr.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_iomem="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_iomem}" "${temp_out}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_iomem=$(mktemp /tmp/perf.iomem.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +cat << 'EOF' > "${temp_iomem}" +00000000-ffffffffffffffff : System RAM + 00000000-7fffffffffffffff : Low RAM + 00001000-00ffffff : Kernel code + 8000000000000000-ffffffffffffffff : High RAM +EOF + +test_iomem_hierarchy() { + echo "Testing mem-phys-addr.py hierarchical iomem resolution..." + "$PYTHON" - "$script_path" "${temp_iomem}" << 'PYEOF' > "${temp_out}" +import importlib.util +import sys + +spec = importlib.util.spec_from_file_location("mem_phys_addr", sys.argv[1]) +mod = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = mod +spec.loader.exec_module(mod) + +mod.parse_iomem(sys.argv[2]) +entry_kernel = mod.find_memory_type(0x100000) +entry_high = mod.find_memory_type(0x9000000000000000) +assert entry_kernel is not None and entry_kernel.label == "Kernel code" +assert entry_high is not None and entry_high.label == "High RAM" +mod.event_counts["cpu/mem-loads/"][entry_kernel] += 3 +mod.event_counts["cpu/mem-loads/"][entry_high] += 1 +mod.print_memory_type() +PYEOF + if ! grep -q "System RAM" "${temp_out}" || \ + ! grep -q "Kernel code" "${temp_out}" || \ + ! grep -q "High RAM" "${temp_out}"; then + echo "Hierarchical iomem resolution test failed." + err=1 + else + echo "Hierarchical iomem resolution test passed." + fi +} + +test_file_mode() { + echo "Testing mem-phys-addr.py file mode..." + + # Generate memory access events (try unprivileged user-space first, then system-wide) + if ! perf record -B -N --no-bpf-event --phys-data -d -o "${temp_data}" \ + -- perf test -w datasym >/dev/null 2>&1 && \ + ! perf record -B -N --no-bpf-event -d -o "${temp_data}" \ + -- perf test -w datasym >/dev/null 2>&1 && \ + ! perf record -B -N --no-bpf-event -d -a -o "${temp_data}" \ + -- sleep 0.1 >/dev/null 2>&1; then + echo "Skipping file mode record test, perf record -d not supported" + return 0 + fi + + # Run the script with custom --iomem + if ! perf script mem-phys-addr -i "${temp_data}" \ + --iomem "${temp_iomem}" > "${temp_out}" || \ + [ ! -s "${temp_out}" ]; then + echo "File mode test failed." + err=1 + else + echo "File mode test passed." + fi +} + +test_iomem_hierarchy +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_net_dropmonitor_python.sh b/tools/perf/tests/shell/test_net_dropmonitor_python.sh new file mode 100755 index 000000000000..2e37994418fc --- /dev/null +++ b/tools/perf/tests/shell/test_net_dropmonitor_python.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# net_dropmonitor python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/net_dropmonitor.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, net_dropmonitor.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT +trap 'cleanup; exit 1' TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing net_dropmonitor.py..." + +# Create a perf.data file. Force dropping a packet if tracepoint is available! +if ! perf record -B -N --no-bpf-event -e skb:kfree_skb -o "${temp_data}" -a \ + -- ping -c 1 -W 1 255.255.255.255 >/dev/null 2>&1; then + if ! perf record -B -N --no-bpf-event -e skb:kfree_skb -o "${temp_data}" \ + -- sleep 0.1 >/dev/null 2>&1; then + if ! perf record -B -N --no-bpf-event -o "${temp_data}" \ + -- uname >/dev/null 2>&1; then + echo "Skipping test, cannot record perf events" + exit 2 + fi + fi +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes and outputs table header +if ! perf script net_dropmonitor -i "${temp_data}" > "${temp_out}"; then + echo "net_dropmonitor.py test failed" + err=1 +else + if ! grep -q "LOCATION.*OFFSET.*COUNT" "${temp_out}"; then + echo "Failed to find the metrics table header" + err=1 + fi +fi + +# Verify DropMonitor event processing and symbol resolution +if [ $err -eq 0 ]; then + if ! "$PYTHON" -c " +import sys +sys.path.insert(0, sys.argv[1]) +import net_dropmonitor + +class DummySample: + evsel = 'skb:kfree_skb' + location = 0xffffffff81001010 + sample_ip = 0xffffffff81001010 + symbol = 'ip_rcv_finish' + sym_offset = 16 + callchain = [] + +dm = net_dropmonitor.DropMonitor() +dm.process_event(DummySample()) +dm.print_drop_table() +" "${script_dir}" > "${temp_out}"; then + echo "net_dropmonitor.py unit test failed" + err=1 + elif ! grep -q "ip_rcv_finish.*16.*1" "${temp_out}"; then + echo "Failed to find expected symbol resolution in net_dropmonitor.py" + err=1 + else + echo "net_dropmonitor test passed." + fi +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_netdev_times_python.sh b/tools/perf/tests/shell/test_netdev_times_python.sh new file mode 100755 index 000000000000..0d8d10a42cab --- /dev/null +++ b/tools/perf/tests/shell/test_netdev_times_python.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# netdev_times python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/netdev-times.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, netdev-times.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing netdev-times.py..." + +if ! "$PYTHON" - "$script_path" << 'EOF' +import argparse +import contextlib +import importlib.util +import io +import sys + +spec = importlib.util.spec_from_file_location("netdev_times", sys.argv[1]) +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) + +cfg = argparse.Namespace(tx=True, rx=False, dev=None, debug=False, input="perf.data") +analyzer = mod.NetdevTimes(cfg) +analyzer.handle_net_dev_queue({ + "time": 1_000_000_000, + "skbaddr": 0xdeadbeef, + "skblen": 1500, + "dev_name": "eth0", +}) +analyzer.handle_net_dev_xmit({ + "time": 1_000_500_000, + "skbaddr": 0xdeadbeef, + "rc": 0, +}) +analyzer.handle_kfree_skb({ + "time": 1_000_750_000, + "skbaddr": 0xdeadbeef, + "comm": "ping", + "pid": 42, + "location": 0xffffffff81000000, +}) + +buf = io.StringIO() +with contextlib.redirect_stdout(buf): + analyzer.print_summary() +out = buf.getvalue() +assert "eth0" in out and "1500" in out and "1.000000sec" in out, out +assert "0.500msec" in out and "0.250msec" in out, out +EOF +then + echo "netdev-times.py synthetic unit test failed" + exit 1 +fi + +# Create a perf.data file. Force dropping a packet if tracepoint is available! +if ! perf record -B -N --no-bpf-event -e skb:kfree_skb -a -o "${temp_data}" \ + -- ping -c 1 -W 1 127.0.0.1 >/dev/null 2>&1; then + perf record -B -N --no-bpf-event -e cycles -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } +fi + +# Check that the script executes +if ! perf script netdev-times -i "${temp_data}" > "${temp_out}"; then + echo "netdev-times.py test failed" + err=1 +else + echo "netdev-times test passed." +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_perf_data_converter_json.sh b/tools/perf/tests/shell/test_perf_data_converter_json.sh index 35d81e39a26c..c2c4ade7609a 100755 --- a/tools/perf/tests/shell/test_perf_data_converter_json.sh +++ b/tools/perf/tests/shell/test_perf_data_converter_json.sh @@ -7,7 +7,6 @@ set -e err=0 shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) diff --git a/tools/perf/tests/shell/test_powerpc_hcalls_python.sh b/tools/perf/tests/shell/test_powerpc_hcalls_python.sh new file mode 100755 index 000000000000..b140ccf00ffa --- /dev/null +++ b/tools/perf/tests/shell/test_powerpc_hcalls_python.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# powerpc-hcalls python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/powerpc-hcalls.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, powerpc-hcalls.py not found at $script_path" + exit 2 +fi + +err=0 +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing powerpc-hcalls.py..." + +# Verify HCallAnalyzer aggregation logic on synthetic hcall_entry/hcall_exit samples +if ! "$PYTHON" - "$script_path" > "${temp_out}" <<'EOF' +import argparse +import importlib.util +import sys +from types import SimpleNamespace + +spec = importlib.util.spec_from_file_location("powerpc_hcalls", sys.argv[1]) +assert spec and spec.loader +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) + +analyzer = mod.HCallAnalyzer(argparse.Namespace(sort='count')) +analyzer.process_event(SimpleNamespace( + evsel="evsel(powerpc:hcall_entry)", sample_cpu=0, sample_time=1000, opcode=4)) +analyzer.process_event(SimpleNamespace( + evsel="evsel(powerpc:hcall_exit)", sample_cpu=0, sample_time=2500, opcode=4)) +analyzer.print_summary() +EOF +then + echo "powerpc-hcalls.py synthetic aggregation test failed" + exit 1 +fi + +if ! grep -q "H_REMOVE.*1.*1500.*1500.*1500" "${temp_out}"; then + echo "Failed to find aggregated H_REMOVE metrics in output" + exit 1 +fi + +# Create a perf.data file if powerpc hcall tracepoints are available on this host. +if ! perf record -B -N --no-bpf-event -e powerpc:hcall_entry,powerpc:hcall_exit \ + -a -o "${temp_data}" -- perf test -w noploop >/dev/null 2>&1; then + echo "Skipping live record test, powerpc hcall tracepoints not available" + exit 0 +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping live record test, perf record failed to create data" + exit 0 +fi + +# Check that the script executes on recorded perf.data +if ! perf script powerpc-hcalls -i "${temp_data}" > "${temp_out}"; then + echo "powerpc-hcalls.py test failed" + err=1 +else + if ! grep -q "hcall.*count.*min.*max.*avg" "${temp_out}"; then + echo "Failed to find the metrics table header" + err=1 + else + echo "powerpc-hcalls test passed." + fi +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_rw_by_file_python.sh b/tools/perf/tests/shell/test_rw_by_file_python.sh new file mode 100755 index 000000000000..e3c538c3ace3 --- /dev/null +++ b/tools/perf/tests/shell/test_rw_by_file_python.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# rw-by-file python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/rw-by-file.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, rw-by-file.py not found at $script_path" + exit 2 +fi + +err=0 +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing rw-by-file.py..." + +# Create a perf.data file. Try to get tracepoint data. +if perf list tracepoint | grep -q "syscalls:sys_enter_read"; then + perf record -B -N --no-bpf-event -e syscalls:sys_enter_read,syscalls:sys_enter_write \ + -o "${temp_data}" \ + -- dd if=/dev/urandom of=/dev/null bs=1M count=10 >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } +else + echo "Skipping test, no syscalls:sys_enter_read event" + exit 2 +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes - filtering for "dd" since that's what we ran +if ! perf script rw-by-file -i "${temp_data}" "dd" > "${temp_out}"; then + echo "rw-by-file.py test failed" + err=1 +else + if ! grep -E -q "^ *[0-9]+" "${temp_out}"; then + echo "Failed to find metric data rows" + err=1 + else + echo "rw-by-file test passed." + fi +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_rw_by_pid_python.sh b/tools/perf/tests/shell/test_rw_by_pid_python.sh new file mode 100755 index 000000000000..cd93c1d9714c --- /dev/null +++ b/tools/perf/tests/shell/test_rw_by_pid_python.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# rw-by-pid python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/rw-by-pid.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, rw-by-pid.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing rw-by-pid.py..." + +# Create a perf.data file. Try to get tracepoint data. +if perf list tracepoint | grep -q "syscalls:sys_enter_read"; then + ev="syscalls:sys_enter_read,syscalls:sys_exit_read" + ev="${ev},syscalls:sys_enter_write,syscalls:sys_exit_write" + perf record -B -N --no-bpf-event -e "$ev" -o "${temp_data}" \ + -- dd if=/dev/urandom of=/dev/null bs=1M count=10 >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } +else + echo "Skipping test, no syscalls:sys_enter_read event" + exit 2 +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes +if ! perf script rw-by-pid -i "${temp_data}" > "${temp_out}"; then + echo "rw-by-pid.py test failed" + err=1 +else + if ! grep -E -q "^ *[0-9]+" "${temp_out}"; then + echo "Failed to find metric data rows in output" + cat "${temp_out}" + err=1 + else + echo "rw-by-pid test passed." + fi +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_rwtop_python.sh b/tools/perf/tests/shell/test_rwtop_python.sh new file mode 100755 index 000000000000..d33807923c92 --- /dev/null +++ b/tools/perf/tests/shell/test_rwtop_python.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# rwtop python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/rwtop.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, rwtop.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing rwtop.py..." + +# Create a perf.data file. Try to get tracepoint data. +if perf list tracepoint | grep -q "syscalls:sys_enter_read"; then + ev="syscalls:sys_enter_read,syscalls:sys_exit_read" + ev="${ev},syscalls:sys_enter_write,syscalls:sys_exit_write" + perf record -B -N --no-bpf-event -e "$ev" -o "${temp_data}" \ + -- dd if=/dev/urandom of=/dev/null bs=1M count=10 >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } +else + echo "Skipping test, no syscalls:sys_enter_read event" + exit 2 +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes +if ! perf script rwtop -i "${temp_data}" > "${temp_out}"; then + echo "rwtop.py test failed" + err=1 +else + if ! grep -E -q "^ *[0-9]+" "${temp_out}"; then + echo "Failed to find metric data rows" + err=1 + else + echo "rwtop test passed." + fi +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_sched_migration_python.sh b/tools/perf/tests/shell/test_sched_migration_python.sh new file mode 100755 index 000000000000..714275b0e492 --- /dev/null +++ b/tools/perf/tests/shell/test_sched_migration_python.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# sched-migration python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/sched-migration.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, sched-migration.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing sched-migration.py..." + +# Create a perf.data file. Force dropping a packet if tracepoint is available! +ev="sched:sched_switch,sched:sched_migrate_task" +ev="${ev},sched:sched_wakeup_new,sched:sched_wakeup" +has_sched=1 +if ! perf record -B -N --no-bpf-event -e "$ev" -a -o "${temp_data}" \ + -- sleep 0.1 >/dev/null 2>&1; then + has_sched=0 + perf record -B -N --no-bpf-event -e cycles -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes +if ! perf script sched-migration -v --no-gui -i "${temp_data}" > "${temp_out}"; then + echo "sched-migration.py test failed" + err=1 +elif [ "$has_sched" -eq 1 ] && ! grep -q "Timeslices:" "${temp_out}"; then + echo "sched-migration.py missing Timeslices output" + err=1 +else + echo "sched-migration test passed." +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_sctop_python.sh b/tools/perf/tests/shell/test_sctop_python.sh new file mode 100755 index 000000000000..b042fc3eefe5 --- /dev/null +++ b/tools/perf/tests/shell/test_sctop_python.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# sctop python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/sctop.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, sctop.py not found at $script_path" + exit 2 +fi + +err=0 +temp_dir=$(mktemp -d /tmp/perf-sctop-XXXXXX) +temp_data="${temp_dir}/perf.data" +temp_out="${temp_dir}/perf.out" + +cleanup() { + rm -rf "${temp_dir}" +} +trap 'cleanup' EXIT TERM INT + +echo "Testing sctop.py..." + +# Create a perf.data file. +if ! perf list tracepoint | grep -q "raw_syscalls:sys_enter"; then + echo "Skipping test, no raw_syscalls:sys_enter event" + exit 2 +fi + +passed=0 +for _ in 1 2 3 4 5; do + rm -f "${temp_data}" "${temp_out}" + if ! perf record -B -N --no-bpf-event -e raw_syscalls:sys_enter -o "${temp_data}" \ + -- sh -c "sleep 0.1; sleep 0.05" >/dev/null 2>&1; then + echo "Skipping test, perf record failed" + exit 2 + fi + + if [ ! -s "${temp_data}" ]; then + continue + fi + + # Check that the script executes + if perf script sctop -i "${temp_data}" > "${temp_out}" && \ + grep -E -q "[0-9]+$" "${temp_out}" && \ + perf script sctop -i "${temp_data}" sleep 1 > "${temp_out}" && \ + grep -E -q "[0-9]+$" "${temp_out}"; then + passed=1 + break + fi +done + +if [ "$passed" -eq 0 ]; then + echo "sctop.py test failed" + err=1 +else + echo "sctop test passed." +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_stackcollapse_python.sh b/tools/perf/tests/shell/test_stackcollapse_python.sh new file mode 100755 index 000000000000..2048734c5e0d --- /dev/null +++ b/tools/perf/tests/shell/test_stackcollapse_python.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# stackcollapse python test + +set -e -o pipefail + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/stackcollapse.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, stackcollapse.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing stackcollapse.py..." + +# Create a perf.data file with callchains. Use a busy workload rather than +# sleep, as an idle system may not generate any samples at all. +perf record -B -N --no-bpf-event -g -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes with default options +if ! perf script stackcollapse -i "${temp_data}" > "${temp_out}"; then + echo "stackcollapse.py test failed" + err=1 +else + # It outputs stacks like: swapper;...;... 2 + if [ ! -s "${temp_out}" ]; then + echo "Expected stack traces in output, but output is empty." + err=1 + else + echo "stackcollapse default test passed." + fi +fi + +# Test CLI flags (--include-pid, --include-tid, --tidy-java, --kernel) and BrokenPipeError +if ! perf script stackcollapse -i "${temp_data}" \ + --include-pid --include-tid --tidy-java --kernel | head -n 1 > "${temp_out}" || \ + [ ! -s "${temp_out}" ]; then + echo "stackcollapse.py options/pipe test failed" + err=1 +elif ! perf script stackcollapse -i "${temp_data}" --no-comm > "${temp_out}" || \ + [ ! -s "${temp_out}" ]; then + echo "stackcollapse.py --no-comm test failed" + err=1 +else + echo "stackcollapse options test passed." +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/test_stat_cpi_python.sh b/tools/perf/tests/shell/test_stat_cpi_python.sh new file mode 100755 index 000000000000..d1571a20f468 --- /dev/null +++ b/tools/perf/tests/shell/test_stat_cpi_python.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# stat-cpi python test (no_mlock) + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + return 2 2>/dev/null || exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/stat-cpi.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, stat-cpi.py not found at $script_path" + return 2 2>/dev/null || exit 2 +fi + +err=0 +ran=0 +temp_data="" +temp_out="" + +cleanup() { + [ -n "${pid}" ] && kill "$pid" 2>/dev/null || true + [ -n "${workload_pid}" ] && kill "$workload_pid" 2>/dev/null || true + rm -f "${temp_data}" "${temp_out}" + trap - exit term int +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup exit term int + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +test_live_mode() { + echo "Testing stat-cpi.py live mode..." + if ! perf stat -e cycles,instructions -- sleep 0.1 2>/dev/null && \ + ! perf stat -e cycles:u,instructions:u -- sleep 0.1 2>/dev/null; then + echo "perf stat failed (permissions?), skipping live mode test." + return 0 + fi + perf test -w noploop 60 & + workload_pid=$! + if ! perf stat -e cycles,instructions -p "$workload_pid" -- sleep 0.05 2>/dev/null && \ + ! perf stat -e cycles:u,instructions:u -p "$workload_pid" -- sleep 0.05 2>/dev/null; then + kill "$workload_pid" 2>/dev/null || true + workload_pid="" + echo "perf stat -p failed (ptrace_scope?), skipping live mode test." + return 0 + fi + ran=1 + + # Run live mode in the background, wait until at least one interval is + # printed, then interrupt. + perf script stat-cpi -I 0.1 -p "$workload_pid" > "${temp_out}" & + pid=$! + for _ in $(seq 1 50); do + if grep -q "cpi" "${temp_out}"; then + break + fi + sleep 0.1 + done + kill -INT "$pid" 2>/dev/null || true + set +e + wait "$pid" + res=$? + set -e + pid="" + kill "$workload_pid" 2>/dev/null || true + workload_pid="" + if [ $res -ne 0 ] && [ $res -ne 130 ] && [ $res -ne 143 ]; then + echo "Live mode failed or crashed" + err=1 + elif ! grep -q "cpi" "${temp_out}"; then + echo "Live mode produced no cpi output" + err=1 + else + echo "Live mode test passed." + fi +} + +test_file_mode() { + echo "Testing stat-cpi.py file mode..." + # Generate some stat events - perf stat -I represents interval reporting + if ! perf stat -e cycles,instructions -I 100 record -o "${temp_data}" \ + -- sleep 0.5 2>/dev/null && \ + ! perf stat -e cycles:u,instructions:u -I 100 record -o "${temp_data}" \ + -- sleep 0.5 2>/dev/null; then + echo "perf stat failed (permissions?), skipping file mode test." + return + fi + ran=1 + + out=$(perf script stat-cpi -i "${temp_data}") + if ! echo "$out" | grep -q "cpi"; then + echo "File mode test failed." + err=1 + else + echo "File mode test passed." + fi +} + +test_live_mode +test_file_mode + +cleanup +if [ $ran -eq 0 ]; then + exit 2 +fi +exit $err diff --git a/tools/perf/tests/shell/test_syscall_counts_by_pid_python.sh b/tools/perf/tests/shell/test_syscall_counts_by_pid_python.sh new file mode 100755 index 000000000000..300289fe33b4 --- /dev/null +++ b/tools/perf/tests/shell/test_syscall_counts_by_pid_python.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# syscall-counts-by-pid python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/syscall-counts-by-pid.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, syscall-counts-by-pid.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +test_file_mode() { + echo "Testing syscall-counts-by-pid.py..." + # Some systems might not have raw_syscalls:sys_enter + if ! perf list tracepoint | grep -q raw_syscalls:sys_enter; then + echo "Skipping test, raw_syscalls:sys_enter not found" + exit 2 + fi + + # Generate some syscall events + perf record -B -N --no-bpf-event -e raw_syscalls:sys_enter -o "${temp_data}" \ + -- sh -c "sleep 0.1; sleep 0.05" >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } + + if ! perf script syscall-counts-by-pid -i "${temp_data}" > "${temp_out}"; then + echo "File mode test failed." + err=1 + elif ! grep -E -q "^ [a-zA-Z0-9_]+ +[0-9]+$" "${temp_out}"; then + echo "File mode output validation failed." + err=1 + else + echo "File mode test passed." + fi + + # Test with a comm argument + if ! perf script syscall-counts-by-pid -i "${temp_data}" "sleep" > "${temp_out}"; then + echo "Comm filter test failed." + err=1 + elif ! grep -E -q "^ [a-zA-Z0-9_]+ +[0-9]+$" "${temp_out}"; then + echo "Comm filter output validation failed." + err=1 + else + echo "Comm filter test passed." + fi + + # Extract sleep PID from "sleep []" header and test with a numeric PID filter argument + sleep_pid=$(sed -n 's/^sleep \[\([0-9]\+\)\]$/\1/p' "${temp_out}" | head -n 1) + if [ -z "${sleep_pid}" ] || \ + ! perf script syscall-counts-by-pid -i "${temp_data}" "${sleep_pid}" > "${temp_out}"; then + echo "PID filter test failed." + err=1 + elif ! grep -E -q "^ [a-zA-Z0-9_]+ +[0-9]+$" "${temp_out}"; then + echo "PID filter output validation failed." + err=1 + else + echo "PID filter test passed." + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_syscall_counts_python.sh b/tools/perf/tests/shell/test_syscall_counts_python.sh new file mode 100755 index 000000000000..86463f14391b --- /dev/null +++ b/tools/perf/tests/shell/test_syscall_counts_python.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# syscall-counts python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +# If we don't have the perf python module, we can't test +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/syscall-counts.py" + +if ! perf check feature -q libtraceevent > /dev/null 2>&1; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if [ ! -f "$script_path" ]; then + echo "Skipping test, syscall-counts.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} + +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +test_file_mode() { + echo "Testing syscall-counts.py..." + # Some systems might not have raw_syscalls:sys_enter (e.g. stripped kernels or permissions) + if ! perf list tracepoint | grep -q raw_syscalls:sys_enter; then + echo "Skipping test, raw_syscalls:sys_enter not found" + exit 2 + fi + + # Generate some syscall events + if ! perf record -B -N --no-bpf-event -e raw_syscalls:sys_enter -o "${temp_data}" \ + -- sh -c "sleep 0.1; sleep 0.05" 2>/dev/null; then + echo "perf record failed (permissions?), skipping file mode test." + exit 2 + fi + + if ! perf script syscall-counts -i "${temp_data}" > "${temp_out}"; then + echo "File mode test failed." + err=1 + elif ! grep -E -q "^[a-zA-Z0-9_]+ +[0-9]+$" "${temp_out}"; then + echo "File mode output validation failed." + err=1 + else + echo "File mode test passed." + fi + + # Test with a comm argument + if ! perf script syscall-counts -i "${temp_data}" "sleep" > "${temp_out}"; then + echo "Comm filter test failed." + err=1 + elif ! grep -E -q "^[a-zA-Z0-9_]+ +[0-9]+$" "${temp_out}"; then + echo "Comm filter output validation failed." + err=1 + else + echo "Comm filter test passed." + fi +} + +test_file_mode + +exit $err diff --git a/tools/perf/tests/shell/test_task_analyzer.sh b/tools/perf/tests/shell/test_task_analyzer.sh index 0314412e63b4..f53fa4a27c79 100755 --- a/tools/perf/tests/shell/test_task_analyzer.sh +++ b/tools/perf/tests/shell/test_task_analyzer.sh @@ -1,21 +1,16 @@ #!/bin/bash -# perf script task-analyzer tests (exclusive) # SPDX-License-Identifier: GPL-2.0 +# task-analyzer python test + +# shellcheck source=lib/setup_python.sh +. "$(dirname "$0")"/lib/setup_python.sh tmpdir=$(mktemp -d /tmp/perf-script-task-analyzer-XXXXX) -# TODO: perf script report only supports input from the CWD perf.data file, make -# it support input from any file. -perfdata="perf.data" +perfdata="$tmpdir/perf.data" csv="$tmpdir/csv" csvsummary="$tmpdir/csvsummary" err=0 -# set PERF_EXEC_PATH to find scripts in the source directory -perfdir=$(dirname "$0")/../.. -if [ -e "$perfdir/scripts/python/Perf-Trace-Util" ]; then - export PERF_EXEC_PATH=$perfdir -fi - # Disable lsan to avoid warnings about python memory leaks. export ASAN_OPTIONS=detect_leaks=0 @@ -65,7 +60,8 @@ skip_no_probe_record_support() { prepare_perf_data() { # 1s should be sufficient to catch at least some switches - perf record -e sched:sched_switch -a -o "${perfdata}" -- sleep 1 > /dev/null 2>&1 + perf record -B -N --no-bpf-event -e sched:sched_switch -a -o "${perfdata}" \ + -- sleep 1 > /dev/null 2>&1 # check if perf data file got created in above step. if [ ! -e "${perfdata}" ]; then printf "FAIL: perf record failed to create \"${perfdata}\" \n" @@ -76,85 +72,106 @@ prepare_perf_data() { # check standard inkvokation with no arguments test_basic() { out="$tmpdir/perf.out" - perf script report task-analyzer > "$out" - check_exec_0 "perf script report task-analyzer" + perf script -i "${perfdata}" task-analyzer > "$out" + check_exec_0 "perf script -i ${perfdata} task-analyzer" find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}" } test_ns_rename(){ out="$tmpdir/perf.out" - perf script report task-analyzer --ns --rename-comms-by-tids 0:random > "$out" - check_exec_0 "perf script report task-analyzer --ns --rename-comms-by-tids 0:random" + perf script -i "${perfdata}" task-analyzer \ + --ns \ + --rename-comms-by-tids 0:random > "$out" + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --ns \ + --rename-comms-by-tids 0:random" find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}" } test_ms_filtertasks_highlight(){ out="$tmpdir/perf.out" - perf script report task-analyzer --ms --filter-tasks perf --highlight-tasks perf \ + perf script -i "${perfdata}" task-analyzer \ + --ms --filter-tasks perf --highlight-tasks perf \ > "$out" - check_exec_0 "perf script report task-analyzer --ms --filter-tasks perf --highlight-tasks perf" + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --ms --filter-tasks perf --highlight-tasks perf" find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}" } test_extended_times_timelimit_limittasks() { out="$tmpdir/perf.out" - perf script report task-analyzer --extended-times --time-limit :99999 \ + perf script -i "${perfdata}" task-analyzer --extended-times \ + --time-limit :99999 \ --limit-to-tasks perf > "$out" - check_exec_0 "perf script report task-analyzer --extended-times --time-limit :99999 --limit-to-tasks perf" + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --extended-times \ + --time-limit :99999 --limit-to-tasks perf" find_str_or_fail "Out-Out" "$out" "${FUNCNAME[0]}" } test_summary() { out="$tmpdir/perf.out" - perf script report task-analyzer --summary > "$out" - check_exec_0 "perf script report task-analyzer --summary" + perf script -i "${perfdata}" task-analyzer --summary > "$out" + check_exec_0 "perf script -i ${perfdata} task-analyzer --summary" find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}" } test_summaryextended() { out="$tmpdir/perf.out" - perf script report task-analyzer --summary-extended > "$out" - check_exec_0 "perf script report task-analyzer --summary-extended" + perf script -i "${perfdata}" task-analyzer --summary-extended > "$out" + check_exec_0 "perf script -i ${perfdata} task-analyzer --summary-extended" find_str_or_fail "Inter Task Times" "$out" "${FUNCNAME[0]}" } test_summaryonly() { out="$tmpdir/perf.out" - perf script report task-analyzer --summary-only > "$out" - check_exec_0 "perf script report task-analyzer --summary-only" + perf script -i "${perfdata}" task-analyzer --summary-only > "$out" + check_exec_0 "perf script -i ${perfdata} task-analyzer --summary-only" find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}" } test_extended_times_summary_ns() { out="$tmpdir/perf.out" - perf script report task-analyzer --extended-times --summary --ns > "$out" - check_exec_0 "perf script report task-analyzer --extended-times --summary --ns" + perf script -i "${perfdata}" task-analyzer --extended-times \ + --summary --ns > "$out" + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --extended-times \ + --summary \ + --ns" find_str_or_fail "Out-Out" "$out" "${FUNCNAME[0]}" find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}" } test_csv() { - perf script report task-analyzer --csv "${csv}" > /dev/null - check_exec_0 "perf script report task-analyzer --csv ${csv}" + perf script -i "${perfdata}" task-analyzer --csv "${csv}" > /dev/null + check_exec_0 "perf script -i ${perfdata} task-analyzer --csv ${csv}" find_str_or_fail "Comm;" "${csv}" "${FUNCNAME[0]}" } test_csv_extended_times() { - perf script report task-analyzer --csv "${csv}" --extended-times > /dev/null - check_exec_0 "perf script report task-analyzer --csv ${csv} --extended-times" + perf script -i "${perfdata}" task-analyzer \ + --csv "${csv}" \ + --extended-times > /dev/null + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --csv ${csv} \ + --extended-times" find_str_or_fail "Out-Out;" "${csv}" "${FUNCNAME[0]}" } test_csvsummary() { - perf script report task-analyzer --csv-summary "${csvsummary}" > /dev/null - check_exec_0 "perf script report task-analyzer --csv-summary ${csvsummary}" + perf script -i "${perfdata}" task-analyzer \ + --csv-summary "${csvsummary}" > /dev/null + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --csv-summary ${csvsummary}" find_str_or_fail "Comm;" "${csvsummary}" "${FUNCNAME[0]}" } test_csvsummary_extended() { - perf script report task-analyzer --csv-summary "${csvsummary}" --summary-extended \ + perf script -i "${perfdata}" task-analyzer \ + --csv-summary "${csvsummary}" --summary-extended \ >/dev/null - check_exec_0 "perf script report task-analyzer --csv-summary ${csvsummary} --summary-extended" + check_exec_0 "perf script -i ${perfdata} task-analyzer \ + --csv-summary ${csvsummary} --summary-extended" find_str_or_fail "Out-Out;" "${csvsummary}" "${FUNCNAME[0]}" } @@ -165,7 +182,11 @@ if [ $err -ne 0 ]; then cleanup exit $err fi -prepare_perf_data +prepare_perf_data || { + echo "Skipping tests, failed to prepare perf.data" + cleanup + exit 2 +} test_basic test_ns_rename test_ms_filtertasks_highlight diff --git a/tools/perf/tests/shell/test_test_junit_output.sh b/tools/perf/tests/shell/test_test_junit_output.sh index 5104ac1e1e6d..8fc92e083c34 100755 --- a/tools/perf/tests/shell/test_test_junit_output.sh +++ b/tools/perf/tests/shell/test_test_junit_output.sh @@ -1,13 +1,15 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -# perf test junit XML output validation +# perf test junit XML output validation (no_mlock) +# +# Waiving the mlock requisite this way holds only while the 'perf test' +# runs below keep picking tests that don't map ring buffers. set -e err=0 shelldir=$(dirname "$0") -# shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh result=$(mktemp /tmp/__perf_test.output.xml.XXXXX) diff --git a/tools/perf/tests/shell/test_treport_python.sh b/tools/perf/tests/shell/test_treport_python.sh new file mode 100755 index 000000000000..155993b8be79 --- /dev/null +++ b/tools/perf/tests/shell/test_treport_python.sh @@ -0,0 +1,247 @@ +#!/bin/bash +# perf script treport test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +if ! "$PYTHON" -c 'import textual' > /dev/null 2>&1; then + echo "Skipping test, python textual library not found" + exit 2 +fi + +err=0 +perfdata= +output= +badfile= + +cleanup() { + [ -n "${perfdata}" ] && rm -f "${perfdata}"* + rm -f "${output}" "${badfile}" + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +perfdata=$(mktemp /tmp/__perf_treport_test.perf.data.XXXXX) +output=$(mktemp /tmp/__perf_treport_test.output.XXXXX) +badfile=$(mktemp /tmp/__perf_treport_test.bad.XXXXX) + +test_plumbing() { + echo "treport plumbing test" + # A missing input file should be reported by the script launched + # through perf script, this doesn't need a terminal. + if perf script treport -i "${perfdata}.missing" > "${output}" 2>&1; then + echo "treport plumbing test [Failed: missing file not an error]" + err=1 + return + fi + if ! grep -q "not found" "${output}"; then + echo "treport plumbing test [Failed: script not launched]" + cat "${output}" + err=1 + return + fi + echo "treport plumbing test [Success]" +} + +test_bad_file() { + echo "treport bad file test" + echo "not a perf.data file" > "${badfile}" + # The app starts before the file is read, so the error is reported by + # the app exiting with a failure. + if ! "$PYTHON" - "${badfile}" > "${output}" 2>&1 <<'EOF' +import asyncio +import sys +import treport + +async def run() -> None: + app = treport.ReportApp(sys.argv[1]) + async with app.run_test(size=(120, 40)) as pilot: + for _ in range(100): + if app.return_code is not None: + break + await pilot.pause(0.1) + if app.return_code != 1: + raise RuntimeError(f"Unexpected return code {app.return_code}") + print("bad file ok") + +asyncio.run(run()) +EOF + then + echo "treport bad file test [Failed: exception]" + cat "${output}" + err=1 + return + fi + if ! grep -q "bad file ok" "${output}"; then + echo "treport bad file test [Failed: unexpected output]" + cat "${output}" + err=1 + return + fi + echo "treport bad file test [Success]" +} + +record() { + # A software event with callchains that doesn't need privileges. + perf record -e task-clock -g -o "${perfdata}" -- perf test -w noploop \ + > /dev/null 2>&1 +} + +test_headless_ui() { + echo "treport headless UI test" + # Drive the textual app without a terminal, check the profile shown + # matches one built without the UI and exercise the key bindings. + if ! "$PYTHON" - "${perfdata}" > "${output}" 2>&1 <<'EOF' +import asyncio +import sys +import treport + +def labels(tree, tnode, out): + for child in tnode.children: + out.append(str(child.label)) + if child.is_expanded: + labels(tree, child, out) + return out + +async def run() -> None: + expected = treport.ProfileBuilder() + expected.read(sys.argv[1]) + if not expected.root.children: + raise RuntimeError("No samples in the profile") + + # The app starts before the profile is built in a background thread. + app = treport.ReportApp(sys.argv[1]) + async with app.run_test(size=(120, 40)) as pilot: + for _ in range(600): + if app.loaded: + break + await pilot.pause(0.1) + if not app.loaded: + raise RuntimeError("Timed out loading data") + await pilot.pause() + if app.sub_title.startswith("Loading") or "samples" not in app.sub_title: + raise RuntimeError(f"Unexpected sub-title: {app.sub_title}") + totals = {name: node.value for name, node in app.root.children.items()} + want = {name: node.value for name, node in expected.root.children.items()} + if totals != want: + raise RuntimeError(f"Profile {totals} differs from {want}") + tree = app.query_one(treport.ProfileTree) + shown = labels(tree, tree.root, []) + if not any("noploop" in label for label in shown): + raise RuntimeError(f"noploop not shown in: {shown}") + await pilot.press("down", "down", "enter", "up", "enter") + tabs = app.query_one(treport.TabbedContent) + tabs.active = "flame" + await pilot.pause() + await pilot.press("down", "down", "right", "enter", "escape", "up", "left") + await pilot.pause() + print("headless UI ok") + +asyncio.run(run()) +EOF + then + echo "treport headless UI test [Failed: exception]" + cat "${output}" + err=1 + return + fi + if ! grep -q "headless UI ok" "${output}"; then + echo "treport headless UI test [Failed: unexpected output]" + cat "${output}" + err=1 + return + fi + echo "treport headless UI test [Success]" +} + +test_cancel() { + echo "treport cancel test" + # Quitting while loading should stop the background thread without a + # "processing failed" error from the session. + if ! "$PYTHON" - "${perfdata}" > "${output}" 2>&1 <<'EOF' +import asyncio +import sys +import time +import treport + +# Check for cancellation often and slow processing each sample so that the +# load can't finish before it is cancelled. +treport.ProfileBuilder.PROGRESS_INTERVAL = 1 +process_event = treport.ProfileBuilder.process_event + +def slow_process_event(self, sample) -> None: + time.sleep(0.001) + process_event(self, sample) + +treport.ProfileBuilder.process_event = slow_process_event + +async def run() -> None: + app = treport.ReportApp(sys.argv[1]) + async with app.run_test(size=(120, 40)) as pilot: + for _ in range(100): + loading = app.loading + if loading is not None and loading.nr_samples > 0: + break + await pilot.pause(0.1) + if loading is None or app.loaded: + raise RuntimeError("Background load not in progress") + await pilot.press("ctrl+q") + for _ in range(100): + if app.loading is None: + break + time.sleep(0.1) + if app.loading is not None: + raise RuntimeError("Background load didn't stop") + if not loading.cancelled or app.loaded: + raise RuntimeError("Background load wasn't cancelled") + print("cancel ok") + +asyncio.run(run()) +EOF + then + echo "treport cancel test [Failed: exception]" + cat "${output}" + err=1 + return + fi + if ! grep -q "cancel ok" "${output}" || grep -q "processing failed" "${output}"; then + echo "treport cancel test [Failed: unexpected output]" + cat "${output}" + err=1 + return + fi + echo "treport cancel test [Success]" +} + +test_plumbing +test_bad_file + +if ! record; then + echo "Skipping remaining tests, failed to record samples" + if [ $err -eq 0 ]; then + err=2 + fi + cleanup + exit $err +fi + +test_headless_ui +test_cancel + +cleanup +exit $err diff --git a/tools/perf/tests/shell/test_ttimechart_python.sh b/tools/perf/tests/shell/test_ttimechart_python.sh new file mode 100755 index 000000000000..87b91ed8fc16 --- /dev/null +++ b/tools/perf/tests/shell/test_ttimechart_python.sh @@ -0,0 +1,237 @@ +#!/bin/bash +# perf timechart --tui (ttimechart.py) test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! perf check feature -q libtraceevent; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +if ! "$PYTHON" -c 'import textual' > /dev/null 2>&1; then + echo "Skipping test, python textual library not found" + exit 2 +fi + +err=0 +perfdata= +output= +clockdata= + +cleanup() { + [ -n "${perfdata}" ] && rm -f "${perfdata}"* + [ -n "${clockdata}" ] && rm -f "${clockdata}"* + rm -f "${output}" + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +perfdata=$(mktemp /tmp/__perf_ttimechart_test.perf.data.XXXXX) +output=$(mktemp /tmp/__perf_ttimechart_test.output.XXXXX) +clockdata=$(mktemp /tmp/__perf_ttimechart_test.clock.data.XXXXX) + +workload() { + for _ in 1 2 3 4 5; do + sleep 0.01 + done +} + +record() { + # perf timechart record is system wide and so may need privileges, + # fall back to recording just the workload's scheduler events. + if perf timechart record -o "${perfdata}" -- \ + bash -c "$(declare -f workload); workload" > /dev/null 2>&1; then + return 0 + fi + if perf record -R -c 1 -e sched:sched_switch -e sched:sched_wakeup \ + -o "${perfdata}" -- bash -c "$(declare -f workload); workload" \ + > /dev/null 2>&1; then + return 0 + fi + return 1 +} + +test_plumbing() { + echo "perf timechart --tui plumbing test" + # A missing input file should be reported by the script launched + # through perf script, this doesn't need a terminal. + if perf timechart --tui -i "${perfdata}.missing" > "${output}" 2>&1; then + echo "perf timechart --tui plumbing test [Failed: missing file not an error]" + err=1 + return + fi + if ! grep -q "not found" "${output}"; then + echo "perf timechart --tui plumbing test [Failed: script not launched]" + cat "${output}" + err=1 + return + fi + echo "perf timechart --tui plumbing test [Success]" +} + +test_no_events() { + echo "ttimechart no events test" + if ! perf record -e task-clock -o "${clockdata}" -- true > /dev/null 2>&1; then + echo "ttimechart no events test [Skipped: perf record failed]" + return + fi + if perf script ttimechart -i "${clockdata}" --dump > "${output}" 2>&1; then + echo "ttimechart no events test [Failed: expected an error]" + err=1 + return + fi + if ! grep -q "no scheduler, power or I/O events" "${output}"; then + echo "ttimechart no events test [Failed: unexpected error message]" + cat "${output}" + err=1 + return + fi + # The same should be reported when reading the data from a pipe. + if perf record -e task-clock -o - -- true 2> /dev/null | \ + perf script ttimechart -i - --dump > "${output}" 2>&1; then + echo "ttimechart no events test [Failed: expected an error from a pipe]" + err=1 + return + fi + if ! grep -q "no scheduler, power or I/O events" "${output}"; then + echo "ttimechart no events test [Failed: unexpected error message from a pipe]" + cat "${output}" + err=1 + return + fi + echo "ttimechart no events test [Success]" +} + +test_dump() { + echo "ttimechart dump test" + if ! perf script ttimechart -i "${perfdata}" --dump > "${output}" 2>&1; then + echo "ttimechart dump test [Failed: script failed]" + cat "${output}" + err=1 + return + fi + if ! grep -q "^Duration: .*sched events: [1-9]" "${output}" || + ! grep -q "^CPU [0-9]*: busy" "${output}" || + ! grep -q "^Task .*Running .*Waiting .*Blocked" "${output}" || + ! grep -q "sleep" "${output}"; then + echo "ttimechart dump test [Failed: unexpected output]" + cat "${output}" + err=1 + return + fi + + # Tasks only shouldn't show CPUs, power only shouldn't show tasks. + perf script ttimechart -i "${perfdata}" --dump -T > "${output}" 2>&1 || true + if grep -q "^CPU [0-9]*: " "${output}" || + ! grep -q "^Task .*Running .*Waiting .*Blocked" "${output}"; then + echo "ttimechart dump test [Failed: -T output]" + cat "${output}" + err=1 + return + fi + perf script ttimechart -i "${perfdata}" --dump -P > "${output}" 2>&1 || true + if grep -q "^Task .*Running .*Waiting .*Blocked" "${output}" || + ! grep -q "^CPU [0-9]*: " "${output}"; then + echo "ttimechart dump test [Failed: -P output]" + cat "${output}" + err=1 + return + fi + + # Filtering on the workload shouldn't show other tasks. + perf script ttimechart -i "${perfdata}" --dump -p sleep > "${output}" 2>&1 || true + if ! grep -q "^sleep " "${output}" || grep -q "^bash " "${output}"; then + echo "ttimechart dump test [Failed: -p output]" + cat "${output}" + err=1 + return + fi + echo "ttimechart dump test [Success]" +} + +test_headless_ui() { + echo "ttimechart headless UI test" + # Drive the textual app without a terminal, exercising the views and + # key bindings. + if ! "$PYTHON" - "${perfdata}" > "${output}" 2>&1 <<'EOF' +import asyncio +import sys +import ttimechart as tc + +async def run() -> None: + # The app starts before the data is loaded in a background thread. + app = tc.TimechartApp(sys.argv[1], False, False, []) + async with app.run_test(size=(120, 40)) as pilot: + for _ in range(600): + if app.loaded: + break + await pilot.pause(0.1) + if not app.loaded: + raise RuntimeError("Timed out loading data") + await pilot.pause() + tabs = app.query_one(tc.TabbedContent) + for tab in ["cpus", "tasks", "summary"]: + tabs.active = tab + await pilot.pause() + await pilot.press("down", "plus", "plus", "right", "n", "p", "minus", + "shift+right", "shift+left", "s", "w", "0") + await pilot.pause() + tabs.active = "summary" + await pilot.pause() + await pilot.press("enter") + await pilot.pause() + details = str(app.query_one("#details", tc.Static).render()) + if "Cursor" not in details: + raise RuntimeError(f"Unexpected details: {details}") + print("headless UI ok") + +asyncio.run(run()) +EOF + then + echo "ttimechart headless UI test [Failed: exception]" + cat "${output}" + err=1 + return + fi + if ! grep -q "headless UI ok" "${output}"; then + echo "ttimechart headless UI test [Failed: unexpected output]" + cat "${output}" + err=1 + return + fi + echo "ttimechart headless UI test [Success]" +} + +test_plumbing +test_no_events + +if ! record; then + echo "Skipping remaining tests, failed to record scheduler events (permissions?)" + if [ $err -eq 0 ]; then + err=2 + fi + cleanup + exit $err +fi + +test_dump +test_headless_ui + +cleanup +exit $err diff --git a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh index 7adf9755d6de..0a02ff9b86d6 100755 --- a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh +++ b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh @@ -1,10 +1,9 @@ #!/bin/bash -# test perf probe of function from different CU +# test perf probe of function from different CU (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh skip_if_no_perf_probe || exit 2 @@ -18,12 +17,15 @@ fi temp_dir=$(mktemp -d /tmp/perf-uprobe-different-cu-sh.XXXXXXXXXX) +# Scope to the pid, otherwise every run adds the same probe_testfile:foo. +probe_name="foo_$$" + cleanup() { trap - EXIT TERM INT if [[ "${temp_dir}" =~ ^/tmp/perf-uprobe-different-cu-sh.*$ ]]; then echo "--- Cleaning up ---" - perf probe -x ${temp_dir}/testfile -d foo || true + perf probe -x ${temp_dir}/testfile -d ${probe_name} || true rm -f "${temp_dir}/"* rmdir "${temp_dir}" fi @@ -84,6 +86,6 @@ gcc -g -Og -c ${temp_dir}/testfile-main.c -o ${temp_dir}/testfile-main.o gcc -g -Og -o ${temp_dir}/testfile ${temp_dir}/testfile-foo.o ${temp_dir}/testfile-main.o perf probe -x ${temp_dir}/testfile --funcs foo | grep "foo" -perf probe -x ${temp_dir}/testfile foo +perf probe -x ${temp_dir}/testfile ${probe_name}=foo cleanup diff --git a/tools/perf/tests/shell/test_wakeup_latency_python.sh b/tools/perf/tests/shell/test_wakeup_latency_python.sh new file mode 100755 index 000000000000..b43a86996be2 --- /dev/null +++ b/tools/perf/tests/shell/test_wakeup_latency_python.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# wakeup-latency python test + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then + echo "Skipping test, perf python module not found" + exit 2 +fi + +if ! perf check feature -q libtraceevent; then + echo "Skipping test, libtraceevent is disabled" + exit 2 +fi + +script_dir="$(dirname "$0")/../../python" +script_path="${script_dir}/wakeup-latency.py" + +if [ ! -f "$script_path" ]; then + echo "Skipping test, wakeup-latency.py not found at $script_path" + exit 2 +fi + +err=0 +temp_data="" +temp_out="" + +cleanup() { + rm -f "${temp_data}" "${temp_out}" +} +trap 'cleanup' EXIT TERM INT + +temp_data=$(mktemp /tmp/perf.data.XXXXXX) +temp_out=$(mktemp /tmp/perf.out.XXXXXX) + +echo "Testing wakeup-latency.py..." + +# Create a perf.data file. Try to get tracepoint data. +if perf list tracepoint | grep -q "sched:sched_wakeup"; then + ev="sched:sched_wakeup,sched:sched_wakeup_new,sched:sched_switch" + perf record -B -N --no-bpf-event -e "$ev" -a -o "${temp_data}" \ + -- sleep 0.1 >/dev/null 2>&1 || \ + { echo "Skipping test, perf record failed"; exit 2; } +else + echo "Skipping test, no sched:sched_wakeup event" + exit 2 +fi + +if [ ! -s "${temp_data}" ]; then + echo "Skipping test, perf record failed to create data" + exit 2 +fi + +# Check that the script executes +if ! perf script wakeup-latency -i "${temp_data}" > "${temp_out}"; then + echo "wakeup-latency.py test failed" + err=1 +else + if ! grep -E -q "avg_wakeup_latency.*[0-9]+" "${temp_out}"; then + echo "Failed to find metric data rows" + err=1 + else + echo "wakeup-latency test passed." + fi +fi + +# Also test zero-wakeups / unhandled events path to verify division-by-zero protection +if perf record -B -N --no-bpf-event -e cycles -o "${temp_data}" \ + -- perf test -w noploop >/dev/null 2>&1; then + if ! perf script wakeup-latency -i "${temp_data}" > "${temp_out}" || \ + ! grep -q "avg_wakeup_latency (ns): N/A" "${temp_out}"; then + echo "wakeup-latency zero-wakeups guard test failed" + err=1 + fi +fi +rm -f "${temp_out}" + +exit $err diff --git a/tools/perf/tests/shell/top.sh b/tools/perf/tests/shell/top.sh index ad7fccd09025..3d52677ccb5f 100755 --- a/tools/perf/tests/shell/top.sh +++ b/tools/perf/tests/shell/top.sh @@ -35,21 +35,22 @@ test_basic_perf_top() { # Use -d 1 to avoid flooding output # Use -e cpu-clock to ensure we get samples # Use sleep to keep stdin open but silent, preventing EOF loop or interactive spam - if ! sleep 10 | timeout 5s perf top --stdio -d 1 -e cpu-clock -p $PID > "${log_file}" 2>&1; then - retval=$? - if [ $retval -ne 124 ] && [ $retval -ne 0 ]; then - echo "Basic perf top test [Failed: perf top failed to start or run (ret=$retval)]" - head -n 50 "${log_file}" - kill $PID - wait $PID 2>/dev/null || true - err=1 - return - fi + retval=0 + sleep 10 | timeout 5s perf top --stdio -d 1 -e cpu-clock \ + -p $PID > "${log_file}" 2>&1 || retval=$? + if [ "${retval:-0}" -ne 124 ] && [ "${retval:-0}" -ne 0 ]; then + echo "Basic perf top test [Failed: perf top failed to start or run (ret=$retval)]" + head -n 50 "${log_file}" + kill $PID 2>/dev/null || true + wait $PID 2>/dev/null || true + err=1 + return fi - kill $PID + kill $PID 2>/dev/null || true wait $PID 2>/dev/null || true + # Check for some sample data (percentage) if ! grep -E -q "[0-9]+\.[0-9]+%" "${log_file}"; then echo "Basic perf top test [Failed: no sample percentage found]" @@ -69,6 +70,56 @@ test_basic_perf_top() { echo "Basic perf top test [Success]" } +test_hybrid_merge_perf_top() { + echo "Perf top hybrid merge test" + + perf test -w thloop 20 & + PID=$! + + # Allow it to start + sleep 0.1 + + # Run without explicitly requesting -e cycles so heavily virtualized + # environments can seamlessly fall back to cpu-clock while real + # hybrid hardware will naturally cover the merge logic. + retval=0 + sleep 10 | timeout 5s perf top \ + --stdio --hybrid-merge -d 1 -p $PID > "${log_file}" 2>&1 || retval=$? + if [ "${retval:-0}" -ne 124 ] && [ "${retval:-0}" -ne 0 ]; then + echo "Perf top hybrid merge test [Failed: run err=$retval]" + head -n 50 "${log_file}" + kill $PID 2>/dev/null || true + wait $PID 2>/dev/null || true + err=1 + return + fi + + kill $PID 2>/dev/null || true + wait $PID 2>/dev/null || true + + # Wait a tiny bit for the file system to catch up on the logs + sleep 0.1 + + # Check for some sample data (percentage) + if ! grep -E -q "[0-9]+\.[0-9]+%" "${log_file}"; then + echo "Perf top hybrid merge test [Failed: no sample percentage found]" + head -n 50 "${log_file}" + err=1 + return + fi + + # Check for the test loop symbol to ensure attribution worked + if ! grep -q "test_loop" "${log_file}"; then + echo "Perf top hybrid merge test [Failed: test_loop symbol not found]" + head -n 50 "${log_file}" + err=1 + return + fi + + echo "Perf top hybrid merge test [Success]" +} + test_basic_perf_top +test_hybrid_merge_perf_top cleanup exit $err diff --git a/tools/perf/tests/shell/trace+probe_vfs_getname.sh b/tools/perf/tests/shell/trace+probe_vfs_getname.sh index 7a0b1145d0cd..ab13801d9e99 100755 --- a/tools/perf/tests/shell/trace+probe_vfs_getname.sh +++ b/tools/perf/tests/shell/trace+probe_vfs_getname.sh @@ -10,13 +10,17 @@ # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo , 2017 -# shellcheck source=lib/probe.sh +# Exclusive as, without BPF, any perf trace opens all probe:vfs_getname* events +# and so would pin this test's probe. + . "$(dirname $0)"/lib/probe.sh skip_if_no_perf_probe || exit 2 skip_if_no_perf_trace || exit 2 [ "$(id -u)" = 0 ] || exit 2 +# shellcheck disable=SC2034 # consumed by lib/probe_vfs_getname.sh +vfs_getname="vfs_getname_$$" . "$(dirname $0)"/lib/probe_vfs_getname.sh trace_open_vfs_getname() { diff --git a/tools/perf/tests/shell/trace_btf_enum.sh b/tools/perf/tests/shell/trace_btf_enum.sh index 03e9f680a4a6..b1a659e0245f 100755 --- a/tools/perf/tests/shell/trace_btf_enum.sh +++ b/tools/perf/tests/shell/trace_btf_enum.sh @@ -9,7 +9,6 @@ non_syscall="timer:hrtimer_start" TESTPROG="perf test -w landlock" -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh skip_if_no_perf_trace || exit 2 [ "$(id -u)" = 0 ] || exit 2 diff --git a/tools/perf/tests/shell/trace_btf_general.sh b/tools/perf/tests/shell/trace_btf_general.sh index 7a94a5743924..d1fa7c846478 100755 --- a/tools/perf/tests/shell/trace_btf_general.sh +++ b/tools/perf/tests/shell/trace_btf_general.sh @@ -1,10 +1,9 @@ #!/bin/bash -# perf trace BTF general tests (exclusive) +# perf trace BTF general tests # SPDX-License-Identifier: GPL-2.0 err=0 -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh file1=$(mktemp /tmp/file1_XXXX) @@ -27,7 +26,7 @@ check_vmlinux() { trace_test_string() { echo "Testing perf trace's string augmentation" - output="$(perf trace --sort-events -e renameat* --max-events=1 -- mv ${file1} ${file2} 2>&1)" + output="$(perf trace --sort-events -e renameat* -- mv ${file1} ${file2} 2>&1)" if ! echo "$output" | grep -q -E "^mv/[0-9]+ renameat(2)?\(.*, \"${file1}\", .*, \"${file2}\", .*\) += +[0-9]+$" then printf "String augmentation test failed, output:\n$output\n" @@ -38,7 +37,7 @@ trace_test_string() { trace_test_buffer() { echo "Testing perf trace's buffer augmentation" # echo will insert a newline (\10) at the end of the buffer - output="$(perf trace --sort-events -e write --max-events=1 -- echo "${buffer}" 2>&1)" + output="$(perf trace --sort-events -e write -- echo "${buffer}" 2>&1)" if ! echo "$output" | grep -q -E "^echo/[0-9]+ write\([0-9]+, ${buffer}.*, [0-9]+\) += +[0-9]+$" then printf "Buffer augmentation test failed, output:\n$output\n" @@ -48,7 +47,7 @@ trace_test_buffer() { trace_test_struct_btf() { echo "Testing perf trace's struct augmentation" - output="$(perf trace --sort-events -e clock_nanosleep --force-btf --max-events=1 -- sleep 1 2>&1)" + output="$(perf trace --sort-events -e clock_nanosleep --force-btf -- sleep 1 2>&1)" if ! echo "$output" | grep -q -E "^sleep/[0-9]+ clock_nanosleep\(0, 0, \{1,.*\}, 0x[0-9a-f]+\) += +[0-9]+$" then printf "BTF struct augmentation test failed, output:\n$output\n" diff --git a/tools/perf/tests/shell/trace_exit_race.sh b/tools/perf/tests/shell/trace_exit_race.sh index db300cde94fb..024ff8f1d388 100755 --- a/tools/perf/tests/shell/trace_exit_race.sh +++ b/tools/perf/tests/shell/trace_exit_race.sh @@ -6,7 +6,6 @@ # lost. Specifically, trace the exiting syscall of "true" 10 times and ensure # the output contains 10 correct lines. -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh skip_if_no_perf_trace || exit 2 diff --git a/tools/perf/tests/shell/trace_ksym_beautifier.sh b/tools/perf/tests/shell/trace_ksym_beautifier.sh new file mode 100755 index 000000000000..7c441f8551a2 --- /dev/null +++ b/tools/perf/tests/shell/trace_ksym_beautifier.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# perf trace kernel symbol beautifier tests +# SPDX-License-Identifier: GPL-2.0 + +err=0 + +# shellcheck source=lib/probe.sh +. "$(dirname "$0")"/lib/probe.sh +skip_if_no_perf_trace || exit 2 +[ "$(id -u)" = 0 ] || exit 2 + +test_ksym_call_site() { + echo "Testing perf trace kernel symbol beautifier (call_site)" + output="$(perf trace -e kmem:kmalloc --max-events=1 -- true 2>&1)" + if ! echo "$output" | grep -q -E "call_site: [a-zA-Z_][a-zA-Z0-9_]*" || echo "$output" | grep -q -E "call_site: 0x[0-9a-fA-F]+" + then + printf "Call site kernel symbol beautification failed, output:\n%s\n" "$output" + err=1 + fi +} + +test_ksym_function_ptr() { + echo "Testing perf trace kernel symbol beautifier (function pointer)" + output="$(perf trace -e timer:hrtimer_start --max-events=1 -- sleep 0.01 2>&1)" + if ! echo "$output" | grep -q -E "function: [a-zA-Z_][a-zA-Z0-9_]*" || echo "$output" | grep -q -E "function: 0x[0-9a-fA-F]+" + then + printf "Function pointer kernel symbol beautification failed, output:\n%s\n" "$output" + err=1 + fi +} + +test_ksym_call_site + +if [ $err = 0 ]; then + test_ksym_function_ptr +fi + +exit $err diff --git a/tools/perf/tests/shell/trace_record_replay.sh b/tools/perf/tests/shell/trace_record_replay.sh index 38fcafcdfb91..76a7bba6ef23 100755 --- a/tools/perf/tests/shell/trace_record_replay.sh +++ b/tools/perf/tests/shell/trace_record_replay.sh @@ -4,9 +4,7 @@ # Check that perf trace works with record and replay -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh -# shellcheck source=lib/perf_record.sh . "$(dirname $0)"/lib/perf_record.sh skip_if_no_perf_trace || exit 2 diff --git a/tools/perf/tests/shell/trace_summary.sh b/tools/perf/tests/shell/trace_summary.sh index b80dea77cec6..0e603addb2a9 100755 --- a/tools/perf/tests/shell/trace_summary.sh +++ b/tools/perf/tests/shell/trace_summary.sh @@ -1,10 +1,9 @@ #!/bin/bash -# perf trace summary (exclusive) +# perf trace summary # SPDX-License-Identifier: GPL-2.0 # Check that perf trace works with various summary mode -# shellcheck source=lib/probe.sh . "$(dirname $0)"/lib/probe.sh skip_if_no_perf_trace || exit 2 @@ -28,10 +27,15 @@ test_perf_trace() { count=$(grep -E -c -m 3 "${search}" ${OUTPUT}) if [ "${count}" != "3" ]; then - echo "Error: cannot find enough pattern ${search} in the output" - cat ${OUTPUT} - rm -f ${OUTPUT} - exit 1 + echo "Error: cannot find enough pattern ${search} (count=${count}) in output of:" + echo "Error: perf trace ${args} -- ${workload}" + echo "Error: matched lines:" + grep -E "${search}" ${OUTPUT} || echo "none" + echo "Error: last 20 lines of output:" + # The summary comes last, after any -S trace output. + tail -n 20 ${OUTPUT} + rm -f ${OUTPUT} + exit 1 fi } diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c index d18185881635..1b62dae55f07 100644 --- a/tools/perf/tests/sw-clock.c +++ b/tools/perf/tests/sw-clock.c @@ -149,4 +149,4 @@ static int test__sw_clock_freq(struct test_suite *test __maybe_unused, int subte return ret; } -DEFINE_SUITE("Software clock events period values", sw_clock_freq); +DEFINE_SUITE_NEEDS_MLOCK("Software clock events period values", sw_clock_freq); diff --git a/tools/perf/tests/symbols.c b/tools/perf/tests/symbols.c index c09e04f36035..b644d95c6be9 100644 --- a/tools/perf/tests/symbols.c +++ b/tools/perf/tests/symbols.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "debug.h" #include "dso.h" @@ -223,4 +224,97 @@ static int test__symbols(struct test_suite *test __maybe_unused, int subtest __m return ret; } -DEFINE_SUITE("Symbols", symbols); +struct kallsyms_sym { + u64 start; + const char *name; +}; + +/* + * kallsyms from an x86 host where nf_tables was loaded in the page right + * after the last symbol of dca, plus a kernel symbol followed closely by a + * module and a module far away from the others. + */ +static const struct kallsyms_sym kallsyms_syms[] = { + { 0xffffffff81000000, "_stext" }, + { 0xffffffff81000ff0, "last_kernel_symbol" }, + { 0xffffffff81001000, "near_module_symbol\t[near]" }, + { 0xffffffffc0c4aa40, "dca_exit\t[dca]" }, + { 0xffffffffc0c4aa50, "dca_sysfs_exit\t[dca]" }, + { 0xffffffffc0c4b000, "__nft_trace_packet\t[nf_tables]" }, + { 0xffffffffc0c4b0b0, "nft_do_chain\t[nf_tables]" }, + { 0xffffffffc0c4b4e0, "nf_tables_core_module_exit\t[nf_tables]" }, + { 0xffffffffc2000000, "far_module_symbol\t[far]" }, +}; + +static int check_symbol(struct dso *dso, u64 addr, const char *name, u64 end) +{ + struct symbol *sym = dso__find_symbol_nocache(dso, addr); + + if (!sym || strcmp(sym->name, name)) { + pr_debug("%#" PRIx64 ": expected %s, got %s\n", addr, name, + sym ? sym->name : "no symbol"); + return TEST_FAIL; + } + if (sym->end != end) { + pr_debug("%s: expected end %#" PRIx64 ", got %#" PRIx64 "\n", + name, end, sym->end); + return TEST_FAIL; + } + return TEST_OK; +} + +static int test__kallsyms_fixup_end(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct dso *dso = dso__new("[kernel.kallsyms]"); + int ret = TEST_FAIL; + + if (!dso) + return TEST_FAIL; + + for (unsigned int i = 0; i < ARRAY_SIZE(kallsyms_syms); i++) { + struct symbol *sym = symbol__new(kallsyms_syms[i].start, 0, 0, 0, + kallsyms_syms[i].name); + + if (!sym) + goto out; + symbols__insert(dso__symbols(dso), sym); + } + + symbols__fixup_end(dso__symbols(dso), true); + + ret = TEST_OK; + /* The next symbol is too close for the end of the page. */ + if (check_symbol(dso, 0xffffffff81000ff8, "last_kernel_symbol", + 0xffffffff81001000)) + ret = TEST_FAIL; + if (check_symbol(dso, 0xffffffffc0c4aa58, "dca_sysfs_exit\t[dca]", + 0xffffffffc0c4b000)) + ret = TEST_FAIL; + if (check_symbol(dso, 0xffffffffc0c4b280, "nft_do_chain\t[nf_tables]", + 0xffffffffc0c4b4e0)) + ret = TEST_FAIL; + + /* Far from the next module, the end of the page is still used. */ + if (check_symbol(dso, 0xffffffffc0c4b4f0, + "nf_tables_core_module_exit\t[nf_tables]", + 0xffffffffc0c4d000)) + ret = TEST_FAIL; + if (check_symbol(dso, 0xffffffff81001008, "near_module_symbol\t[near]", + 0xffffffff81002000)) + ret = TEST_FAIL; +out: + dso__put(dso); + return ret; +} + +static struct test_case tests__symbols[] = { + TEST_CASE("Symbols", symbols), + TEST_CASE("Kallsyms symbol ends", kallsyms_fixup_end), + { .name = NULL, } +}; + +struct test_suite suite__symbols = { + .desc = "Symbols", + .test_cases = tests__symbols, +}; diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c index 95393edbfe36..9fac426ac2f9 100644 --- a/tools/perf/tests/task-exit.c +++ b/tools/perf/tests/task-exit.c @@ -158,4 +158,5 @@ struct test_case tests__task_exit[] = { struct test_suite suite__task_exit = { .desc = "Number of exit events of a simple workload", .test_cases = tests__task_exit, + .needs_mlock = true, }; diff --git a/tools/perf/tests/tests-scripts.c b/tools/perf/tests/tests-scripts.c index 0370bc701373..a86b5289f09b 100644 --- a/tools/perf/tests/tests-scripts.c +++ b/tools/perf/tests/tests-scripts.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -179,6 +180,79 @@ static int shell_test__run(struct test_suite *test, int subtest __maybe_unused) return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; } +/* + * The requisites are a comma separated list in the parens ending the + * description line, e.g. "perf stat tests (no_mlock)": "exclusive" is the + * catch-all, implying every other requisite, "no_mlock" waives the default of + * needing room in the per user budget set in kernel.perf_event_mlock_kb. + */ +static void shell_test__requisites(char *desc, bool *exclusive, bool *needs_mlock) +{ + char *lparen, *rparen, *requisites, *token, *tokp; + bool known = false; + + /* Only a list closing the description line is taken for requisites. */ + rparen = strrchr(desc, ')'); + if (rparen == NULL || rparen[1] != '\0') + return; + + lparen = strrchr(desc, '('); + if (lparen == NULL || lparen > rparen) + return; + + /* Tokenize a copy: a list with nothing recognized leaves desc alone. */ + requisites = strndup(lparen + 1, rparen - lparen - 1); + if (requisites == NULL) { + pr_err("Failed to copy the requisites of '%s'\n", desc); + return; + } + + for (token = strtok_r(requisites, ",", &tokp); token; + token = strtok_r(NULL, ",", &tokp)) { + char *end = token + strlen(token); + const char *requisite = token; + bool waived; + + while (isspace(*token)) + token++; + while (end > token && isspace(end[-1])) + end--; + *end = '\0'; + + /* A "no_" prefix waives the requisite named after it. */ + waived = strncmp(token, "no_", sizeof("no_") - 1) == 0; + if (waived) + token += sizeof("no_") - 1; + + if (!strcmp(token, "mlock")) { + *needs_mlock = !waived; + } else if (!strcmp(token, "exclusive") && !waived) { + *exclusive = true; + } else { + pr_debug("Ignoring %s requisite '%s' in '%s'\n", + strcmp(token, "exclusive") == 0 ? "a waived" : "an unknown", + requisite, desc); + continue; + } + + known = true; + } + + zfree(&requisites); + + if (!known) + return; + + /* Drop the list and the space before it from the description. */ + *lparen = '\0'; + if (lparen > desc && isspace(lparen[-1])) + lparen[-1] = '\0'; + + /* The catch-all implies every other requisite, whatever the order. */ + if (*exclusive) + *needs_mlock = true; +} + static void append_script(int dir_fd, const char *name, char *desc, struct test_suite ***result, size_t *result_sz) @@ -187,7 +261,8 @@ static void append_script(int dir_fd, const char *name, char *desc, struct test_suite *test_suite, **result_tmp; struct test_case *tests; ssize_t len; - char *exclusive; + /* Shell tests run perf, that maps ring buffers, so they need the budget. */ + bool exclusive = false, needs_mlock = true; snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd); len = readlink(link, filename, sizeof(filename) - 1); @@ -204,11 +279,8 @@ static void append_script(int dir_fd, const char *name, char *desc, return; } tests[0].name = strdup_check(name); - exclusive = strstr(desc, " (exclusive)"); - if (exclusive != NULL) { - tests[0].exclusive = true; - exclusive[0] = '\0'; - } + shell_test__requisites(desc, &exclusive, &needs_mlock); + tests[0].exclusive = exclusive; tests[0].desc = strdup_check(desc); tests[0].run_case = shell_test__run; test_suite = zalloc(sizeof(*test_suite)); @@ -220,6 +292,7 @@ static void append_script(int dir_fd, const char *name, char *desc, test_suite->desc = desc; test_suite->test_cases = tests; test_suite->priv = strdup_check(filename); + test_suite->needs_mlock = needs_mlock; /* Realloc is good enough, though we could realloc by chunks, not that * anyone will ever measure performance here */ result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp)); diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index cee9e6b62dcc..20ae3609892b 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -46,6 +46,12 @@ struct test_suite { struct test_case *test_cases; void *priv; int (*setup)(struct test_suite *suite); + /* + * Set for the suites mapping perf ring buffers, that need room in the + * per user budget set in kernel.perf_event_mlock_kb, see + * shell_test__requisites() for the shell suites. + */ + bool needs_mlock; }; #define DECLARE_SUITE(name) \ @@ -103,6 +109,17 @@ struct test_suite { .test_cases = tests__##_name, \ } +#define DEFINE_SUITE_NEEDS_MLOCK(description, _name) \ + struct test_case tests__##_name[] = { \ + TEST_CASE(description, _name), \ + { .name = NULL, } \ + }; \ + struct test_suite suite__##_name = { \ + .desc = description, \ + .test_cases = tests__##_name, \ + .needs_mlock = true, \ + } + /* Tests */ DECLARE_SUITE(vmlinux_matches_kallsyms); DECLARE_SUITE(openat_syscall_event); @@ -124,6 +141,7 @@ DECLARE_SUITE(parse_events); DECLARE_SUITE(hists_link); DECLARE_SUITE(bp_signal); DECLARE_SUITE(bp_signal_overflow); +DECLARE_SUITE(event_refresh); DECLARE_SUITE(bp_accounting); DECLARE_SUITE(wp); DECLARE_SUITE(task_exit); @@ -177,6 +195,7 @@ DECLARE_SUITE(perf_time_to_tsc); DECLARE_SUITE(dlfilter); DECLARE_SUITE(sigtrap); DECLARE_SUITE(event_groups); +DECLARE_SUITE(hybrid_merge); DECLARE_SUITE(symbols); DECLARE_SUITE(util); DECLARE_SUITE(uncore_event_sorting); @@ -249,6 +268,7 @@ DECLARE_WORKLOAD(jitdump); DECLARE_WORKLOAD(context_switch_loop); DECLARE_WORKLOAD(deterministic); DECLARE_WORKLOAD(callchain); +DECLARE_WORKLOAD(false_sharing); #ifdef HAVE_RUST_SUPPORT DECLARE_WORKLOAD(code_with_type); diff --git a/tools/perf/tests/thread-maps-share.c b/tools/perf/tests/thread-maps-share.c index 0431bff31b3a..4a0bbf715acb 100644 --- a/tools/perf/tests/thread-maps-share.c +++ b/tools/perf/tests/thread-maps-share.c @@ -27,7 +27,8 @@ static int test__thread_maps_share(struct test_suite *test __maybe_unused, int s * other group (pid: 4, tids: 4, 5) */ - TEST_ASSERT_VAL("failed to init machines", machines__init(&machines) == 0); + TEST_ASSERT_VAL("failed to init machines", + machines__init(&machines, /*session=*/NULL) == 0); machine = &machines.host; /* create process with 4 threads */ diff --git a/tools/perf/tests/tool_pmu.c b/tools/perf/tests/tool_pmu.c index c6c5ebf0e935..667160f9e2cd 100644 --- a/tools/perf/tests/tool_pmu.c +++ b/tools/perf/tests/tool_pmu.c @@ -1,8 +1,16 @@ // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +#include +#include +#include +#include +#include +#include "cpumap.h" #include "debug.h" #include "evlist.h" +#include "evsel.h" #include "parse-events.h" #include "tests.h" +#include "thread_map.h" #include "tool_pmu.h" static int do_test(enum tool_pmu_event ev, bool with_pmu) @@ -100,9 +108,80 @@ static int test__tool_pmu_with_pmu(struct test_suite *test __maybe_unused, return TEST_OK; } +static int test__tool_pmu_incremental_open_unwind(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct evlist *evlist = evlist__new(); + struct parse_events_error err; + struct perf_cpu_map *cpus = NULL; + struct perf_thread_map *threads = NULL; + struct evsel *evsel; + int ret = TEST_FAIL, fd0 = -1; + + if (!evlist) + return TEST_FAIL; + + parse_events_error__init(&err); + if (parse_events(evlist, "tool/user_time/", &err)) { + parse_events_error__exit(&err); + evlist__put(evlist); + return TEST_FAIL; + } + parse_events_error__exit(&err); + + evsel = evlist__first(evlist); + cpus = perf_cpu_map__new("0,1"); + threads = thread_map__new_by_tid(getpid()); + if (!cpus || !threads) + goto out; + + /* Step 1: Open CPU index 0 successfully */ + if (evsel__open_per_cpu_and_thread(evsel, cpus, 0, threads) < 0) { + pr_debug("Failed to open CPU index 0\n"); + goto out; + } + + fd0 = (*(int *)xyarray__entry(evsel->core.fd, 0, 0)); + if (fd0 < 0 || fcntl(fd0, F_GETFD) < 0) { + pr_debug("CPU index 0 FD is invalid (%d)\n", fd0); + goto out; + } + + /* Step 2: Intentionally trigger failure on CPU index 1 */ + evsel->core.attr.sample_period = 1; /* Not supported for tool PMU -> -EINVAL */ + if (evsel__open_per_cpu_and_thread(evsel, cpus, 1, threads) >= 0) { + pr_debug("Unexpected success opening CPU index 1 with sample_period=1\n"); + goto out; + } + + /* + * Step 3: Check that CPU index 0's FD was NOT destroyed by CPU index 1's unwind. + */ + if ((*(int *)xyarray__entry(evsel->core.fd, 0, 0)) != fd0) { + pr_debug("FAILED: CPU 0 FD overwritten: FD(evsel, 0, 0)=%d, expected %d\n", + (*(int *)xyarray__entry(evsel->core.fd, 0, 0)), fd0); + goto out; + } + + if (fcntl(fd0, F_GETFD) < 0) { + pr_debug("FAILED: CPU 0 FD %d was closed by error unwind! errno=%d (%s)\n", + fd0, errno, strerror(errno)); + goto out; + } + + ret = TEST_OK; +out: + evsel__close(evsel); + perf_cpu_map__put(cpus); + perf_thread_map__put(threads); + evlist__put(evlist); + return ret; +} + static struct test_case tests__tool_pmu[] = { TEST_CASE("Parsing without PMU name", tool_pmu_without_pmu), TEST_CASE("Parsing with PMU name", tool_pmu_with_pmu), + TEST_CASE("Incremental open error unwind boundary", tool_pmu_incremental_open_unwind), { .name = NULL, } }; diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build index 048e371eb63e..18fceddccb56 100644 --- a/tools/perf/tests/workloads/Build +++ b/tools/perf/tests/workloads/Build @@ -14,6 +14,7 @@ perf-test-y += jitdump.o perf-test-y += context_switch_loop.o perf-test-y += deterministic.o perf-test-y += callchain.o +perf-test-y += false_sharing.o ifeq ($(CONFIG_RUST_SUPPORT),y) perf-test-y += code_with_type.o @@ -29,3 +30,4 @@ CFLAGS_inlineloop.o = -g -O2 CFLAGS_deterministic.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE CFLAGS_named_threads.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE CFLAGS_callchain.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE +CFLAGS_false_sharing.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE diff --git a/tools/perf/tests/workloads/code_with_type.c b/tools/perf/tests/workloads/code_with_type.c index 65d7be7dac24..d2d437b86e0c 100644 --- a/tools/perf/tests/workloads/code_with_type.c +++ b/tools/perf/tests/workloads/code_with_type.c @@ -6,7 +6,7 @@ #include #include "../tests.h" -extern void test_rs(uint count); +extern void test_rs(unsigned int count); static volatile sig_atomic_t done; diff --git a/tools/perf/tests/workloads/false_sharing.c b/tools/perf/tests/workloads/false_sharing.c new file mode 100644 index 000000000000..e949bb36646a --- /dev/null +++ b/tools/perf/tests/workloads/false_sharing.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * False-sharing demo for data type profiling, shaped as a TCP + * connection: a read-mostly identity shares a cacheline with per-packet + * rx counters (the false-sharing line), a second line has packet-path + * private tx and congestion control counters, and a third the connection + * config. + * + * 'perf mem record' of this workload followed by 'perf report -s type' + * (see tests/shell/data_type_profiling.sh) resolves the accesses to + * struct net_conn members, showing the rx counters and the read-mostly + * identity sharing cacheline 0. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../tests.h" + +struct net_conn { + /* cacheline 0: identity (read-mostly) + rx counters (per packet) */ + uint32_t saddr; /* 0 */ + uint32_t daddr; /* 4 */ + uint16_t sport; /* 8 */ + uint16_t dport; /* 10 */ + uint8_t state; /* 12: 1 == ESTABLISHED */ + uint8_t protocol; /* 13: 6 == TCP */ + uint16_t __pad0; /* 14 */ + uint64_t bytes_rx; /* 16: every packet */ + uint64_t packets_rx; /* 24: every packet */ + uint32_t rx_queue; /* 32: backlog depth, fluctuates */ + uint8_t __pad1[24]; /* 36..59 */ + uint32_t last_ack; /* 60: written per ACK */ + /* cacheline 1: tx + congestion control (packet-path private) */ + uint64_t bytes_tx; /* 64: every packet */ + uint64_t packets_tx; /* 72: every packet */ + uint32_t cwnd; /* 80: on every ACK */ + uint32_t ssthresh; /* 84: on loss */ + uint32_t rtt_us; /* 88: on every ACK */ + uint32_t retrans; /* 92: on timeout */ + uint32_t __pad2[8]; /* 96..127 */ + /* cacheline 2: config, set at setup, read by everybody */ + uint16_t mss; /* 128 */ + uint8_t snd_wscale; /* 130 */ + uint8_t rcv_wscale; /* 131 */ + uint32_t keepalive_int; /* 132 */ + uint32_t mark; /* 136: firewall mark */ + uint32_t priority; /* 140: traffic class */ + uint32_t __pad3[12]; /* 144..191 */ +} __attribute__((aligned(64))); + +/* Volatile so every iteration really loads and stores. */ +static volatile struct net_conn conn; +/* Keeps the reader checksums alive after the threads join. */ +static volatile unsigned long fs_sink; + +static volatile sig_atomic_t done; + +/* + * One cacheline each: sum before cpu, or the implicit padding after cpu + * pushes the struct past 64 bytes, and aligning it to a cacheline then + * rounds it up to 128. + */ +struct fs_reader { + pthread_t thread; + unsigned long sum; + int cpu; + char __pad[64 - sizeof(pthread_t) - sizeof(unsigned long) - sizeof(int)]; +} __attribute__((aligned(64))); + +static void sighandler(int sig __maybe_unused) +{ + done = 1; +} + +static void pin_to_cpu(int cpu) +{ + cpu_set_t set; + + /* There may be no second CPU in a restricted cpuset. */ + if (cpu < 0) + return; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + /* Best effort: in a restricted cpuset this fails and the thread runs unpinned. */ + pthread_setaffinity_np(pthread_self(), sizeof(set), &set); +} + +/* + * Connection lookup, as a load balancer or 'ss' scrape would do it: the + * reads go straight to the global, this file is built -O0 and a local + * pointer would be reloaded from a stack slot, a form the data type + * resolver does not track. + */ +static void *reader_fn(void *arg) +{ + struct fs_reader *r = arg; + unsigned long sum = 0; + + pthread_setname_np(pthread_self(), "fs-reader"); + pin_to_cpu(r->cpu); + + while (!done) { + sum += conn.saddr + conn.daddr + conn.sport + conn.dport + + conn.state + conn.protocol; + sum += conn.mss + conn.snd_wscale + conn.rcv_wscale + + conn.keepalive_int + conn.mark + conn.priority; + } + r->sum = sum; + return NULL; +} + +static int false_sharing(int argc, const char **argv) +{ + double sec = 2.0; + int nreaders = 0, nr_allowed = 0, err = 1; + int *allowed = NULL, nallowed = 0; + cpu_set_t set; + int nr_mask_bits = sizeof(set) * 8 < CPU_SETSIZE ? sizeof(set) * 8 : CPU_SETSIZE; + struct fs_reader *readers = NULL; + int i, writer_cpu; + unsigned long n = 0; + + pthread_setname_np(pthread_self(), "fs-writer"); + if (argc > 0) + sec = atof(argv[0]); + if (!(sec > 0.0)) { + fprintf(stderr, "Error: seconds (%f) must be > 0\n", sec); + return 1; + } + if (argc > 1) + nreaders = atoi(argv[1]); + + /* + * A connection that just got established: identity and config fixed + * from here on, counters at zero. + */ + conn.saddr = 0x0a000001; /* 10.0.0.1 */ + conn.daddr = 0x0a000002; /* 10.0.0.2 */ + conn.sport = 54321; + conn.dport = 443; + conn.state = 1; /* ESTABLISHED */ + conn.protocol = 6; /* TCP */ + conn.mss = 1448; + conn.snd_wscale = 7; + conn.rcv_wscale = 7; + conn.keepalive_int = 7200; + conn.cwnd = 10; + conn.ssthresh = 65535; + conn.rtt_us = 50; + + /* + * Pin against the allowed set, restricted cpusets still spread the threads. + * The whole mask is looked at, not the CPU count: the count can be lower + * than the highest ID in it, as when a cpuset allows only high numbered + * CPUs, and then no allowed CPU would be found at all. + */ + if (sched_getaffinity(0, sizeof(set), &set) == 0) { + for (i = 0; i < nr_mask_bits; i++) { + if (!CPU_ISSET(i, &set)) + continue; + nr_allowed++; + } + allowed = malloc(nr_allowed * sizeof(int)); + if (allowed == NULL) { + fprintf(stderr, "Error: malloc failed for CPU list\n"); + return 1; + } + for (i = 0; i < nr_mask_bits; i++) { + if (CPU_ISSET(i, &set)) + allowed[nallowed++] = i; + } + } + if (nreaders <= 0) { + /* By default leave one CPU for the packet path, up to 4 readers. */ + nreaders = nallowed > 1 ? nallowed - 1 : 1; + if (nreaders > 4) + nreaders = 4; + } + + signal(SIGINT, sighandler); + signal(SIGALRM, sighandler); + + readers = calloc(nreaders, sizeof(*readers)); + if (readers == NULL) { + fprintf(stderr, "Error: calloc failed for %d readers\n", nreaders); + goto out; + } + for (i = 0; i < nreaders; i++) { + int cpu = nallowed > 1 ? allowed[(i + 1) % nallowed] : -1; + + readers[i].cpu = cpu; + if (pthread_create(&readers[i].thread, NULL, reader_fn, &readers[i])) { + fprintf(stderr, "Error: failed to create reader %d\n", i); + done = 1; // Ensure started threads terminate. + nreaders = i; + goto out_join; + } + } + writer_cpu = nallowed > 0 ? allowed[0] : -1; + if (nallowed == 1) + fprintf(stderr, "Warning: single CPU allowed, no cross-CPU traffic expected\n"); + if (writer_cpu >= 0) + pin_to_cpu(writer_cpu); + + /* + * The packet path: receive, acknowledge, transmit, repeat; every 64th + * packet simulates a loss so ssthresh/retrans get sampled too. + */ + if (sec < 1.0) { + useconds_t usecs = (useconds_t)(sec * 1000000.0); + + ualarm(usecs > 0 ? usecs : 1, 0); + } else + alarm((unsigned int)sec); + while (!done) { + conn.bytes_rx += conn.mss; + conn.packets_rx++; + conn.rx_queue = (uint32_t)(n & 0x3f); + conn.last_ack = (uint32_t)n; + conn.bytes_tx += conn.mss; + conn.packets_tx++; + conn.cwnd = 10 + (n & 15); + conn.rtt_us = 50 + (n & 7); + if ((n & 63) == 0) { + conn.ssthresh = conn.cwnd / 2; + conn.retrans++; + } + n++; + } + err = 0; +out_join: + for (i = 0; i < nreaders; i++) { + if (readers[i].thread) { + pthread_join(readers[i].thread, /*retval=*/NULL); + fs_sink += readers[i].sum; + } + } + fs_sink += (unsigned long)(conn.bytes_rx + conn.bytes_tx + n); + free(readers); +out: + free(allowed); + return err; +} + +DEFINE_WORKLOAD(false_sharing); diff --git a/tools/perf/trace/beauty/arch_errno_names.sh b/tools/perf/trace/beauty/arch_errno_names.sh index 8751bfa4a2b2..e75cd5bc4328 100755 --- a/tools/perf/trace/beauty/arch_errno_names.sh +++ b/tools/perf/trace/beauty/arch_errno_names.sh @@ -46,7 +46,7 @@ process_arch() asm_errno=$(asm_errno_file "$arch") $gcc $CFLAGS $include_path -E -dM -x c $asm_errno \ - |grep -hE '^#define[[:blank:]]+(E[^[:blank:]]+)[[:blank:]]+([[:digit:]]+).*' \ + |grep -hE '^#define[[:blank:]]+(E[A-Z0-9]+)[[:blank:]]+([[:digit:]]+).*' \ |awk '{ print $2","$3; }' \ |sort -t, -k2 -nu \ |IFS=, create_errno_lookup_func "$arch" @@ -69,7 +69,7 @@ arch_to_e_machine() riscv) printf '\tcase EM_RISCV:\n' ;; s390) printf '\tcase EM_S390:\n' ;; sh) printf '\tcase EM_SH:\n' ;; - sparc) printf '\tcase EM_SPARC:\n\tcase EM_SPARCV9:\n' ;; + sparc) printf '\tcase EM_SPARC:\n\tcase EM_SPARC32PLUS:\n\tcase EM_SPARCV9:\n' ;; x86) printf '\tcase EM_386:\n\tcase EM_X86_64:\n' ;; xtensa) printf '\tcase EM_XTENSA:\n' ;; esac diff --git a/tools/perf/trace/beauty/beauty.h b/tools/perf/trace/beauty/beauty.h index 58a3206481ae..1cd307328bb2 100644 --- a/tools/perf/trace/beauty/beauty.h +++ b/tools/perf/trace/beauty/beauty.h @@ -124,6 +124,23 @@ struct syscall_arg { unsigned long syscall_arg__val(struct syscall_arg *arg, u8 idx); +/* Is there a whole augmented arg left with at least @need bytes of payload? */ +static inline bool syscall_arg__augmented_args_valid(struct syscall_arg *arg, size_t need) +{ + const struct augmented_arg *augmented_arg; + + if (arg == NULL || arg->augmented.args == NULL || + arg->augmented.size < (int)sizeof(*augmented_arg)) + return false; + + augmented_arg = arg->augmented.args; + if (augmented_arg->size < 0 || + augmented_arg->size > arg->augmented.size - (int)sizeof(*augmented_arg)) + return false; + + return (size_t)augmented_arg->size >= need; +} + size_t syscall_arg__scnprintf_strarray_flags(char *bf, size_t size, struct syscall_arg *arg); #define SCA_STRARRAY_FLAGS syscall_arg__scnprintf_strarray_flags @@ -160,6 +177,9 @@ size_t syscall_arg__scnprintf_hex(char *bf, size_t size, struct syscall_arg *arg size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg); #define SCA_PTR syscall_arg__scnprintf_ptr +size_t syscall_arg__scnprintf_ksym(char *bf, size_t size, struct syscall_arg *arg); +#define SCA_KSYM syscall_arg__scnprintf_ksym + size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg); #define SCA_INT syscall_arg__scnprintf_int diff --git a/tools/perf/trace/beauty/include/linux/socket.h b/tools/perf/trace/beauty/include/linux/socket.h index 2a8d7b14f1d1..b17fdb7d38a7 100644 --- a/tools/perf/trace/beauty/include/linux/socket.h +++ b/tools/perf/trace/beauty/include/linux/socket.h @@ -189,10 +189,11 @@ static inline size_t msg_data_left(const struct msghdr *msg) /* "Socket"-level control message types: */ -#define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ -#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ -#define SCM_SECURITY 0x03 /* rw: security label */ -#define SCM_PIDFD 0x04 /* ro: pidfd (int) */ +#define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ +#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ +#define SCM_SECURITY 0x03 /* rw: security label */ +#define SCM_PIDFD 0x04 /* ro: pidfd (int) */ +#define SCM_PIDFD_THREAD 0x05 /* ro: thread pidfd (int) */ struct ucred { __u32 pid; @@ -461,6 +462,8 @@ extern struct file *__sys_socket_file(int family, int type, int protocol); extern int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen); extern int __sys_bind_socket(struct socket *sock, struct sockaddr_storage *address, int addrlen); +int connect_socket(struct socket *sock, struct sockaddr_storage *addr, + int addrlen, int flags); extern int __sys_connect_file(struct file *file, struct sockaddr_storage *addr, int addrlen, int file_flags); extern int __sys_connect(int fd, struct sockaddr __user *uservaddr, diff --git a/tools/perf/trace/beauty/include/uapi/linux/fcntl.h b/tools/perf/trace/beauty/include/uapi/linux/fcntl.h index aadfbf6e0cb3..e43e3de3e9ee 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fcntl.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fcntl.h @@ -124,6 +124,7 @@ struct delegation { #define FD_PIDFS_ROOT -10002 /* Root of the pidfs filesystem */ #define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ +#define FD_FAILFS_ROOT -10004 /* Root of the failfs filesystem */ #define FD_INVALID -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */ /* Generic flags for the *at(2) family of syscalls. */ diff --git a/tools/perf/trace/beauty/include/uapi/linux/fs.h b/tools/perf/trace/beauty/include/uapi/linux/fs.h index bd87262f2e34..a46c33692aa2 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fs.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fs.h @@ -88,7 +88,7 @@ struct fstrim_range { * We include a length field because some filesystems (vfat) have an identifier * that we do want to expose as a UUID, but doesn't have the standard length. * - * We use a fixed size buffer beacuse this interface will, by fiat, never + * We use a fixed size buffer because this interface will, by fiat, never * support "UUIDs" longer than 16 bytes; we don't want to force all downstream * users to have to deal with that. */ @@ -471,6 +471,7 @@ typedef int __bitwise __kernel_rwf_t; #define PAGE_IS_HUGE (1 << 6) #define PAGE_IS_SOFT_DIRTY (1 << 7) #define PAGE_IS_GUARD (1 << 8) +#define PAGE_IS_ACCESSED (1 << 9) /* * struct page_region - Page region with flags diff --git a/tools/perf/trace/beauty/include/uapi/linux/mount.h b/tools/perf/trace/beauty/include/uapi/linux/mount.h index 2204708dbf7a..ac865edac517 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/mount.h +++ b/tools/perf/trace/beauty/include/uapi/linux/mount.h @@ -63,6 +63,7 @@ */ #define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */ #define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */ +#define OPEN_TREE_DROP_MNTNS_MOUNTS (1 << 2) /* Drop mntns mounts from the clone */ #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ /* diff --git a/tools/perf/trace/beauty/include/uapi/sound/asound.h b/tools/perf/trace/beauty/include/uapi/sound/asound.h index d3ce75ba938a..c11da9656e38 100644 --- a/tools/perf/trace/beauty/include/uapi/sound/asound.h +++ b/tools/perf/trace/beauty/include/uapi/sound/asound.h @@ -169,72 +169,72 @@ enum { SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, }; -typedef int __bitwise snd_pcm_access_t; -#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ -#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ -#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ -#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ -#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ +typedef int snd_pcm_access_t; +#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ +#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ +#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ +#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ +#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED -typedef int __bitwise snd_pcm_format_t; -#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) -#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) -#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) -#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) -#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) -#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) -#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ -#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ -#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ -#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ +typedef int snd_pcm_format_t; +#define SNDRV_PCM_FORMAT_S8 0 +#define SNDRV_PCM_FORMAT_U8 1 +#define SNDRV_PCM_FORMAT_S16_LE 2 +#define SNDRV_PCM_FORMAT_S16_BE 3 +#define SNDRV_PCM_FORMAT_U16_LE 4 +#define SNDRV_PCM_FORMAT_U16_BE 5 +#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ +#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ /* * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the * available bit count in most significant bit. It's for the case of so-called 'left-justified' or * `right-padding` sample which has less width than 32 bit. */ -#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) -#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) -#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) -#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) -#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ -#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ -#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) -#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) -#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) -#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) -#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) -#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S32_LE 10 +#define SNDRV_PCM_FORMAT_S32_BE 11 +#define SNDRV_PCM_FORMAT_U32_LE 12 +#define SNDRV_PCM_FORMAT_U32_BE 13 +#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ +#define SNDRV_PCM_FORMAT_MU_LAW 20 +#define SNDRV_PCM_FORMAT_A_LAW 21 +#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 +#define SNDRV_PCM_FORMAT_MPEG 23 +#define SNDRV_PCM_FORMAT_GSM 24 +#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ /* gap in the numbering for a future standard linear format */ -#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) -#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ -#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ -#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ -#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ -#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ -#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ -#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ -#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ -#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ -#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ +#define SNDRV_PCM_FORMAT_SPECIAL 31 +#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ +#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ +#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ +#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ +#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ +#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ +#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ +#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 @@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE #endif -typedef int __bitwise snd_pcm_subformat_t; -#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) -#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) -#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) -#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) +typedef int snd_pcm_subformat_t; +#define SNDRV_PCM_SUBFORMAT_STD 0 +#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 +#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 +#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ @@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; #define __SND_STRUCT_TIME64 #endif -typedef int __bitwise snd_pcm_state_t; -#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ -#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ -#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ -#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ -#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ -#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ -#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ -#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ -#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ +typedef int snd_pcm_state_t; +#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ +#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ +#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ +#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ +#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ +#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ +#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ +#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ +#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED enum { @@ -1058,7 +1058,7 @@ struct snd_timer_tread { * * ****************************************************************************/ -#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) +#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) struct snd_ctl_card_info { int card; /* card number */ @@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ }; -typedef int __bitwise snd_ctl_elem_type_t; -#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ -#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ -#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ -#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ -#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ -#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ -#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ +/* + * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. + * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS + * to retrieve the full string. + */ + +/* Type values for struct snd_ctl_card_bytes::type */ +enum { + SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ +}; + +struct snd_ctl_card_bytes { + __u32 type; /* SND_CTL_CARD_BTYPE_* */ + __u32 data_allocated; /* size of @data buffer in bytes */ + __u32 data_len; /* in/out: actual data length in bytes */ + __u32 reserved; /* explicit pad */ + __u64 data; /* user buffer (pointer stored as __u64) */ +}; + +typedef int snd_ctl_elem_type_t; +#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ +#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ +#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ +#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ +#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ +#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ +#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 -typedef int __bitwise snd_ctl_elem_iface_t; -#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ -#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ -#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ -#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ -#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ -#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ -#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ +typedef int snd_ctl_elem_iface_t; +#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ +#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ +#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ +#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ +#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ +#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ +#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) @@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) +#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) diff --git a/tools/perf/trace/beauty/perf_event_open.c b/tools/perf/trace/beauty/perf_event_open.c index 6315b46bcdf0..bfdf322c6ede 100644 --- a/tools/perf/trace/beauty/perf_event_open.c +++ b/tools/perf/trace/beauty/perf_event_open.c @@ -81,33 +81,28 @@ static size_t perf_event_attr___scnprintf(struct perf_event_attr *attr, char *bf static size_t syscall_arg__scnprintf_augmented_perf_event_attr(struct syscall_arg *arg, char *bf, size_t size) { - struct perf_event_attr *attr = (void *)arg->augmented.args->value; + const struct augmented_arg *augmented_arg = arg->augmented.args; struct perf_event_attr local_attr; + size_t copied = (size_t)augmented_arg->size; - /* - * augmented_raw_syscalls.bpf.c (shipped with perf) copies - * PERF_ATTR_SIZE_VER0 bytes when the tracee passes size=0, - * but leaves the size field as 0. The payload size is - * guaranteed by perf's own BPF program, not externally - * controllable. Copy to a local so we can fix up size - * without writing to the potentially read-only augmented - * args buffer. - */ - if (!attr->size) { - memcpy(&local_attr, attr, PERF_ATTR_SIZE_VER0); - memset((void *)&local_attr + PERF_ATTR_SIZE_VER0, 0, - sizeof(local_attr) - PERF_ATTR_SIZE_VER0); + /* Zero pad, as the tracee's attr may be smaller than perf's. */ + if (copied > sizeof(local_attr)) + copied = sizeof(local_attr); + + memcpy(&local_attr, augmented_arg->value, copied); + memset((void *)&local_attr + copied, 0, sizeof(local_attr) - copied); + + /* The BPF program copies PERF_ATTR_SIZE_VER0 bytes for size 0. */ + if (!local_attr.size) local_attr.size = PERF_ATTR_SIZE_VER0; - attr = &local_attr; - } - return perf_event_attr___scnprintf(attr, bf, size, + return perf_event_attr___scnprintf(&local_attr, bf, size, trace__show_zeros(arg->trace)); } size_t syscall_arg__scnprintf_perf_event_attr(char *bf, size_t size, struct syscall_arg *arg) { - if (arg->augmented.args) + if (syscall_arg__augmented_args_valid(arg, PERF_ATTR_SIZE_VER0)) return syscall_arg__scnprintf_augmented_perf_event_attr(arg, bf, size); return scnprintf(bf, size, "%#lx", arg->val); diff --git a/tools/perf/trace/beauty/sockaddr.c b/tools/perf/trace/beauty/sockaddr.c index a17a27ac2a6f..b4b188879598 100644 --- a/tools/perf/trace/beauty/sockaddr.c +++ b/tools/perf/trace/beauty/sockaddr.c @@ -2,6 +2,7 @@ // Copyright (C) 2018, Red Hat Inc, Arnaldo Carvalho de Melo #include "trace/beauty/beauty.h" +#include #include #include #include @@ -10,36 +11,57 @@ #include "trace/beauty/generated/sockaddr.c" DEFINE_STRARRAY(socket_families, "PF_"); -static size_t af_inet__scnprintf(struct sockaddr *sa, char *bf, size_t size) +static size_t af_inet__scnprintf(struct sockaddr *sa, size_t sa_size, char *bf, size_t size) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; char tmp[16]; + + if (sa_size < sizeof(*sin)) + return 0; + return scnprintf(bf, size, ", port: %d, addr: %s", ntohs(sin->sin_port), inet_ntop(sin->sin_family, &sin->sin_addr, tmp, sizeof(tmp))); } -static size_t af_inet6__scnprintf(struct sockaddr *sa, char *bf, size_t size) +static size_t af_inet6__scnprintf(struct sockaddr *sa, size_t sa_size, char *bf, size_t size) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; - u32 flowinfo = ntohl(sin6->sin6_flowinfo); + u32 flowinfo; char tmp[512]; - size_t printed = scnprintf(bf, size, ", port: %d, addr: %s", ntohs(sin6->sin6_port), - inet_ntop(sin6->sin6_family, &sin6->sin6_addr, tmp, sizeof(tmp))); + size_t printed; + + /* RFC 2133's version, which the kernel accepts, lacks sin6_scope_id. */ + if (sa_size < offsetof(struct sockaddr_in6, sin6_scope_id)) + return 0; + + flowinfo = ntohl(sin6->sin6_flowinfo); + printed = scnprintf(bf, size, ", port: %d, addr: %s", ntohs(sin6->sin6_port), + inet_ntop(sin6->sin6_family, &sin6->sin6_addr, tmp, sizeof(tmp))); if (flowinfo != 0) printed += scnprintf(bf + printed, size - printed, ", flowinfo: %lu", flowinfo); - if (sin6->sin6_scope_id != 0) + if (sa_size >= sizeof(*sin6) && sin6->sin6_scope_id != 0) printed += scnprintf(bf + printed, size - printed, ", scope_id: %lu", sin6->sin6_scope_id); return printed; } -static size_t af_local__scnprintf(struct sockaddr *sa, char *bf, size_t size) +static size_t af_local__scnprintf(struct sockaddr *sa, size_t sa_size, char *bf, size_t size) { struct sockaddr_un *sun = (struct sockaddr_un *)sa; - return scnprintf(bf, size, ", path: %s", sun->sun_path); + size_t path_size; + + if (sa_size <= offsetof(struct sockaddr_un, sun_path)) + return 0; + + /* The path needn't be NUL terminated. */ + path_size = sa_size - offsetof(struct sockaddr_un, sun_path); + if (path_size > sizeof(sun->sun_path)) + path_size = sizeof(sun->sun_path); + + return scnprintf(bf, size, ", path: %.*s", (int)path_size, sun->sun_path); } -static size_t (*af_scnprintfs[])(struct sockaddr *sa, char *bf, size_t size) = { +static size_t (*af_scnprintfs[])(struct sockaddr *sa, size_t sa_size, char *bf, size_t size) = { [AF_LOCAL] = af_local__scnprintf, [AF_INET] = af_inet__scnprintf, [AF_INET6] = af_inet6__scnprintf, @@ -47,7 +69,9 @@ static size_t (*af_scnprintfs[])(struct sockaddr *sa, char *bf, size_t size) = { static size_t syscall_arg__scnprintf_augmented_sockaddr(struct syscall_arg *arg, char *bf, size_t size) { - struct sockaddr *sa = (struct sockaddr *)&arg->augmented.args->value; + const struct augmented_arg *augmented_arg = arg->augmented.args; + struct sockaddr *sa = (struct sockaddr *)&augmented_arg->value; + size_t sa_size = (size_t)augmented_arg->size; char family[32]; size_t printed; @@ -55,14 +79,15 @@ static size_t syscall_arg__scnprintf_augmented_sockaddr(struct syscall_arg *arg, printed = scnprintf(bf, size, "{ .family: %s", family); if (sa->sa_family < ARRAY_SIZE(af_scnprintfs) && af_scnprintfs[sa->sa_family]) - printed += af_scnprintfs[sa->sa_family](sa, bf + printed, size - printed); + printed += af_scnprintfs[sa->sa_family](sa, sa_size, bf + printed, size - printed); return printed + scnprintf(bf + printed, size - printed, " }"); } size_t syscall_arg__scnprintf_sockaddr(char *bf, size_t size, struct syscall_arg *arg) { - if (arg->augmented.args) + /* The family printers check the rest of the payload. */ + if (syscall_arg__augmented_args_valid(arg, sizeof(sa_family_t))) return syscall_arg__scnprintf_augmented_sockaddr(arg, bf, size); return scnprintf(bf, size, "%#lx", arg->val); diff --git a/tools/perf/trace/beauty/syscalltbl.sh b/tools/perf/trace/beauty/syscalltbl.sh index 1199618dc178..e70a8c79e019 100755 --- a/tools/perf/trace/beauty/syscalltbl.sh +++ b/tools/perf/trace/beauty/syscalltbl.sh @@ -55,7 +55,7 @@ build_tables() { echo "static const uint16_t syscall_sorted_names_${e_machine}[] = {" >> "$outfile" - # When sorting by name, add a suffix of 0s upto 20 characters so that + # When sorting by name, add a suffix of 0s up to 20 characters so that # system calls that differ with a numerical suffix don't sort before # those without. This default behavior of sort differs from that of # strcmp used at runtime. Use sed to strip the trailing 0s suffix diff --git a/tools/perf/trace/beauty/timespec.c b/tools/perf/trace/beauty/timespec.c index b14ab72a2738..8da0b28be0af 100644 --- a/tools/perf/trace/beauty/timespec.c +++ b/tools/perf/trace/beauty/timespec.c @@ -14,7 +14,7 @@ static size_t syscall_arg__scnprintf_augmented_timespec(struct syscall_arg *arg, size_t syscall_arg__scnprintf_timespec(char *bf, size_t size, struct syscall_arg *arg) { - if (arg->augmented.args) + if (syscall_arg__augmented_args_valid(arg, sizeof(struct timespec))) return syscall_arg__scnprintf_augmented_timespec(arg, bf, size); return scnprintf(bf, size, "%#lx", arg->val); diff --git a/tools/perf/ui/Build b/tools/perf/ui/Build index 6005f813c9e3..a7b1740d51c8 100644 --- a/tools/perf/ui/Build +++ b/tools/perf/ui/Build @@ -4,6 +4,7 @@ perf-ui-y += progress.o perf-ui-y += util.o perf-ui-y += hist.o perf-ui-y += stdio/hist.o +perf-ui-y += stdio/progress.o CFLAGS_setup.o += -DLIBDIR="BUILD_STR($(LIBDIR))" diff --git a/tools/perf/ui/browsers/annotate-data.c b/tools/perf/ui/browsers/annotate-data.c index aa8c89fe2e82..a15608f6ee53 100644 --- a/tools/perf/ui/browsers/annotate-data.c +++ b/tools/perf/ui/browsers/annotate-data.c @@ -62,12 +62,16 @@ static int get_member_overhead(struct annotated_data_type *adt, k = 0; for_each_group_evsel(evsel, leader) { + struct type_hist_entry *hist; + if (symbol_conf.skip_empty && evsel__hists(evsel)->stats.nr_samples == 0) continue; - h = adt->histograms[evsel->core.idx]; - update_hist_entry(&entry->hists[k++], &h->addr[offset]); + h = &adt->histograms[evsel->core.idx]; + if (hashmap__find(&h->samples, offset, &hist)) + update_hist_entry(&entry->hists[k], hist); + k++; } } return 0; @@ -370,7 +374,7 @@ static void browser__write_overhead(struct ui_browser *uib, u64 period = hist->period; double percent = total->period ? (100.0 * period / total->period) : 0; bool current = ui_browser__is_current_entry(uib, row); - int nr_samples = 0; + int nr_samples = hist->nr_samples; ui_browser__set_percent_color(uib, percent, current); @@ -416,7 +420,7 @@ static void browser__write(struct ui_browser *uib, void *entry, int row) /* print the number */ for_each_group_evsel(evsel, leader) { - struct type_hist *h = adt->histograms[evsel->core.idx]; + struct type_hist *h = &adt->histograms[evsel->core.idx]; if (symbol_conf.skip_empty && evsel__hists(evsel)->stats.nr_samples == 0) diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c index e47a46775089..cf36b5347f72 100644 --- a/tools/perf/ui/browsers/annotate.c +++ b/tools/perf/ui/browsers/annotate.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "../browser.h" #include "../helpline.h" +#include "../keysyms.h" #include "../ui.h" #include "../../util/annotate.h" #include "../../util/debug.h" @@ -189,7 +190,7 @@ static void annotate_browser__draw_current_jump(struct ui_browser *browser) struct map_symbol *ms = ab->b.priv; struct symbol *sym = ms->sym; struct annotation *notes = symbol__annotation(sym); - u8 pcnt_width = annotation__pcnt_width(notes); + u8 pcnt_width = annotation__pcnt_width(notes, ab->evsel); u8 cntr_width = annotation__br_cntr_width(); int width; int diff = 0; @@ -255,7 +256,8 @@ static unsigned int annotate_browser__refresh(struct ui_browser *browser) { struct annotation *notes = browser__annotation(browser); int ret = ui_browser__list_head_refresh(browser); - int pcnt_width = annotation__pcnt_width(notes); + int pcnt_width = annotation__pcnt_width(notes, + container_of(browser, struct annotate_browser, b)->evsel); if (annotate_opts.jump_arrows) annotate_browser__draw_current_jump(browser); @@ -972,6 +974,7 @@ static int annotate_browser__run(struct annotate_browser *browser, "O Bump offset level (jump targets -> +call -> all -> cycle thru)\n" "s Toggle source code view\n" "t Circulate percent, total period, samples view\n" + "w Toggle weight columns\n" "c Show min/max cycle\n" "/ Search string\n" "k Toggle line numbers\n" @@ -1091,6 +1094,14 @@ static int annotate_browser__run(struct annotate_browser *browser, symbol_conf.show_total_period = true; annotation__update_column_widths(notes); continue; + case 'w': + symbol_conf.annotate_weight = !symbol_conf.annotate_weight; + browser->b.width = notes->src->widths.max_line_len + + annotation__pcnt_width(notes, evsel) + + annotation__cycles_width(notes) + + annotation__br_cntr_width(); + ui_browser__refresh_dimensions(&browser->b); + continue; case 'c': if (annotate_opts.show_minmax_cycle) annotate_opts.show_minmax_cycle = false; @@ -1225,10 +1236,12 @@ int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms, browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL); } - browser.b.width = notes->src->widths.max_line_len; + browser.b.width = notes->src->widths.max_line_len + + annotation__pcnt_width(notes, evsel) + + annotation__cycles_width(notes) + + annotation__br_cntr_width(); browser.b.nr_entries = notes->src->nr_entries; browser.b.entries = ¬es->src->source; - browser.b.width += 18; /* Percentage */ if (annotate_opts.hide_src_code) ui_browser__init_asm_mode(&browser.b); diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index c15874a491b4..593e1fd5759d 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -35,6 +35,7 @@ #include "../browsers/hists.h" #include "../helpline.h" +#include "../keysyms.h" #include "../util.h" #include "../ui.h" #include "map.h" @@ -3579,9 +3580,12 @@ static int perf_evsel_menu__run(struct evsel_menu *menu, const char *title = "Available samples"; int delay_secs = hbt ? hbt->refresh : 0; int key; + const char *help_text = "ESC: exit, ENTER|->: Browse histograms"; - if (ui_browser__show(&menu->b, title, - "ESC: exit, ENTER|->: Browse histograms") < 0) + if (!symbol_conf.hybrid_merge && evlist__can_merge_hybrid(evlist, menu->env)) + help_text = "ESC: exit, ENTER|->: Browse. Try --hybrid-merge to combine events."; + + if (ui_browser__show(&menu->b, title, help_text) < 0) return -1; while (1) { diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c index 1e8c2c2f952d..d6d09b4a348c 100644 --- a/tools/perf/ui/browsers/scripts.c +++ b/tools/perf/ui/browsers/scripts.c @@ -1,4 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include #include "../../util/util.h" // perf_exe() #include "../util.h" #include "../../util/evlist.h" @@ -7,13 +14,13 @@ #include "../../util/session.h" #include "../../util/symbol.h" #include "../browser.h" +#include "../keysyms.h" #include "../libslang.h" #include "config.h" #include #include #include #include -#include #define SCRIPT_NAMELEN 128 #define SCRIPT_MAX_NO 64 @@ -79,75 +86,6 @@ static int scripts_config(const char *var, const char *value, void *data) return 0; } -/* - * Some scripts specify the required events in their "xxx-record" file, - * this function will check if the events in perf.data match those - * mentioned in the "xxx-record". - * - * Fixme: All existing "xxx-record" are all in good formats "-e event ", - * which is covered well now. And new parsing code should be added to - * cover the future complex formats like event groups etc. - */ -static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session) -{ - char line[BUFSIZ]; - FILE *fp; - - { - char filename[NAME_MAX + 5]; - int fd; - - scnprintf(filename, sizeof(filename), "bin/%s-record", scriptname); - fd = openat(dir_fd, filename, O_RDONLY); - if (fd == -1) - return -1; - fp = fdopen(fd, "r"); - if (!fp) - return -1; - } - - while (fgets(line, sizeof(line), fp)) { - char *p = skip_spaces(line); - - if (*p == '#') - continue; - - while (strlen(p)) { - int match, len; - struct evsel *pos; - char evname[128]; - - p = strstr(p, "-e"); - if (!p) - break; - - p += 2; - p = skip_spaces(p); - len = strcspn(p, " \t"); - if (!len) - break; - - snprintf(evname, len + 1, "%s", p); - - match = 0; - evlist__for_each_entry(session->evlist, pos) { - if (evsel__name_is(pos, evname)) { - match = 1; - break; - } - } - - if (!match) { - fclose(fp); - return -1; - } - } - } - - fclose(fp); - return 0; -} - /* * Return -1 if none is found, otherwise the actual scripts number. * @@ -155,98 +93,66 @@ static int check_ev_match(int dir_fd, const char *scriptname, struct perf_sessio * will list all statically runnable scripts, select one, execute it and * show the output in a perf browser. */ -static int find_scripts(char **scripts_array, char **scripts_path_array, int num, - int pathlen) +static int find_scripts(char **scripts_array __maybe_unused, + char **scripts_path_array __maybe_unused, int num __maybe_unused, + int pathlen __maybe_unused) { - struct dirent *script_dirent, *lang_dirent; - int scripts_dir_fd, lang_dir_fd; - DIR *scripts_dir, *lang_dir; - struct perf_session *session; - struct perf_data data = { - .path = input_name, - .mode = PERF_DATA_MODE_READ, - }; - char *temp; int i = 0; - const char *exec_path = get_argv_exec_path(); - - session = perf_session__new(&data, NULL); - if (IS_ERR(session)) - return PTR_ERR(session); +#ifdef HAVE_PYTHON_MODULE_SUPPORT { - char scripts_path[PATH_MAX]; + int namelen; + struct dirent *script_dirent; + char *temp; + const char *exec_path = get_argv_exec_path(); + char py_scripts_path[PATH_MAX]; + int py_scripts_dir_fd; + DIR *py_scripts_dir; + int len; - snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path); - scripts_dir_fd = open(scripts_path, O_DIRECTORY); - pr_err("Failed to open directory '%s'", scripts_path); - if (scripts_dir_fd == -1) { - perf_session__delete(session); - return -1; + snprintf(py_scripts_path, sizeof(py_scripts_path), "%s/python", exec_path); + py_scripts_dir_fd = open(py_scripts_path, O_DIRECTORY); + if (py_scripts_dir_fd != -1) { + py_scripts_dir = fdopendir(py_scripts_dir_fd); + if (py_scripts_dir) { + while ((script_dirent = readdir(py_scripts_dir)) != NULL) { + if (script_dirent->d_type == DT_DIR) + continue; + if (script_dirent->d_type == DT_UNKNOWN && + is_directory_at(py_scripts_dir_fd, + script_dirent->d_name)) + continue; + /* Skip those real time scripts: xxxtop.p[yl] */ + if (strstr(script_dirent->d_name, "top.")) + continue; + if (i >= num) + break; + len = strlen(script_dirent->d_name); + if (len <= 3 || + strcmp(script_dirent->d_name + len - 3, ".py")) + continue; + + scnprintf(scripts_path_array[i], pathlen, "%s/python/%s", + exec_path, + script_dirent->d_name); + temp = strrchr(script_dirent->d_name, '.'); + namelen = temp ? (int)(temp - script_dirent->d_name) + : (int)strlen(script_dirent->d_name); + + if (namelen >= SCRIPT_NAMELEN) + namelen = SCRIPT_NAMELEN - 1; + snprintf(scripts_array[i], namelen + 1, "%s", + script_dirent->d_name); + + i++; + } + closedir(py_scripts_dir); + } else { + close(py_scripts_dir_fd); + } } } - scripts_dir = fdopendir(scripts_dir_fd); - if (!scripts_dir) { - close(scripts_dir_fd); - perf_session__delete(session); - return -1; - } - - while ((lang_dirent = readdir(scripts_dir)) != NULL) { - if (lang_dirent->d_type != DT_DIR && - (lang_dirent->d_type == DT_UNKNOWN && - !is_directory_at(scripts_dir_fd, lang_dirent->d_name))) - continue; - if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, "..")) - continue; - -#ifndef HAVE_LIBPERL_SUPPORT - if (strstr(lang_dirent->d_name, "perl")) - continue; #endif -#ifndef HAVE_LIBPYTHON_SUPPORT - if (strstr(lang_dirent->d_name, "python")) - continue; -#endif - - lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY); - if (lang_dir_fd == -1) - continue; - lang_dir = fdopendir(lang_dir_fd); - if (!lang_dir) { - close(lang_dir_fd); - continue; - } - while ((script_dirent = readdir(lang_dir)) != NULL) { - if (script_dirent->d_type == DT_DIR) - continue; - if (script_dirent->d_type == DT_UNKNOWN && - is_directory_at(lang_dir_fd, script_dirent->d_name)) - continue; - /* Skip those real time scripts: xxxtop.p[yl] */ - if (strstr(script_dirent->d_name, "top.")) - continue; - if (i >= num) - break; - scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s", - exec_path, - lang_dirent->d_name, - script_dirent->d_name); - temp = strchr(script_dirent->d_name, '.'); - snprintf(scripts_array[i], - (temp - script_dirent->d_name) + 1, - "%s", script_dirent->d_name); - - if (check_ev_match(lang_dir_fd, scripts_array[i], session)) - continue; - - i++; - } - closedir(lang_dir); - } - - closedir(scripts_dir); - perf_session__delete(session); return i; } @@ -263,7 +169,7 @@ static int list_scripts(char *script_name, bool *custom, int ret = 0; int max_std, custom_perf; char pbuf[256]; - const char *perf = perf_exe(pbuf, sizeof pbuf); + const char *perf = perf_exe(pbuf, sizeof(pbuf)); struct script_config scriptc = { .names = (const char **)names, .paths = paths, @@ -353,7 +259,7 @@ int script_browse(const char *script_opt, struct evsel *evsel) return -1; if (asprintf(&cmd, "%s%s %s %s%s 2>&1 | less", - custom ? "perf script -s " : "", + custom ? "perf script " : "", script_name, script_opt ? script_opt : "", input_name ? "-i " : "", diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c index 8920e298420a..094dfa045b77 100644 --- a/tools/perf/ui/gtk/annotate.c +++ b/tools/perf/ui/gtk/annotate.c @@ -11,6 +11,7 @@ #include "ui/helpline.h" #include #include +#include enum { ANN_COL__PERCENT, @@ -161,7 +162,7 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct map_symbol *ms, gtk_list_store_set(store, &iter, ANN_COL__LINE, s, -1); } - gtk_container_add(GTK_CONTAINER(window), view); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window), view); list_for_each_entry_safe(pos, n, ¬es->src->source, al.node) { list_del_init(&pos->al.node); @@ -205,40 +206,34 @@ static int symbol__gtk_annotate(struct map_symbol *ms, struct evsel *evsel, GtkWidget *infobar; GtkWidget *statbar; - signal(SIGSEGV, perf_gtk__signal); - signal(SIGFPE, perf_gtk__signal); - signal(SIGINT, perf_gtk__signal); - signal(SIGQUIT, perf_gtk__signal); - signal(SIGTERM, perf_gtk__signal); + signal(SIGSEGV, perf_gtk__fatal_signal); + signal(SIGFPE, perf_gtk__fatal_signal); + perf_gtk__install_quit_signals(); - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(window), "perf annotate"); - g_signal_connect(window, "delete_event", gtk_main_quit, NULL); - pgctx = perf_gtk__activate_context(window); if (!pgctx) return -1; - vbox = gtk_vbox_new(FALSE, 0); + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); notebook = gtk_notebook_new(); pgctx->notebook = notebook; - gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + gtk_widget_set_vexpand(notebook, TRUE); + gtk_box_append(GTK_BOX(vbox), notebook); infobar = perf_gtk__setup_info_bar(); - if (infobar) { - gtk_box_pack_start(GTK_BOX(vbox), infobar, - FALSE, FALSE, 0); - } + gtk_box_append(GTK_BOX(vbox), infobar); statbar = perf_gtk__setup_statusbar(); - gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); + gtk_box_append(GTK_BOX(vbox), statbar); - gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_window_set_child(GTK_WINDOW(window), vbox); } - scrolled_window = gtk_scrolled_window_new(NULL, NULL); + scrolled_window = gtk_scrolled_window_new(); tab_label = gtk_label_new(sym->name); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), @@ -267,12 +262,11 @@ void perf_gtk__show_annotations(void) return; window = pgctx->main_window; - gtk_widget_show_all(window); perf_gtk__resize_window(window); - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_set_visible(window, TRUE); - gtk_main(); + perf_gtk__run_main_loop(window); perf_gtk__deactivate_context(&pgctx); } diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c index d2dadf3873fb..98f1d6bfcb24 100644 --- a/tools/perf/ui/gtk/browser.c +++ b/tools/perf/ui/gtk/browser.c @@ -5,32 +5,92 @@ #include "../hist.h" #include "../helpline.h" +#include #include -void perf_gtk__signal(int sig) +/* + * SIGINT/SIGQUIT/SIGTERM are asynchronous: deferring the actual exit to a + * GLib source dispatched from the main loop means it always runs on the + * main thread, serialized with everything else the main loop does + * (including perf_gtk__error()'s updates to perf_gtk__error_loops), instead + * of racing them from arbitrary signal-handler context. + */ +static gboolean perf_gtk__quit_signal(gpointer data __maybe_unused) { perf_gtk__exit(false); + return G_SOURCE_REMOVE; +} + +void perf_gtk__install_quit_signals(void) +{ + g_unix_signal_add(SIGINT, perf_gtk__quit_signal, NULL); + g_unix_signal_add(SIGQUIT, perf_gtk__quit_signal, NULL); + g_unix_signal_add(SIGTERM, perf_gtk__quit_signal, NULL); +} + +/* + * SIGSEGV/SIGFPE are synchronous faults: there's no "later" to defer to, + * and no safe way to run GTK/GLib code (or anything else non-async-signal- + * safe) from the faulting context. Report and let the default disposition + * (core dump) happen instead of trying to tear down GTK state here. + */ +void perf_gtk__fatal_signal(int sig) +{ psignal(sig, "perf"); + signal(sig, SIG_DFL); + raise(sig); } void perf_gtk__resize_window(GtkWidget *window) { GdkRectangle rect; - GdkScreen *screen; - int monitor; + GdkMonitor *monitor; + GdkDisplay *display; + GListModel *monitors; int height; int width; - screen = gtk_widget_get_screen(window); + display = gtk_widget_get_display(window); + monitors = gdk_display_get_monitors(display); + monitor = g_list_model_get_item(monitors, 0); + if (!monitor) { + gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); + return; + } - monitor = gdk_screen_get_monitor_at_window(screen, window->window); - - gdk_screen_get_monitor_geometry(screen, monitor, &rect); + gdk_monitor_get_geometry(monitor, &rect); + g_object_unref(monitor); width = rect.width * 3 / 4; height = rect.height * 3 / 4; - gtk_window_resize(GTK_WINDOW(window), width, height); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); +} + +static GMainLoop *perf_gtk__main_loop; + +void perf_gtk__quit_main_loop(void) +{ + if (perf_gtk__main_loop) + g_main_loop_quit(perf_gtk__main_loop); +} + +static gboolean perf_gtk__close_request(GtkWidget *widget __maybe_unused, + gpointer data __maybe_unused) +{ + perf_gtk__quit_main_loop(); + + return FALSE; +} + +void perf_gtk__run_main_loop(GtkWidget *window) +{ + g_signal_connect(window, "close-request", + G_CALLBACK(perf_gtk__close_request), NULL); + + perf_gtk__main_loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(perf_gtk__main_loop); + g_clear_pointer(&perf_gtk__main_loop, g_main_loop_unref); } const char *perf_gtk__get_percent_color(double percent) @@ -42,33 +102,35 @@ const char *perf_gtk__get_percent_color(double percent) return NULL; } -#ifdef HAVE_GTK_INFO_BAR_SUPPORT +static void perf_gtk__hide_widget(GtkWidget *widget, gint response_id __maybe_unused, + gpointer data __maybe_unused) +{ + gtk_widget_set_visible(widget, FALSE); +} + GtkWidget *perf_gtk__setup_info_bar(void) { GtkWidget *info_bar; GtkWidget *label; - GtkWidget *content_area; info_bar = gtk_info_bar_new(); - gtk_widget_set_no_show_all(info_bar, TRUE); + gtk_widget_set_visible(info_bar, FALSE); label = gtk_label_new(""); - gtk_widget_show(label); + gtk_widget_set_visible(label, TRUE); - content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar)); - gtk_container_add(GTK_CONTAINER(content_area), label); + gtk_info_bar_add_child(GTK_INFO_BAR(info_bar), label); - gtk_info_bar_add_button(GTK_INFO_BAR(info_bar), GTK_STOCK_OK, + gtk_info_bar_add_button(GTK_INFO_BAR(info_bar), "_OK", GTK_RESPONSE_OK); g_signal_connect(info_bar, "response", - G_CALLBACK(gtk_widget_hide), NULL); + G_CALLBACK(perf_gtk__hide_widget), NULL); pgctx->info_bar = info_bar; pgctx->message_label = label; return info_bar; } -#endif GtkWidget *perf_gtk__setup_statusbar(void) { diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h index a2b497f03fd6..6b37fd9943c9 100644 --- a/tools/perf/ui/gtk/gtk.h +++ b/tools/perf/ui/gtk/gtk.h @@ -13,10 +13,8 @@ struct perf_gtk_context { GtkWidget *main_window; GtkWidget *notebook; -#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *info_bar; GtkWidget *message_label; -#endif GtkWidget *statbar; guint statbar_ctx_id; }; @@ -38,19 +36,15 @@ void perf_gtk__init_helpline(void); void gtk_ui_progress__init(void); void perf_gtk__init_hpp(void); -void perf_gtk__signal(int sig); +void perf_gtk__install_quit_signals(void); +void perf_gtk__fatal_signal(int sig); void perf_gtk__resize_window(GtkWidget *window); +void perf_gtk__run_main_loop(GtkWidget *window); +void perf_gtk__quit_main_loop(void); +void perf_gtk__quit_error_dialog(void); const char *perf_gtk__get_percent_color(double percent); GtkWidget *perf_gtk__setup_statusbar(void); - -#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *perf_gtk__setup_info_bar(void); -#else -static inline GtkWidget *perf_gtk__setup_info_bar(void) -{ - return NULL; -} -#endif struct evsel; struct evlist; diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index bae21f336ae6..80df3fec8ea1 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -8,6 +8,8 @@ #include "../helpline.h" #include "../string2.h" #include +#include +#include #include #include @@ -395,11 +397,9 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, } } - gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); - g_signal_connect(view, "row-activated", G_CALLBACK(on_row_activated), NULL); - gtk_container_add(GTK_CONTAINER(window), view); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window), view); } static void perf_gtk__add_hierarchy_entries(struct hists *hists, @@ -449,7 +449,7 @@ static void perf_gtk__add_hierarchy_entries(struct hists *hists, bf = hpp->buf; size = hpp->size; perf_hpp_list__for_each_format(he->hpp_list, fmt) { - int ret; + int ret, inc; if (fmt->color) ret = fmt->color(fmt, hpp, he); @@ -457,15 +457,26 @@ static void perf_gtk__add_hierarchy_entries(struct hists *hists, ret = fmt->entry(fmt, hpp, he); snprintf(hpp->buf + ret, hpp->size - ret, " "); - advance_hpp(hpp, ret + 2); + /* + * ret can be as large as hpp->size - 1, so ret + 2 + * can exceed hpp->size. advance_hpp() doesn't clamp, + * so passing that through would underflow the + * size_t hpp->size and let a later fmt->entry() in + * this loop write past the end of the caller's + * stack buffer. + */ + inc = ret + 2; + if (inc > (int)hpp->size) + inc = hpp->size; + advance_hpp(hpp, inc); } gtk_tree_store_set(store, &iter, col_idx, strim(bf), -1); - if (!he->leaf) { - hpp->buf = bf; - hpp->size = size; + hpp->buf = bf; + hpp->size = size; + if (!he->leaf) { perf_gtk__add_hierarchy_entries(hists, &he->hroot_out, store, &iter, hpp, min_pcnt); @@ -505,6 +516,7 @@ static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, GtkWidget *view; int col_idx; int nr_cols = 0; + int ret; char s[512]; char buf[512]; bool first_node, first_col; @@ -541,9 +553,10 @@ static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, /* construct merged column header since sort keys share single column */ buf[0] = '\0'; first_node = true; + ret = 0; list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { if (!first_node) - strcat(buf, " / "); + ret += scnprintf(buf + ret, sizeof(buf) - ret, " / "); first_node = false; first_col = true; @@ -552,11 +565,11 @@ static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, continue; if (!first_col) - strcat(buf, "+"); + ret += scnprintf(buf + ret, sizeof(buf) - ret, "+"); first_col = false; fmt->header(fmt, &hpp, hists, 0, NULL); - strcat(buf, strim(hpp.buf)); + ret += scnprintf(buf + ret, sizeof(buf) - ret, "%s", strim(hpp.buf)); } } @@ -583,11 +596,9 @@ static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, perf_gtk__add_hierarchy_entries(hists, &hists->entries, store, NULL, &hpp, min_pcnt); - gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); - g_signal_connect(view, "row-activated", G_CALLBACK(on_row_activated), NULL); - gtk_container_add(GTK_CONTAINER(window), view); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window), view); } int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, @@ -600,36 +611,32 @@ int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, GtkWidget *statbar; GtkWidget *window; - signal(SIGSEGV, perf_gtk__signal); - signal(SIGFPE, perf_gtk__signal); - signal(SIGINT, perf_gtk__signal); - signal(SIGQUIT, perf_gtk__signal); - signal(SIGTERM, perf_gtk__signal); + signal(SIGSEGV, perf_gtk__fatal_signal); + signal(SIGFPE, perf_gtk__fatal_signal); + perf_gtk__install_quit_signals(); - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(window), "perf report"); - g_signal_connect(window, "delete_event", gtk_main_quit, NULL); - pgctx = perf_gtk__activate_context(window); if (!pgctx) return -1; - vbox = gtk_vbox_new(FALSE, 0); + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); notebook = gtk_notebook_new(); - gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + gtk_widget_set_vexpand(notebook, TRUE); + gtk_box_append(GTK_BOX(vbox), notebook); info_bar = perf_gtk__setup_info_bar(); - if (info_bar) - gtk_box_pack_start(GTK_BOX(vbox), info_bar, FALSE, FALSE, 0); + gtk_box_append(GTK_BOX(vbox), info_bar); statbar = perf_gtk__setup_statusbar(); - gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); + gtk_box_append(GTK_BOX(vbox), statbar); - gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_window_set_child(GTK_WINDOW(window), vbox); evlist__for_each_entry(evlist, pos) { struct hists *hists = evsel__hists(pos); @@ -649,7 +656,7 @@ int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, } } - scrolled_window = gtk_scrolled_window_new(NULL, NULL); + scrolled_window = gtk_scrolled_window_new(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, @@ -665,15 +672,12 @@ int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); } - gtk_widget_show_all(window); - perf_gtk__resize_window(window); - - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_set_visible(window, TRUE); ui_helpline__push(help); - gtk_main(); + perf_gtk__run_main_loop(window); perf_gtk__deactivate_context(&pgctx); diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c index eea6fcde518a..770f9251b54b 100644 --- a/tools/perf/ui/gtk/progress.c +++ b/tools/perf/ui/gtk/progress.c @@ -1,49 +1,65 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include "gtk.h" #include "../progress.h" +#include static GtkWidget *dialog; static GtkWidget *progress; +static void gtk_ui_progress__destroyed(GtkWidget *widget __maybe_unused, + gpointer data __maybe_unused) +{ + dialog = NULL; + progress = NULL; +} + static void gtk_ui_progress__update(struct ui_progress *p) { double fraction = p->total ? 1.0 * p->curr / p->total : 0.0; char buf[1024]; if (dialog == NULL) { - GtkWidget *vbox = gtk_vbox_new(TRUE, 5); + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); GtkWidget *label = gtk_label_new(p->title); - dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog = gtk_window_new(); progress = gtk_progress_bar_new(); - gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 3); - gtk_box_pack_start(GTK_BOX(vbox), progress, TRUE, TRUE, 3); + gtk_widget_set_vexpand(label, TRUE); + gtk_box_append(GTK_BOX(vbox), label); + gtk_widget_set_vexpand(progress, TRUE); + gtk_box_append(GTK_BOX(vbox), progress); - gtk_container_add(GTK_CONTAINER(dialog), vbox); + gtk_window_set_child(GTK_WINDOW(dialog), vbox); + + g_signal_connect(dialog, "destroy", + G_CALLBACK(gtk_ui_progress__destroyed), NULL); gtk_window_set_title(GTK_WINDOW(dialog), "perf"); - gtk_window_resize(GTK_WINDOW(dialog), 300, 80); - gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 80); - gtk_widget_show_all(dialog); + gtk_widget_set_visible(dialog, TRUE); } gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction); snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, p->curr, p->total); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), buf); - /* we didn't call gtk_main yet, so do it manually */ - while (gtk_events_pending()) - gtk_main_iteration(); + /* we didn't start a main loop yet, so pump events manually */ + while (g_main_context_pending(NULL)) + g_main_context_iteration(NULL, FALSE); } static void gtk_ui_progress__finish(void) { + if (dialog == NULL) + return; + /* this will also destroy all of its children */ - gtk_widget_destroy(dialog); + gtk_window_destroy(GTK_WINDOW(dialog)); dialog = NULL; } diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c index f5eee4d66873..9b44f3719747 100644 --- a/tools/perf/ui/gtk/setup.c +++ b/tools/perf/ui/gtk/setup.c @@ -12,7 +12,7 @@ int perf_gtk__init(void) gtk_ui_progress__init(); perf_gtk__init_hpp(); - return gtk_init_check(NULL, NULL) ? 0 : -1; + return gtk_init_check() ? 0 : -1; } void perf_gtk__exit(bool wait_for_ok __maybe_unused) @@ -20,5 +20,6 @@ void perf_gtk__exit(bool wait_for_ok __maybe_unused) if (!perf_gtk__is_active_context(pgctx)) return; perf_error__unregister(&perf_gtk_eops); - gtk_main_quit(); + perf_gtk__quit_error_dialog(); + perf_gtk__quit_main_loop(); } diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c index c47f5c387838..5823ff0d7c2a 100644 --- a/tools/perf/ui/gtk/util.c +++ b/tools/perf/ui/gtk/util.c @@ -2,8 +2,10 @@ #include "../util.h" #include "gtk.h" +#include +#include #include -#include +#include #include struct perf_gtk_context *pgctx; @@ -28,86 +30,103 @@ int perf_gtk__deactivate_context(struct perf_gtk_context **ctx) return 0; } +/* + * perf_gtk__error() can be called re-entrantly, since the dialog isn't + * modal and its nested loop still pumps events for the main window. + * Track every currently running loop instead of a single pointer, so a + * nested call can't clobber an outer call's loop and leak it. + */ +static GSList *perf_gtk__error_loops; + +static void perf_gtk__quit_loop(gpointer data, gpointer user_data __maybe_unused) +{ + g_main_loop_quit(data); +} + +void perf_gtk__quit_error_dialog(void) +{ + g_slist_foreach(perf_gtk__error_loops, perf_gtk__quit_loop, NULL); +} + +static void perf_gtk__dialog_response(GtkDialog *dialog, + gint response_id __maybe_unused, + gpointer data __maybe_unused) +{ + gtk_window_destroy(GTK_WINDOW(dialog)); +} + static int perf_gtk__error(const char *format, va_list args) { char *msg; GtkWidget *dialog; + GMainLoop *loop; + va_list args_copy; + va_copy(args_copy, args); if (!perf_gtk__is_active_context(pgctx) || - vasprintf(&msg, format, args) < 0) { + vasprintf(&msg, format, args_copy) < 0) { + va_end(args_copy); fprintf(stderr, "Error:\n"); vfprintf(stderr, format, args); fprintf(stderr, "\n"); return -1; } + va_end(args_copy); dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(pgctx->main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Error\n\n%s", msg); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); + /* + * "response" only fires when a button is clicked; DESTROY_WITH_PARENT + * destroys the dialog directly without it. Quit from "destroy" + * instead, which fires either way, so the nested loop below can't + * outlive the dialog and hang. + */ + loop = g_main_loop_new(NULL, FALSE); + perf_gtk__error_loops = g_slist_prepend(perf_gtk__error_loops, loop); + g_signal_connect(dialog, "response", + G_CALLBACK(perf_gtk__dialog_response), NULL); + g_signal_connect_swapped(dialog, "destroy", + G_CALLBACK(g_main_loop_quit), loop); + + gtk_widget_set_visible(dialog, TRUE); + g_main_loop_run(loop); + perf_gtk__error_loops = g_slist_remove(perf_gtk__error_loops, loop); + g_main_loop_unref(loop); + free(msg); return 0; } -#ifdef HAVE_GTK_INFO_BAR_SUPPORT static int perf_gtk__warning_info_bar(const char *format, va_list args) { char *msg; + va_list args_copy; + va_copy(args_copy, args); if (!perf_gtk__is_active_context(pgctx) || - vasprintf(&msg, format, args) < 0) { + vasprintf(&msg, format, args_copy) < 0) { + va_end(args_copy); fprintf(stderr, "Warning:\n"); vfprintf(stderr, format, args); fprintf(stderr, "\n"); return -1; } + va_end(args_copy); gtk_label_set_text(GTK_LABEL(pgctx->message_label), msg); gtk_info_bar_set_message_type(GTK_INFO_BAR(pgctx->info_bar), GTK_MESSAGE_WARNING); - gtk_widget_show(pgctx->info_bar); + gtk_widget_set_visible(pgctx->info_bar, TRUE); free(msg); return 0; } -#else -static int perf_gtk__warning_statusbar(const char *format, va_list args) -{ - char *msg, *p; - - if (!perf_gtk__is_active_context(pgctx) || - vasprintf(&msg, format, args) < 0) { - fprintf(stderr, "Warning:\n"); - vfprintf(stderr, format, args); - fprintf(stderr, "\n"); - return -1; - } - - gtk_statusbar_pop(GTK_STATUSBAR(pgctx->statbar), - pgctx->statbar_ctx_id); - - /* Only first line can be displayed */ - p = strchr(msg, '\n'); - if (p) - *p = '\0'; - - gtk_statusbar_push(GTK_STATUSBAR(pgctx->statbar), - pgctx->statbar_ctx_id, msg); - - free(msg); - return 0; -} -#endif struct perf_error_ops perf_gtk_eops = { .error = perf_gtk__error, -#ifdef HAVE_GTK_INFO_BAR_SUPPORT .warning = perf_gtk__warning_info_bar, -#else - .warning = perf_gtk__warning_statusbar, -#endif }; diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index e58327595d37..a61c4b1816e1 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -57,8 +57,9 @@ struct hpp_fmt_value { }; static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, - hpp_field_fn get_field, const char *fmt, int len, - hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype) + hpp_field_fn get_field, const char *fmtstr, int len, + hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype, + struct perf_hpp_fmt *fmt __maybe_unused) { int ret = 0; struct hists *hists = he->hists; @@ -98,13 +99,50 @@ static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, } } + /* Note, merge_entries implies !symbol_conf.report_hierarchy. */ + if (he->hists->merge_entries) { + u64 total_val = 0; + u64 total_samples = 0; + u64 total_period = 0; + + for (i = 0; i < nr_members; i++) { + struct evsel *member_evsel = hists_to_evsel(values[i].hists); + + struct evsel *he_evsel = hists_to_evsel(he->hists); + + if (member_evsel != he_evsel && + member_evsel->first_wildcard_match != he_evsel) + continue; + + total_val += values[i].val; + total_samples += values[i].samples; + total_period += fmtype == PERF_HPP_FMT_TYPE__PERCENT ? + hists__total_period(values[i].hists) : + hists__total_latency(values[i].hists); + } + + if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) { + double percent = 0.0; + + if (total_period) + percent = 100.0 * total_val / total_period; + ret += hpp__call_print_fn(hpp, print_fn, fmtstr, len, percent); + } else if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) { + double avg = total_samples ? (1.0 * total_val / total_samples) : 0; + + ret += hpp__call_print_fn(hpp, print_fn, fmtstr, len, avg); + } else { + ret += hpp__call_print_fn(hpp, print_fn, fmtstr, len, total_val); + } + } + for (i = 0; i < nr_members; i++) { if (symbol_conf.skip_empty && values[i].hists->stats.nr_samples == 0) continue; ret += __hpp__fmt_print(hpp, values[i].hists, values[i].val, - values[i].samples, fmt, len, + values[i].samples, fmtstr, len, print_fn, fmtype); } @@ -129,7 +167,7 @@ int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, if (symbol_conf.field_sep) { return __hpp__fmt(hpp, he, get_field, fmtstr, 1, - print_fn, fmtype); + print_fn, fmtype, fmt); } if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) @@ -137,7 +175,7 @@ int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, else len -= 1; - return __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmtype); + return __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmtype, fmt); } int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, @@ -287,6 +325,35 @@ static int __hpp__sort(struct hist_entry *a, struct hist_entry *b, return __hpp__group_sort_idx(a, b, get_field, symbol_conf.group_sort_idx); } + /* + * Relies on merge_entries being only enabled if there are + * only matching events. If that is ever relaxed will need + * more logic here. merge_entries also implies that + * symbol_conf.report_hierarchy is false. + */ + if (a->hists->merge_entries && b->hists->merge_entries) { + u64 val_a = get_field(a), val_b = get_field(b); + struct hist_entry *pair; + struct evsel *evsel_a = hists_to_evsel(a->hists); + struct evsel *evsel_b = hists_to_evsel(b->hists); + + list_for_each_entry(pair, &a->pairs.head, pairs.node) { + struct evsel *pair_evsel = hists_to_evsel(pair->hists); + + if (pair_evsel->first_wildcard_match == evsel_a) + val_a += get_field(pair); + } + list_for_each_entry(pair, &b->pairs.head, pairs.node) { + struct evsel *pair_evsel = hists_to_evsel(pair->hists); + + if (pair_evsel->first_wildcard_match == evsel_b) + val_b += get_field(pair); + } + + ret = field_cmp(val_a, val_b); + if (ret) + return ret; + } ret = field_cmp(get_field(a), get_field(b)); if (ret || !symbol_conf.event_group) @@ -323,7 +390,29 @@ static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b, /* * Put caller above callee when they have equal period. */ - ret = field_cmp(get_field(a), get_field(b)); + if (a->hists->merge_entries && b->hists->merge_entries) { + u64 val_a = get_field(a), val_b = get_field(b); + struct hist_entry *pair; + struct evsel *evsel_a = hists_to_evsel(a->hists); + struct evsel *evsel_b = hists_to_evsel(b->hists); + + list_for_each_entry(pair, &a->pairs.head, pairs.node) { + struct evsel *pair_evsel = hists_to_evsel(pair->hists); + + if (pair_evsel->first_wildcard_match == evsel_a) + val_a += get_field(pair); + } + list_for_each_entry(pair, &b->pairs.head, pairs.node) { + struct evsel *pair_evsel = hists_to_evsel(pair->hists); + + if (pair_evsel->first_wildcard_match == evsel_b) + val_b += get_field(pair); + } + + ret = field_cmp(val_a, val_b); + } else { + ret = field_cmp(get_field(a), get_field(b)); + } if (ret) return ret; @@ -386,6 +475,8 @@ static int hpp__width_fn(struct perf_hpp_fmt *fmt, evsel__hists(pos)->stats.nr_samples) nr++; } + if (hists->merge_entries) + nr++; /* Add 1 extra unit of width generically for the 'Total' */ len = max(len, nr * fmt->len); } @@ -403,8 +494,38 @@ static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, int len = hpp__width_fn(fmt, hpp, hists); const char *hdr = ""; - if (line == hists->hpp_list->nr_header_lines - 1) + if (line == hists->hpp_list->nr_header_lines - 1) { hdr = fmt->name; + if (hists->merge_entries) { + int w = 0; + int f_len = fmt->user_len ?: fmt->len; + struct evsel *pos, *evsel = hists_to_evsel(hists); + int string_len = f_len; + + for_each_group_evsel(pos, evsel) { + if (symbol_conf.skip_empty && + evsel__hists(pos)->stats.nr_samples == 0) + continue; + string_len += f_len; + } + + if (len > string_len) { + w += scnprintf(hpp->buf + w, hpp->size - w, "%*s", + len - string_len, ""); + } + + w += scnprintf(hpp->buf + w, hpp->size - w, "%*.*s", + f_len, f_len, fmt->name); + for_each_group_evsel(pos, evsel) { + if (symbol_conf.skip_empty && + evsel__hists(pos)->stats.nr_samples == 0) + continue; + w += scnprintf(hpp->buf + w, hpp->size - w, " %*.*s", + f_len - 1, f_len - 1, evsel__name(pos)); + } + return w; + } + } return scnprintf(hpp->buf, hpp->size, "%*s", len, hdr); } @@ -431,7 +552,7 @@ static int hpp__header_mem_stat_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hp int left, right; len = 0; - /* update fmt->len for acutally used columns only */ + /* update fmt->len for actually used columns only */ for (int i = 0; i < MEM_STAT_LEN; i++) { if (hists->mem_stat_total[mem_stat_idx].entries[i]) len += MEM_STAT_PRINT_LEN; @@ -1271,3 +1392,114 @@ int perf_hpp__alloc_mem_stats(struct perf_hpp_list *list, struct evlist *evlist) } return 0; } + +float hist_entry__get_percent_limit_merged(struct hist_entry *he) +{ + struct hist_entry *pair; + u64 period = he->stat.period; + u64 total_period = hists__total_period(he->hists); + struct evsel *evsel = hists_to_evsel(he->hists); + struct evsel *pos; + + /* Accumulate global total_period across all merged hists matching hybrid type */ + for_each_group_member(pos, evsel) { + if (pos->first_wildcard_match == evsel) + total_period += hists__total_period(evsel__hists(pos)); + } + + if (unlikely(total_period == 0)) + return 0; + + if (symbol_conf.cumulate_callchain) { + period = he->stat_acc->period; + list_for_each_entry(pair, &he->pairs.head, pairs.node) { + struct evsel *pair_evsel = hists_to_evsel(pair->hists); + + if (pair_evsel->first_wildcard_match == evsel) + period += pair->stat_acc->period; + } + } else { + /* Accumulate symbol specific period across pairs matching hybrid type */ + list_for_each_entry(pair, &he->pairs.head, pairs.node) { + struct evsel *pair_evsel = hists_to_evsel(pair->hists); + + if (pair_evsel->first_wildcard_match == evsel) + period += pair->stat.period; + } + } + + return period * 100.0 / total_period; +} + +void evlist__merge_hists_hybrid(struct evlist *evlist, bool refresh_hists) +{ + struct evsel *pos; + struct evsel *member; + bool hybrid_group; + + /* + * Merged hists display all the events of a group in a single set of + * entries, which the hierarchy display has no way to render. Keeping + * hists->merge_entries false here means the rest of the display code + * can assume merge_entries implies !symbol_conf.report_hierarchy. + */ + if (symbol_conf.report_hierarchy) + return; + + /* Set merge_entries flag strictly on leaders formulated by hybrid topology */ + evlist__for_each_entry(evlist, pos) { + if (evsel__is_dummy_event(pos)) + continue; + + if (pos->core.leader == &pos->core && pos->core.nr_members > 1) { + hybrid_group = false; + + if (pos->first_wildcard_match || pos->merged_hybrid_group) { + hybrid_group = true; + } else { + for_each_group_member(member, pos) { + if (member->first_wildcard_match || + member->merged_hybrid_group) { + hybrid_group = true; + break; + } + } + } + + if (hybrid_group) { + evsel__hists(pos)->merge_entries = true; + symbol_conf.event_group = true; + symbol_conf.hybrid_merge = true; + if (!pos->group_name) { + pos->group_name = strdup("Merged hybrid events"); + if (!pos->group_name) + pr_warning("Failed to allocate hybrid group name\n"); + } + } + } + } + + if (!refresh_hists) + return; + + evlist__for_each_entry(evlist, pos) { + /* Match histograms dynamically since parsing happened before group toggling */ + if (symbol_conf.event_group && !evsel__is_group_leader(pos)) { + struct hists *leader_hists = evsel__hists(evsel__leader(pos)); + struct hists *hists = evsel__hists(pos); + + hists__match(leader_hists, hists); + hists__link(leader_hists, hists); + } + } + + /* Now that links are formed, safely resort the active tree so the UI renders accurately */ + if (symbol_conf.event_group) { + evlist__for_each_entry(evlist, pos) { + if (evsel__is_dummy_event(pos) || !evsel__is_group_leader(pos)) + continue; + if (pos->core.nr_members > 1 && evsel__hists(pos)->merge_entries) + hists__output_resort(evsel__hists(pos), NULL); + } + } +} diff --git a/tools/perf/ui/libslang.h b/tools/perf/ui/libslang.h index 6722561e0458..dd6b9f192155 100644 --- a/tools/perf/ui/libslang.h +++ b/tools/perf/ui/libslang.h @@ -15,7 +15,9 @@ #define ENABLE_SLFUTURE_CONST 1 #define ENABLE_SLFUTURE_VOID 1 +#ifdef HAVE_SLANG_SUPPORT #include +#endif #define SL_KEY_UNTAB 0x1000 diff --git a/tools/perf/ui/progress.c b/tools/perf/ui/progress.c index 99d60223c74b..362680989ace 100644 --- a/tools/perf/ui/progress.c +++ b/tools/perf/ui/progress.c @@ -13,6 +13,12 @@ static struct ui_progress_ops null_progress__ops = struct ui_progress_ops *ui_progress__ops = &null_progress__ops; +/* Everything counts but nothing is shown, the way it starts out. */ +void ui_progress__noop_init(void) +{ + ui_progress__ops = &null_progress__ops; +} + void ui_progress__update(struct ui_progress *p, u64 adv) { u64 last = p->curr; diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h index 4f52c37b2f09..e8c4f9f768aa 100644 --- a/tools/perf/ui/progress.h +++ b/tools/perf/ui/progress.h @@ -23,6 +23,10 @@ void __ui_progress__init(struct ui_progress *p, u64 total, void ui_progress__update(struct ui_progress *p, u64 adv); +void stdio_progress__init(void); + +void ui_progress__noop_init(void); + struct ui_progress_ops { void (*init)(struct ui_progress *p); void (*update)(struct ui_progress *p); diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c index ff800047e697..d887346c7a63 100644 --- a/tools/perf/ui/setup.c +++ b/tools/perf/ui/setup.c @@ -14,7 +14,7 @@ int use_browser = -1; #define PERF_GTK_DSO "libperf-gtk.so" -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT static int setup_gtk_browser(void) { diff --git a/tools/perf/ui/stdio/progress.c b/tools/perf/ui/stdio/progress.c new file mode 100644 index 000000000000..57f615c455b0 --- /dev/null +++ b/tools/perf/ui/stdio/progress.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Progress feedback for the stdio (non-TUI/GTK) case, enabled with + * 'perf report --progress'. + */ +#include +#include +#include +#include +#include +#include "../../util/debug.h" +#include "../../util/units.h" +#include "../progress.h" + +/* + * Phases can be nested, so keep track of the ones started so far to be + * able to complete the right one on ui_progress__finish(), which gets + * no arguments. + */ +#define STDIO_PROGRESS__MAX_DEPTH 8 + +struct stdio_progress_phase { + struct ui_progress *p; + u64 last_printed; + size_t last_len; +}; + +static struct stdio_progress_phase stdio_progress__stack[STDIO_PROGRESS__MAX_DEPTH]; +static int stdio_progress__depth; +static FILE *stdio_progress__out; +static bool stdio_progress__is_tty; +/* Phases that didn't fit on the stack are not shown. */ +static int stdio_progress__dropped; + +static void stdio_progress__print_phase(struct stdio_progress_phase *phase, + u64 curr) +{ + struct ui_progress *p = phase->p; + char buf_cur[20], buf_tot[20], buf[128]; + double percent = p->total ? 100.0 * (double)curr / (double)p->total : 0.0; + size_t len; + + /* + * Only the completion line shows 100.0%: a 99.99% progress would round + * up to it and look like a duplicate at finish time. + */ + if (curr < p->total && percent > 99.9) + percent = 99.9; + + if (p->size) { + unit_number__scnprintf(buf_cur, sizeof(buf_cur), curr); + unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total); + len = scnprintf(buf, sizeof(buf), "%s [%5.1f%%] %s / %s", + p->title, percent, buf_cur, buf_tot); + } else { + len = scnprintf(buf, sizeof(buf), "%s [%5.1f%%] %" PRIu64 " / %" PRIu64, + p->title, percent, curr, p->total); + } + + if (!stdio_progress__is_tty) { + fprintf(stdio_progress__out, "%s\n", buf); + goto out; + } + + /* Pad to the length of the previous line to erase its leftovers. */ + fprintf(stdio_progress__out, "\r%s%*s", buf, + (int)(len < phase->last_len ? phase->last_len - len : 0), ""); + phase->last_len = len; +out: + phase->last_printed = curr; + fflush(stdio_progress__out); +} + +static void __stdio_progress__init(struct ui_progress *p) +{ + /* The default step is meant for the TUI bar, use 1% steps for stdio. */ + p->next = p->step = p->total / 100 ?: 1; + + if (stdio_progress__depth == STDIO_PROGRESS__MAX_DEPTH) { + /* + * Out of room: don't start this phase, its finish() is + * swallowed and its updates ignored below. + */ + pr_warning("progress phases nested deeper than %d, not showing progress for %s\n", + STDIO_PROGRESS__MAX_DEPTH, p->title); + stdio_progress__dropped++; + return; + } + + /* Start a nested phase in a line of its own. */ + if (stdio_progress__depth && stdio_progress__is_tty) + fputc('\n', stdio_progress__out); + + stdio_progress__stack[stdio_progress__depth++] = + (struct stdio_progress_phase) { + .p = p, + .last_printed = 0, + .last_len = 0, + }; + + stdio_progress__print_phase(&stdio_progress__stack[stdio_progress__depth - 1], + p->curr); +} + +static void stdio_progress__update(struct ui_progress *p) +{ + /* + * An update that doesn't match the innermost phase means something + * is out of sync: print nothing rather than another phase's + * numbers, or read past the stack. + */ + if (!stdio_progress__depth || + stdio_progress__stack[stdio_progress__depth - 1].p != p) + return; + + stdio_progress__print_phase(&stdio_progress__stack[stdio_progress__depth - 1], + p->curr); +} + +static void stdio_progress__finish(void) +{ + struct stdio_progress_phase *phase; + + /* + * Being the innermost phase, its finish() comes first: swallow it, or + * it would complete the phase that encloses it. + */ + if (stdio_progress__dropped) { + stdio_progress__dropped--; + return; + } + + if (!stdio_progress__depth) + return; + + phase = &stdio_progress__stack[--stdio_progress__depth]; + + /* + * The last line may have stopped short of the total, close this phase + * showing it as complete unless that was already printed. + */ + if (phase->last_printed != phase->p->total) + stdio_progress__print_phase(phase, phase->p->total); + + phase->last_printed = 0; + phase->last_len = 0; + + if (stdio_progress__is_tty) + fputc('\n', stdio_progress__out); + + fflush(stdio_progress__out); +} + +static struct ui_progress_ops stdio_progress__ops = { + .init = __stdio_progress__init, + .update = stdio_progress__update, + .finish = stdio_progress__finish, +}; + +void stdio_progress__init(void) +{ + if (pager_in_use()) { + /* stderr leads to the pager, the updates go to the terminal. */ + stdio_progress__out = fopen("/dev/tty", "w"); + } + if (!stdio_progress__out) + stdio_progress__out = stderr; + stdio_progress__is_tty = isatty(fileno(stdio_progress__out)) == 1; + ui_progress__ops = &stdio_progress__ops; +} diff --git a/tools/perf/util/Build b/tools/perf/util/Build index b26a0b1ddfa3..2c1f880c4c47 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -54,7 +54,6 @@ perf-util-y += string.o perf-util-y += strlist.o perf-util-y += strfilter.o perf-util-y += top.o -perf-util-y += usage.o perf-util-y += dso.o perf-util-y += dsos.o perf-util-y += symbol.o @@ -96,7 +95,6 @@ perf-util-y += tool_pmu.o perf-util-y += tp_pmu.o perf-util-y += svghelper.o perf-util-y += trace-event-info.o -perf-util-y += trace-event-scripting.o perf-util-$(CONFIG_LIBTRACEEVENT) += trace-event.o perf-util-$(CONFIG_LIBTRACEEVENT) += trace-event-parse.o perf-util-$(CONFIG_LIBTRACEEVENT) += trace-event-read.o @@ -237,8 +235,6 @@ endif perf-util-y += data-convert-json.o -perf-util-y += scripting-engines/ - perf-util-$(CONFIG_ZLIB) += zlib.o perf-util-$(CONFIG_LZMA) += lzma.o perf-util-$(CONFIG_ZSTD) += zstd.o @@ -340,7 +336,7 @@ bison_flags := -DYYENABLE_NLS=0 -Wno-unused-but-set-variable # Old clangs don't grok -Wno-unused-but-set-variable, remove it ifeq ($(CC_NO_CLANG), 0) - CLANG_VERSION := $(shell $(CLANG) --version | head -1 | sed 's/.*clang version \([[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+\).*/\1/g') + CLANG_VERSION := $(shell $(CC) --version | head -1 | sed 's/.*clang version \([[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+\).*/\1/g') ifeq ($(call version-lt3,$(CLANG_VERSION),13.0.0),1) bison_flags := $(subst -Wno-unused-but-set-variable,,$(bison_flags)) flex_flags := $(subst -Wno-unused-but-set-variable,,$(flex_flags)) @@ -427,9 +423,11 @@ endif $(OUTPUT)%.mypy_log: % $(call rule_mkdir) - $(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false) + $(Q)$(call echo-cmd,test)$(MYPY) "$<" > $@ || (cat $@ && rm $@ && false) -perf-util-y += $(MYPY_TEST_LOGS) +.PHONY: mypy +mypy: $(addprefix $(OUTPUT)util/,$(MYPY_TEST_LOGS)) + @: ifdef PYLINT PYLINT_TEST_LOGS := $(PY_TESTS:%=%.pylint_log) @@ -439,9 +437,11 @@ endif $(OUTPUT)%.pylint_log: % $(call rule_mkdir) - $(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false) + $(Q)$(call echo-cmd,test)$(PYLINT) "$<" > $@ || (cat $@ && rm $@ && false) -perf-util-y += $(PYLINT_TEST_LOGS) +.PHONY: pylint +pylint: $(addprefix $(OUTPUT)util/,$(PYLINT_TEST_LOGS)) + @: ifeq ($(CONFIG_PERF_BPF_SKEL),y) include $(srctree)/tools/perf/bpf_skel.mak diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c index 4b0d349ed334..960dd48701bb 100644 --- a/tools/perf/util/addr2line.c +++ b/tools/perf/util/addr2line.c @@ -15,6 +15,7 @@ #include #include #include +#include #define MAX_INLINE_NEST 1024 @@ -284,7 +285,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, struct inline_node *node, struct symbol *sym __maybe_unused) { - struct child_process *a2l = dso__a2l(dso); + struct child_process *a2l; char *record_function = NULL; char *record_filename = NULL; unsigned int record_line_nr = 0; @@ -296,24 +297,42 @@ int cmd__addr2line(const char *dso_name, u64 addr, ssize_t written; struct io io = { .eof = false }; enum cmd_a2l_style cmd_a2l_style; + const char *current_dso_name; + bool a2l_synced = false; + + mutex_lock(dso__lock(dso)); + current_dso_name = dso__symsrc_filename(dso) ?: dso_name; + a2l = dso__a2l(dso); if (!a2l) { - if (!filename__has_section(dso_name, ".debug_line")) - goto out; + if (!filename__has_section(current_dso_name, ".debug_line")) + goto out_unlock; dso__set_a2l(dso, - addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name)); + addr2line_subprocess_init(symbol_conf.addr2line_path, + current_dso_name)); a2l = dso__a2l(dso); } if (a2l == NULL) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); - goto out; + ret = -1; + goto out_unlock; } cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name); - if (cmd_a2l_style == BROKEN) - goto out; + if (cmd_a2l_style == BROKEN) { + ret = -1; + goto out_unlock; + } + + /* + * Take ownership of the a2l subprocess so we can safely perform + * blocking IPC without holding the dso lock. If another thread + * resolves a symbol concurrently, it will spawn a new a2l process. + */ + dso__set_a2l(dso, NULL); + mutex_unlock(dso__lock(dso)); /* * Send our request and then *deliberately* send something that can't be @@ -329,6 +348,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (written != len) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not send request\n", __func__, dso_name); + ret = -1; goto out; } io__init(&io, a2l->out, buf, sizeof(buf)); @@ -338,6 +358,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, case -1: if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not read first record\n", __func__, dso_name); + ret = -1; goto out; case 0: /* @@ -358,6 +379,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, break; case 0: /* The sentinel as expected. */ + a2l_synced = true; break; default: if (!symbol_conf.addr2line_disable_warn) @@ -373,17 +395,23 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (file) { *file = strdup(record_filename); - ret = 1; + if (!*file) { + ret = -ENOMEM; + goto out; + } } if (line_nr) *line_nr = record_line_nr; + ret = 1; if (unwind_inlines) { if (node && inline_list__append_record(dso, node, sym, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + ret = -ENOMEM; goto out; } } @@ -406,20 +434,34 @@ int cmd__addr2line(const char *dso_name, u64 addr, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + inline_node__clear_frames(node); + ret = -ENOMEM; goto out; } ret = 1; /* found at least one inline frame */ } } + if (record_status == 0) + a2l_synced = true; out: + mutex_lock(dso__lock(dso)); free(record_function); free(record_filename); - if (io.eof) { - dso__set_a2l(dso, NULL); + + current_dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso); + if (a2l_synced && !io.eof && dso__a2l(dso) == NULL && current_dso_name && + !strcmp(current_dso_name, dso_name)) + dso__set_a2l(dso, a2l); + else addr2line_subprocess_cleanup(a2l); - } + mutex_unlock(dso__lock(dso)); + return ret; + +out_unlock: + mutex_unlock(dso__lock(dso)); return ret; } diff --git a/tools/perf/util/annotate-arch/Build b/tools/perf/util/annotate-arch/Build index 23316743fdc5..0a74e1a9f17b 100644 --- a/tools/perf/util/annotate-arch/Build +++ b/tools/perf/util/annotate-arch/Build @@ -1,3 +1,4 @@ +perf-util-y += annotate-alpha.o perf-util-y += annotate-arc.o perf-util-y += annotate-arm.o perf-util-y += annotate-arm64.o diff --git a/tools/perf/util/annotate-arch/annotate-alpha.c b/tools/perf/util/annotate-arch/annotate-alpha.c new file mode 100644 index 000000000000..ccb13e2338a2 --- /dev/null +++ b/tools/perf/util/annotate-arch/annotate-alpha.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include "../../util/disasm.h" +#include "../../util/map.h" +#include "../../util/maps.h" +#include "../../util/symbol.h" +#include "../../util/thread.h" + +/* + * Alpha control-transfer instructions, as printed by objdump: + * + * PC-relative (opcode group 0x30-0x3f), 21-bit displacement: + * br, bsr unconditional / to-subroutine + * beq bne blt ble bgt bge blbc blbs integer conditional + * fbeq fbne fblt fble fbgt fbge floating conditional + * + * Register-indirect (JSR group, opcode 0x1a): + * jmp, jsr, ret, jcr + * + * bsr/jsr (and jcr, the coroutine form, which binutils prints in preference to + * the jsr_coroutine spelling) save a return address, so they are calls; ret + * returns; everything else that transfers control is a jump. + * + * Alpha has no machine "mov"; objdump prints "mov"/"fmov" as pseudos for + * bis/cpys, so map them to mov_ops when present. The no-ops are deliberately + * left alone: nop_ops would let delete_last_nop() trim the padding gcc leaves + * at the end of a function, but its scnprintf() prints the literal "nop", and + * Alpha pads with unop (ldq_u $31) rather than nop. + */ + +/* + * The generic call__parse() expects the target address to be the first thing + * in the operand string, but a bsr prints its return-address register first: + * + * bsr t0,fffffc0001031dc0 + * + * so take the address from after the comma. Without this the address comes + * out as 0, and neither the callee symbol nor the annotation browser's + * "go to target" work. + */ +static int alpha_call__parse(const struct arch *arch, struct ins_operands *ops, + struct map_symbol *ms, + struct disasm_line *dl __maybe_unused) +{ + char *endptr, *tok, *name; + struct map *map = ms->map; + struct addr_map_symbol target; + + tok = strchr(ops->raw, ','); + if (tok == NULL) + return -1; + + ops->target.addr = strtoull(tok + 1, &endptr, 16); + if (endptr == tok + 1) + return -1; + + /* A stripped object has no "" to name the target with. */ + name = strchr(endptr, '<'); + if (name == NULL) + goto find_target; + + name++; + + if (arch->objdump.skip_functions_char && + strchr(name, arch->objdump.skip_functions_char)) + return -1; + + tok = strchr(name, '>'); + if (tok == NULL) + return -1; + + *tok = '\0'; + ops->target.name = strdup(name); + *tok = '>'; + + if (ops->target.name == NULL) + return -1; + +find_target: + target = (struct addr_map_symbol) { + .ms = { .map = map__get(map), }, + .addr = map__objdump_2mem(map, ops->target.addr), + }; + + if (maps__find_ams(thread__maps(ms->thread), &target) == 0 && + map__rip_2objdump(target.ms.map, + map__map_ip(target.ms.map, target.addr)) == ops->target.addr) + ops->target.sym = target.ms.sym; + + addr_map_symbol__exit(&target); + return 0; +} + +static const struct ins_ops alpha_call_ops = { + .parse = alpha_call__parse, + .scnprintf = call__scnprintf, + .is_call = true, +}; + +/* + * jsr and jmp transfer control to a register, and their trailing operand is + * only a branch prediction hint: + * + * jsr ra,(t12),fffffc0001014ee8 <_printk> + * + * binutils extracts that hint as a 14-bit signed field scaled by four and + * prints it relative to the next instruction (extract_jhint() in alpha-opc.c, + * print_insn_alpha() in alpha-dis.c), so it can name the callee only when the + * callee lies within the resulting +-32KB. It also defaults to zero, which + * prints as the next instruction. Most hints are therefore not the callee at + * all, and parsing one would invent a call target, so these resolve no target + * and keep their operands, as an indirect call does elsewhere. The hint on + * jcr is not even an address. + */ +static const struct ins_ops alpha_indirect_call_ops = { + .scnprintf = ins__raw_scnprintf, + .is_call = true, +}; + +static const struct ins_ops alpha_indirect_jump_ops = { + .scnprintf = ins__raw_scnprintf, + .is_jump = true, +}; + +static int is_alpha_cond_branch(const char *name) +{ + static const char *const branches[] = { + "beq", "bne", "blt", "ble", "bgt", "bge", "blbc", "blbs", + "fbeq", "fbne", "fblt", "fble", "fbgt", "fbge", + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(branches); i++) { + if (!strcmp(name, branches[i])) + return 1; + } + return 0; +} + +static const struct ins_ops *alpha__associate_instruction_ops(struct arch *arch, const char *name) +{ + const struct ins_ops *ops = NULL; + + if (!strcmp(name, "bsr")) { + ops = &alpha_call_ops; + } else if (!strcmp(name, "jsr") || + !strcmp(name, "jcr")) { + ops = &alpha_indirect_call_ops; + } else if (!strcmp(name, "ret")) { + ops = &ret_ops; + } else if (!strcmp(name, "jmp")) { + ops = &alpha_indirect_jump_ops; + } else if (!strcmp(name, "br") || + is_alpha_cond_branch(name)) { + ops = &jump_ops; + } else if (!strcmp(name, "mov") || + !strcmp(name, "fmov")) { + ops = &mov_ops; + } + + if (ops) + arch__associate_ins_ops(arch, name, ops); + + return ops; +} + +const struct arch *arch__new_alpha(const struct e_machine_and_e_flags *id, + const char *cpuid __maybe_unused) +{ + struct arch *arch = zalloc(sizeof(*arch)); + + if (!arch) + return NULL; + + arch->name = "alpha"; + arch->id = *id; + arch->associate_instruction_ops = alpha__associate_instruction_ops; + /* objdump emits no comments for Alpha; '#' is what the assembler uses. */ + arch->objdump.comment_char = '#'; + return arch; +} diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c index 33080fdca125..81369ae47290 100644 --- a/tools/perf/util/annotate-arch/annotate-arm64.c +++ b/tools/perf/util/annotate-arch/annotate-arm64.c @@ -1,72 +1,306 @@ // SPDX-License-Identifier: GPL-2.0 #include #include +#include #include #include +#include +#include +#include #include #include #include "../annotate.h" #include "../disasm.h" +#include "../annotate-data.h" +#include "../debug.h" +#include "../dso.h" +#include "../map.h" +#include "../symbol.h" +#include "../strbuf.h" struct arch_arm64 { struct arch arch; regex_t call_insn; regex_t jump_insn; + regex_t ldst_insn; /* load and store instruction */ }; -static int arm64_mov__parse(const struct arch *arch __maybe_unused, +static bool arm64__is_reg(const char *op) +{ + if (!op || !*op) + return false; + + /* + * General-purpose registers: x0-x30, w0-w30. + * Check for 'x' or 'w' prefix followed by a numeric index. + */ + if ((op[0] == 'x' || op[0] == 'w') && isdigit(op[1])) + return true; + + /* Special-purpose registers: sp, wzr, xzr. */ + if (!strncmp(op, "sp", 2) || !strncmp(op, "xzr", 3) || + !strncmp(op, "wzr", 3)) + return true; + + /* TODO: Support more registers. */ + return false; +} + +static bool arm64__check_multi_regs(const struct arch *arch, const char *op) +{ + const char *p = op; + int reg_count = 0; + + while (p && *p) { + p = skip_spaces(p); + if (*p == arch->objdump.memory_ref_char) + p++; + + if (arm64__is_reg(p)) + reg_count++; + + if (reg_count >= 2) + return true; + + /* Move to next operand after comma */ + p = strchr(p, ','); + if (p) + p++; + } + + return false; +} + +/* + * Duplicate @insn, stripping the comment and trailing whitespace. + * Returns a newly allocated string which the caller must free(), + * or NULL on allocation failure or if @insn is NULL. + */ +static char *rstrip_space_and_comment(const char *insn, char comment_char) +{ + const char *end, *comment; + size_t len; + char *result; + + if (insn == NULL) + return NULL; + + comment = strchr(insn, comment_char); + if (comment != NULL) + end = comment; + else + end = insn + strlen(insn); + + while (end > insn && isspace(end[-1])) + --end; + + len = end - insn; + result = malloc(len + 1); + if (result == NULL) + return NULL; + + memcpy(result, insn, len); + result[len] = '\0'; + + return result; +} + +static int arm64_mov__parse(const struct arch *arch, struct ins_operands *ops, struct map_symbol *ms __maybe_unused, struct disasm_line *dl __maybe_unused) { - char *s = strchr(ops->raw, ','), *target, *endptr; + char *s = strchr(ops->raw, ','), *source, *endptr; if (s == NULL) return -1; + /* Parse target */ *s = '\0'; - ops->source.raw = strdup(ops->raw); + ops->target.raw = strdup(ops->raw); *s = ','; - if (ops->source.raw == NULL) + if (ops->target.raw == NULL) return -1; - target = ++s; - ops->target.raw = strdup(target); - if (ops->target.raw == NULL) - goto out_free_source; + ops->target.multi_regs = arm64__check_multi_regs(arch, ops->target.raw); - ops->target.addr = strtoull(target, &endptr, 16); - if (endptr == target) - goto out_free_target; + /* Parse source, stripping comment if present */ + source = skip_spaces(++s); + ops->source.raw = rstrip_space_and_comment(source, arch->objdump.comment_char); + if (ops->source.raw == NULL) { + zfree(&ops->target.raw); + return -1; + } - s = strchr(endptr, '<'); - if (s == NULL) - goto out_free_target; - endptr = strchr(s + 1, '>'); - if (endptr == NULL) - goto out_free_target; + ops->source.multi_regs = arm64__check_multi_regs(arch, ops->source.raw); - *endptr = '\0'; - *s = ' '; - ops->target.name = strdup(s); - *s = '<'; - *endptr = '>'; - if (ops->target.name == NULL) - goto out_free_target; + /* + * Parse 'addr ' from source (if any). + * + * A raw hex string may be a scalar immediate, not an address. + * Only validate 'source.addr' if accompanied by a '' tag, + * otherwise reset it to 0 to avoid false positive address tracking. + */ + ops->source.addr = strtoull(ops->source.raw, &endptr, 16); + if (endptr != ops->source.raw) { + s = strchr(endptr, '<'); + if (s == NULL) { + ops->source.addr = 0; + return 0; + } + endptr = strrchr(s + 1, '>'); + if (endptr == NULL) { + ops->source.addr = 0; + return 0; + } + + *endptr = '\0'; + ops->source.name = strdup(s + 1); + *endptr = '>'; + if (ops->source.name == NULL) { + ops->source.addr = 0; + return 0; + } + } return 0; +} -out_free_target: - zfree(&ops->target.raw); -out_free_source: - zfree(&ops->source.raw); - return -1; +static int arm64_mov__scnprintf(const struct ins *ins, char *bf, size_t size, + struct ins_operands *ops, int max_ins_name) +{ + return scnprintf(bf, size, "%-*s %s, %s", max_ins_name, ins->name, + ops->target.raw, ops->source.name ?: ops->source.raw); } static const struct ins_ops arm64_mov_ops = { .parse = arm64_mov__parse, - .scnprintf = mov__scnprintf, + .scnprintf = arm64_mov__scnprintf, +}; + +static bool arm64__insn_is_target_on_right(const char *ins_name) +{ + /* + * Store instructions write to the memory operand on the right, + * unlike standard syntax where the target is the left operand. + */ + return !strncmp(ins_name, "st", 2); +} + +/* + * This function is used to parse arm64 load/store instructions into + * instruction operands. + * + * Typical instructions and their parsing logic: + * + * 1. Immediate offset: + * ldr x2, [x0] -> target="x2", source="[x0]" + * ldr x2, [x0, #24] -> target="x2", source="[x0, #24]" + * ldp x19, x20, [sp, #16] -> target="x19, x20", source="[sp, #16]" + * + * 2. Pre-index addressing: + * stp x29, x30, [sp, #-64]! -> target="[sp, #-64]!", source="x29, x30" + * + * 3. Post-index addressing: + * str x1, [x0], #8 -> target="[x0], #8", source="x1" + * ldr w1, [x21], #4 -> target="w1", source="[x21], #4" + * ldp x29, x30, [sp], #32 -> target="x29, x30", source="[sp], #32" + * + * 4. Register offset / extension: + * ldr x0, [x1, w0, sxtw #3] -> target="x0", source="[x1, w0, sxtw #3]" + * ldr x0, [x1, x0, lsl #3] -> target="x0", source="[x1, x0, lsl #3]" + * + * 5. Atomic operations: + * cas w3, w1, [x0] -> target="w3, w1", source="[x0]" + * swp x3, x0, [x2] -> target="x3, x0", source="[x2]" + * + * 6. Prefetch memory: + * prfm pstl1strm, [x4] -> target="pstl1strm", source="[x4]" + * + * 7. PC-relative loads (No bracket found): + * ldr x0, ffff800080f40c68 <__kvm_nvhe_$d> -> Fallback to default parser + * + * Parsing strategy: + * Use the '[' bracket as the boundary to split the operands into left + * and right sides. For non-store instructions, the left side is the + * target and the right side is the source. For store instructions, the + * roles are reversed. + */ +static int arm64_ldst__parse(const struct arch *arch, struct ins_operands *ops, + struct map_symbol *ms, struct disasm_line *dl) +{ + char *raw, *s, *left, *right; + int ret = -1; + + raw = rstrip_space_and_comment(ops->raw, arch->objdump.comment_char); + if (!raw) + return -1; + + s = strchr(raw, arch->objdump.memory_ref_char); + if (!s) { + /* Fallback to default parser for PC-relative loads. */ + free(raw); + return arm64_mov__parse(arch, ops, ms, dl); + } + + right = strdup(s); + if (!right) + goto out_free_raw; + + while (s > raw && *s != ',') + --s; + + if (s == raw) + goto out_free_right; + + *s = '\0'; + left = strdup(raw); + *s = ','; + if (!left) + goto out_free_right; + + free(raw); + + if (arm64__insn_is_target_on_right(dl->ins.name)) { + ops->source.raw = left; + ops->source.mem_ref = false; + + ops->target.raw = right; + ops->target.mem_ref = true; + } else { + ops->source.raw = right; + ops->source.mem_ref = true; + + ops->target.raw = left; + ops->target.mem_ref = false; + } + + ops->source.multi_regs = arm64__check_multi_regs(arch, ops->source.raw); + ops->target.multi_regs = arm64__check_multi_regs(arch, ops->target.raw); + + return 0; + +out_free_right: + free(right); +out_free_raw: + free(raw); + return ret; +} + +static int arm64_ldst__scnprintf(const struct ins *ins, char *bf, size_t size, + struct ins_operands *ops, int max_ins_name) +{ + if (arm64__insn_is_target_on_right(ins->name)) + return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->raw); + + return scnprintf(bf, size, "%-*s %s, %s", max_ins_name, ins->name, + ops->target.raw, ops->source.name ?: ops->source.raw); +} + +static struct ins_ops arm64_ldst_ops = { + .parse = arm64_ldst__parse, + .scnprintf = arm64_ldst__scnprintf, }; static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch, const char *name) @@ -79,6 +313,8 @@ static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch, ops = &jump_ops; else if (!regexec(&arm->call_insn, name, 2, match, 0)) ops = &call_ops; + else if (!regexec(&arm->ldst_insn, name, 2, match, 0)) + ops = &arm64_ldst_ops; else if (!strcmp(name, "ret")) ops = &ret_ops; else @@ -88,6 +324,901 @@ static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch, return ops; } +static enum annotated_ext_type arm64__check_ext_type(const char *op) +{ + if (!strncmp(op, "uxtb", 4)) return PERF_EXT_UXTB; + if (!strncmp(op, "uxth", 4)) return PERF_EXT_UXTH; + if (!strncmp(op, "uxtw", 4)) return PERF_EXT_UXTW; + if (!strncmp(op, "uxtx", 4)) return PERF_EXT_UXTX; + if (!strncmp(op, "sxtb", 4)) return PERF_EXT_SXTB; + if (!strncmp(op, "sxth", 4)) return PERF_EXT_SXTH; + if (!strncmp(op, "sxtw", 4)) return PERF_EXT_SXTW; + if (!strncmp(op, "sxtx", 4)) return PERF_EXT_SXTX; + + return PERF_EXT_NONE; +} + +static enum annotated_shift_type arm64__check_shift_type(const char *op) +{ + if (!strncmp(op, "lsl", 3)) return PERF_SHIFT_LSL; + if (!strncmp(op, "lsr", 3)) return PERF_SHIFT_LSR; + if (!strncmp(op, "asr", 3)) return PERF_SHIFT_ASR; + if (!strncmp(op, "ror", 3)) return PERF_SHIFT_ROR; + + return PERF_SHIFT_NONE; +} + +static const char *next_operand_field(const char *s) +{ + s = strchr(s, ','); + return s ? skip_spaces(s + 1) : NULL; +} + +static void extract_op_location_arm64(const struct arch *arch, + struct disasm_line *dl __maybe_unused, + const char *op_str, int op_idx __maybe_unused, + struct annotated_op_loc *op_loc) +{ + const char *s = op_str; + char *p = NULL; + + if (op_str == NULL) + return; + + /* + * Handle immediate operand. + * e.g., "#0xc600" -> offset = 0xc600, imm = true + * + * After parsing, check for post-modifiers like "lsl #16", which + * typically occur in instructions such as movk, movz, movn, etc. + */ + if (*s == arch->objdump.imm_char) { + op_loc->offset = strtol(s + 1, &p, 0); + if (p && p != s + 1) { + op_loc->imm = true; + s = p; + } + s = next_operand_field(s); + goto check_modifiers; + } + + /* + * Handle memory references, identify arm64 specific addressing modes. + * Reference: Arm Architecture Reference Manual + * (DDI 0487), Chapter C1.3.3: Load/store addressing modes. + */ + if (*s == arch->objdump.memory_ref_char) { + op_loc->mem_ref = true; + + p = (char *)strchr(s, ']'); + if (p == NULL) + return; + + /* Pre-index: [base, #imm]! */ + if (p[1] == '!') + op_loc->addr_mode = PERF_AAM_PRE_INDEX; + /* Post-index: [base], #imm|reg */ + else if (p[1] == ',' && + (strchr(p + 1, arch->objdump.imm_char) || + arm64__is_reg(skip_spaces(p + 2)))) + op_loc->addr_mode = PERF_AAM_POST_INDEX; + /* Signed offset: [base{, #imm|reg}] */ + else + op_loc->addr_mode = PERF_AAM_SIGNED_OFFSET; + + s++; + } + + /* Extract the primary register */ + op_loc->reg1 = arch__dwarf_regnum(arch, s); + if (op_loc->reg1 == -1) + return; + + s = next_operand_field(s); + if (s == NULL) + return; + + /* Extract secondary register or immediate offset */ + if (op_loc->multi_regs) { + op_loc->reg2 = arch__dwarf_regnum(arch, s); + s = next_operand_field(s); + } else if (*s == arch->objdump.imm_char) { + op_loc->offset = strtol(s + 1, NULL, 0); + s = next_operand_field(s); + } + +check_modifiers: + /* + * Look for a following shift or extension modifier: + * "lsl #3" -> extend_type = PERF_EXT_NONE, + * shift_type = PERF_SHIFT_LSL, amount = 3 + * "uxtw #3" -> extend_type = PERF_EXT_UXTW, + * shift_type = PERF_SHIFT_LSL, amount = 3 + * "uxtw" -> extend_type = PERF_EXT_UXTW, + * shift_type = PERF_SHIFT_LSL, amount = 0 + */ + if (s == NULL) + return; + + op_loc->extend_type = arm64__check_ext_type(s); + op_loc->shift_type = arm64__check_shift_type(s); + /* ARM64 extended operands are implicitly shifted by LSL. */ + if (op_loc->extend_type != PERF_EXT_NONE) + op_loc->shift_type = PERF_SHIFT_LSL; + + /* Parse shift amount if present */ + op_loc->amount = 0; + if (op_loc->extend_type != PERF_EXT_NONE || + op_loc->shift_type != PERF_SHIFT_NONE) { + s = strchr(s, arch->objdump.imm_char); + if (s) + op_loc->amount = (u8)strtol(s + 1, NULL, 0); + } +} + +#ifdef HAVE_LIBDW_SUPPORT +/* Check if the instruction is read-only (does not modify register state). */ +static bool is_readonly_insn(const char *name) +{ + return !strncmp(name, "cb", 2) || !strncmp(name, "tb", 2) || + !strcmp(name, "tst") || !strcmp(name, "teq") || + !strcmp(name, "cmp") || !strcmp(name, "cmn") || + !strcmp(name, "ccmp") || !strcmp(name, "ccmn"); +} + +static void update_call_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl) +{ + struct symbol *func = dl->ops.target.sym; + const char *call_name; + u64 call_addr; + struct type_state_reg *tsr; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + + call_name = func ? func->name : dl->ops.target.name; + pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: ""); + + /* Invalidate caller-saved registers after call */ + call_addr = map__rip_2objdump(dloc->ms->map, + dloc->ms->sym->start + dl->al.offset); + for (unsigned int i = 0; i < ARRAY_SIZE(state->regs); i++) { + struct type_state_reg *reg = &state->regs[i]; + + if (!reg->caller_saved) + continue; + /* Keep register valid within DWARF location lifetime */ + if (reg->lifetime_active && call_addr < reg->lifetime_end) + continue; + invalidate_reg_state(reg); + } + + /* Update register with the return type (if any) */ + if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) { + tsr = &state->regs[state->ret_reg]; + invalidate_reg_state(tsr); + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->ok = true; + + pr_debug_dtp("call [%x] return -> reg%d", insn_offset, state->ret_reg); + pr_debug_type_name(&type_die, tsr->kind); + } +} + +/* Invalidate registers explicitly written by an unhandled instruction. */ +static void invalidate_unhandled_insn_regs(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + u32 insn_offset = dl->al.offset; + + /* Invalidate destination registers */ + if (has_reg_type(state, dst->reg1) && !dst->mem_ref) { + pr_debug_dtp("%s [%x] invalidate reg%d", + dl->ins.name, insn_offset, dst->reg1); + invalidate_reg_state(&state->regs[dst->reg1]); + + if (dst->multi_regs && has_reg_type(state, dst->reg2)) { + pr_debug_dtp(" and reg%d", dst->reg2); + invalidate_reg_state(&state->regs[dst->reg2]); + } + pr_debug_dtp("\n"); + } + + /* + * Exclusive store instructions (stxr, stxp, stlxr, stlxp, etc.) + * write their execution status (0 or 1) into the status register + * (parsed into src->reg1). Invalidate this status register to prevent + * stale type propagation. + */ + if (!strncmp(dl->ins.name, "stx", 3) || /* stxr, stxp, stxrb, stxrh */ + !strncmp(dl->ins.name, "stlx", 4)) { /* stlxr, stlxp, stlxrb, stlxrh */ + if (has_reg_type(state, src->reg1)) { + pr_debug_dtp("%s [%x] invalidate reg%d\n", + dl->ins.name, insn_offset, src->reg1); + invalidate_reg_state(&state->regs[src->reg1]); + } + } + + /* TODO: Extend invalidation support to more special instructions. */ +} + +static int arm64__reg_size(const char *reg) +{ + if (!reg || !*reg || !arm64__is_reg(reg)) + return -1; + + if (reg[0] == 'w') + return 4; + + if (reg[0] == 'x' || !strncmp(reg, "sp", 2)) + return 8; + + return -1; +} + +/* Apply addressing mode (pre-index, post-index) to register state */ +static void adjust_reg_index_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *op_loc) +{ + struct type_state_reg *tsr; + int reg = op_loc->reg1; + int offset; + + if (op_loc->addr_mode != PERF_AAM_PRE_INDEX && + op_loc->addr_mode != PERF_AAM_POST_INDEX) + return; + + if (!has_reg_type(state, reg) || !state->regs[reg].ok) + return; + + tsr = &state->regs[reg]; + tsr->copied_from = -1; + + if (arch_get_reg_offset(dloc->arch, op_loc, reg, state, true, &offset)) { + invalidate_reg_state(tsr); + return; + } + + tsr->offset += offset; + + pr_debug_dtp("%s [%x] %s-index %#x(reg%d) -> reg%d", dl->ins.name, + (u32) dl->al.offset, op_loc->addr_mode == PERF_AAM_PRE_INDEX ? + "pre" : "post", offset, reg, reg); + pr_debug_type_name(&tsr->type, tsr->kind); +} + +/* + * Match standard load variants (ldr, ldur, ldar, ldp) that follow + * straightforward load semantics: memory source on the right + * and target registers on the left (written). Excludes exclusive loads + * (ldxr, ldxp, etc.) and acquire/exclusive combination variants. + */ +static bool is_standard_load_insn(const char *name) +{ + return !strncmp(name, "ldr", 3) || /* ldr, ldrb, ldrh, ldrsb, ldrsh, ldrsw */ + !strncmp(name, "ldur", 4) || /* ldur, ldurb, ldurh, ldursb, ldursh, ldursw */ + !strncmp(name, "ldar", 4) || /* ldar, ldarb, ldarh */ + !strncmp(name, "ldp", 3); /* ldp, ldpsw */ +} + +/* + * For load insns: propagate type from source reg state @src_states to @dreg. + * + * @mem_spacing accounts for the additional in-memory spacing between the two + * loads when handling multi-reg loads (e.g. the second target reg in 'ldp'), + * which is added to the reg offset. + */ +static int propagate_load_reg_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, int dreg, + struct annotated_op_loc *src, + struct type_state_reg **src_states, + int mem_spacing) +{ + struct type_state_reg *tsr; + struct type_state_reg *src_tsr = src_states[0]; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int reg_offset; + int fbreg = dloc->fbreg; + int fboff = 0; + + if (!has_reg_type(state, dreg)) + return -1; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + + if (dloc->fb_cfa) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 pc = map__rip_2objdump(dloc->ms->map, ip); + + if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0) + fbreg = -1; + } + +retry: + if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, ®_offset)) + goto load_non_regoff; + + reg_offset += mem_spacing; + + /* Check stack variables with offset */ + if (sreg != -1 && (sreg == fbreg || sreg == state->stack_reg)) { + struct type_state_stack *stack; + int offset = sreg == fbreg ? reg_offset - fboff : reg_offset; + + stack = find_stack_state(state, offset); + if (stack == NULL) { + return -1; + } else if (!stack->compound) { + tsr->type = stack->type; + tsr->kind = stack->kind; + tsr->offset = stack->ptr_offset; + tsr->imm_value = stack->imm_value; + tsr->ok = true; + } else if (die_get_member_type(&stack->type, + offset - stack->offset, + &type_die)) { + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + } else { + return -1; + } + + if (sreg == fbreg) { + pr_debug_dtp("%s [%x] -%#x(stack) -> reg%d", + dl->ins.name, insn_offset, -offset, dreg); + } else { + pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, offset, sreg, dreg); + } + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + if (!src_tsr || !src_tsr->ok) + return -1; + + /* Dereference the pointer if it has one */ + if (src_tsr->kind == TSR_KIND_TYPE && + die_deref_ptr_type(&src_tsr->type, + src_tsr->offset + reg_offset, &type_die)) { + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + + pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* Handle dereference of TSR_KIND_POINTER registers */ + if (src_tsr->kind == TSR_KIND_POINTER && + die_get_member_type(&src_tsr->type, + src_tsr->offset + reg_offset, &type_die)) { + tsr->type = src_tsr->type; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = src_tsr->offset + reg_offset; + tsr->imm_value = 0; + tsr->ok = true; + + pr_debug_dtp("%s [%x] addr %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* Or check if it's a global variable */ + if (src_tsr->kind == TSR_KIND_GLOBAL_ADDR) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 addr = src_tsr->imm_value + reg_offset; + int offset; + u8 kind; + const char *var_name = NULL; + + /* it might be per-cpu offset */ + if (dso__kernel(map__dso(dloc->ms->map)) && + get_global_var_info(dloc, addr, &var_name, &offset) && + !strcmp(var_name, "__per_cpu_offset")) + kind = TSR_KIND_PERCPU_BASE; + else + kind = TSR_KIND_TYPE; + + if (!get_global_var_type(cu_die, dloc, ip, addr, &offset, &type_die) || + !die_get_member_type(&type_die, offset, &type_die)) + return -1; + + tsr->type = type_die; + tsr->kind = kind; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + + if (src->multi_regs) { + pr_debug_dtp("%s [%x] global (reg%d, reg%d) -> reg%d", + dl->ins.name, insn_offset, src->reg1, src->reg2, dreg); + } else { + pr_debug_dtp("%s [%x] global (reg%d) -> reg%d", + dl->ins.name, insn_offset, sreg, dreg); + } + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* + * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array. + * No special handling is needed here; just fall through to retry + * and resolve the type from the second register. + */ + +load_non_regoff: + /* Try another register if any */ + if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 && + !(src->extend_type || src->shift_type)) { + sreg = src->reg2; + src_tsr = src_states[1]; + goto retry; + } + + return -1; +} + +static void update_load_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg snapshots[2]; + struct type_state_reg *src_states[2] = {}; + int mem_spacing = 0; + + if (!src->mem_ref || /* exclude PC-relative loads */ + !has_reg_type(state, src->reg1)) { + /* Invalidate tracked destination registers */ + if (has_reg_type(state, dst->reg1)) + invalidate_reg_state(&state->regs[dst->reg1]); + if (dst->multi_regs && has_reg_type(state, dst->reg2)) + invalidate_reg_state(&state->regs[dst->reg2]); + goto out_adjust; + } + + /* + * Snapshot source register states before updating destination registers. + * This preserves the original source state for aliased loads (e.g., + * ldp x0, x1, [x0]), where updating the first destination may overwrite + * the source register. + */ + snapshots[0] = state->regs[src->reg1]; + src_states[0] = &snapshots[0]; + if (src->multi_regs && has_reg_type(state, src->reg2)) { + snapshots[1] = state->regs[src->reg2]; + src_states[1] = &snapshots[1]; + } + + /* Handle the first destination register */ + if (has_reg_type(state, dst->reg1)) { + if (propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg1, + src, src_states, mem_spacing)) + invalidate_reg_state(&state->regs[dst->reg1]); + } + + if (!dst->multi_regs || !has_reg_type(state, dst->reg2)) + goto out_adjust; + + /* Handle the second destination register */ + if (!strcmp(dl->ins.name, "ldpsw")) + mem_spacing = 4; /* ldpsw loads two 32-bit signed words (4-byte spacing) */ + else + mem_spacing = arm64__reg_size(dl->ops.target.raw); + + if (mem_spacing < 0 || + propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg2, + src, src_states, mem_spacing)) + invalidate_reg_state(&state->regs[dst->reg2]); + +out_adjust: + adjust_reg_index_state(state, dloc, dl, src); +} + +/* + * Match standard store variants (str, stur, stlr, stp) that follow + * straightforward store semantics: source registers on the left (read-only) + * and memory target on the right. Excludes exclusive stores (stxr, stxp, etc.) + * which have status/result register side effects. + */ +static bool is_standard_store_insn(const char *name) +{ + return !strncmp(name, "str", 3) || /* str, strb, strh */ + !strncmp(name, "stur", 4) || /* stur, sturb, sturh */ + !strncmp(name, "stlr", 4) || /* stlr, stlrb, stlrh */ + !strncmp(name, "stp", 3); /* stp */ +} + +/* + * For store insns: propagate type from @sreg to the memory location referenced + * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp). + */ +static void propagate_store_reg_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, int sreg, + struct annotated_op_loc *dst, + int mem_spacing) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + int reg_offset; + int fbreg = dloc->fbreg; + int fboff = 0; + + if (arch_get_reg_offset(dloc->arch, dst, dreg, state, false, ®_offset)) + return; + + reg_offset += mem_spacing; + + if (dloc->fb_cfa) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 pc = map__rip_2objdump(dloc->ms->map, ip); + + if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0) + fbreg = -1; + } + + /* Check stack variables with offset */ + if (dreg != -1 && (dreg == fbreg || dreg == state->stack_reg)) { + struct type_state_stack *stack; + int offset = dreg == fbreg ? reg_offset - fboff : reg_offset; + + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + stack = find_stack_state(state, offset); + /* Preserve compound states when only a member is overwritten. */ + if (stack && !stack->compound) + delete_stack_state(state, offset); + + return; + } + + tsr = &state->regs[sreg]; + + stack = find_stack_state(state, offset); + if (stack) { + if (!stack->compound) + set_stack_state(stack, offset, tsr->kind, &tsr->type, + tsr->offset, tsr->imm_value); + /* + * If it's a compound type, it means attempting to + * write to a member value of the compound type without + * changing the compound type itself, so do nothing. + */ + } else { + findnew_stack_state(state, offset, tsr->kind, &tsr->type, + tsr->offset, tsr->imm_value); + } + + if (dreg == fbreg) { + pr_debug_dtp("%s [%x] reg%d -> -%#x(stack)", + dl->ins.name, insn_offset, sreg, -offset); + } else { + pr_debug_dtp("%s [%x] reg%d -> %#x(reg%d)", + dl->ins.name, insn_offset, sreg, offset, dreg); + } + if (tsr->offset != 0) + pr_debug_dtp(" reg%d offset %#x ->", sreg, tsr->offset); + + pr_debug_type_name(&tsr->type, tsr->kind); + } + /* + * Ignore other transfers since it'd set a value in a struct + * and won't change the type. + */ +} + +static void update_store_insn_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + int mem_spacing = 0; + + if (!has_reg_type(state, dst->reg1)) + return; + + /* Handle the first source register */ + propagate_store_reg_state(state, dloc, dl, src->reg1, dst, mem_spacing); + + /* Handle the second source register (if any) */ + if (src->multi_regs) { + mem_spacing = arm64__reg_size(dl->ops.source.raw); + + if (mem_spacing >= 0) + propagate_store_reg_state(state, dloc, dl, src->reg2, + dst, mem_spacing); + } + + adjust_reg_index_state(state, dloc, dl, dst); +} + +static void update_mov_insn_state(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int dreg = dst->reg1; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + if (src->imm) { + invalidate_reg_state(tsr); + tsr->kind = TSR_KIND_CONST; + tsr->imm_value = (s64)src->offset; + tsr->offset = 0; + tsr->ok = true; + + pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n", + insn_offset, tsr->imm_value, dreg); + return; + } + + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + invalidate_reg_state(tsr); + return; + } + + tsr->type = state->regs[sreg].type; + tsr->kind = state->regs[sreg].kind; + tsr->imm_value = state->regs[sreg].imm_value; + tsr->offset = state->regs[sreg].offset; + tsr->lifetime_active = state->regs[sreg].lifetime_active; + tsr->lifetime_end = state->regs[sreg].lifetime_end; + tsr->ok = state->regs[sreg].ok; + + if (tsr->kind == TSR_KIND_TYPE || tsr->kind == TSR_KIND_POINTER) + tsr->copied_from = sreg; + + pr_debug_dtp("mov [%x] reg%d -> reg%d", insn_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); +} + +static void update_add_insn_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + struct type_state_reg src_tsr; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int dreg = dst->reg1; + int reg_offset; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + +retry: + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + invalidate_reg_state(tsr); + return; + } + + src_tsr = state->regs[sreg]; + + if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, ®_offset)) + goto add_non_regoff; + + if (src_tsr.kind == TSR_KIND_CONST || src_tsr.kind == TSR_KIND_GLOBAL_ADDR) { + tsr->kind = src_tsr.kind; + /* For 'adrp + add' pair: resolve the full global variable address. */ + tsr->imm_value = src_tsr.imm_value + reg_offset; + tsr->offset = 0; + tsr->ok = src_tsr.ok; + + pr_debug_dtp("add [%x] %s %#x(reg%d) -> reg%d\n", + insn_offset, src_tsr.kind == TSR_KIND_CONST ? + "imm" : "global", reg_offset, sreg, dreg); + return; + } + + if (src_tsr.kind == TSR_KIND_POINTER || + (src_tsr.kind == TSR_KIND_TYPE && + dwarf_tag(&src_tsr.type) == DW_TAG_pointer_type)) { + tsr->type = src_tsr.type; + tsr->kind = src_tsr.kind; + tsr->imm_value = src_tsr.imm_value; + tsr->offset = src_tsr.offset + reg_offset; + tsr->ok = src_tsr.ok; + + pr_debug_dtp("add [%x] address of %#x(reg%d) -> reg%d", + insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return; + } + + /* + * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array. + * No special handling is needed here; just fall through to retry + * and resolve the type from the second register. + */ + +add_non_regoff: + /* Try another register if any */ + if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 && + !(src->extend_type || src->shift_type)) { + sreg = src->reg2; + goto retry; + } + invalidate_reg_state(tsr); +} + +static void update_adrp_insn_state(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + if (!dl->ops.source.addr) { + invalidate_reg_state(tsr); + return; + } + + tsr->kind = TSR_KIND_GLOBAL_ADDR; + /* + * Stores a partial page-relative address. The full absolute address + * of the global variable will be resolved when a subsequent 'add' or + * 'ldr' instruction consumes this register. + */ + tsr->imm_value = dl->ops.source.addr; + tsr->offset = 0; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + tsr->ok = true; + + pr_debug_dtp("adrp [%x] global addr=%#"PRIx64" -> reg%d\n", + insn_offset, tsr->imm_value, dreg); +} + +static void update_mrs_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + Dwarf_Die func_die; + Dwarf_Attribute attr; + u64 ip, pc; + struct strbuf sb; + char *type_name; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + /* Handle case difference: LLVM (SP_EL0) vs objdump (sp_el0) */ + if (!dso__kernel(map__dso(dloc->ms->map)) || + strcasecmp(dl->ops.source.raw, "sp_el0")) { + invalidate_reg_state(tsr); + return; + } + + ip = dloc->ms->sym->start + dl->al.offset; + pc = map__rip_2objdump(dloc->ms->map, ip); + + /* + * Find the inline function 'get_current()' Dwarf_Die and obtain its + * return value data type, which should be 'struct task_struct*'. + */ + if (!die_find_inlinefunc(cu_die, pc, &func_die) || + !die_compare_name(&func_die, "get_current") || + !dwarf_attr_integrate(&func_die, DW_AT_type, &attr) || + !dwarf_formref_die(&attr, &type_die)) { + invalidate_reg_state(tsr); + return; + } + + strbuf_init(&sb, 32); + die_get_typename_from_type(&type_die, &sb); + type_name = strbuf_detach(&sb, NULL); + + if (!type_name || strcmp(type_name, "struct task_struct*")) { + invalidate_reg_state(tsr); + goto out_free; + } + + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + tsr->ok = true; + + pr_debug_dtp("mrs [%x] sp_el0 -> reg%d", insn_offset, dreg); + pr_debug_type_name(&type_die, tsr->kind); + +out_free: + free(type_name); +} + +static void update_insn_state_arm64(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl) +{ + struct annotated_insn_loc loc; + struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE]; + struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET]; + + if (is_readonly_insn(dl->ins.name)) + return; + + if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0) + return; + + if (ins__is_call(&dl->ins)) + update_call_insn_state(state, dloc, cu_die, dl); + else if (!strcmp(dl->ins.name, "mrs")) + update_mrs_insn_state(state, dloc, cu_die, dl, dst); + else if (!strcmp(dl->ins.name, "adrp")) + update_adrp_insn_state(state, dl, dst); + else if (!strcmp(dl->ins.name, "add")) + update_add_insn_state(state, dloc, dl, src, dst); + /* Register to register or imm value to register transfers */ + else if (!strcmp(dl->ins.name, "mov")) + update_mov_insn_state(state, dl, src, dst); + /* Memory to register transfers */ + else if (is_standard_load_insn(dl->ins.name)) + update_load_insn_state(state, dloc, cu_die, dl, src, dst); + /* Register to memory transfers */ + else if (is_standard_store_insn(dl->ins.name)) + update_store_insn_state(state, dloc, dl, src, dst); + else + invalidate_unhandled_insn_regs(state, dl, src, dst); +} +#endif + const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, const char *cpuid __maybe_unused) { @@ -103,7 +1234,13 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, arch->id = *id; arch->objdump.comment_char = '/'; arch->objdump.skip_functions_char = '+'; + arch->objdump.memory_ref_char = '['; + arch->objdump.imm_char = '#'; arch->associate_instruction_ops = arm64__associate_instruction_ops; + arch->extract_op_location = extract_op_location_arm64; +#ifdef HAVE_LIBDW_SUPPORT + arch->update_insn_state = update_insn_state_arm64; +#endif /* bl, blr */ err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED); @@ -116,8 +1253,20 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, if (err) goto out_free_call; + /* + * The ARM64 architecture has many variants of load/store instructions. + * It is quite challenging to match all of them completely. Here, we + * only match the prefixes of these instructions. + */ + err = regcomp(&arm->ldst_insn, "^(ld|st|cas|prf|swp)", + REG_EXTENDED); + if (err) + goto out_free_jump; + return arch; +out_free_jump: + regfree(&arm->jump_insn); out_free_call: regfree(&arm->call_insn); out_free_arm: diff --git a/tools/perf/util/annotate-arch/annotate-loongarch.c b/tools/perf/util/annotate-arch/annotate-loongarch.c index c2addca77320..a949b4392dd4 100644 --- a/tools/perf/util/annotate-arch/annotate-loongarch.c +++ b/tools/perf/util/annotate-arch/annotate-loongarch.c @@ -29,6 +29,9 @@ static int loongarch_call__parse(const struct arch *arch, struct ins_operands *o ops->target.addr = strtoull(c, &endptr, 16); name = strchr(endptr, '<'); + if (name == NULL) + goto find_target; + name++; if (arch->objdump.skip_functions_char && @@ -46,6 +49,7 @@ static int loongarch_call__parse(const struct arch *arch, struct ins_operands *o if (ops->target.name == NULL) return -1; +find_target: target = (struct addr_map_symbol) { .ms = { .map = map__get(map), }, .addr = map__objdump_2mem(map, ops->target.addr), diff --git a/tools/perf/util/annotate-arch/annotate-powerpc.c b/tools/perf/util/annotate-arch/annotate-powerpc.c index 218207b52581..00b93296fc94 100644 --- a/tools/perf/util/annotate-arch/annotate-powerpc.c +++ b/tools/perf/util/annotate-arch/annotate-powerpc.c @@ -390,6 +390,14 @@ static void update_insn_state_powerpc(struct type_state *state, } #endif /* HAVE_LIBDW_SUPPORT */ +static void extract_op_location_powerpc(const struct arch *arch __maybe_unused, + struct disasm_line *dl, + const char *op_str __maybe_unused, int op_idx, + struct annotated_op_loc *op_loc) +{ + get_powerpc_regs(dl->raw.raw_insn, !op_idx, op_loc); +} + const struct arch *arch__new_powerpc(const struct e_machine_and_e_flags *id, const char *cpuid __maybe_unused) { @@ -406,5 +414,6 @@ const struct arch *arch__new_powerpc(const struct e_machine_and_e_flags *id, #ifdef HAVE_LIBDW_SUPPORT arch->update_insn_state = update_insn_state_powerpc; #endif + arch->extract_op_location = extract_op_location_powerpc; return arch; } diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c index 7e6136536393..3a6ea7e978e0 100644 --- a/tools/perf/util/annotate-arch/annotate-x86.c +++ b/tools/perf/util/annotate-arch/annotate-x86.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include +#include #include #include #include @@ -205,15 +207,6 @@ static int x86__cpuid_parse(struct arch *arch, const char *cpuid) } #ifdef HAVE_LIBDW_SUPPORT -static void invalidate_reg_state(struct type_state_reg *reg) -{ - reg->kind = TSR_KIND_INVALID; - reg->ok = false; - reg->lifetime_active = false; - reg->lifetime_end = 0; - reg->copied_from = -1; -} - static void update_insn_state_x86(struct type_state *state, struct data_loc_info *dloc, Dwarf_Die *cu_die, struct disasm_line *dl) @@ -543,11 +536,11 @@ static void update_insn_state_x86(struct type_state *state, if (src->imm) { tsr->kind = TSR_KIND_CONST; - tsr->imm_value = src->offset; + tsr->imm_value = (s64)src->offset; tsr->offset = 0; tsr->ok = true; - pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n", + pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n", insn_offset, tsr->imm_value, dst->reg1); return; } @@ -598,6 +591,7 @@ static void update_insn_state_x86(struct type_state *state, tsr->type = stack->type; tsr->kind = stack->kind; tsr->offset = stack->ptr_offset; + tsr->imm_value = stack->imm_value; tsr->ok = true; } else if (die_get_member_type(&stack->type, offset - stack->offset, @@ -605,6 +599,7 @@ static void update_insn_state_x86(struct type_state *state, tsr->type = type_die; tsr->kind = TSR_KIND_TYPE; tsr->offset = 0; + tsr->imm_value = 0; tsr->ok = true; } else { invalidate_reg_state(tsr); @@ -758,15 +753,24 @@ static void update_insn_state_x86(struct type_state *state, } /* Case 3. register to memory transfers */ if (!src->mem_ref && dst->mem_ref) { - if (!has_reg_type(state, src->reg1) || - !state->regs[src->reg1].ok) - return; - /* Check stack variables with offset */ if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) { struct type_state_stack *stack; int offset = dst->offset - fboff; + if (!has_reg_type(state, src->reg1) || + !state->regs[src->reg1].ok) { + stack = find_stack_state(state, offset); + /* + * Preserve compound states when only a member + * is overwritten. + */ + if (stack && !stack->compound) + delete_stack_state(state, offset); + + return; + } + tsr = &state->regs[src->reg1]; stack = find_stack_state(state, offset); @@ -780,10 +784,12 @@ static void update_insn_state_x86(struct type_state *state, */ if (!stack->compound) set_stack_state(stack, offset, tsr->kind, - &tsr->type, tsr->offset); + &tsr->type, tsr->offset, + tsr->imm_value); } else { findnew_stack_state(state, offset, tsr->kind, - &tsr->type, tsr->offset); + &tsr->type, tsr->offset, + tsr->imm_value); } if (dst->reg1 == fbreg) { @@ -809,6 +815,82 @@ static void update_insn_state_x86(struct type_state *state, } #endif +/* + * Get register number and access offset from the given instruction. + * It assumes AT&T x86 asm format like OFFSET(REG). + * Fills @reg and @offset when return 0. + */ +static int extract_reg_offset(const struct arch *arch, const char *str, + struct annotated_op_loc *op_loc) +{ + char *p; + + if (arch->objdump.register_char == 0) + return -1; + + /* + * It should start from offset, but it's possible to skip 0 + * in the asm. So 0(%rax) should be same as (%rax). + * + * However, it also start with a segment select register like + * %gs:0x18(%rbx). In that case it should skip the part. + */ + if (*str == arch->objdump.register_char) { + /* FIXME: Handle other segment registers */ + if (!strncmp(str, "%gs:", 4)) + op_loc->segment = INSN_SEG_X86_GS; + + while (*str && !isdigit(*str) && + *str != arch->objdump.memory_ref_char) + str++; + } + + op_loc->offset = strtol(str, &p, 0); + op_loc->reg1 = arch__dwarf_regnum(arch, p); + if (op_loc->reg1 == -1) + return -1; + + /* Get the second register */ + if (op_loc->multi_regs) + op_loc->reg2 = arch__dwarf_regnum(arch, p + 1); + + return 0; +} + +static void extract_op_location_x86(const struct arch *arch, + struct disasm_line *dl __maybe_unused, + const char *op_str, int op_idx __maybe_unused, + struct annotated_op_loc *op_loc) +{ + if (op_str == NULL) + return; + + if (strchr(op_str, arch->objdump.memory_ref_char)) { + op_loc->mem_ref = true; + extract_reg_offset(arch, op_str, op_loc); + } else { + const char *s = op_str; + char *p = NULL; + + /* FIXME: Handle other segment registers */ + if (!strncmp(op_str, "%gs:", 4)) { + op_loc->segment = INSN_SEG_X86_GS; + op_loc->offset = strtol(op_str + 4, &p, 0); + if (p && p != op_str + 4) + op_loc->imm = true; + return; + } + + if (*s == arch->objdump.register_char) { + op_loc->reg1 = arch__dwarf_regnum(arch, s); + } else if (*s == arch->objdump.imm_char) { + op_loc->offset = strtol(s + 1, &p, 0); + if (p && p != s + 1) + op_loc->imm = true; + } + } +} + const struct arch *arch__new_x86(const struct e_machine_and_e_flags *id, const char *cpuid) { struct arch *arch = zalloc(sizeof(*arch)); @@ -848,5 +930,6 @@ const struct arch *arch__new_x86(const struct e_machine_and_e_flags *id, const c #ifdef HAVE_LIBDW_SUPPORT arch->update_insn_state = update_insn_state_x86; #endif + arch->extract_op_location = extract_op_location_x86; return arch; } diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c index 4e4c58764082..b3de1ca688a2 100644 --- a/tools/perf/util/annotate-data.c +++ b/tools/perf/util/annotate-data.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ /* register number of the stack pointer */ #define X86_REG_SP 7 +#define ARM64_REG_SP 31 static void delete_var_types(struct die_var_type *var_types); @@ -69,6 +71,9 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind) case TSR_KIND_CANARY: pr_info(" stack canary\n"); return; + case TSR_KIND_GLOBAL_ADDR: + pr_info(" global address\n"); + return; case TSR_KIND_TYPE: default: break; @@ -178,6 +183,17 @@ static void init_type_state(struct type_state *state, const struct arch *arch) state->regs[11].caller_saved = true; state->ret_reg = 0; state->stack_reg = X86_REG_SP; + } else if (arch__is_arm64(arch)) { + /* + * Mark x0..x18 as caller-saved. x30 (LR) is intentionally + * omitted from the caller_saved set: it is clobbered by + * calls, but holds only the return address and is not used + * to carry data type information. + */ + for (int i = 0; i < 19; i++) + state->regs[i].caller_saved = true; + state->ret_reg = 0; + state->stack_reg = ARM64_REG_SP; } } @@ -221,12 +237,18 @@ static bool data_type_less(struct rb_node *node_a, const struct rb_node *node_b) return strcmp(a->self.type_name, b->self.type_name) < 0; } +/* + * A broken type can point back at one of its own ancestors: bound the + * nesting so it doesn't recurse until the stack is gone. + */ +#define MAX_MEMBER_DEPTH 32 + /* Recursively add new members for struct/union */ static int __add_member_cb(Dwarf_Die *die, void *arg) { struct annotated_member *parent = arg; - struct annotated_member *member; - Dwarf_Die member_type, die_mem; + struct annotated_member *member, *prev; + Dwarf_Die die_mem; Dwarf_Word size, loc, bit_size = 0; Dwarf_Attribute attr; struct strbuf sb; @@ -235,21 +257,31 @@ static int __add_member_cb(Dwarf_Die *die, void *arg) if (dwarf_tag(die) != DW_TAG_member) return DIE_FIND_CB_SIBLING; + if (die_get_real_type(die, &die_mem) == NULL) + return DIE_FIND_CB_SIBLING; + member = zalloc(sizeof(*member)); if (member == NULL) return DIE_FIND_CB_END; - strbuf_init(&sb, 32); - die_get_typename(die, &sb); + if (strbuf_init(&sb, 32) < 0) { + free(member); + return DIE_FIND_CB_END; + } - __die_get_real_type(die, &member_type); - if (dwarf_tag(&member_type) == DW_TAG_typedef) - die_get_real_type(&member_type, &die_mem); - else - die_mem = member_type; + if (die_get_typename(die, &sb) < 0) + strbuf_add(&sb, "(unknown type)", 14); - if (dwarf_aggregate_size(&die_mem, &size) < 0) - size = 0; + if (dwarf_aggregate_size(&die_mem, &size) < 0 || size == 0) { + if (dwarf_tag(&die_mem) == DW_TAG_array_type) { /* flex-array? */ + die_get_real_type(&die_mem, &die_mem); + member->is_flex_array = true; + if (dwarf_aggregate_size(&die_mem, &size) < 0) + size = 0; + } else { + size = 0; + } + } if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) { if (dwarf_formudata(&attr, &loc) != 0) { @@ -289,13 +321,29 @@ static int __add_member_cb(Dwarf_Die *die, void *arg) } member->size = size; member->offset = loc + parent->offset; + member->depth = parent->depth + 1; INIT_LIST_HEAD(&member->children); - list_add_tail(&member->node, &parent->children); + + list_for_each_entry_reverse(prev, &parent->children, node) { + if (prev->offset <= member->offset) + break; + } + list_add(&member->node, &prev->node); tag = dwarf_tag(&die_mem); switch (tag) { - case DW_TAG_structure_type: case DW_TAG_union_type: + member->is_union = true; + /* fall through */ + case DW_TAG_structure_type: + /* Only aggregates have children to expand, so only they get truncated. */ + if (member->depth >= MAX_MEMBER_DEPTH) { + /* Consumed by the JSON exporter added in a later series. */ + member->truncated = true; + pr_debug_dtp("member nesting limit reached at %s\n", + member->type_name ?: "(unknown type)"); + break; + } die_find_child(&die_mem, __add_member_cb, member, &die_mem); break; default: @@ -324,20 +372,84 @@ static void delete_members(struct annotated_member *member) } } -static int fill_member_name(char *buf, size_t sz, struct annotated_member *m, - int offset, bool first) +static struct annotated_member *find_flex_array(struct annotated_member *m) { struct annotated_member *child; + if (list_empty(&m->children)) + return NULL; + + if (m->is_union) { + list_for_each_entry(child, &m->children, node) { + if (child->is_flex_array) + return child; + } + list_for_each_entry(child, &m->children, node) { + struct annotated_member *grand_child; + + grand_child = find_flex_array(child); + if (grand_child) + return grand_child; + } + return NULL; + } + + child = list_last_entry(&m->children, struct annotated_member, node); + if (child->is_flex_array) + return child; + + return find_flex_array(child); +} + +static struct annotated_member *get_flex_array_member(struct annotated_data_type *adt) +{ + return find_flex_array(&adt->self); +} + +static int fill_member_name(char *buf, size_t sz, struct annotated_member *m, + int offset, bool first, bool has_flex_array) +{ + struct annotated_member *child; + bool found = false; + int len; + if (list_empty(&m->children)) return 0; list_for_each_entry(child, &m->children, node) { - int len; - if (offset < child->offset || offset >= child->offset + child->size) continue; + found = true; + break; + } + + if (!found && has_flex_array) { + /* + * It may have an intermediate struct that has another struct that + * contains a flex array. In that case, the outer struct itself is + * has no array and the size is less than the offset so the above + * logic won't find the outer struct at the offset. + */ + child = find_flex_array(m); + if (child == NULL || offset < child->offset) + return 0; + + /* find the immediate child that includes a flex array */ + if (m->is_union) { + list_for_each_entry(child, &m->children, node) { + if (child->is_flex_array || find_flex_array(child)) { + found = true; + break; + } + } + } else { + child = list_last_entry(&m->children, struct annotated_member, node); + found = true; + } + } + + if (found) { /* It can have anonymous struct/union members */ if (child->var_name) { len = scnprintf(buf, sz, "%s%s", @@ -347,15 +459,18 @@ static int fill_member_name(char *buf, size_t sz, struct annotated_member *m, len = 0; } - return fill_member_name(buf + len, sz - len, child, offset, first) + len; + return fill_member_name(buf + len, sz - len, child, offset, first, + has_flex_array) + len; } + return 0; } int annotated_data_type__get_member_name(struct annotated_data_type *adt, char *buf, size_t sz, int member_offset) { - return fill_member_name(buf, sz, &adt->self, member_offset, /*first=*/true); + return fill_member_name(buf, sz, &adt->self, member_offset, /*first=*/true, + adt->flex_array); } static struct annotated_data_type *dso__findnew_data_type(struct dso *dso, @@ -399,6 +514,7 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso, result->self.type_name = type_name; result->self.size = size; INIT_LIST_HEAD(&result->self.children); + result->flex_array = die_has_flex_array(type_die); if (symbol_conf.annotate_data_member) add_member_types(result, type_die); @@ -517,13 +633,30 @@ static bool is_better_type(Dwarf_Die *type_a, Dwarf_Die *type_b) return false; } +static enum type_match_result check_type_offset(Dwarf_Die *type_die, int offset) +{ + Dwarf_Word size; + + /* Get the size of the actual type */ + if (dwarf_aggregate_size(type_die, &size) < 0) + return PERF_TMR_NO_SIZE; + + /* Minimal sanity check */ + if (offset < 0) + return PERF_TMR_BAD_OFFSET; + + if ((unsigned)offset >= size && !die_has_flex_array(type_die)) + return PERF_TMR_BAD_OFFSET; + + return PERF_TMR_OK; +} + /* The type info will be saved in @type_die */ static enum type_match_result check_variable(struct data_loc_info *dloc, Dwarf_Die *var_die, Dwarf_Die *type_die, int reg, int offset, bool is_fbreg) { - Dwarf_Word size; bool needs_pointer = true; Dwarf_Die sized_type; @@ -554,15 +687,7 @@ static enum type_match_result check_variable(struct data_loc_info *dloc, else sized_type = *type_die; - /* Get the size of the actual type */ - if (dwarf_aggregate_size(&sized_type, &size) < 0) - return PERF_TMR_NO_SIZE; - - /* Minimal sanity check */ - if ((unsigned)offset >= size) - return PERF_TMR_BAD_OFFSET; - - return PERF_TMR_OK; + return check_type_offset(&sized_type, offset); } struct type_state_stack *find_stack_state(struct type_state *state, @@ -582,7 +707,7 @@ struct type_state_stack *find_stack_state(struct type_state *state, } void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, - Dwarf_Die *type_die, int ptr_offset) + Dwarf_Die *type_die, int ptr_offset, u64 imm_value) { int tag; Dwarf_Word size; @@ -599,8 +724,10 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, stack->offset = offset; stack->ptr_offset = ptr_offset; stack->kind = kind; + stack->imm_value = imm_value; - if (kind == TSR_KIND_POINTER) { + if (kind == TSR_KIND_POINTER || kind == TSR_KIND_CONST || + kind == TSR_KIND_GLOBAL_ADDR) { stack->compound = false; return; } @@ -621,23 +748,42 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, struct type_state_stack *findnew_stack_state(struct type_state *state, int offset, u8 kind, Dwarf_Die *type_die, - int ptr_offset) + int ptr_offset, u64 imm_value) { struct type_state_stack *stack = find_stack_state(state, offset); if (stack) { - set_stack_state(stack, offset, kind, type_die, ptr_offset); + set_stack_state(stack, offset, kind, type_die, ptr_offset, imm_value); return stack; } stack = malloc(sizeof(*stack)); if (stack) { - set_stack_state(stack, offset, kind, type_die, ptr_offset); + set_stack_state(stack, offset, kind, type_die, ptr_offset, imm_value); list_add(&stack->list, &state->stack_vars); } return stack; } +void delete_stack_state(struct type_state *state, int offset) +{ + struct type_state_stack *stack = find_stack_state(state, offset); + + if (stack) { + list_del(&stack->list); + free(stack); + } +} + +void invalidate_reg_state(struct type_state_reg *reg) +{ + reg->kind = TSR_KIND_INVALID; + reg->ok = false; + reg->lifetime_active = false; + reg->lifetime_end = 0; + reg->copied_from = -1; +} + /* Maintain a cache for quick global variable lookup */ struct global_var_entry { struct rb_node node; @@ -645,6 +791,8 @@ struct global_var_entry { u64 start; u64 end; u64 die_offset; + int die_tag; + bool from_alt; /* die_offset is relative to the alt (dwz) file */ }; static int global_var_cmp(const void *_key, const struct rb_node *node) @@ -682,7 +830,7 @@ static struct global_var_entry *global_var__find(struct data_loc_info *dloc, u64 } static bool global_var__add(struct data_loc_info *dloc, u64 addr, - const char *name, Dwarf_Die *type_die) + const char *name, Dwarf_Die *type_die, bool from_alt) { struct dso *dso = map__dso(dloc->ms->map); struct global_var_entry *gvar; @@ -704,6 +852,8 @@ static bool global_var__add(struct data_loc_info *dloc, u64 addr, gvar->start = addr; gvar->end = addr + size; gvar->die_offset = dwarf_dieoffset(type_die); + gvar->die_tag = dwarf_tag(type_die); + gvar->from_alt = from_alt; rb_add(&gvar->node, dso__global_vars(dso), global_var_less); return true; @@ -778,12 +928,14 @@ static void global_var__collect(struct data_loc_info *dloc) if (pos->reg != -1) continue; - if (!dwarf_offdie(dwarf, pos->die_off, &type_die)) + if (!die_get_type_die(dwarf, pos->die_off, pos->die_tag, + pos->from_alt, &type_die)) continue; get_global_var_info(dloc, pos->addr, &var_name, &var_offset); - global_var__add(dloc, pos->addr, var_name, &type_die); + global_var__add(dloc, pos->addr, var_name, &type_die, + pos->from_alt); } delete_var_types(var_types); @@ -808,7 +960,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc, gvar = global_var__find(dloc, var_addr); if (gvar) { - if (!dwarf_offdie(dloc->di->dbg, gvar->die_offset, type_die)) + if (!die_get_type_die(dloc->di->dbg, gvar->die_offset, + gvar->die_tag, gvar->from_alt, type_die)) return false; *var_offset = var_addr - gvar->start; @@ -838,7 +991,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc, ok: /* The address should point to the start of the variable */ - global_var__add(dloc, var_addr - *var_offset, var_name, type_die); + global_var__add(dloc, var_addr - *var_offset, var_name, type_die, + !die_same_file(cu_die, type_die)); return true; } @@ -893,7 +1047,8 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo continue; } /* Get the type DIE using the offset */ - if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die)) + if (!die_get_type_die(dloc->di->dbg, var->die_off, + var->die_tag, var->from_alt, &mem_die)) continue; if (var->reg == DWARF_REG_FB || var->reg == fbreg || var->reg == state->stack_reg) { @@ -918,7 +1073,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo continue; findnew_stack_state(state, offset, TSR_KIND_TYPE, - &mem_die, /*ptr_offset=*/0); + &mem_die, /*ptr_offset=*/0, /*imm_value=*/0); if (var->reg == state->stack_reg) { pr_debug_dtp("var [%"PRIx64"] %#x(reg%d)", @@ -1101,6 +1256,78 @@ static void setup_stack_canary(struct data_loc_info *dloc) } } +int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc, + int breg, struct type_state *state, bool apply_index, + int *offset) +{ + if (arch__is_arm64(arch)) { + int reg2; + + /* Post-indexed addressing: offset is 0 when not applying index */ + if (op_loc->addr_mode == PERF_AAM_POST_INDEX && !apply_index) { + *offset = 0; + return 0; + } + + /* Single-register addressing: return the stored offset directly */ + if (!op_loc->multi_regs) { + *offset = op_loc->offset; + return 0; + } + + /* + * SIB (Scale-Index-Base) style indexed addressing, e.g.: + * Array element load: ldr dst, [base, index, lsl #3] + * Array element load: ldr dst, [base, index, uxtw #3] + * Array address calc: add dst, base, index lsl #3 + * + * The index reg only computes element offset without modifying + * the underlying data type; the dst type is inherited from base. + * Therefore, the base reg offset can be treated as 0. + */ + if (op_loc->extend_type || op_loc->shift_type) { + /* A memory reference implies breg is a pointer. */ + if (op_loc->mem_ref) { + *offset = 0; + return 0; + } + /* + * Otherwise, require breg to be a known pointer to avoid + * treating integer arithmetic as indexed address calculation. + */ + if (has_reg_type(state, breg) && state->regs[breg].ok && + (state->regs[breg].kind == TSR_KIND_POINTER || + (state->regs[breg].kind == TSR_KIND_TYPE && + dwarf_tag(&state->regs[breg].type) == DW_TAG_pointer_type))) { + *offset = 0; + return 0; + } + return -1; + } + + /* + * For register-based addressing without shift/extension, + * derive the offset from the type of reg2. + */ + reg2 = op_loc->reg1 == breg ? op_loc->reg2 : op_loc->reg1; + if (has_reg_type(state, reg2) && state->regs[reg2].ok) { + if (state->regs[reg2].kind == TSR_KIND_CONST) + *offset = (s64)state->regs[reg2].imm_value; + else if (state->regs[reg2].kind == TSR_KIND_PERCPU_BASE) + *offset = 0; /* it does not contribute to the offset */ + else + return -1; + + return 0; + } + + return -1; + } + + *offset = op_loc->offset; + return 0; +} + /* * It's at the target address, check if it has a matching type. * It returns PERF_TMR_BAIL_OUT when it looks up per-cpu variables which @@ -1112,21 +1339,27 @@ static enum type_match_result check_matching_type(struct type_state *state, struct disasm_line *dl, Dwarf_Die *type_die) { - Dwarf_Word size; u32 insn_offset = dl->al.offset; int reg = dloc->op->reg1; - int offset = dloc->op->offset; - const char *offset_sign = ""; + int offset; + const char *offset_sign; bool retry = true; - if (offset < 0) { - offset = -offset; - offset_sign = "-"; +again: + if (arch_get_reg_offset(dloc->arch, dloc->op, reg, state, false, &offset)) { + /* + * Fall back to the instruction's offset. This prevents the + * register type of the instruction from being completely lost. + * Compared to dropping this type inference entirely, providing + * a rough type hint is likely more useful. + */ + offset = dloc->op->offset; } -again: + offset_sign = offset < 0 ? "-" : ""; + pr_debug_dtp("chk [%x] reg%d offset=%s%#x ok=%d kind=%d ", - insn_offset, reg, offset_sign, offset, + insn_offset, reg, offset_sign, abs(offset), state->regs[reg].ok, state->regs[reg].kind); if (!state->regs[reg].ok) @@ -1149,7 +1382,7 @@ static enum type_match_result check_matching_type(struct type_state *state, */ ptr_type = die_get_pointer_type(&state->regs[reg].type, &ptr_die); if (!ptr_type) { - if (dloc->op->offset < 0 && reg != state->stack_reg) + if (offset < 0 && reg != state->stack_reg) goto check_kernel; return PERF_TMR_NO_POINTER; @@ -1159,19 +1392,14 @@ static enum type_match_result check_matching_type(struct type_state *state, if (__die_get_real_type(ptr_type, type_die) == NULL) return PERF_TMR_NO_POINTER; - dloc->type_offset = dloc->op->offset + state->regs[reg].offset; + dloc->type_offset = offset + state->regs[reg].offset; if (dwarf_tag(type_die) == DW_TAG_typedef) die_get_real_type(type_die, &sized_type); else sized_type = *type_die; - /* Get the size of the actual type */ - if (dwarf_aggregate_size(&sized_type, &size) < 0 || - (unsigned)dloc->type_offset >= size) - return PERF_TMR_BAD_OFFSET; - - return PERF_TMR_OK; + return check_type_offset(&sized_type, dloc->type_offset); } if (state->regs[reg].kind == TSR_KIND_POINTER) { @@ -1188,14 +1416,9 @@ static enum type_match_result check_matching_type(struct type_state *state, */ *type_die = state->regs[reg].type; - dloc->type_offset = dloc->op->offset + state->regs[reg].offset; + dloc->type_offset = offset + state->regs[reg].offset; - /* Get the size of the actual type */ - if (dwarf_aggregate_size(type_die, &size) < 0 || - (unsigned)dloc->type_offset >= size) - return PERF_TMR_BAD_OFFSET; - - return PERF_TMR_OK; + return check_type_offset(type_die, dloc->type_offset); } if (state->regs[reg].kind == TSR_KIND_PERCPU_POINTER) { @@ -1207,11 +1430,9 @@ static enum type_match_result check_matching_type(struct type_state *state, */ *type_die = state->regs[reg].type; - dloc->type_offset = dloc->op->offset; + dloc->type_offset = offset; - /* Get the size of the actual type */ - if (dwarf_aggregate_size(type_die, &size) < 0 || - (unsigned)dloc->type_offset >= size) + if (check_type_offset(type_die, dloc->type_offset) != PERF_TMR_OK) return PERF_TMR_BAIL_OUT; return PERF_TMR_OK; @@ -1231,11 +1452,31 @@ static enum type_match_result check_matching_type(struct type_state *state, } if (state->regs[reg].kind == TSR_KIND_PERCPU_BASE) { - u64 var_addr = dloc->op->offset; + u64 var_addr; int var_offset; pr_debug_dtp("percpu var"); + if (arch__is_arm64(dloc->arch)) { + int reg2; + + if (!retry || !dloc->op->multi_regs || + dloc->op->reg1 == dloc->op->reg2 || + dloc->op->extend_type || dloc->op->shift_type) + return PERF_TMR_BAIL_OUT; + + reg2 = dloc->op->reg2; + if (!has_reg_type(state, reg2) || !state->regs[reg2].ok) + return PERF_TMR_NO_TYPE; + + pr_debug_dtp(" : retry\n"); + retry = false; + reg = reg2; + goto again; + } + + var_addr = (s64) offset; + if (dloc->op->multi_regs) { int reg2 = dloc->op->reg2; @@ -1258,16 +1499,35 @@ static enum type_match_result check_matching_type(struct type_state *state, if (state->regs[reg].kind == TSR_KIND_CONST && dso__kernel(map__dso(dloc->ms->map))) { - if (dloc->op->offset < 0 && reg != state->stack_reg && reg != dloc->fbreg) + if (offset < 0 && reg != state->stack_reg && reg != dloc->fbreg) goto check_kernel; } + + if (state->regs[reg].kind == TSR_KIND_GLOBAL_ADDR) { + u64 var_addr = state->regs[reg].imm_value + offset; + int var_offset; + + pr_debug_dtp("global addr"); + + /* + * The register holds the address of a global variable. Try to + * find the variable by the address and get its type. + */ + if (get_global_var_type(cu_die, dloc, dloc->ip, var_addr, + &var_offset, type_die)) { + dloc->type_offset = var_offset; + return PERF_TMR_OK; + } + /* No need to retry global variables */ + return PERF_TMR_BAIL_OUT; + } check_non_register: if (reg == dloc->fbreg || reg == state->stack_reg) { struct type_state_stack *stack; pr_debug_dtp("%s", reg == dloc->fbreg ? "fbreg" : "stack"); - stack = find_stack_state(state, dloc->type_offset); + stack = find_stack_state(state, offset); if (stack == NULL) { if (retry) { pr_debug_dtp(" : retry\n"); @@ -1290,7 +1550,7 @@ static enum type_match_result check_matching_type(struct type_state *state, *type_die = stack->type; /* Update the type offset from the start of slot */ - dloc->type_offset -= stack->offset; + dloc->type_offset = offset - stack->offset; return PERF_TMR_OK; } @@ -1308,7 +1568,7 @@ static enum type_match_result check_matching_type(struct type_state *state, if (reg != fbreg) return PERF_TMR_NO_TYPE; - stack = find_stack_state(state, dloc->type_offset - fboff); + stack = find_stack_state(state, offset - fboff); if (stack == NULL) { if (retry) { pr_debug_dtp(" : retry\n"); @@ -1331,7 +1591,7 @@ static enum type_match_result check_matching_type(struct type_state *state, *type_die = stack->type; /* Update the type offset from the start of slot */ - dloc->type_offset -= fboff + stack->offset; + dloc->type_offset = offset - fboff - stack->offset; return PERF_TMR_OK; } @@ -1345,7 +1605,7 @@ static enum type_match_result check_matching_type(struct type_state *state, arch__is_x86(dloc->arch)) { pr_debug_dtp("this-cpu var"); - addr = dloc->op->offset; + addr = (s64) offset; if (get_global_var_type(cu_die, dloc, dloc->ip, addr, &offset, type_die)) { @@ -1356,8 +1616,8 @@ static enum type_match_result check_matching_type(struct type_state *state, } /* Access to global variable like "-0x7dcf0500(,%rdx,8)" */ - if (dloc->op->offset < 0 && reg != state->stack_reg) { - addr = (s64) dloc->op->offset; + if (offset < 0 && reg != state->stack_reg) { + addr = (s64) offset; if (get_global_var_type(cu_die, dloc, dloc->ip, addr, &offset, type_die)) { @@ -1428,7 +1688,8 @@ static enum type_match_result find_data_type_insn(struct data_loc_info *dloc, static int arch_supports_insn_tracking(struct data_loc_info *dloc) { - if ((arch__is_x86(dloc->arch)) || (arch__is_powerpc(dloc->arch))) + if (arch__is_x86(dloc->arch) || arch__is_powerpc(dloc->arch) || + arch__is_arm64(dloc->arch)) return 1; return 0; } @@ -1735,6 +1996,7 @@ struct annotated_data_type *find_data_type(struct data_loc_info *dloc) { struct dso *dso = map__dso(dloc->ms->map); Dwarf_Die type_die; + struct annotated_data_type *result; /* * The type offset is the same as instruction offset by default. @@ -1747,45 +2009,66 @@ struct annotated_data_type *find_data_type(struct data_loc_info *dloc) if (find_data_type_die(dloc, &type_die) < 0) return NULL; - return dso__findnew_data_type(dso, &type_die); + result = dso__findnew_data_type(dso, &type_die); + if (result == NULL) + return NULL; + + if (result->flex_array && dloc->type_offset > result->self.size) { + struct annotated_member *flex_array = get_flex_array_member(result); + + if (flex_array && flex_array->size > 0) { + int offset = dloc->type_offset; + + /* adjust offset in the flex array */ + offset -= flex_array->offset; + offset %= flex_array->size; + offset += flex_array->offset; + + dloc->type_offset = offset; + } + } + return result; +} + +static size_t data_type_hash(long key, void *ctx __maybe_unused) +{ + return key; +} + +static bool data_type_equal(long key1, long key2, void *ctx __maybe_unused) +{ + return key1 == key2; } static int alloc_data_type_histograms(struct annotated_data_type *adt, int nr_entries) { int i; - size_t sz = sizeof(struct type_hist); - sz += sizeof(struct type_hist_entry) * adt->self.size; - - /* Allocate a table of pointers for each event */ + /* Allocate a histogram for each event */ adt->histograms = calloc(nr_entries, sizeof(*adt->histograms)); if (adt->histograms == NULL) return -ENOMEM; - /* - * Each histogram is allocated for the whole size of the type. - * TODO: Probably we can move the histogram to members. - */ for (i = 0; i < nr_entries; i++) { - adt->histograms[i] = zalloc(sz); - if (adt->histograms[i] == NULL) - goto err; + hashmap__init(&adt->histograms[i].samples, data_type_hash, + data_type_equal, /*ctx=*/NULL); } adt->nr_histograms = nr_entries; return 0; - -err: - while (--i >= 0) - zfree(&(adt->histograms[i])); - zfree(&adt->histograms); - return -ENOMEM; } static void delete_data_type_histograms(struct annotated_data_type *adt) { - for (int i = 0; i < adt->nr_histograms; i++) - zfree(&(adt->histograms[i])); + for (int i = 0; i < adt->nr_histograms; i++) { + struct hashmap *map = &adt->histograms[i].samples; + struct hashmap_entry *pos, *tmp; + size_t bkt; + + hashmap__for_each_entry_safe(map, pos, tmp, bkt) + free(pos->pvalue); + hashmap__clear(map); + } zfree(&adt->histograms); adt->nr_histograms = 0; @@ -1824,6 +2107,7 @@ int annotated_data_type__update_samples(struct annotated_data_type *adt, int nr_samples, u64 period) { struct type_hist *h; + struct type_hist_entry *entry; if (adt == NULL) return 0; @@ -1835,15 +2119,26 @@ int annotated_data_type__update_samples(struct annotated_data_type *adt, return -1; } - if (offset < 0 || offset >= adt->self.size) + if (offset < 0 || (offset >= adt->self.size && !adt->flex_array)) return -1; - h = adt->histograms[evsel->core.idx]; + h = &adt->histograms[evsel->core.idx]; h->nr_samples += nr_samples; - h->addr[offset].nr_samples += nr_samples; h->period += period; - h->addr[offset].period += period; + + if (!hashmap__find(&h->samples, offset, &entry)) { + entry = zalloc(sizeof(*entry)); + if (entry == NULL) + return -1; + + if (hashmap__append(&h->samples, offset, entry) < 0) { + free(entry); + return -1; + } + } + entry->nr_samples += nr_samples; + entry->period += period; return 0; } @@ -1911,14 +2206,14 @@ static void print_annotated_data_type(struct annotated_data_type *mem_type, struct evsel *evsel, int indent) { struct annotated_member *child; - struct type_hist *h = mem_type->histograms[evsel->core.idx]; + struct type_hist *h; int i, nr_events = 0, samples = 0; u64 period = 0; int width = symbol_conf.show_total_period ? 11 : 7; struct evsel *pos; for_each_group_evsel(pos, evsel) { - h = mem_type->histograms[pos->core.idx]; + h = &mem_type->histograms[pos->core.idx]; if (symbol_conf.skip_empty && evsel__hists(pos)->stats.nr_samples == 0) @@ -1927,8 +2222,13 @@ static void print_annotated_data_type(struct annotated_data_type *mem_type, samples = 0; period = 0; for (i = 0; i < member->size; i++) { - samples += h->addr[member->offset + i].nr_samples; - period += h->addr[member->offset + i].period; + struct type_hist_entry *entry; + + if (!hashmap__find(&h->samples, member->offset + i, &entry)) + continue; + + samples += entry->nr_samples; + period += entry->period; } print_annotated_data_value(h, period, samples); nr_events++; diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h index c26130744260..eee1f8dae3dc 100644 --- a/tools/perf/util/annotate-data.h +++ b/tools/perf/util/annotate-data.h @@ -37,6 +37,7 @@ enum type_state_kind { TSR_KIND_PERCPU_POINTER, TSR_KIND_POINTER, TSR_KIND_CANARY, + TSR_KIND_GLOBAL_ADDR, }; /** @@ -47,6 +48,8 @@ enum type_state_kind { * @var_name: Name of the member variable * @offset: Offset from the outer data type * @size: Size of the member field + * @is_union: Whether it's an union type + * @is_flex_array: Whether it's a flex array * * This represents a member type in a data type. */ @@ -57,6 +60,11 @@ struct annotated_member { char *var_name; int offset; int size; + bool is_union; + bool is_flex_array; + /* Children not expanded because the nesting limit was reached */ + bool truncated; + u8 depth; }; /** @@ -73,28 +81,30 @@ struct type_hist_entry { * struct type_hist - Type histogram for each event * @nr_samples: Total number of samples in this data type * @period: Total count of the event in this data type - * @offset: Array of histogram entry + * @samples: Hashmap of (offset, type_hist_entry) */ struct type_hist { u64 nr_samples; u64 period; - struct type_hist_entry addr[]; + struct hashmap samples; }; /** * struct annotated_data_type - Data type to profile * @node: RB-tree node for dso->type_tree * @self: Actual type information + * @flex_array: Whether it has a flex array * @nr_histogram: Number of histogram entries - * @histograms: An array of pointers to histograms + * @histograms: An array of histograms * * This represents a data type accessed by samples in the profile data. */ struct annotated_data_type { struct rb_node node; struct annotated_member self; + bool flex_array; int nr_histograms; - struct type_hist **histograms; + struct type_hist *histograms; }; extern struct annotated_data_type unknown_type; @@ -173,7 +183,7 @@ extern struct annotated_data_stat ann_data_stat; */ struct type_state_reg { Dwarf_Die type; - u32 imm_value; + u64 imm_value; /* * The offset within the struct that the register points to. * A value of 0 means the register points to the beginning. @@ -199,6 +209,7 @@ struct type_state_stack { int size; bool compound; u8 kind; + u64 imm_value; }; /* @@ -253,18 +264,22 @@ bool has_reg_type(struct type_state *state, int reg); struct type_state_stack *findnew_stack_state(struct type_state *state, int offset, u8 kind, Dwarf_Die *type_die, - int ptr_offset); + int ptr_offset, u64 imm_value); void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, - Dwarf_Die *type_die, int ptr_offset); + Dwarf_Die *type_die, int ptr_offset, u64 imm_value); struct type_state_stack *find_stack_state(struct type_state *state, int offset); +void delete_stack_state(struct type_state *state, int offset); +void invalidate_reg_state(struct type_state_reg *reg); bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc, u64 ip, u64 var_addr, int *var_offset, Dwarf_Die *type_die); bool get_global_var_info(struct data_loc_info *dloc, u64 addr, const char **var_name, int *var_offset); void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind); - +int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc, + int breg, struct type_state *state, bool apply_index, + int *offset); #else /* HAVE_LIBDW_SUPPORT */ static inline struct annotated_data_type * diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index df70e95a8470..86a4beec6528 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -145,6 +145,17 @@ static int annotated_source__alloc_histograms(struct annotated_source *src, return src->histograms ? 0 : -1; } +struct sym_hist_entry * +annotated_source__hist_entry(struct annotated_source *src, const struct evsel *evsel, u64 offset) +{ + struct sym_hist_entry *entry; + long key = offset << 16 | evsel->core.idx; + + if (!hashmap__find(src->samples, key, &entry)) + return NULL; + return entry; +} + void symbol__annotate_zero_histograms(struct symbol *sym) { struct annotation *notes = symbol__annotation(sym); @@ -222,6 +233,7 @@ static int __symbol__inc_addr_samples(struct map_symbol *ms, u64 offset; struct sym_hist *h; struct sym_hist_entry *entry; + u64 weight = sample->weight ?: sample->ins_lat ?: sample->weight3; pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, map__unmap_ip(ms->map, addr)); @@ -256,10 +268,28 @@ static int __symbol__inc_addr_samples(struct map_symbol *ms, entry->nr_samples++; entry->period += sample->period; + if (sample->evsel->core.attr.sample_type & PERF_SAMPLE_WEIGHT_TYPE) { + entry->weight_sum[WEIGHT_WEIGHT] += sample->weight; + entry->weight_num[WEIGHT_WEIGHT]++; + if (sample->weight) + sym_hist__set_weight_mask(h, BIT(WEIGHT_WEIGHT)); + } + if (sample->evsel->core.attr.sample_type & PERF_SAMPLE_WEIGHT_STRUCT) { + entry->weight_sum[WEIGHT_INSNLAT] += sample->ins_lat; + entry->weight_num[WEIGHT_INSNLAT]++; + entry->weight_sum[WEIGHT_WEIGHT3] += sample->weight3; + entry->weight_num[WEIGHT_WEIGHT3]++; + if (sample->ins_lat) + sym_hist__set_weight_mask(h, BIT(WEIGHT_INSNLAT)); + if (sample->weight3) + sym_hist__set_weight_mask(h, BIT(WEIGHT_WEIGHT3)); + } + pr_debug3("%#" PRIx64 " %s: period++ [addr: %#" PRIx64 ", %#" PRIx64 - ", evidx=%d] => nr_samples: %" PRIu64 ", period: %" PRIu64 "\n", + ", evidx=%d] => nr_samples: %" PRIu64 ", period: %" PRIu64 + " weight %" PRIu64 "\n", sym->start, sym->name, addr, addr - sym->start, evsel->core.idx, - entry->nr_samples, entry->period); + entry->nr_samples, entry->period, weight); return 0; } @@ -778,6 +808,50 @@ static bool needs_type_info(struct annotated_data_type *data_type) return (data_type != &stackop_type) && (data_type != &canary_type); } +static const char *annotation__weight_mode_str(enum symbol__weight_mode mode, + const struct evsel *evsel) +{ + switch (mode) { + case WEIGHT_NONE: + return ""; + case WEIGHT_WEIGHT: + return "Weight"; + case WEIGHT_INSNLAT: + return "InsnLat"; + case WEIGHT_WEIGHT3: + switch (evsel__e_machine((struct evsel *)evsel, NULL)) { + case EM_PPC: + case EM_PPC64: + return "PCycleLat"; + case EM_X86_64: + return "RetireLat"; + default: + return "Weight3"; + } + default: + return ""; + } +} + +static void annotation__column_title(char *buf, size_t size, + struct annotation *notes, + const struct evsel *evsel) +{ + const char *base = symbol_conf.show_total_period ? "Period" : + symbol_conf.show_nr_samples ? "Samples" : "Percent"; + int weight; + u8 weight_mask = annotation__weight_mask(notes, evsel); + + scnprintf(buf, size, "%s", base); + for_each_weight(weight) { + if (weight_mask & BIT(weight)) + scnprintf(buf + strlen(buf), + size - strlen(buf), + " %s", + annotation__weight_mode_str(weight, evsel)); + } +} + static int annotation_line__print(struct annotation_line *al, struct annotation_print_data *apd, struct annotation_options *opts, int printed, @@ -833,6 +907,8 @@ annotation_line__print(struct annotation_line *al, struct annotation_print_data for (i = 0; i < nr_percent; i++) { struct annotation_data *data = &al->data[i]; double percent; + int weight; + u8 weight_mask = annotation__weight_mask(notes, apd->evsel); percent = annotation_data__percent(data, percent_type); color = get_percent_color(percent); @@ -845,6 +921,14 @@ annotation_line__print(struct annotation_line *al, struct annotation_print_data data->he.nr_samples); else color_fprintf(stdout, color, " %7.2f", percent); + for_each_weight(weight) { + if (weight_mask & BIT(weight)) + color_fprintf(stdout, color, " %7" PRIu64, + data->he.weight_num[weight] ? + data->he.weight_sum[weight] / + data->he.weight_num[weight] + : 0); + } } printf(" : "); @@ -891,7 +975,7 @@ annotation_line__print(struct annotation_line *al, struct annotation_print_data } else if (max_lines && printed >= max_lines) return 1; else { - int width = annotation__pcnt_width(notes); + int width = annotation__pcnt_width(notes, apd->evsel); if (queue) return -1; @@ -915,6 +999,9 @@ static void calc_percent(struct annotation *notes, struct sym_hist *sym_hist = annotation__histogram(notes, evsel); unsigned int hits = 0; u64 period = 0; + int i; + u64 weight_sum[WEIGHT_WEIGHT3 + 1] = { 0 }; + u64 weight_num[WEIGHT_WEIGHT3 + 1] = { 0 }; while (offset < end) { struct sym_hist_entry *entry; @@ -923,6 +1010,10 @@ static void calc_percent(struct annotation *notes, if (entry) { hits += entry->nr_samples; period += entry->period; + for_each_weight(i) { + weight_sum[i] += entry->weight_sum[i]; + weight_num[i] += entry->weight_num[i]; + } } ++offset; } @@ -930,6 +1021,10 @@ static void calc_percent(struct annotation *notes, if (sym_hist->nr_samples) { data->he.period = period; data->he.nr_samples = hits; + for_each_weight(i) { + data->he.weight_sum[i] = weight_sum[i]; + data->he.weight_num[i] = weight_num[i]; + } data->percent[PERCENT_HITS_LOCAL] = 100.0 * hits / sym_hist->nr_samples; } @@ -1252,8 +1347,9 @@ int hist_entry__annotate_printf(struct hist_entry *he, struct evsel *evsel) int printed = 2, queue_len = 0; int more = 0; bool context = opts->context; - int width = annotation__pcnt_width(notes); + int width = annotation__pcnt_width(notes, evsel); int graph_dotted_len; + char title[64]; char buf[512]; filename = strdup(dso__long_name(dso)); @@ -1275,10 +1371,10 @@ int hist_entry__annotate_printf(struct hist_entry *he, struct evsel *evsel) return ENOTSUP; } - graph_dotted_len = printf(" %-*.*s| Source code & Disassembly of %s for %s (%" PRIu64 " samples, " + annotation__column_title(title, sizeof(title), notes, evsel); + graph_dotted_len = printf(" %-*.*s|\tSource code & Disassembly of %s for %s (%" PRIu64 " samples, " "percent: %s)\n", - width, width, symbol_conf.show_total_period ? "Period" : - symbol_conf.show_nr_samples ? "Samples" : "Percent", + width, width, title, d_filename, evsel_name, h->nr_samples, percent_type_str(opts->percent_type)); @@ -2029,7 +2125,8 @@ static int disasm_line__snprint_type_info(struct disasm_line *dl, return printed; } -void annotation_line__write(struct annotation_line *al, struct annotation *notes, +void annotation_line__write(struct annotation_line *al, + struct annotation *notes, const struct annotation_write_ops *wops, struct annotation_print_data *apd) { @@ -2037,7 +2134,8 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes bool change_color = wops->change_color; double percent_max = annotation_line__max_percent(al, annotate_opts.percent_type); int width = wops->width; - int pcnt_width = annotation__pcnt_width(notes); + int pcnt_width = annotation__pcnt_width(notes, apd->evsel); + u8 weight_mask = annotation__weight_mask(notes, apd->evsel); int cycles_width = annotation__cycles_width(notes); bool show_title = false; char bf[256]; @@ -2062,6 +2160,7 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes for (i = 0; i < al->data_nr; i++) { double percent; + int weight; percent = annotation_data__percent(&al->data[i], annotate_opts.percent_type); @@ -2075,6 +2174,15 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes } else { obj__printf(obj, "%7.2f ", percent); } + + for_each_weight(weight) { + if (weight_mask & BIT(weight)) + obj__printf(obj, "%7" PRIu64 " ", + al->data[i].he.weight_num[weight] ? + al->data[i].he.weight_sum[weight] / + al->data[i].he.weight_num[weight] : + 0); + } } } else { obj__set_percent_color(obj, 0, current_entry); @@ -2082,9 +2190,9 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes if (!show_title) obj__printf(obj, "%-*s", pcnt_width, " "); else { - obj__printf(obj, "%-*s", pcnt_width, - symbol_conf.show_total_period ? "Period" : - symbol_conf.show_nr_samples ? "Samples" : "Percent"); + char buf[64]; + annotation__column_title(buf, sizeof(buf), notes, apd->evsel); + obj__printf(obj, "%-*s", pcnt_width, buf); } } width -= pcnt_width; @@ -2472,72 +2580,29 @@ int annotate_check_args(void) return 0; } -static int arch__dwarf_regnum(const struct arch *arch, const char *str) +int arch__dwarf_regnum(const struct arch *arch, const char *str) { - const char *p; + const char *p = str; char *regname, *q; int reg; - p = strchr(str, arch->objdump.register_char); - if (p == NULL) - return -1; + if (arch->objdump.register_char) { + p = strchr(str, arch->objdump.register_char); + if (p == NULL) + return -1; + } regname = strdup(p); if (regname == NULL) return -1; - q = strpbrk(regname, ",) "); + q = strpbrk(regname, ",)] "); if (q) *q = '\0'; reg = get_dwarf_regnum(regname, arch->id.e_machine, arch->id.e_flags); free(regname); - return reg; -} - -/* - * Get register number and access offset from the given instruction. - * It assumes AT&T x86 asm format like OFFSET(REG). Maybe it needs - * to revisit the format when it handles different architecture. - * Fills @reg and @offset when return 0. - */ -static int extract_reg_offset(const struct arch *arch, const char *str, - struct annotated_op_loc *op_loc) -{ - char *p; - - if (arch->objdump.register_char == 0) - return -1; - - /* - * It should start from offset, but it's possible to skip 0 - * in the asm. So 0(%rax) should be same as (%rax). - * - * However, it also start with a segment select register like - * %gs:0x18(%rbx). In that case it should skip the part. - */ - if (*str == arch->objdump.register_char) { - if (arch__is_x86(arch)) { - /* FIXME: Handle other segment registers */ - if (!strncmp(str, "%gs:", 4)) - op_loc->segment = INSN_SEG_X86_GS; - } - - while (*str && !isdigit(*str) && - *str != arch->objdump.memory_ref_char) - str++; - } - - op_loc->offset = strtol(str, &p, 0); - op_loc->reg1 = arch__dwarf_regnum(arch, p); - if (op_loc->reg1 == -1) - return -1; - - /* Get the second register */ - if (op_loc->multi_regs) - op_loc->reg2 = arch__dwarf_regnum(arch, p + 1); - - return 0; + return reg < 0 ? -1 : reg; } /** @@ -2595,50 +2660,11 @@ int annotate_get_insn_location(const struct arch *arch, struct disasm_line *dl, /* Invalidate the register by default */ op_loc->reg1 = -1; op_loc->reg2 = -1; + op_loc->mem_ref = mem_ref; + op_loc->multi_regs = multi_regs; - if (insn_str == NULL) { - if (!arch__is_powerpc(arch)) - continue; - } - - /* - * For powerpc, call get_powerpc_regs function which extracts the - * required fields for op_loc, ie reg1, reg2, offset from the - * raw instruction. - */ - if (arch__is_powerpc(arch)) { - op_loc->mem_ref = mem_ref; - op_loc->multi_regs = multi_regs; - get_powerpc_regs(dl->raw.raw_insn, !i, op_loc); - } else if (strchr(insn_str, arch->objdump.memory_ref_char)) { - op_loc->mem_ref = true; - op_loc->multi_regs = multi_regs; - extract_reg_offset(arch, insn_str, op_loc); - } else { - const char *s = insn_str; - char *p = NULL; - - if (arch__is_x86(arch)) { - /* FIXME: Handle other segment registers */ - if (!strncmp(insn_str, "%gs:", 4)) { - op_loc->segment = INSN_SEG_X86_GS; - op_loc->offset = strtol(insn_str + 4, - &p, 0); - if (p && p != insn_str + 4) - op_loc->imm = true; - continue; - } - } - - if (*s == arch->objdump.register_char) { - op_loc->reg1 = arch__dwarf_regnum(arch, s); - } - else if (*s == arch->objdump.imm_char) { - op_loc->offset = strtol(s + 1, &p, 0); - if (p && p != s + 1) - op_loc->imm = true; - } - } + if (arch->extract_op_location) + arch->extract_op_location(arch, dl, insn_str, i, op_loc); } return 0; diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h index fa08d09b80f7..1b47c1156b9a 100644 --- a/tools/perf/util/annotate.h +++ b/tools/perf/util/annotate.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,8 @@ struct annotation; struct sym_hist_entry { u64 nr_samples; u64 period; + u64 weight_sum[WEIGHT_WEIGHT3 + 1]; + u64 weight_num[WEIGHT_WEIGHT3 + 1]; }; enum { @@ -231,8 +234,20 @@ void symbol__calc_percent(struct symbol *sym, struct evsel *evsel); struct sym_hist { u64 nr_samples; u64 period; + u8 weight_mask; }; +/* Can be set asynchronously by top. */ +static inline u8 sym_hist__weight_mask(const struct sym_hist *hist) +{ + return __atomic_load_n(&hist->weight_mask, __ATOMIC_RELAXED); +} + +static inline void sym_hist__set_weight_mask(struct sym_hist *hist, u8 mask) +{ + __atomic_fetch_or(&hist->weight_mask, mask, __ATOMIC_RELAXED); +} + /** * struct cyc_hist - (CPU) cycle histogram for a basic block * @@ -275,7 +290,7 @@ struct cyc_hist { /** * struct annotated_source - symbols with hits have this attached as in annotation * - * @source: List head for annotated_line (embeded in disasm_line). + * @source: List head for annotated_line (embedded in disasm_line). * @histograms: Array of symbol histograms per event to maintain the total number * of samples and period. * @nr_histograms: This may not be the same as evsel->evlist->core.nr_entries if @@ -376,9 +391,34 @@ static inline int annotation__cycles_width(struct annotation *notes) return notes->branch ? ANNOTATION__IPC_WIDTH + ANNOTATION__CYCLES_WIDTH : 0; } -static inline int annotation__pcnt_width(struct annotation *notes) +static inline u8 annotation__weight_mask(struct annotation *notes, + const struct evsel *evsel) { - return (symbol_conf.show_total_period ? 12 : 8) * notes->src->nr_events; + u8 mask = 0; + struct evsel *pos; + int i; + + if (!symbol_conf.annotate_weight) + return 0; + + if (evsel__is_group_event((struct evsel *)evsel)) { + pos = (struct evsel *)evsel; + for (i = 0; i < evsel->core.nr_members; i++) { + mask |= sym_hist__weight_mask(¬es->src->histograms[pos->core.idx]); + pos = evsel__next(pos); + } + return mask; + } + + return sym_hist__weight_mask(¬es->src->histograms[evsel->core.idx]); +} + +static inline int annotation__pcnt_width(struct annotation *notes, + const struct evsel *evsel) +{ + int extra = hweight8(annotation__weight_mask(notes, evsel)) * 8; + return ((symbol_conf.show_total_period ? 12 : 8) + extra) * + notes->src->nr_events; } static inline bool annotation_line__filter(struct annotation_line *al) @@ -406,16 +446,8 @@ static inline struct sym_hist *annotation__histogram(struct annotation *notes, return annotated_source__histogram(notes->src, evsel); } -static inline struct sym_hist_entry * -annotated_source__hist_entry(struct annotated_source *src, const struct evsel *evsel, u64 offset) -{ - struct sym_hist_entry *entry; - long key = offset << 16 | evsel->core.idx; - - if (!hashmap__find(src->samples, key, &entry)) - return NULL; - return entry; -} +struct sym_hist_entry * +annotated_source__hist_entry(struct annotated_source *src, const struct evsel *evsel, u64 offset); static inline struct annotation *symbol__annotation(struct symbol *sym) { @@ -492,12 +524,18 @@ int annotate_parse_percent_type(const struct option *opt, const char *_str, int annotate_check_args(void); +int arch__dwarf_regnum(const struct arch *arch, const char *str); + /** * struct annotated_op_loc - Location info of instruction operand * @reg1: First register in the operand * @reg2: Second register in the operand * @offset: Memory access offset in the operand * @segment: Segment selector register + * @addr_mode: Addressing mode, only valid if @mem_ref is true + * @extend_type: Operand extension type (enum annotated_ext_type) + * @shift_type: Operand shift type (enum annotated_shift_type) + * @amount: Number of bits to shift * @mem_ref: Whether the operand accesses memory * @multi_regs: Whether the second register is used * @imm: Whether the operand is an immediate value (in offset) @@ -507,6 +545,10 @@ struct annotated_op_loc { int reg2; int offset; u8 segment; + u8 addr_mode; + u8 extend_type; + u8 shift_type; + u8 amount; bool mem_ref; bool multi_regs; bool imm; @@ -530,6 +572,59 @@ enum annotated_x86_segment { INSN_SEG_X86_SS, }; +/* + * ARM64 addressing modes for memory operations. + * + * [Xn, #imm] -> SIGNED_OFFSET (base + offset, base unchanged) + * [Xn, #imm]! -> PRE_INDEX (base += offset, then access) + * [Xn], #imm -> POST_INDEX (access, then base += offset) + */ +enum annotated_addr_mode { + PERF_AAM_NONE = 0, + + PERF_AAM_SIGNED_OFFSET, + PERF_AAM_PRE_INDEX, + PERF_AAM_POST_INDEX, +}; + +/* + * ARM64 register extension types. + * UXT* = zero-extend, SXT* = sign-extend. + * B=8bit, H=16bit, W=32bit, X=64bit. + * + * Example: UXTW = zero-extend 32-bit Wn to 64-bit Xn + */ +enum annotated_ext_type { + PERF_EXT_NONE = 0, + + PERF_EXT_UXTB, + PERF_EXT_UXTH, + PERF_EXT_UXTW, + PERF_EXT_UXTX, + PERF_EXT_SXTB, + PERF_EXT_SXTH, + PERF_EXT_SXTW, + PERF_EXT_SXTX, +}; + +/* + * ARM64 operand shift types. + * LSL = logical shift left. + * LSR = logical shift right. + * ASR = arithmetic shift right. + * ROR = rotate right. + * + * Example: LSL #3 = shift the operand left by 3 bits. + */ +enum annotated_shift_type { + PERF_SHIFT_NONE = 0, + + PERF_SHIFT_LSL, + PERF_SHIFT_LSR, + PERF_SHIFT_ASR, + PERF_SHIFT_ROR, +}; + /** * struct annotated_insn_loc - Location info of instruction * @ops: Array of location info for source and target operands diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c index 401aab529309..8d95df23be6f 100644 --- a/tools/perf/util/arm-spe.c +++ b/tools/perf/util/arm-spe.c @@ -626,6 +626,7 @@ static const struct midr_range common_ds_encoding_cpus[] = { MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V1), MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V2), MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V3), + MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V3AE), MIDR_ALL_VERSIONS(MIDR_NVIDIA_OLYMPUS), {}, }; @@ -2029,7 +2030,10 @@ int arm_spe_process_auxtrace_info(union perf_event *event, if (session->itrace_synth_opts && session->itrace_synth_opts->set) { spe->synth_opts = *session->itrace_synth_opts; } else { - itrace_synth_opts__set_default(&spe->synth_opts, false); + struct itrace_synth_opts *opts = session->itrace_synth_opts; + bool single_event_per_ip = opts ? opts->default_single_event_per_ip : false; + + itrace_synth_opts__set_default(&spe->synth_opts, false, single_event_per_ip); /* Default nanoseconds period not supported */ spe->synth_opts.period_type = PERF_ITRACE_PERIOD_INSTRUCTIONS; spe->synth_opts.period = 1; @@ -2040,6 +2044,17 @@ int arm_spe_process_auxtrace_info(union perf_event *event, err = -EINVAL; goto err_free_queues; } + + /* + * When --itrace is used with non-i/y options (e.g., --itrace=M), + * the period remains 0 because the parser does not apply a default + * for those paths. However, synthesized SPE events such as memory + * accesses, TLB walks, and cache misses still require a valid + * sample->period to correctly accumulate periods and compute event + * percentages. Set it to 1 to ensure proper accounting. + */ + spe->synth_opts.period = spe->synth_opts.period ?: 1; + if (spe->synth_opts.period > 1) ui__warning("Arm SPE has a hardware-based sampling period.\n\n" "--itrace periods > 1i downsample by an interval of n SPE samples rather than n instructions.\n"); diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c index 027695d96779..e365f807b021 100644 --- a/tools/perf/util/aslr.c +++ b/tools/perf/util/aslr.c @@ -19,6 +19,7 @@ #include /* page_size */ #include #include +#include #include #include #include @@ -992,6 +993,34 @@ static int aslr_tool__process_sample(const struct perf_tool *tool, COPY_U64(); } } + +#define CHECK_SIMD() \ + do { \ + if (abi & PERF_SAMPLE_REGS_ABI_SIMD) { \ + u64 nr_vector, vec_qwords; \ + u64 nr_pred, pred_qwords; \ + u64 header_len, vec_len, pred_len, simd_len; \ + if (CHECK_BOUNDS(nr + 4, nr + 4)) { \ + ret = -EFAULT; \ + goto out_put; \ + } \ + header_len = 4; \ + nr_vector = in_array[i + nr]; \ + vec_qwords = in_array[i + nr + 1]; \ + nr_pred = in_array[i + nr + 2]; \ + pred_qwords = in_array[i + nr + 3]; \ + if (check_mul_overflow(nr_vector, vec_qwords, &vec_len) || \ + check_mul_overflow(nr_pred, pred_qwords, &pred_len) || \ + check_add_overflow(vec_len, pred_len, &simd_len) || \ + check_add_overflow(header_len, simd_len, &simd_len) || \ + check_add_overflow(nr, simd_len, &nr) || \ + nr > max_i - i) { \ + ret = -EFAULT; \ + goto out_put; \ + } \ + } \ + } while (0) + if (orig_sample_type & PERF_SAMPLE_REGS_USER) { u64 abi; @@ -1007,6 +1036,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool, ret = -EFAULT; goto out_put; } + CHECK_SIMD(); i += nr; } } @@ -1061,6 +1091,7 @@ static int aslr_tool__process_sample(const struct perf_tool *tool, ret = -EFAULT; goto out_put; } + CHECK_SIMD(); i += nr; } } @@ -1243,7 +1274,7 @@ static int aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate) delegate_tool__init(&aslr->tool, delegate); aslr->tool.tool.ordered_events = true; - if (machines__init(&aslr->machines)) + if (machines__init(&aslr->machines, /*session=*/NULL)) return -ENOMEM; hashmap__init(&aslr->remap_addresses, @@ -1343,6 +1374,14 @@ void aslr_tool__delete(struct perf_tool *tool) free(aslr); } +void aslr_tool__register_session(struct perf_tool *tool, struct perf_session *session) +{ + struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool); + struct aslr_tool *aslr = container_of(del_tool, struct aslr_tool, tool); + + aslr->machines.session = session; +} + int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel) { struct delegate_tool *del_tool = container_of(tool, struct delegate_tool, tool); diff --git a/tools/perf/util/aslr.h b/tools/perf/util/aslr.h index 522e31c8e2c0..9cc14f0263db 100644 --- a/tools/perf/util/aslr.h +++ b/tools/perf/util/aslr.h @@ -28,6 +28,7 @@ PERF_SAMPLE_CODE_PAGE_SIZE | \ PERF_SAMPLE_AUX) +struct perf_session; struct perf_tool; struct evsel; struct evlist; @@ -36,6 +37,7 @@ union perf_event; struct perf_tool *aslr_tool__new(struct perf_tool *delegate); void aslr_tool__delete(struct perf_tool *tool); +void aslr_tool__register_session(struct perf_tool *tool, struct perf_session *session); void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist); int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel); void aslr_tool__strip_evlist(const struct perf_tool *tool, struct evlist *evlist); diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index aa749e1c3036..8bb491d389a2 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -123,7 +123,7 @@ static int evlist__regroup(struct evlist *evlist, struct evsel *leader, struct e return 0; } -static bool auxtrace__dont_decode(struct perf_session *session) +bool auxtrace__dont_decode(struct perf_session *session) { return !session->itrace_synth_opts || session->itrace_synth_opts->dont_decode; @@ -1478,8 +1478,15 @@ s64 perf_event__process_auxtrace(const struct perf_tool *tool __maybe_unused, #define PERF_ITRACE_MAX_LAST_BRANCH_SZ 1024 void itrace_synth_opts__set_default(struct itrace_synth_opts *synth_opts, - bool no_sample) + bool no_sample, bool single_event_per_ip) { + if (single_event_per_ip) { + synth_opts->instructions = true; + synth_opts->period_type = PERF_ITRACE_PERIOD_INSTRUCTIONS; + synth_opts->period = 1; + return; + } + synth_opts->branches = true; synth_opts->transactions = true; synth_opts->ptwrites = true; @@ -1582,7 +1589,8 @@ int itrace_do_parse_synth_opts(struct itrace_synth_opts *synth_opts, if (!str) { itrace_synth_opts__set_default(synth_opts, - synth_opts->default_no_sample); + synth_opts->default_no_sample, + synth_opts->default_single_event_per_ip); return 0; } @@ -2970,6 +2978,13 @@ int auxtrace__flush_events(struct perf_session *session, const struct perf_tool if (!session->auxtrace) return 0; + /* + * Processing was stopped early, e.g. by SIGINT, so don't decode and + * deliver the remaining trace data which can be slow. + */ + if (session_done()) + return 0; + return session->auxtrace->flush_events(session, tool); } diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h index 6947f3f284c0..c29a8444f08e 100644 --- a/tools/perf/util/auxtrace.h +++ b/tools/perf/util/auxtrace.h @@ -65,6 +65,10 @@ enum itrace_period_type { * struct itrace_synth_opts - AUX area tracing synthesis options. * @set: indicates whether or not options have been set * @default_no_sample: Default to no sampling. + * @default_single_event_per_ip: Default to synthesizing at most 1 event per IP + * (equivalent to --itrace=i1i). Used by commands + * like 'perf annotate --data-type' to avoid duplicate + * sample counts for a single instruction. * @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 @@ -117,6 +121,7 @@ enum itrace_period_type { struct itrace_synth_opts { bool set; bool default_no_sample; + bool default_single_event_per_ip; bool inject; bool instructions; bool cycles; @@ -622,7 +627,7 @@ int itrace_do_parse_synth_opts(struct itrace_synth_opts *synth_opts, int itrace_parse_synth_opts(const struct option *opt, const char *str, int unset); void itrace_synth_opts__set_default(struct itrace_synth_opts *synth_opts, - bool no_sample); + bool no_sample, bool single_event_per_ip); size_t perf_event__fprintf_auxtrace_error(union perf_event *event, FILE *fp); void perf_session__auxtrace_error_inc(struct perf_session *session, @@ -645,6 +650,7 @@ void auxtrace__free(struct perf_session *session); bool auxtrace__evsel_is_auxtrace(struct perf_session *session, struct evsel *evsel); u64 auxtrace_synth_id_range_start(struct evsel *evsel); +bool auxtrace__dont_decode(struct perf_session *session); #define ITRACE_HELP \ " i[period]: synthesize instructions events\n" \ diff --git a/tools/perf/util/bpf-filter.c b/tools/perf/util/bpf-filter.c index bcd81084e342..72e83ac1d6a4 100644 --- a/tools/perf/util/bpf-filter.c +++ b/tools/perf/util/bpf-filter.c @@ -56,6 +56,7 @@ #include "util/debug.h" #include "util/evsel.h" #include "util/target.h" +#include "util/thread_map.h" #include "util/bpf-utils.h" #include "util/bpf-filter.h" @@ -202,31 +203,6 @@ static int get_filter_entries(struct evsel *evsel, struct perf_bpf_filter_entry return 0; } -static int convert_to_tgid(int tid) -{ - char path[128]; - char *buf, *p, *q; - int tgid; - size_t len; - - scnprintf(path, sizeof(path), "%d/status", tid); - if (procfs__read_str(path, &buf, &len) < 0) - return -1; - - p = strstr(buf, "Tgid:"); - if (p == NULL) { - free(buf); - return -1; - } - - tgid = strtol(p + 6, &q, 0); - free(buf); - if (*q != '\n') - return -1; - - return tgid; -} - /* * The event might be closed already so we cannot get the list of ids using FD * like in create_event_hash() below, let's iterate the event_hash map and @@ -407,14 +383,13 @@ static int create_idx_hash(struct evsel *evsel, struct perf_bpf_filter_entry *en last = -1; nr = perf_thread_map__nr(threads); for (int i = 0; i < nr; i++) { - int pid = perf_thread_map__pid(threads, i); int tgid; struct idx_hash_key key = { .evt_id = event_id, }; /* it actually needs tgid, let's get tgid from /proc. */ - tgid = convert_to_tgid(pid); + tgid = thread_map__tgid(threads, i); if (tgid < 0) { /* the thread may be dead, ignore. */ continue; diff --git a/tools/perf/util/bpf-filter.l b/tools/perf/util/bpf-filter.l index 6aa65ade3385..1be9df6550fc 100644 --- a/tools/perf/util/bpf-filter.l +++ b/tools/perf/util/bpf-filter.l @@ -131,6 +131,7 @@ store { return constant(PERF_MEM_OP_STORE); } pfetch { return constant(PERF_MEM_OP_PFETCH); } exec { return constant(PERF_MEM_OP_EXEC); } +l0 { return constant(PERF_MEM_LVLNUM_L0); } l1 { return constant(PERF_MEM_LVLNUM_L1); } l2 { return constant(PERF_MEM_LVLNUM_L2); } l3 { return constant(PERF_MEM_LVLNUM_L3); } diff --git a/tools/perf/util/bpf_skel/augmented_raw_syscalls.bpf.c b/tools/perf/util/bpf_skel/augmented_raw_syscalls.bpf.c index 3bc9e28a9b8a..28ebb9a72776 100644 --- a/tools/perf/util/bpf_skel/augmented_raw_syscalls.bpf.c +++ b/tools/perf/util/bpf_skel/augmented_raw_syscalls.bpf.c @@ -7,8 +7,10 @@ */ #include "vmlinux.h" +#include "perf_trace_u.h" #include +#include #include #define PERF_ALIGN(x, a) __PERF_ALIGN_MASK(x, (typeof(x))(a)-1) @@ -26,7 +28,7 @@ #define MAX_CPUS 4096 -#define TRACE_AUG_MAX_BUF 32 /* for buffer augmentation in perf trace */ +#define TRACE_AUG_MAX_BUF 128 /* for buffer augmentation in perf trace */ /* bpf-output associated map */ struct __augmented_syscalls__ { @@ -61,13 +63,19 @@ struct syscalls_sys_exit { } syscalls_sys_exit SEC(".maps"); struct syscall_enter_args { - unsigned long long common_tp_fields; + union { + unsigned long long common_tp_fields; + unsigned short common_type; + }; long syscall_nr; unsigned long args[6]; }; struct syscall_exit_args { - unsigned long long common_tp_fields; + union { + unsigned long long common_tp_fields; + unsigned short common_type; + }; long syscall_nr; long ret; }; @@ -114,6 +122,22 @@ struct pids_filtered { __uint(max_entries, 64); } pids_filtered SEC(".maps"); +/* With a target only its tasks are traced, those set false from their next exec. */ +struct pids_to_trace { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, pid_t); + __type(value, bool); + __uint(max_entries, 16384); +} pids_to_trace SEC(".maps"); + +bool has_pids_to_trace; +/* Also trace the children of traced tasks. */ +bool inherit; +/* Key pids_to_trace by tgid, so a process's threads share an entry. */ +bool uses_tgid; +/* Tasks not traced as pids_to_trace was full. */ +int lost_tasks; + struct augmented_args_payload { struct syscall_enter_args args; struct augmented_arg arg, arg2; // We have to reserve space for two arguments (rename, etc) @@ -152,10 +176,11 @@ static inline struct augmented_args_payload *augmented_args_payload(void) return bpf_map_lookup_elem(&augmented_args_tmp, &key); } -static inline int augmented__output(void *ctx, struct augmented_args_payload *args, int len) +/* Returning 0 would drop the tracepoint for other perf sessions. */ +static inline int augmented__output(void *ctx, void *args, int len) { - /* If perf_event_output fails, return non-zero so that it gets recorded unaugmented */ - return bpf_perf_event_output(ctx, &__augmented_syscalls__, BPF_F_CURRENT_CPU, args, len); + bpf_perf_event_output(ctx, &__augmented_syscalls__, BPF_F_CURRENT_CPU, args, len); + return 1; } static inline int augmented__beauty_output(void *ctx, void *data, int len) @@ -192,8 +217,22 @@ unsigned int augmented_arg__read_str(struct augmented_arg *augmented_arg, const } SEC("tp/raw_syscalls/sys_enter") -int syscall_unaugmented(struct syscall_enter_args *args) +int sys_enter_unaugmented(struct syscall_enter_args *args) { + struct augmented_args_payload *augmented_args = augmented_args_payload(); + + if (augmented_args) + augmented__output(args, &augmented_args->args, sizeof(augmented_args->args)); + return 1; +} + +SEC("tp/raw_syscalls/sys_exit") +int sys_exit_unaugmented(struct syscall_exit_args *args) +{ + struct augmented_args_payload *augmented_args = augmented_args_payload(); + + if (augmented_args) + augmented__output(args, &augmented_args->args, sizeof(struct syscall_exit_args)); return 1; } @@ -210,6 +249,7 @@ int sys_enter_connect(struct syscall_enter_args *args) const void *sockaddr_arg = (const void *)args->args[1]; unsigned int socklen = args->args[2]; unsigned int len = sizeof(u64) + sizeof(augmented_args->args); // the size + err in all 'augmented_arg' structs + int err; if (augmented_args == NULL) return 1; /* Failure: don't filter */ @@ -217,9 +257,11 @@ int sys_enter_connect(struct syscall_enter_args *args) _Static_assert(is_power_of_2(sizeof(augmented_args->arg.saddr)), "sizeof(augmented_args->arg.saddr) needs to be a power of two"); socklen &= sizeof(augmented_args->arg.saddr) - 1; - bpf_probe_read_user(&augmented_args->arg.saddr, socklen, sockaddr_arg); + err = bpf_probe_read_user(&augmented_args->arg.saddr, socklen, sockaddr_arg); + if (err) + socklen = 0; augmented_args->arg.size = socklen; - augmented_args->arg.err = 0; + augmented_args->arg.err = err; return augmented__output(args, augmented_args, len + socklen); } @@ -231,13 +273,18 @@ int sys_enter_sendto(struct syscall_enter_args *args) const void *sockaddr_arg = (const void *)args->args[4]; unsigned int socklen = args->args[5]; unsigned int len = sizeof(u64) + sizeof(augmented_args->args); // the size + err in all 'augmented_arg' structs + int err; if (augmented_args == NULL) return 1; /* Failure: don't filter */ socklen &= sizeof(augmented_args->arg.saddr) - 1; - bpf_probe_read_user(&augmented_args->arg.saddr, socklen, sockaddr_arg); + err = bpf_probe_read_user(&augmented_args->arg.saddr, socklen, sockaddr_arg); + if (err) + socklen = 0; + augmented_args->arg.size = socklen; + augmented_args->arg.err = err; return augmented__output(args, augmented_args, len + socklen); } @@ -372,9 +419,14 @@ int sys_enter_perf_event_open(struct syscall_enter_args *args) if (bpf_probe_read_user(&augmented_args->arg.value, size, attr) < 0) goto failure; + augmented_args->arg.size = size; + augmented_args->arg.err = 0; + return augmented__output(args, augmented_args, len + size); failure: - return 1; /* Failure: don't filter */ + if (augmented_args) + augmented__output(args, augmented_args, sizeof(augmented_args->args)); + return 1; } SEC("tp/syscalls/sys_enter_clock_nanosleep") @@ -384,6 +436,7 @@ int sys_enter_clock_nanosleep(struct syscall_enter_args *args) const void *rqtp_arg = (const void *)args->args[2]; unsigned int len = sizeof(u64) + sizeof(augmented_args->args); // the size + err in all 'augmented_arg' structs __u32 size = sizeof(struct timespec64); + int err; if (augmented_args == NULL) goto failure; @@ -391,7 +444,11 @@ int sys_enter_clock_nanosleep(struct syscall_enter_args *args) if (size > sizeof(augmented_args->arg.value)) goto failure; - bpf_probe_read_user(&augmented_args->arg.value, size, rqtp_arg); + err = bpf_probe_read_user(&augmented_args->arg.value, size, rqtp_arg); + if (err) + size = 0; + augmented_args->arg.size = size; + augmented_args->arg.err = err; return augmented__output(args, augmented_args, len + size); failure: @@ -403,8 +460,10 @@ int sys_enter_nanosleep(struct syscall_enter_args *args) { struct augmented_args_payload *augmented_args = augmented_args_payload(); const void *req_arg = (const void *)args->args[0]; - unsigned int len = sizeof(augmented_args->args); + /* the size + err in all 'augmented_arg' structs */ + unsigned int len = sizeof(u64) + sizeof(augmented_args->args); __u32 size = sizeof(struct timespec64); + int err; if (augmented_args == NULL) goto failure; @@ -412,7 +471,11 @@ int sys_enter_nanosleep(struct syscall_enter_args *args) if (size > sizeof(augmented_args->arg.value)) goto failure; - bpf_probe_read_user(&augmented_args->arg.value, size, req_arg); + err = bpf_probe_read_user(&augmented_args->arg.value, size, req_arg); + if (err) + size = 0; + augmented_args->arg.size = size; + augmented_args->arg.err = err; return augmented__output(args, augmented_args, len + size); failure: @@ -429,6 +492,21 @@ static bool pid_filter__has(struct pids_filtered *pids, pid_t pid) return bpf_map_lookup_elem(pids, &pid) != NULL; } +static bool task_traced(void) +{ + u64 pid_tgid; + pid_t pid; + bool *traced; + + if (!has_pids_to_trace) + return true; + + pid_tgid = bpf_get_current_pid_tgid(); + pid = uses_tgid ? pid_tgid >> 32 : (pid_t)pid_tgid; + traced = bpf_map_lookup_elem(&pids_to_trace, &pid); + return traced && *traced; +} + u64 ZERO = 0; /* @@ -436,15 +514,16 @@ u64 ZERO = 0; * value in the beauty_map. This is the relation of parameter type and its corresponding * value in the beauty map, and how many bytes we read eventually: * - * string: 1 -> size of string - * struct: size of struct -> size of struct - * buffer: -1 * (index of paired len) -> value of paired len (maximum: TRACE_AUG_MAX_BUF) + * string: 1 -> size of string + * struct: size of struct -> size of struct + * buffer: -(0-based index of paired len + 1) -> value of paired len (maximum: TRACE_AUG_MAX_BUF) */ static inline int augment_arg(struct syscall_enter_args *args, int i, unsigned int *beauty_map, struct beauty_payload_enter *payload, u64 offset) { int index, value_size = sizeof(struct augmented_arg) - offsetof(struct augmented_arg, value); + int read_err = 0; struct augmented_arg *payload_offset; s64 aug_size, size; bool augmented; @@ -467,8 +546,10 @@ static inline int augment_arg(struct syscall_enter_args *args, int i, if (size == 1) { /* string */ aug_size = bpf_probe_read_user_str(payload_offset->value, value_size, arg); /* minimum of 0 to pass the verifier */ - if (aug_size < 0) + if (aug_size < 0) { + read_err = aug_size; aug_size = 0; + } augmented = true; } else if (size > 0 && size <= value_size) { /* struct */ @@ -498,6 +579,7 @@ static inline int augment_arg(struct syscall_enter_args *args, int i, return -1; payload_offset->size = aug_size; + payload_offset->err = read_err; return written; } @@ -528,6 +610,7 @@ static int augment_sys_enter(void *ctx, struct syscall_enter_args *args) /* copy the sys_enter header, which has the syscall_nr */ __builtin_memcpy(&payload->args, args, sizeof(struct syscall_enter_args)); + payload->args.common_type = SYSCALL_TRACE_ENTER; if (bpf_ksym_exists(bpf_iter_num_new)) { bpf_for(i, 0, 6) { @@ -576,45 +659,122 @@ int sys_enter(struct syscall_enter_args *args) * initial, non-augmented raw_syscalls:sys_enter payload. */ + if (!task_traced()) + return 1; + if (pid_filter__has(&pids_filtered, getpid())) - return 0; + return 1; augmented_args = augmented_args_payload(); if (augmented_args == NULL) return 1; bpf_probe_read_kernel(&augmented_args->args, sizeof(augmented_args->args), args); + augmented_args->args.common_type = SYSCALL_TRACE_ENTER; /* * Jump to syscall specific augmenter, even if the default one, - * "!raw_syscalls:unaugmented" that will just return 1 to return the - * unaugmented tracepoint payload. + * "!raw_syscalls:unaugmented" that will just output the unaugmented + * payload. */ if (augment_sys_enter(args, &augmented_args->args)) bpf_tail_call(args, &syscalls_sys_enter, augmented_args->args.syscall_nr); - // If not found on the PROG_ARRAY syscalls map, then we're filtering it: - return 0; + return 1; } SEC("tp/raw_syscalls/sys_exit") int sys_exit(struct syscall_exit_args *args) { - struct syscall_exit_args exit_args; + struct augmented_args_payload *augmented_args; + + if (!task_traced()) + return 1; if (pid_filter__has(&pids_filtered, getpid())) - return 0; + return 1; + + augmented_args = augmented_args_payload(); + if (augmented_args == NULL) + return 1; + + bpf_probe_read_kernel(&augmented_args->args, sizeof(*args), args); + augmented_args->args.common_type = SYSCALL_TRACE_EXIT; - bpf_probe_read_kernel(&exit_args, sizeof(exit_args), args); /* * Jump to syscall specific return augmenter, even if the default one, - * "!raw_syscalls:unaugmented" that will just return 1 to return the - * unaugmented tracepoint payload. + * "!raw_syscalls:unaugmented" that will just output the unaugmented + * payload. */ - bpf_tail_call(args, &syscalls_sys_exit, exit_args.syscall_nr); + bpf_tail_call(args, &syscalls_sys_exit, augmented_args->args.syscall_nr); /* - * If not found on the PROG_ARRAY syscalls map, then we're filtering it: + * If not found on the PROG_ARRAY syscalls map, then we're filtering it + * by not emitting bpf-output event. */ + return 1; +} + +/* Trace the children of traced tasks, added before they can run. */ +SEC("tp_btf/sched_process_fork") +int BPF_PROG(sched_process_fork, struct task_struct *parent, struct task_struct *child) +{ + pid_t parent_pid = parent->pid, child_pid = child->pid; + bool *traced = NULL, val; + + if (uses_tgid) { + /* A new thread is traced with its process. */ + if (child->tgid != child_pid) + return 0; + parent_pid = parent->tgid; + } + if (inherit) + traced = bpf_map_lookup_elem(&pids_to_trace, &parent_pid); + if (traced) { + val = *traced; + /* Not __sync_fetch_and_add(), as its fetch needs a 5.12 kernel. */ + if (bpf_map_update_elem(&pids_to_trace, &child_pid, &val, BPF_ANY)) + __atomic_fetch_add(&lost_tasks, 1, __ATOMIC_RELAXED); + } else { + /* A new pid, so any entry was seeded for an exited task, e.g. a zombie. */ + bpf_map_delete_elem(&pids_to_trace, &child_pid); + } + return 0; +} + +/* Forget exited tasks, as their pid may be reused. */ +SEC("tp_btf/sched_process_exit") +int BPF_PROG(sched_process_exit, struct task_struct *task) +{ + pid_t pid = task->pid; + + if (uses_tgid) { + /* A process exits with its last thread. */ + if (task->signal->live.counter) + return 0; + pid = task->tgid; + } + bpf_map_delete_elem(&pids_to_trace, &pid); + return 0; +} + +/* Start tracing waiting tasks, and follow a thread given the leader's pid by exec. */ +SEC("tp_btf/sched_process_exec") +int BPF_PROG(sched_process_exec, struct task_struct *task, pid_t old_pid) +{ + pid_t pid = task->pid; + bool traced = true; + + /* A process keeps its tgid, which pid now is. */ + if (uses_tgid) + old_pid = pid; + if (!bpf_map_lookup_elem(&pids_to_trace, &old_pid)) + return 0; + + if (pid != old_pid) + bpf_map_delete_elem(&pids_to_trace, &old_pid); + /* A fork may have taken old_pid's entry, leaving the map full. */ + if (bpf_map_update_elem(&pids_to_trace, &pid, &traced, BPF_ANY)) + __atomic_fetch_add(&lost_tasks, 1, __ATOMIC_RELAXED); return 0; } diff --git a/tools/perf/util/bpf_skel/perf_trace_u.h b/tools/perf/util/bpf_skel/perf_trace_u.h new file mode 100644 index 000000000000..004ba96aaba9 --- /dev/null +++ b/tools/perf/util/bpf_skel/perf_trace_u.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* Copyright (c) 2025 Google */ + +/* This file will be shared between BPF and userspace. */ + +#ifndef __PERF_TRACE_U_H +#define __PERF_TRACE_U_H + +enum syscall_trace_type { + SYSCALL_TRACE_ENTER = 0, + SYSCALL_TRACE_EXIT, +}; + +#endif /* __PERF_TRACE_U_H */ diff --git a/tools/perf/util/bpf_skel/vmlinux/vmlinux.h b/tools/perf/util/bpf_skel/vmlinux/vmlinux.h index a59ce912be18..94e42b022ab1 100644 --- a/tools/perf/util/bpf_skel/vmlinux/vmlinux.h +++ b/tools/perf/util/bpf_skel/vmlinux/vmlinux.h @@ -47,6 +47,10 @@ enum { NR_SOFTIRQS }; +typedef struct { + int counter; +} __attribute__((preserve_access_index)) atomic_t; + typedef struct { s64 counter; } __attribute__((preserve_access_index)) atomic64_t; @@ -67,6 +71,10 @@ struct sighand_struct { spinlock_t siglock; } __attribute__((preserve_access_index)); +struct signal_struct { + atomic_t live; +} __attribute__((preserve_access_index)); + struct rw_semaphore { atomic_long_t owner; } __attribute__((preserve_access_index)); @@ -103,6 +111,7 @@ struct task_struct { pid_t pid; pid_t tgid; char comm[16]; + struct signal_struct *signal; struct sighand_struct *sighand; struct css_set *cgroups; } __attribute__((preserve_access_index)); diff --git a/tools/perf/util/bpf_trace_augment.c b/tools/perf/util/bpf_trace_augment.c index a9cf2a77ded1..e590eb7a3d91 100644 --- a/tools/perf/util/bpf_trace_augment.c +++ b/tools/perf/util/bpf_trace_augment.c @@ -1,10 +1,15 @@ +#include #include +#include #include +#include +#include #include "bpf_skel/augmented_raw_syscalls.skel.h" #include "debug.h" #include "evlist.h" #include "parse-events.h" +#include "thread_map.h" #include "trace_augment.h" static struct augmented_raw_syscalls_bpf *skel; @@ -23,11 +28,13 @@ int augmented_syscalls__prepare(void) } /* - * Disable attaching the BPF programs except for sys_enter and - * sys_exit that tail call into this as necessary. + * Attach just the programs maintaining pids_to_trace now. sys_enter and + * sys_exit wait for augmented_syscalls__attach(), the rest are tail called. */ bpf_object__for_each_program(prog, skel->obj) { - if (prog != skel->progs.sys_enter && prog != skel->progs.sys_exit) + if (prog != skel->progs.sched_process_fork && + prog != skel->progs.sched_process_exit && + prog != skel->progs.sched_process_exec) bpf_program__set_autoattach(prog, /*autoattach=*/false); } @@ -35,10 +42,34 @@ int augmented_syscalls__prepare(void) if (err < 0) { libbpf_strerror(err, buf, sizeof(buf)); pr_debug("Failed to load augmented syscalls BPF skeleton: %s\n", buf); + augmented_syscalls__cleanup(); return err; } - augmented_raw_syscalls_bpf__attach(skel); + err = augmented_raw_syscalls_bpf__attach(skel); + if (err < 0) { + libbpf_strerror(err, buf, sizeof(buf)); + pr_debug("Failed to attach augmented syscalls BPF skeleton: %s\n", buf); + augmented_syscalls__cleanup(); + return err; + } + return 0; +} + +/* Attach sys_enter and sys_exit, once the maps they use are populated. */ +int augmented_syscalls__attach(void) +{ + if (skel == NULL) + return 0; + + skel->links.sys_enter = bpf_program__attach(skel->progs.sys_enter); + if (skel->links.sys_enter == NULL) + return -errno; + + skel->links.sys_exit = bpf_program__attach(skel->progs.sys_exit); + if (skel->links.sys_exit == NULL) + return -errno; + return 0; } @@ -74,8 +105,7 @@ void augmented_syscalls__setup_bpf_output(void) bpf_map__update_elem(skel->maps.__augmented_syscalls__, &mycpu, sizeof(mycpu), - xyarray__entry(bpf_output->core.fd, - mycpu, 0), + xyarray__entry(bpf_output->core.fd, i, 0), sizeof(__u32), BPF_ANY); } } @@ -98,6 +128,45 @@ int augmented_syscalls__set_filter_pids(unsigned int nr, pid_t *pids) return err; } +/* Trace just the target's tasks, or their processes, from their next exec if on_exec. */ +void augmented_syscalls__set_target_pids(struct perf_thread_map *threads, bool inherit, + bool uses_tgid, bool on_exec) +{ + bool traced = !on_exec; + pid_t last = -1; + + if (skel == NULL) + return; + + skel->bss->uses_tgid = uses_tgid; + /* Before seeding, so the forks of tasks already added are followed. */ + skel->bss->inherit = inherit; + for (int i = 0; i < perf_thread_map__nr(threads); i++) { + pid_t pid = perf_thread_map__pid(threads, i); + + /* A process's threads are adjacent, add it once, skipping exited threads. */ + if (uses_tgid) { + pid = thread_map__tgid(threads, i); + if (pid < 0 || pid == last) + continue; + last = pid; + } + /* Count a lost task atomically, as the BPF programs do too. */ + if (bpf_map__update_elem(skel->maps.pids_to_trace, &pid, sizeof(pid), + &traced, sizeof(traced), BPF_ANY)) + __atomic_fetch_add(&skel->bss->lost_tasks, 1, __ATOMIC_RELAXED); + } + skel->bss->has_pids_to_trace = true; +} + +int augmented_syscalls__lost_tasks(void) +{ + if (skel == NULL) + return 0; + + return skel->bss->lost_tasks; +} + int augmented_syscalls__get_map_fds(int *enter_fd, int *exit_fd, int *beauty_fd) { if (skel == NULL) @@ -115,9 +184,14 @@ int augmented_syscalls__get_map_fds(int *enter_fd, int *exit_fd, int *beauty_fd) return 0; } -struct bpf_program *augmented_syscalls__unaugmented(void) +struct bpf_program *augmented_syscalls__unaugmented_enter(void) { - return skel->progs.syscall_unaugmented; + return skel->progs.sys_enter_unaugmented; +} + +struct bpf_program *augmented_syscalls__unaugmented_exit(void) +{ + return skel->progs.sys_exit_unaugmented; } struct bpf_program *augmented_syscalls__find_by_title(const char *name) @@ -140,4 +214,5 @@ struct bpf_program *augmented_syscalls__find_by_title(const char *name) void augmented_syscalls__cleanup(void) { augmented_raw_syscalls_bpf__destroy(skel); + skel = NULL; } diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index eb95ab90f974..02233ef0eaff 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -586,9 +586,9 @@ static char *build_id_cache__find_debug(const char *sbuild_id, const char *dirname = "/usr/lib/debug/.build-id/"; char *realname = NULL; char dirbuf[PATH_MAX]; + char pathbuf[PATH_MAX]; char *debugfile; struct nscookie nsc; - size_t len = 0; debugfile = calloc(1, PATH_MAX); if (!debugfile) @@ -599,9 +599,13 @@ static char *build_id_cache__find_debug(const char *sbuild_id, dirname = dirbuf; } - len = __symbol__join_symfs(debugfile, PATH_MAX, dirname); - snprintf(debugfile + len, PATH_MAX - len, "%.2s/%s.debug", sbuild_id, - sbuild_id + 2); + /* See the build id note in dso__read_binary_type_filename(). */ + if (symbol_conf.symfs_layout_flat) + scnprintf(pathbuf, PATH_MAX, "/%s.debug", sbuild_id); + else + scnprintf(pathbuf, PATH_MAX, "%s%.2s/%s.debug", dirname, + sbuild_id, sbuild_id + 2); + __symbol__join_symfs(debugfile, PATH_MAX, pathbuf); nsinfo__mountns_enter(nsi, &nsc); realname = realpath(debugfile, NULL); diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c index 5b6a06a5a067..d410196d0429 100644 --- a/tools/perf/util/c2c-function.c +++ b/tools/perf/util/c2c-function.c @@ -305,8 +305,8 @@ symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, char symbuf[32]; scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr); - ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s", - text_width, text_width, symbuf); + ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%.*s", + text_width, symbuf); } else { /* Level 1 and level 2 are both functions. */ size_t cell_size; @@ -331,9 +331,6 @@ symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, len = min_t(size_t, len, cell_size - 1); ret += len; - if (len < text_width) - ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s", - text_width - len, ""); } return ret; @@ -1519,7 +1516,7 @@ void c2c_function__reset(void) symbol_conf.use_callchain = saved_use_callchain; } -static bool c2c_function__has_iaddr(const char *cl_sort) +bool c2c_function__has_iaddr(const char *cl_sort) { const char *field = cl_sort; diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h index 53f024e25d99..65117108ddb1 100644 --- a/tools/perf/util/c2c.h +++ b/tools/perf/util/c2c.h @@ -33,6 +33,7 @@ struct c2c_hist_entry { unsigned long *nodeset; struct c2c_stats *node_stats; unsigned int cacheline_idx; + unsigned int mem_region; struct compute_stats cstats; @@ -107,6 +108,7 @@ bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b); int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort, bool symbol_full, struct hists **hists); void c2c_function__reset(void); +bool c2c_function__has_iaddr(const char *cl_sort); /* Valid only between a successful build and c2c_function__reset(). */ struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he); diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h deleted file mode 100644 index 0b61840d4226..000000000000 --- a/tools/perf/util/cache.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __PERF_CACHE_H -#define __PERF_CACHE_H - -#include "strbuf.h" -#include -#include "../ui/ui.h" - -#include -#include - -#define CMD_EXEC_PATH "--exec-path" -#define CMD_DEBUGFS_DIR "--debugfs-dir=" - -#define EXEC_PATH_ENVIRONMENT "PERF_EXEC_PATH" -#define PERF_DEBUGFS_ENVIRONMENT "PERF_DEBUGFS_DIR" -#define PERF_TRACEFS_ENVIRONMENT "PERF_TRACEFS_DIR" -#define PERF_PAGER_ENVIRONMENT "PERF_PAGER" - -int split_cmdline(char *cmdline, const char ***argv); - -#define alloc_nr(x) (((x)+16)*3/2) - -static inline int is_absolute_path(const char *path) -{ - return path[0] == '/'; -} - -char *mkpath(char *path_buf, size_t sz, const char *fmt, ...) __printf(3, 4); - -#endif /* __PERF_CACHE_H */ diff --git a/tools/perf/util/capstone.c b/tools/perf/util/capstone.c index 74213daf8786..95881f15324a 100644 --- a/tools/perf/util/capstone.c +++ b/tools/perf/util/capstone.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -31,6 +32,10 @@ #define CS_MODE_RISCVC 4 #endif +#if CS_VERSION_MAJOR < 4 +#define ARM64_GRP_BRANCH_RELATIVE 7 /* = CS_GRP_BRANCH_RELATIVE */ +#endif + #ifdef LIBCAPSTONE_DLOPEN static void *perf_cs_dll_handle(void) { @@ -225,6 +230,12 @@ static int capstone_init(uint16_t e_machine, csh *cs_handle, bool is64, bool is_ * on x86 by investigating instruction details. */ perf_cs_option(*cs_handle, CS_OPT_DETAIL, CS_OPT_ON); + } else if (arch == CS_ARCH_ARM64) { + /* + * Same as x86: arm64 needs instruction details to resolve + * symbolic addresses. + */ + perf_cs_option(*cs_handle, CS_OPT_DETAIL, CS_OPT_ON); } return 0; @@ -292,6 +303,40 @@ ssize_t capstone__fprintf_insn_asm(struct machine *machine, struct thread *threa return printed; } +static struct symbol *find_symbol_from_addr(struct map *map, u64 addr, + struct map **found_map, u64 *sym_offset) +{ + struct symbol *sym; + u64 al_addr; + u64 ip = map__objdump_2mem(map, addr); + + *found_map = NULL; + if (dso__kernel(map__dso(map))) { + /* + * The kernel maps can be split into sections, let's + * find the map first and then search the symbol. + */ + *found_map = maps__find(map__kmaps(map), ip); + if (*found_map == NULL) + return NULL; + map = *found_map; + } + + /* convert it to map-relative address for search */ + al_addr = map__map_ip(map, ip); + + sym = map__find_symbol(map, al_addr); + + if (sym == NULL) { + map__put(*found_map); + *found_map = NULL; + return NULL; + } + + *sym_offset = al_addr - sym->start; + return sym; +} + static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len, struct annotate_args *args, u64 addr) { @@ -299,17 +344,14 @@ static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len, struct map *map = args->ms->map; struct symbol *sym; - /* TODO: support more architectures */ - if (!arch__is_x86(args->arch)) - return; - if (insn->detail == NULL) return; for (i = 0; i < insn->detail->x86.op_count; i++) { struct cs_x86_op *op = &insn->detail->x86.operands[i]; u64 orig_addr; - struct map *found_map = NULL; + struct map *found_map; + u64 sym_offset; if (op->type != X86_OP_MEM) continue; @@ -320,40 +362,119 @@ static void print_capstone_detail(struct cs_insn *insn, char *buf, size_t len, /* get the target address */ orig_addr = addr + insn->size + op->mem.disp; - addr = map__objdump_2mem(map, orig_addr); - - if (dso__kernel(map__dso(map))) { - /* - * The kernel maps can be split into sections, let's - * find the map first and then search the symbol. - */ - found_map = maps__find(map__kmaps(map), addr); - if (found_map == NULL) - continue; - map = found_map; - } - - /* convert it to map-relative address for search */ - addr = map__map_ip(map, addr); - - sym = map__find_symbol(map, addr); - if (sym == NULL) { - map__put(found_map); + sym = find_symbol_from_addr(map, orig_addr, &found_map, &sym_offset); + if (sym == NULL) continue; - } - if (addr == sym->start) { + if (sym_offset == 0) { scnprintf(buf, len, "\t# %"PRIx64" <%s>", orig_addr, sym->name); } else { scnprintf(buf, len, "\t# %"PRIx64" <%s+%#"PRIx64">", - orig_addr, sym->name, addr - sym->start); + orig_addr, sym->name, sym_offset); } map__put(found_map); break; } } +static int print_default_format(struct cs_insn *insn, char *buf, size_t len) +{ + return scnprintf(buf, len, " %-7s %s", + insn->mnemonic, insn->op_str); +} + +static void format_capstone_insn_x86(struct cs_insn *insn, char *buf, + size_t len, struct annotate_args *args, + u64 addr) +{ + int printed; + + printed = print_default_format(insn, buf, len); + buf += printed; + len -= printed; + + print_capstone_detail(insn, buf, len, args, addr); +} + +static bool needs_symbolic_address(struct cs_insn *insn) +{ + int i; + + if (insn->id == ARM64_INS_ADR || insn->id == ARM64_INS_ADRP) + return true; + + if (insn->detail == NULL) + return false; + + for (i = 0; i < insn->detail->groups_count; i++) { + if (insn->detail->groups[i] == ARM64_GRP_JUMP || + insn->detail->groups[i] == ARM64_GRP_CALL || + insn->detail->groups[i] == ARM64_GRP_BRANCH_RELATIVE) + return true; + } + + return false; +} + +static void format_capstone_insn_arm64(struct cs_insn *insn, char *buf, + size_t len, struct annotate_args *args) +{ + struct map *map = args->ms->map; + char *last_imm, *endptr; + u64 addr; + + print_default_format(insn, buf, len); + /* + * Adjust instructions to keep the existing behavior with objdump. + * + * Example conversion: + * From: b #0xffff8000800114c8 + * To: b ffff8000800114c8 + */ + if (needs_symbolic_address(insn)) { + struct symbol *sym; + struct map *found_map; + u64 sym_offset; + + /* Extract last immediate value as address */ + last_imm = strrchr(buf, '#'); + if (!last_imm) + return; + + addr = strtoull(last_imm + 1, &endptr, 16); + if (endptr == last_imm + 1) + return; + + sym = find_symbol_from_addr(map, addr, &found_map, &sym_offset); + if (sym == NULL) + return; + + /* Symbolize the resolved address */ + len = len - (last_imm - buf); + if (sym_offset == 0) { + scnprintf(last_imm, len, "%"PRIx64" <%s>", + addr, sym->name); + } else { + scnprintf(last_imm, len, "%"PRIx64" <%s+%#"PRIx64">", + addr, sym->name, sym_offset); + } + map__put(found_map); + } +} + +static void format_capstone_insn(struct cs_insn *insn, char *buf, size_t len, + struct annotate_args *args, u64 addr) +{ + /* TODO: support more architectures */ + if (arch__is_x86(args->arch)) + format_capstone_insn_x86(insn, buf, len, args, addr); + else if (arch__is_arm64(args->arch)) + format_capstone_insn_arm64(insn, buf, len, args); + else + print_default_format(insn, buf, len); +} + struct find_file_offset_data { u64 ip; u64 offset; @@ -446,14 +567,9 @@ int symbol__disassemble_capstone(const char *filename, struct symbol *sym, free_count = count = perf_cs_disasm(handle, buf, buf_len, start, buf_len, &insn); for (i = 0, offset = 0; i < count; i++) { - int printed; - - printed = scnprintf(disasm_buf, sizeof(disasm_buf), - " %-7s %s", - insn[i].mnemonic, insn[i].op_str); - print_capstone_detail(&insn[i], disasm_buf + printed, - sizeof(disasm_buf) - printed, args, - start + offset); + format_capstone_insn(&insn[i], disasm_buf, + sizeof(disasm_buf), args, + start + offset); args->offset = offset; args->line = disasm_buf; diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index 7988149dc7ed..287401f159e6 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -9,28 +9,36 @@ * Copyright (C) Johannes Schindelin, 2005 * */ -#include -#include -#include "cache.h" -#include "callchain.h" -#include "header.h" -#include -#include "util/event.h" /* proc_map_timeout */ -#include "util/hist.h" /* perf_hist_config */ -#include "util/stat.h" /* perf_stat__set_big_num */ -#include "util/evsel.h" /* evsel__hw_names, evsel__use_bpf_counters */ -#include "srcline.h" -#include "build-id.h" -#include "debug.h" #include "config.h" -#include "unwind.h" -#include -#include + +#include +#include +#include +#include #include -#include +#include + +#include #include #include -#include +#include +#include +#include + +#include + +#include "build-id.h" +#include "callchain.h" +#include "debug.h" +#include "header.h" +#include "mutex.h" +#include "path.h" +#include "srcline.h" +#include "unwind.h" +#include "util/event.h" /* proc_map_timeout */ +#include "util/evsel.h" /* evsel__hw_names, evsel__use_bpf_counters */ +#include "util/hist.h" /* perf_hist_config */ +#include "util/stat.h" /* perf_stat__set_big_num */ #define MAXNAME (256) @@ -364,10 +372,8 @@ static int perf_parse_long(const char *value, long *ret) static void bad_config(const char *name) { - if (config_file_name) - pr_warning("bad config value for '%s' in %s, ignoring...\n", name, config_file_name); - else - pr_warning("bad config value for '%s', ignoring...\n", name); + /* config_file_name is owned by the parsing thread, under config_mutex. */ + pr_warning("bad config value for '%s', ignoring...\n", name); } int perf_config_u64(u64 *dest, const char *name, const char *value) @@ -464,6 +470,16 @@ static int perf_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.addr2line-disable-warn")) symbol_conf.addr2line_disable_warn = perf_config_bool(var, value); + if (!strcmp(var, "core.hybrid-merge")) { + /* + * Note, this is for sampling tools like perf report and top. + * perf stat has its own merging options and the + * stat_config.hybrid_merge of "perf stat --hybrid-merge" is + * deliberately not set here. + */ + symbol_conf.hybrid_merge = perf_config_bool(var, value); + } + /* Add other config variables here. */ return 0; } @@ -533,11 +549,18 @@ int perf_default_config(const char *var, const char *value, return 0; } +/* Parsing and rewriting share the static parser state. */ +static DEFINE_MUTEX(config_mutex); + +/* Serializes whole perf_config__set_variable() updates. */ +static DEFINE_MUTEX(config_update_mutex); + static int perf_config_from_file(config_fn_t fn, const char *filename, void *data) { int ret; FILE *f = fopen(filename, "r"); + mutex_lock(&config_mutex); ret = -1; if (f) { config_file = f; @@ -548,15 +571,24 @@ static int perf_config_from_file(config_fn_t fn, const char *filename, void *dat fclose(f); config_file_name = NULL; } + mutex_unlock(&config_mutex); return ret; } +/* system_path() allocates, so it is computed once. */ +static const char *etc_perfconfig; + +static void perf_etc_perfconfig__init(void) +{ + etc_perfconfig = system_path(ETC_PERFCONFIG); + if (!etc_perfconfig) + etc_perfconfig = ETC_PERFCONFIG; +} + const char *perf_etc_perfconfig(void) { - static const char *system_wide; - if (!system_wide) - system_wide = system_path(ETC_PERFCONFIG); - return system_wide; + DO_ONCE(perf_etc_perfconfig__init); + return etc_perfconfig; } static int perf_env_bool(const char *k, int def) @@ -614,19 +646,18 @@ static char *home_perfconfig(void) return NULL; } +/* home_perfconfig() allocates and warns, so it is computed once. */ +static const char *home_config; + +static void perf_home_perfconfig__init(void) +{ + home_config = home_perfconfig(); +} + const char *perf_home_perfconfig(void) { - static const char *config; - static bool failed; - - if (failed || config) - return config; - - config = home_perfconfig(); - if (!config) - failed = true; - - return config; + DO_ONCE(perf_home_perfconfig__init); + return home_config; } static struct perf_config_section *find_section(struct list_head *sections, @@ -767,8 +798,15 @@ static int collect_config(const char *var, const char *value, int perf_config_set__collect(struct perf_config_set *set, const char *file_name, const char *var, const char *value) { + int ret; + + mutex_lock(&config_mutex); config_file_name = file_name; - return collect_config(var, value, set); + ret = collect_config(var, value, set); + /* Don't leave the static parser state pointing at the caller's buffer. */ + config_file_name = NULL; + mutex_unlock(&config_mutex); + return ret; } static int perf_config_set__init(struct perf_config_set *set) @@ -815,6 +853,10 @@ struct perf_config_set *perf_config_set__load_file(const char *file) return set; } +/* Not config_mutex: building the set parses the config files, which takes it. */ +static DEFINE_MUTEX(config_set_mutex); + +/* Called with config_set_mutex held. */ static int perf_config__init(void) { if (config_set == NULL) @@ -855,16 +897,112 @@ int perf_config_set(struct perf_config_set *set, int perf_config(config_fn_t fn, void *data) { - if (config_set == NULL && perf_config__init()) - return -1; + struct perf_config_set *set; - return perf_config_set(config_set, fn, data); + /* Not held across the dispatch: a callback can call perf_config() again. */ + mutex_lock(&config_set_mutex); + if (perf_config__init()) { + mutex_unlock(&config_set_mutex); + return -1; + } + set = config_set; + mutex_unlock(&config_set_mutex); + + return perf_config_set(set, fn, data); } void perf_config__exit(void) { + mutex_lock(&config_set_mutex); perf_config_set__delete(config_set); config_set = NULL; + mutex_unlock(&config_set_mutex); +} + +int perf_config_set__write(struct perf_config_set *set, + const char *file_name, bool system_config) +{ + struct perf_config_section *section = NULL; + struct perf_config_item *item = NULL; + int ret = 0; + FILE *fp; + + mutex_lock(&config_mutex); + fp = fopen(file_name, "w"); + if (!fp) { + mutex_unlock(&config_mutex); + return -1; + } + + if (fprintf(fp, "# this file is auto-generated.\n") < 0) + ret = -1; + + /* overwrite configvariables */ + perf_config_sections__for_each_entry(&set->sections, section) { + if (!system_config && section->from_system_config) + continue; + if (fprintf(fp, "[%s]\n", section->name) < 0) + ret = -1; + + perf_config_items__for_each_entry(§ion->items, item) { + if (!system_config && item->from_system_config) + continue; + if (item->value && + fprintf(fp, "\t%s = %s\n", item->name, item->value) < 0) + ret = -1; + } + } + if (fclose(fp) != 0) + ret = -1; + mutex_unlock(&config_mutex); + + return ret; +} + +/* + * Set @var=@value in the config file perf is using: ~/.perfconfig or the + * file named by PERF_CONFIG. Same rewrite 'perf config' does, comments + * are not preserved. + */ +int perf_config__set_variable(const char *var, const char *value) +{ + const char *config_filename; + bool system_config; + struct perf_config_set *set = NULL; + int ret = -1; + + mutex_lock(&config_update_mutex); + + /* Static: the parser publishes it as config_file_name. */ + { + static char path[PATH_MAX]; + char *user_config = mkpath(path, sizeof(path), "%s/.perfconfig", getenv("HOME")); + + config_filename = config_exclusive_filename ?: user_config; + } + + /* Rewriting the system wide file keeps its entries, or it is truncated. */ + system_config = strcmp(config_filename, perf_etc_perfconfig()) == 0; + + set = perf_config_set__new(); + if (!set) + goto out_err; + + if (perf_config_set__collect(set, config_filename, var, value) < 0) { + pr_err("Failed to add '%s=%s'\n", var, value); + goto out_err; + } + + if (perf_config_set__write(set, config_filename, system_config) < 0) { + pr_err("Failed to set the configs on %s\n", config_filename); + goto out_err; + } + + ret = 0; +out_err: + perf_config_set__delete(set); + mutex_unlock(&config_update_mutex); + return ret; } static void perf_config_item__delete(struct perf_config_item *item) diff --git a/tools/perf/util/config.h b/tools/perf/util/config.h index 987b47cf54c3..9098f8a04585 100644 --- a/tools/perf/util/config.h +++ b/tools/perf/util/config.h @@ -33,6 +33,8 @@ int perf_config_scan(const char *name, const char *fmt, ...) __scanf(2, 3); const char *perf_config_get(const char *name); int perf_config_set(struct perf_config_set *set, config_fn_t fn, void *data); +int perf_config_set__write(struct perf_config_set *set, + const char *file_name, bool system_config); int perf_config_int(int *dest, const char *, const char *); int perf_config_u8(u8 *dest, const char *name, const char *value); int perf_config_u64(u64 *dest, const char *, const char *); 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 26940f1f1b0b..b56cf57a68c8 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c @@ -741,7 +741,7 @@ cs_etm_decoder__new(int decoders, struct cs_etm_decoder_params *d_params, return decoder; err_free_decoder: - cs_etm_decoder__free(decoder); + cs_etm_decoder__zfree(&decoder); return NULL; } @@ -793,14 +793,14 @@ int cs_etm_decoder__process_data_block(struct cs_etm_decoder *decoder, return ret; } -void cs_etm_decoder__free(struct cs_etm_decoder *decoder) +void cs_etm_decoder__zfree(struct cs_etm_decoder **decoder) { - if (!decoder) + if (!(*decoder)) return; - ocsd_destroy_dcd_tree(decoder->dcd_tree); - decoder->dcd_tree = NULL; - free(decoder); + ocsd_destroy_dcd_tree((*decoder)->dcd_tree); + (*decoder)->dcd_tree = NULL; + zfree(decoder); } const char *cs_etm_decoder__get_name(struct cs_etm_decoder *decoder) diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h index 12c782fa6db2..2a8214f17958 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h @@ -96,7 +96,7 @@ cs_etm_decoder__new(int num_cpu, struct cs_etm_decoder_params *d_params, struct cs_etm_trace_params t_params[]); -void cs_etm_decoder__free(struct cs_etm_decoder *decoder); +void cs_etm_decoder__zfree(struct cs_etm_decoder **decoder); int cs_etm_decoder__add_mem_access_cb(struct cs_etm_decoder *decoder, u64 start, u64 end, diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 114b3cd2da49..bdcf94813129 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -243,8 +243,12 @@ enum cs_etm_pid_fmt cs_etm__get_pid_fmt(struct cs_etm_queue *etmq) return etmq->etm->pid_fmt; } -static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq, - u8 trace_chan_id, u64 *cpu_metadata) +/* + * Add a traceid_list entry that maps a trace ID to a CPU metadata for this etmq + * and update the trace ID in the cpu_metadata. + */ +static int cs_etm__map_trace_id(struct cs_etm_queue *etmq, u8 trace_chan_id, + u64 *cpu_metadata) { /* Get an RB node for this CPU */ struct int_node *inode = intlist__findnew(etmq->traceid_list, trace_chan_id); @@ -289,8 +293,7 @@ static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq, /* Not one we've seen before, associate the traceID with the metadata pointer */ inode->priv = cpu_metadata; - - return 0; + return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_metadata); } static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int cpu) @@ -309,31 +312,29 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id { struct cs_etm_queue *etmq; - /* - * If the queue is unformatted then only save one mapping in the - * queue associated with that CPU so only one decoder is made. - */ etmq = cs_etm__get_queue(etm, cpu_metadata[CS_ETM_CPU]); if (!etmq) return -EINVAL; + /* + * Ignore HW_IDs for unformatted, a decoder will be made + * unconditionally later. + */ if (etmq->format == UNFORMATTED) - return cs_etm__insert_trace_id_node(etmq, trace_chan_id, - cpu_metadata); + return 0; /* * Otherwise, version 0 trace IDs are global so save them into every - * queue. + * formatted queue. */ for (unsigned int i = 0; i < etm->queues.nr_queues; ++i) { int ret; etmq = etm->queues.queue_array[i].priv; - if (!etmq) + if (!etmq || etmq->format == UNFORMATTED) continue; - ret = cs_etm__insert_trace_id_node(etmq, trace_chan_id, - cpu_metadata); + ret = cs_etm__map_trace_id(etmq, trace_chan_id, cpu_metadata); if (ret) return ret; } @@ -344,7 +345,6 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu, u64 hw_id) { - int err; u64 *cpu_data; u8 trace_chan_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id); @@ -352,22 +352,13 @@ static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu, if (cpu_data == NULL) return -EINVAL; - err = cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data); - if (err) - return err; - - /* - * if we are picking up the association from the packet, need to plug - * the correct trace ID into the metadata for setting up decoders later. - */ - return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_data); + return cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data); } static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, u64 hw_id) { struct cs_etm_queue *etmq = cs_etm__get_queue(etm, cpu); - int ret; u64 *cpu_data; u32 sink_id = FIELD_GET(CS_AUX_HW_ID_SINK_ID_MASK, hw_id); u8 trace_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id); @@ -375,6 +366,13 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, if (!etmq) return -EINVAL; + /* + * Ignore HW_IDs for unformatted, a decoder will be made + * unconditionally later. + */ + if (etmq->format == UNFORMATTED) + return 0; + /* * Check sink id hasn't changed in per-cpu mode. In per-thread mode, * let it pass for now until an actual overlapping trace ID is hit. In @@ -419,13 +417,41 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, if (!cpu_data) return -EINVAL; - ret = cs_etm__insert_trace_id_node(etmq, trace_id, cpu_data); - if (ret) - return ret; + return cs_etm__map_trace_id(etmq, trace_id, cpu_data); +} - ret = cs_etm__metadata_set_trace_id(trace_id, cpu_data); - if (ret) - return ret; +static int cs_etm__map_unformatted_trace_ids(struct cs_etm_auxtrace *etm) +{ + struct auxtrace_queues *queues = &etm->queues; + + for (unsigned int i = 0; i < queues->nr_queues; i++) { + struct auxtrace_queue *queue = &queues->queue_array[i]; + struct cs_etm_queue *etmq = queue->priv; + u64 *cpu_data; + int ret; + + if (list_empty(&queue->head) || !etmq || etmq->format != UNFORMATTED) + continue; + + if (!intlist__empty(etmq->traceid_list)) { + pr_err("CS_ETM: Unformatted queue should't have trace ID\n"); + return -EINVAL; + } + + if (etm->per_thread_decoding) + cpu_data = etm->metadata[0]; + else + cpu_data = get_cpu_data(etm, queue->cpu); + + if (!cpu_data) { + pr_err("CS_ETM: Unformatted queue missing CPU metadata\n"); + return -EINVAL; + } + + ret = cs_etm__map_trace_id(etmq, 1, cpu_data); + if (ret) + return ret; + } return 0; } @@ -939,7 +965,7 @@ static int cs_etm__flush_events(struct perf_session *session, struct cs_etm_auxtrace *etm = container_of(session->auxtrace, struct cs_etm_auxtrace, auxtrace); - if (dump_trace) + if (dump_trace || auxtrace__dont_decode(session)) return 0; if (!tool->ordered_events) @@ -1002,7 +1028,7 @@ static void cs_etm__free_queue(void *priv) if (!etmq) return; - cs_etm_decoder__free(etmq->decoder); + cs_etm_decoder__zfree(&etmq->decoder); cs_etm__free_traceid_queues(etmq); if (etmq->own_traceid_list) { @@ -1017,13 +1043,10 @@ static void cs_etm__free_queue(void *priv) free(etmq); } -static void cs_etm__free_events(struct perf_session *session) +static void cs_etm__free_queues(struct cs_etm_auxtrace *etm) { unsigned int i; - struct cs_etm_auxtrace *aux = container_of(session->auxtrace, - struct cs_etm_auxtrace, - auxtrace); - struct auxtrace_queues *queues = &aux->queues; + struct auxtrace_queues *queues = &etm->queues; for (i = 0; i < queues->nr_queues; i++) { cs_etm__free_queue(queues->queue_array[i].priv); @@ -1033,13 +1056,23 @@ static void cs_etm__free_events(struct perf_session *session) auxtrace_queues__free(queues); } +static void cs_etm__free_events(struct perf_session *session) +{ + struct cs_etm_auxtrace *aux = container_of(session->auxtrace, + struct cs_etm_auxtrace, + auxtrace); + + cs_etm__free_queues(aux); +} + static void cs_etm__free(struct perf_session *session) { int i; struct cs_etm_auxtrace *aux = container_of(session->auxtrace, struct cs_etm_auxtrace, auxtrace); - cs_etm__free_events(session); + + cs_etm__free_queues(aux); session->auxtrace = NULL; for (i = 0; i < aux->num_cpu; i++) @@ -2969,7 +3002,7 @@ static int cs_etm__process_event(struct perf_session *session, struct cs_etm_auxtrace, auxtrace); - if (dump_trace) + if (dump_trace || auxtrace__dont_decode(session)) return 0; if (!tool->ordered_events) { @@ -3476,7 +3509,7 @@ static int cs_etm__create_queue_decoders(struct cs_etm_queue *etmq) return 0; out_free_decoder: - cs_etm_decoder__free(etmq->decoder); + cs_etm_decoder__zfree(&etmq->decoder); out_free: zfree(&t_params); return -EINVAL; @@ -3519,27 +3552,49 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, int err = 0; int aux_hw_id_found; int i; - u64 *ptr = NULL; + u64 *ptr = (u64 *) auxtrace_info->priv; u64 **metadata = NULL; - /* First the global part */ - ptr = (u64 *) auxtrace_info->priv; - num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff; + etm = zalloc(sizeof(*etm)); + if (!etm) + return -ENOMEM; + + session->auxtrace = &etm->auxtrace; + etm->auxtrace.free = cs_etm__free; + etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace; + 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; + etm->auxtrace.free_events = cs_etm__free_events; + etm->pmu_type = (unsigned int) ((ptr[CS_PMU_TYPE_CPUS] >> 32) & 0xffffffff); + + /* + * Don't go further than the minimum required to identify this event as + * auxtrace with cs_etm__evsel_is_auxtrace() so unleader_auxtrace() + * works. + */ + if (auxtrace__dont_decode(session)) + return 0; /* * Bound num_cpu by the event size: the global header consumes * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64 * metadata entry after that. */ + num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff; priv_size = total_size - event_header_size - INFO_HEADER_SIZE - CS_ETM_HEADER_SIZE; if (num_cpu <= 0 || priv_size <= 0 || - num_cpu > priv_size / (int)sizeof(u64)) - return -EINVAL; + num_cpu > priv_size / (int)sizeof(u64)) { + err = -EINVAL; + goto err_free_etm; + } metadata = zalloc(sizeof(*metadata) * num_cpu); - if (!metadata) - return -ENOMEM; + if (!metadata) { + err = -ENOMEM; + goto err_free_etm; + } /* Start parsing after the common part of the header */ i = CS_HEADER_VERSION_MAX; @@ -3598,13 +3653,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_free_metadata; } - etm = zalloc(sizeof(*etm)); - - if (!etm) { - err = -ENOMEM; - goto err_free_metadata; - } - /* * As all the ETMs run at the same exception level, the system should * have the same PID format crossing CPUs. So cache the PID format @@ -3614,7 +3662,7 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, err = auxtrace_queues__init_nr(&etm->queues, max_cpu + 1); if (err) - goto err_free_etm; + goto err_free_metadata; for (unsigned int j = 0; j < etm->queues.nr_queues; ++j) { err = cs_etm__setup_queue(etm, &etm->queues.queue_array[j], j); @@ -3626,7 +3674,7 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->synth_opts = *session->itrace_synth_opts; } else { itrace_synth_opts__set_default(&etm->synth_opts, - session->itrace_synth_opts->default_no_sample); + session->itrace_synth_opts->default_no_sample, false); etm->synth_opts.callchain = false; etm->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack; } @@ -3652,7 +3700,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->session = session; etm->num_cpu = num_cpu; - etm->pmu_type = (unsigned int) ((ptr[CS_PMU_TYPE_CPUS] >> 32) & 0xffffffff); etm->snapshot_mode = (ptr[CS_ETM_SNAPSHOT] != 0); etm->metadata = metadata; etm->auxtrace_type = auxtrace_info->type; @@ -3679,14 +3726,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, "you can specify the itrace option 'T' for timestamp decoding\n" "if the Coresight timestamp on the platform is same with the kernel time.\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; - etm->auxtrace.free_events = cs_etm__free_events; - etm->auxtrace.free = cs_etm__free; - etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace; - session->auxtrace = &etm->auxtrace; - cs_etm__setup_timeless_decoding(etm); etm->tc.time_shift = tc->time_shift; @@ -3750,6 +3789,11 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_free_queues; } + /* Create IDs for TRBE sinks so that a decoder is made for each one. */ + err = cs_etm__map_unformatted_trace_ids(etm); + if (err) + goto err_free_queues; + err = cs_etm__create_decoders(etm); if (err) goto err_free_queues; @@ -3758,14 +3802,14 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, return 0; err_free_queues: - auxtrace_queues__free(&etm->queues); - session->auxtrace = NULL; -err_free_etm: - zfree(&etm); + cs_etm__free_queues(etm); err_free_metadata: /* No need to check @metadata[j], free(NULL) is supported */ for (int j = 0; j < num_cpu; j++) zfree(&metadata[j]); zfree(&metadata); +err_free_etm: + session->auxtrace = NULL; + zfree(&etm); return err; } diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 6b5ffe81f141..b7095519419e 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -55,7 +55,7 @@ FILE *debug_file(void) { if (!_debug_file) { debug_set_file(stderr); - pr_warning_once("debug_file not set"); + pr_warning_once("%s not set\n", __func__); } return _debug_file; } diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 6b737e195ce1..7552c9171d90 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -33,10 +33,9 @@ extern int debug_type_profile; }) #define pr_info(fmt, ...) \ eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug(fmt, ...) \ - eprintf(1, verbose, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debugN(n, fmt, ...) \ - eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) + (unlikely(verbose >= (n)) ? eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) : 0) +#define pr_debug(fmt, ...) pr_debugN(1, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__) diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c index 84a78b30ceac..db188c397d63 100644 --- a/tools/perf/util/debuginfo.c +++ b/tools/perf/util/debuginfo.c @@ -91,26 +91,32 @@ static struct debuginfo *__debuginfo__new(const char *path) return dbg; } -struct debuginfo *debuginfo__new(const char *path) +struct debuginfo *debuginfo__new(struct dso *dso) +{ + struct debuginfo *dinfo; + char *path; + bool decomp = false; + + dso__find_dbginfo_type(dso); + path = dso__get_filename(dso, "", &decomp, dso__dbginfo_type(dso)); + if (path == NULL) + return NULL; + + dinfo = __debuginfo__new(path); + dso__put_filename(dso, path, decomp); + return dinfo; +} + +struct debuginfo *debuginfo__from_path(const char *path) { - static const enum dso_binary_type distro_dwarf_types[] = { - DSO_BINARY_TYPE__FEDORA_DEBUGINFO, - DSO_BINARY_TYPE__UBUNTU_DEBUGINFO, - DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, - DSO_BINARY_TYPE__BUILDID_DEBUGINFO, - DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO, - DSO_BINARY_TYPE__NOT_FOUND, - }; - const enum dso_binary_type *type; - char buf[PATH_MAX], nil = '\0'; struct dso *dso; - struct debuginfo *dinfo = NULL; - struct build_id bid = { .size = 0}; + struct debuginfo *dinfo; + struct build_id bid = { .size = 0 }; /* Try to open distro debuginfo files */ dso = dso__new(path); if (!dso) - goto out; + return NULL; /* * Set the build id for DSO_BINARY_TYPE__BUILDID_DEBUGINFO. Don't block @@ -120,23 +126,10 @@ struct debuginfo *debuginfo__new(const char *path) if (filename__read_build_id(path, &bid) > 0) dso__set_build_id(dso, &bid); - for (type = distro_dwarf_types; - !dinfo && *type != DSO_BINARY_TYPE__NOT_FOUND; - type++) { - if (dso__read_binary_type_filename(dso, *type, &nil, - buf, PATH_MAX) < 0) - continue; - dinfo = __debuginfo__new(buf); - } + dinfo = debuginfo__new(dso); dso__put(dso); -out: - if (dinfo) - return dinfo; - - /* if failed to open all distro debuginfo, open given binary */ - symbol__join_symfs(buf, path); - return __debuginfo__new(buf); + return dinfo; } void debuginfo__delete(struct debuginfo *dbg) diff --git a/tools/perf/util/debuginfo.h b/tools/perf/util/debuginfo.h index a52d69932815..5a9ea286dc59 100644 --- a/tools/perf/util/debuginfo.h +++ b/tools/perf/util/debuginfo.h @@ -5,6 +5,8 @@ #include #include +struct dso; + #ifdef HAVE_LIBDW_SUPPORT #include "dwarf-aux.h" @@ -19,7 +21,9 @@ struct debuginfo { }; /* This also tries to open distro debuginfo */ -struct debuginfo *debuginfo__new(const char *path); +struct debuginfo *debuginfo__from_path(const char *path); +/* Create a debuginfo using information in DSO */ +struct debuginfo *debuginfo__new(struct dso *dso); void debuginfo__delete(struct debuginfo *dbg); int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs, @@ -31,7 +35,12 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs, struct debuginfo { }; -static inline struct debuginfo *debuginfo__new(const char *path __maybe_unused) +static inline struct debuginfo *debuginfo__from_path(const char *path __maybe_unused) +{ + return NULL; +} + +static inline struct debuginfo *debuginfo__new(struct dso *dso __maybe_unused) { return NULL; } diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c index 6cfdbabbb8c7..7c289b67b5bd 100644 --- a/tools/perf/util/disasm.c +++ b/tools/perf/util/disasm.c @@ -31,6 +31,7 @@ #include "map.h" #include "maps.h" #include "namespaces.h" +#include "path.h" #include "srcline.h" #include "symbol.h" #include "thread.h" @@ -161,6 +162,8 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char * .e_flags = e_flags, }; const struct arch *result = NULL, **tmp; + const struct arch *(*new_fn)(const struct e_machine_and_e_flags *id, + const char *cpuid) = NULL; if (num_archs > 0) { tmp = bsearch(&key, archs, num_archs, sizeof(*archs), arch__key_cmp); @@ -171,7 +174,16 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char * if (result) return result; - if (e_machine >= ARRAY_SIZE(arch_new_fn) || arch_new_fn[e_machine] == NULL) { + /* + * EM_ALPHA (0x9026) is far too large to index arch_new_fn[], so it is + * selected explicitly; everything else uses the e_machine-indexed table. + */ + if (e_machine == EM_ALPHA) + new_fn = arch__new_alpha; + else if (e_machine < ARRAY_SIZE(arch_new_fn)) + new_fn = arch_new_fn[e_machine]; + + if (new_fn == NULL) { errno = ENOTSUP; return NULL; } @@ -182,7 +194,7 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char * archs = tmp; - result = arch_new_fn[e_machine](&key, cpuid); + result = new_fn(&key, cpuid); if (!result) { pr_err("%s: failed to initialize %u arch priv area\n", __func__, e_machine); @@ -203,6 +215,11 @@ bool arch__is_powerpc(const struct arch *arch) return arch->id.e_machine == EM_PPC || arch->id.e_machine == EM_PPC64; } +bool arch__is_arm64(const struct arch *arch) +{ + return arch->id.e_machine == EM_AARCH64; +} + static void ins_ops__delete(struct ins_operands *ops) { if (ops == NULL) @@ -1159,79 +1176,6 @@ int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, s return 0; } -static int dso__disassemble_filename(struct dso *dso, char *filename, size_t filename_size) -{ - char linkname[PATH_MAX]; - char *build_id_filename; - char *build_id_path = NULL; - char *pos; - int len; - - if (dso__symtab_type(dso) == DSO_BINARY_TYPE__KALLSYMS && - !dso__is_kcore(dso)) - return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX; - - build_id_filename = dso__build_id_filename(dso, NULL, 0, false); - if (build_id_filename) { - __symbol__join_symfs(filename, filename_size, build_id_filename); - free(build_id_filename); - } else { - if (dso__has_build_id(dso)) - return ENOMEM; - goto fallback; - } - - build_id_path = strdup(filename); - if (!build_id_path) - return ENOMEM; - - /* - * old style build-id cache has name of XX/XXXXXXX.. while - * new style has XX/XXXXXXX../{elf,kallsyms,vdso}. - * extract the build-id part of dirname in the new style only. - */ - pos = strrchr(build_id_path, '/'); - if (pos && strlen(pos) < SBUILD_ID_SIZE - 2) - dirname(build_id_path); - - if (dso__is_kcore(dso)) - goto fallback; - - len = readlink(build_id_path, linkname, sizeof(linkname) - 1); - if (len < 0) - goto fallback; - - linkname[len] = '\0'; - if (strstr(linkname, DSO__NAME_KALLSYMS) || - access(filename, R_OK)) { -fallback: - /* - * If we don't have build-ids or the build-id file isn't in the - * cache, or is just a kallsyms file, well, lets hope that this - * DSO is the same as when 'perf record' ran. - */ - if (dso__kernel(dso) && dso__long_name(dso)[0] == '/') - snprintf(filename, filename_size, "%s", dso__long_name(dso)); - else - __symbol__join_symfs(filename, filename_size, dso__long_name(dso)); - - mutex_lock(dso__lock(dso)); - if (access(filename, R_OK) && errno == ENOENT && dso__nsinfo(dso)) { - char *new_name = dso__filename_with_chroot(dso, filename); - if (new_name) { - strlcpy(filename, new_name, filename_size); - free(new_name); - } - } - mutex_unlock(dso__lock(dso)); - } else if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) { - dso__set_binary_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); - } - - free(build_id_path); - return 0; -} - static int symbol__disassemble_raw(char *filename, struct symbol *sym, struct annotate_args *args) { @@ -1544,14 +1488,29 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) struct annotation_options *options = args->options; struct map *map = args->ms->map; struct dso *dso = map__dso(map); - char symfs_filename[PATH_MAX]; + char *symfs_filename; bool delete_extract = false; struct kcore_extract kce; + enum dso_binary_type dbt; bool decomp = false; - int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename)); + int err; - if (err) - return err; + dbt = dso__find_binary_type(dso); + + if (dbt == DSO_BINARY_TYPE__KALLSYMS) + return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX; + + mutex_lock(dso__lock(dso)); + symfs_filename = dso__get_filename(dso, "", &decomp, dbt); + mutex_unlock(dso__lock(dso)); + + if (symfs_filename == NULL) { + if (dbt == DSO_BINARY_TYPE__BPF_PROG_INFO || + dbt == DSO_BINARY_TYPE__BPF_IMAGE) + symfs_filename = (char *)dso__long_name(dso); + else + return SYMBOL_ANNOTATE_ERRNO__COULDNT_DETERMINE_FILE_TYPE; + } pr_debug("%s: filename=%s, sym=%s, start=%#" PRIx64 ", end=%#" PRIx64 "\n", __func__, symfs_filename, sym->name, map__unmap_ip(map, sym->start), @@ -1559,9 +1518,7 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) pr_debug("annotating [%p] %30s : [%p] %30s\n", dso, dso__long_name(dso), sym, sym->name); - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) { - return SYMBOL_ANNOTATE_ERRNO__COULDNT_DETERMINE_FILE_TYPE; - } else if (dso__is_kcore(dso)) { + if (dso__is_kcore(dso)) { kce.addr = map__rip_2objdump(map, sym->start); kce.kcore_filename = symfs_filename; kce.len = sym->end - sym->start; @@ -1569,18 +1526,8 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) if (!kcore_extract__create(&kce)) { delete_extract = true; - strlcpy(symfs_filename, kce.extract_filename, sizeof(symfs_filename)); - } - } else if (dso__needs_decompress(dso)) { - char tmp[KMOD_DECOMP_LEN]; - - if (dso__decompress_kmodule_path(dso, symfs_filename, tmp, sizeof(tmp)) < 0) - return -1; - - /* empty pathname means file wasn't actually compressed */ - if (tmp[0] != '\0') { - decomp = true; - strcpy(symfs_filename, tmp); + dso__put_filename(dso, symfs_filename, decomp); + symfs_filename = kce.extract_filename; } } @@ -1638,11 +1585,10 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) pr_debug("Disassembled with %s\n", perf_disassembler__strs[dis]); } out_remove_tmp: - if (decomp) - unlink(symfs_filename); - if (delete_extract) kcore_extract__delete(&kce); + else if (dbt != DSO_BINARY_TYPE__BPF_PROG_INFO && dbt != DSO_BINARY_TYPE__BPF_IMAGE) + dso__put_filename(dso, symfs_filename, decomp); return err; } diff --git a/tools/perf/util/disasm.h b/tools/perf/util/disasm.h index 25756e3f47e4..7a1c889f2c50 100644 --- a/tools/perf/util/disasm.h +++ b/tools/perf/util/disasm.h @@ -16,6 +16,7 @@ struct symbol; struct data_loc_info; struct type_state; struct disasm_line; +struct annotated_op_loc; struct e_machine_and_e_flags { uint32_t e_flags; @@ -49,6 +50,9 @@ struct arch { struct data_loc_info *dloc, Dwarf_Die *cu_die, struct disasm_line *dl); #endif + void (*extract_op_location)(const struct arch *arch, struct disasm_line *dl, + const char *op_str, int op_idx, + struct annotated_op_loc *op_loc); }; struct ins { @@ -111,6 +115,7 @@ struct annotate_args { const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char *cpuid); bool arch__is_x86(const struct arch *arch); bool arch__is_powerpc(const struct arch *arch); +bool arch__is_arm64(const struct arch *arch); extern const struct ins_ops call_ops; extern const struct ins_ops dec_ops; @@ -122,6 +127,7 @@ extern const struct ins_ops ret_ops; int arch__associate_ins_ops(struct arch *arch, const char *name, const struct ins_ops *ops); +const struct arch *arch__new_alpha(const struct e_machine_and_e_flags *id, const char *cpuid); const struct arch *arch__new_arc(const struct e_machine_and_e_flags *id, const char *cpuid); const struct arch *arch__new_arm(const struct e_machine_and_e_flags *id, const char *cpuid); const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, const char *cpuid); diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 42bfe30a3b51..ce98613868e7 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -1,38 +1,44 @@ // SPDX-License-Identifier: GPL-2.0 +#include "dso.h" + +#include +#include + #include +#include #include #include #include -#include #include -#include #include +#include +#include #include -#include -#include -#include + +#include "annotate-data.h" +#include "auxtrace.h" +#include "compress.h" +#include "debug.h" +#include "dsos.h" +#include "env.h" +#include "libbfd.h" +#include "libdw.h" +#include "machine.h" +#include "map.h" +#include "namespaces.h" +#include "path.h" +#include "srcline.h" +#include "string2.h" +#include "symbol.h" +#include "util.h" /* O_CLOEXEC for older systems */ +#include "vdso.h" + #ifdef HAVE_LIBBPF_SUPPORT #include + #include "bpf-event.h" #include "bpf-utils.h" #endif -#include "compress.h" -#include "env.h" -#include "namespaces.h" -#include "path.h" -#include "map.h" -#include "symbol.h" -#include "srcline.h" -#include "dso.h" -#include "dsos.h" -#include "machine.h" -#include "auxtrace.h" -#include "util.h" /* O_CLOEXEC for older systems */ -#include "debug.h" -#include "string2.h" -#include "vdso.h" -#include "annotate-data.h" -#include "libdw.h" static const char * const debuglink_paths[] = { "%.0s%s", @@ -115,6 +121,7 @@ int dso__read_binary_type_filename(const struct dso *dso, const char *root_dir, char *filename, size_t size) { char build_id_hex[SBUILD_ID_SIZE]; + char relative[PATH_MAX]; int ret = 0; size_t len; @@ -167,13 +174,15 @@ int dso__read_binary_type_filename(const struct dso *dso, break; case DSO_BINARY_TYPE__FEDORA_DEBUGINFO: - len = __symbol__join_symfs(filename, size, "/usr/lib/debug"); - snprintf(filename + len, size - len, "%s.debug", dso__long_name(dso)); + snprintf(relative, sizeof(relative), "/usr/lib/debug%s.debug", + dso__long_name(dso)); + __symbol__join_symfs(filename, size, relative); break; case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO: - len = __symbol__join_symfs(filename, size, "/usr/lib/debug"); - snprintf(filename + len, size - len, "%s", dso__long_name(dso)); + snprintf(relative, sizeof(relative), "/usr/lib/debug%s", + dso__long_name(dso)); + __symbol__join_symfs(filename, size, relative); break; case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO: @@ -187,8 +196,9 @@ int dso__read_binary_type_filename(const struct dso *dso, ret = -1; break; } - len = __symbol__join_symfs(filename, size, "/usr/lib/debug"); - snprintf(filename + len, size - len, "%s", dso__long_name(dso) + 4); + snprintf(relative, sizeof(relative), "/usr/lib/debug%s", + dso__long_name(dso) + 4); + __symbol__join_symfs(filename, size, relative); break; case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO: @@ -200,15 +210,15 @@ int dso__read_binary_type_filename(const struct dso *dso, while (last_slash != dso__long_name(dso) && *last_slash != '/') last_slash--; - len = __symbol__join_symfs(filename, size, ""); dir_size = last_slash - dso__long_name(dso) + 2; - if (dir_size > (size - len)) { + if (dir_size > sizeof(relative)) { ret = -1; break; } - len += scnprintf(filename + len, dir_size, "%s", dso__long_name(dso)); - len += scnprintf(filename + len , size - len, ".debug%s", - last_slash); + len = scnprintf(relative, dir_size, "%s", dso__long_name(dso)); + scnprintf(relative + len, sizeof(relative) - len, ".debug%s", + last_slash); + __symbol__join_symfs(filename, size, relative); break; } @@ -219,9 +229,20 @@ int dso__read_binary_type_filename(const struct dso *dso, } build_id__snprintf(dso__bid(dso), build_id_hex, sizeof(build_id_hex)); - len = __symbol__join_symfs(filename, size, "/usr/lib/debug/.build-id/"); - snprintf(filename + len, size - len, "%.2s/%s.debug", - build_id_hex, build_id_hex + 2); + /* + * The build id cache layout splits the first two characters off + * into a directory name, so the basename of the hierarchy path + * is only part of the build id. Name the flat file after the + * whole build id instead. + */ + if (symbol_conf.symfs_layout_flat) + snprintf(relative, sizeof(relative), "/%s.debug", + build_id_hex); + else + snprintf(relative, sizeof(relative), + "/usr/lib/debug/.build-id/%.2s/%s.debug", + build_id_hex, build_id_hex + 2); + __symbol__join_symfs(filename, size, relative); break; case DSO_BINARY_TYPE__VMLINUX: @@ -569,8 +590,8 @@ char *dso__filename_with_chroot(const struct dso *dso, const char *filename) return filename_with_chroot(nsinfo__pid(dso__nsinfo_const(dso)), filename); } -static char *dso__get_filename(struct dso *dso, const char *root_dir, - bool *decomp) +char *dso__get_filename(struct dso *dso, const char *root_dir, bool *decomp, + enum dso_binary_type type) { char *name = malloc(PATH_MAX); @@ -579,8 +600,7 @@ static char *dso__get_filename(struct dso *dso, const char *root_dir, if (name == NULL) return NULL; - if (dso__read_binary_type_filename(dso, dso__binary_type(dso), - root_dir, name, PATH_MAX)) + if (dso__read_binary_type_filename(dso, type, root_dir, name, PATH_MAX)) goto out; if (!is_regular_file(name)) { @@ -641,6 +661,14 @@ static char *dso__get_filename(struct dso *dso, const char *root_dir, return NULL; } +void dso__put_filename(struct dso *dso __maybe_unused, char *filename, bool decomp) +{ + if (decomp) + unlink(filename); + + free(filename); +} + static int __open_dso(struct dso *dso, struct machine *machine) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) { @@ -650,7 +678,8 @@ static int __open_dso(struct dso *dso, struct machine *machine) mutex_lock(dso__lock(dso)); - name = dso__get_filename(dso, machine ? machine->root_dir : "", &decomp); + name = dso__get_filename(dso, machine ? machine->root_dir : "", &decomp, + dso__binary_type(dso)); if (name) { fd = do_open(name); } else { @@ -659,11 +688,8 @@ static int __open_dso(struct dso *dso, struct machine *machine) fd = -errno; } - if (decomp) - unlink(name); - + dso__put_filename(dso, name, decomp); mutex_unlock(dso__lock(dso)); - free(name); return fd; } @@ -807,34 +833,149 @@ void dso__data_close(struct dso *dso) mutex_unlock(dso__data_open_lock()); } -static void try_to_open_dso(struct dso *dso, struct machine *machine) - EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +static int dso__find_binary_type_locked(struct dso *dso) + EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)) { enum dso_binary_type binary_type_data[] = { DSO_BINARY_TYPE__BUILD_ID_CACHE, DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + dso__symtab_type(dso), DSO_BINARY_TYPE__NOT_FOUND, }; int i = 0; - struct dso_data *dso_data = dso__data(dso); + char *path; + bool found = false; + bool decomp = false; + int saved_errno; - if (dso_data->fd >= 0) - return; + if (dso__bintype_searched(dso)) + return dso__binary_type(dso); + dso__set_bintype_searched(dso); - if (dso__binary_type(dso) != DSO_BINARY_TYPE__NOT_FOUND) { - dso_data->fd = open_dso(dso, machine); - goto out; + switch (dso__binary_type(dso)) { + case DSO_BINARY_TYPE__KALLSYMS: + case DSO_BINARY_TYPE__GUEST_KALLSYMS: + case DSO_BINARY_TYPE__VMLINUX: + case DSO_BINARY_TYPE__GUEST_VMLINUX: + case DSO_BINARY_TYPE__KCORE: + case DSO_BINARY_TYPE__GUEST_KCORE: + /* Nothing to do with kernel images */ + WARN_ON(!dso__kernel(dso)); + return dso__binary_type(dso); + case DSO_BINARY_TYPE__BPF_PROG_INFO: + case DSO_BINARY_TYPE__BPF_IMAGE: + case DSO_BINARY_TYPE__OOL: + case DSO_BINARY_TYPE__JAVA_JIT: + /* Same for the special DSOs */ + return dso__binary_type(dso); + case DSO_BINARY_TYPE__GUEST_KMODULE: + case DSO_BINARY_TYPE__GUEST_KMODULE_COMP: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE_COMP: + case DSO_BINARY_TYPE__SYSTEM_PATH_DSO: + case DSO_BINARY_TYPE__BUILD_ID_CACHE: + /* We don't expect these are set; fall through */ + case DSO_BINARY_TYPE__NOT_FOUND: + /* Let's find it out (for user DSOs or kernel modules) */ + break; + case DSO_BINARY_TYPE__FEDORA_DEBUGINFO: + case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO: + case DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO: + case DSO_BINARY_TYPE__BUILDID_DEBUGINFO: + case DSO_BINARY_TYPE__GNU_DEBUGDATA: + case DSO_BINARY_TYPE__DEBUGLINK: + default: + /* Unexpected binary types (usually debug only) */ + break; } do { dso__set_binary_type(dso, binary_type_data[i++]); - dso_data->fd = open_dso(dso, machine); - if (dso_data->fd >= 0) - goto out; + errno = 0; + path = dso__get_filename(dso, "", &decomp, dso__binary_type(dso)); + if (path == NULL) { + if (errno == EMFILE) + goto open_err; + continue; + } + + errno = 0; + found = filename__has_section(path, ".text"); + if (!found) + saved_errno = errno; + dso__put_filename(dso, path, decomp); + + if (found) + return dso__binary_type(dso); + else if (saved_errno == EMFILE) + goto open_err; } while (dso__binary_type(dso) != DSO_BINARY_TYPE__NOT_FOUND); -out: + + return dso__binary_type(dso); + +open_err: + /* Too many open files. Allow retry after closing one. */ + RC_CHK_ACCESS(dso)->bintype_searched = 0; + dso__set_binary_type(dso, DSO_BINARY_TYPE__NOT_FOUND); + return -1; +} + +#define FIND_MAX_RETRY 3 +static int __dso__find_binary_type(struct dso *dso) + EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +{ + int ret; + int retry = 0; + +again: + mutex_lock(dso__lock(dso)); + ret = dso__find_binary_type_locked(dso); + mutex_unlock(dso__lock(dso)); + + if (ret == -1 && retry++ < FIND_MAX_RETRY) { + if (dso__data_open_cnt) { + close_first_dso(); + goto again; + } + } + + if (ret == -1) { + mutex_lock(dso__lock(dso)); + ret = dso__binary_type(dso); + dso__set_bintype_searched(dso); + mutex_unlock(dso__lock(dso)); + } + + return ret; +} +#undef FIND_MAX_RETRY + +enum dso_binary_type dso__find_binary_type(struct dso *dso) +{ + int ret; + + mutex_lock(dso__data_open_lock()); + ret = __dso__find_binary_type(dso); + mutex_unlock(dso__data_open_lock()); + + return ret; +} + +static void try_to_open_dso(struct dso *dso, struct machine *machine) + EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +{ + struct dso_data *dso_data = dso__data(dso); + + if (dso_data->fd >= 0) + return; + + __dso__find_binary_type(dso); + dso_data->fd = open_dso(dso, machine); + if (dso_data->fd >= 0) dso_data->status = DSO_DATA_STATUS_OK; else @@ -1351,9 +1492,10 @@ uint16_t dso__e_machine_endian(struct dso *dso, struct machine *machine, uint32_ bool *is_big_endian) { uint16_t e_machine = EM_NONE; + enum dso_binary_type dbt = dso__find_binary_type(dso); int fd; - switch (dso__binary_type(dso)) { + switch (dbt) { case DSO_BINARY_TYPE__KALLSYMS: case DSO_BINARY_TYPE__GUEST_KALLSYMS: case DSO_BINARY_TYPE__VMLINUX: @@ -1696,10 +1838,9 @@ struct dso *dso__new_id(const char *name, const struct dso_id *id) dso->srclines = RB_ROOT_CACHED; dso->data_types = RB_ROOT; dso->global_vars = RB_ROOT; - dso->data.fd = -1; - dso->data.status = DSO_DATA_STATUS_UNKNOWN; dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; + dso->dbginfo_type = DSO_BINARY_TYPE__NOT_FOUND; dso->is_64_bit = (sizeof(void *) == 8); dso->loaded = 0; dso->rel = 0; @@ -1757,6 +1898,7 @@ void dso__delete(struct dso *dso) auxtrace_cache__free(RC_CHK_ACCESS(dso)->auxtrace_cache); dso_cache__free(dso); dso__free_a2l(dso); + dso__free_a2l_libbfd(dso); dso__free_libdw(dso); dso__free_symsrc_filename(dso); nsinfo__zput(RC_CHK_ACCESS(dso)->nsinfo); @@ -2058,20 +2200,73 @@ const u8 *dso__read_symbol(struct dso *dso, const char *symfs_filename, struct debuginfo *dso__debuginfo(struct dso *dso) { - char *name; - bool decomp = false; - struct debuginfo *dinfo = NULL; + struct debuginfo *dinfo; mutex_lock(dso__lock(dso)); - - name = dso__get_filename(dso, "", &decomp); - if (name) - dinfo = debuginfo__new(name); - - if (decomp) - unlink(name); - + dinfo = debuginfo__new(dso); mutex_unlock(dso__lock(dso)); - free(name); return dinfo; } + +void dso__set_symsrc_filename(struct dso *dso, char *val) +{ + RC_CHK_ACCESS(dso)->symsrc_filename = val; + dso__free_libdw(dso); + dso__free_a2l(dso); + dso__free_a2l_libbfd(dso); + dso__set_has_srcline(dso, true); + dso__set_a2l_fails(dso, 0); +} + +void dso__find_dbginfo_type(struct dso *dso) +{ + static const enum dso_binary_type dbginfo_types[] = { + DSO_BINARY_TYPE__FEDORA_DEBUGINFO, + DSO_BINARY_TYPE__UBUNTU_DEBUGINFO, + DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO, + DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, + DSO_BINARY_TYPE__BUILDID_DEBUGINFO, + DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + DSO_BINARY_TYPE__DEBUGLINK, + DSO_BINARY_TYPE__NOT_FOUND, + }; + const enum dso_binary_type *type; + char *path; + bool found, decomp = false; + + if (dso__dbginfo_type(dso) != DSO_BINARY_TYPE__NOT_FOUND) + return; + + if (dso__debuginfo_searched(dso)) + return; + dso__set_debuginfo_searched(dso); + + /* Maybe debug info is in the same file with the symbol table */ + path = dso__get_filename(dso, "", &decomp, dso__symtab_type(dso)); + if (path) { + found = filename__has_section(path, ".debug_info") || + filename__has_section(path, ".zdebug_info"); + + dso__put_filename(dso, path, decomp); + if (found) { + dso__set_dbginfo_type(dso, dso__symtab_type(dso)); + return; + } + } + + /* Otherwise check distro debug locations */ + for (type = dbginfo_types; *type != DSO_BINARY_TYPE__NOT_FOUND; type++) { + path = dso__get_filename(dso, "", &decomp, *type); + if (path == NULL) + continue; + + found = filename__has_section(path, ".debug_info") || + filename__has_section(path, ".zdebug_info"); + + dso__put_filename(dso, path, decomp); + if (found) + break; + } + + dso__set_dbginfo_type(dso, *type); +} diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 55c4aaa53c38..d5f7382d4e13 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -304,7 +304,12 @@ DECLARE_RC_STRUCT(dso) { const char *short_name; const char *long_name; void *a2l; +#ifdef HAVE_LIBBFD_SUPPORT + void *a2l_libbfd; +#endif +#ifdef HAVE_LIBDW_SUPPORT void *libdw; +#endif char *symsrc_filename; struct nsinfo *nsinfo; struct auxtrace_cache *auxtrace_cache; @@ -323,8 +328,14 @@ DECLARE_RC_STRUCT(dso) { enum dso_load_errno load_errno; u16 long_name_len; u16 short_name_len; + /* + * DSO file data can be spread into multiple places. Distros usually + * place debug info to a separate file. Symbol tables may exist in + * the binary or the debug file. Let's separate them. + */ enum dso_binary_type symtab_type:8; enum dso_binary_type binary_type:8; + enum dso_binary_type dbginfo_type:8; enum dso_space_type kernel:2; enum dso_swap_type needs_swap:2; bool is_kmod:1; @@ -338,6 +349,8 @@ DECLARE_RC_STRUCT(dso) { u8 short_name_allocated:1; u8 long_name_allocated:1; u8 is_64_bit:1; + u8 bintype_searched:1; + u8 debuginfo_searched:1; bool sorted_by_name; bool loaded; u8 rel; @@ -368,6 +381,20 @@ static inline void dso__set_a2l(struct dso *dso, void *val) RC_CHK_ACCESS(dso)->a2l = val; } +#ifdef HAVE_LIBBFD_SUPPORT +static inline void *dso__a2l_libbfd(const struct dso *dso) +{ + return RC_CHK_ACCESS(dso)->a2l_libbfd; +} + +static inline void dso__set_a2l_libbfd(struct dso *dso, void *val) +{ + RC_CHK_ACCESS(dso)->a2l_libbfd = val; +} +#endif + +struct Dwfl; +#ifdef HAVE_LIBDW_SUPPORT static inline void *dso__libdw(const struct dso *dso) { return RC_CHK_ACCESS(dso)->libdw; @@ -378,8 +405,6 @@ static inline void dso__set_libdw(struct dso *dso, void *val) RC_CHK_ACCESS(dso)->libdw = val; } -struct Dwfl; -#ifdef HAVE_LIBDW_SUPPORT struct Dwfl *dso__libdw_dwfl(struct dso *dso); #else static inline struct Dwfl *dso__libdw_dwfl(struct dso *dso __maybe_unused) @@ -735,10 +760,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso) return RC_CHK_ACCESS(dso)->symsrc_filename; } -static inline void dso__set_symsrc_filename(struct dso *dso, char *val) -{ - RC_CHK_ACCESS(dso)->symsrc_filename = val; -} +void dso__set_symsrc_filename(struct dso *dso, char *val) EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)); static inline void dso__free_symsrc_filename(struct dso *dso) { @@ -821,6 +843,10 @@ int dso__kernel_module_get_build_id(struct dso *dso, const char *root_dir); char dso__symtab_origin(const struct dso *dso); int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, const char *root_dir, char *filename, size_t size); +/* returned filename should be freed by dso__put_filename() */ +char *dso__get_filename(struct dso *dso, const char *root_dir, bool *decomp, + enum dso_binary_type type); +void dso__put_filename(struct dso *dso, char *filename, bool decomp); bool is_kernel_module(const char *pathname, int cpumode); bool dso__needs_decompress(struct dso *dso); int dso__decompress_kmodule_fd(struct dso *dso, const char *name); @@ -932,6 +958,8 @@ void dso__reset_find_symbol_cache(struct dso *dso); size_t dso__fprintf_symbols_by_name(struct dso *dso, FILE *fp); size_t dso__fprintf(struct dso *dso, FILE *fp); +enum dso_binary_type dso__find_binary_type(struct dso *dso); + static inline enum dso_binary_type dso__binary_type(const struct dso *dso) { return RC_CHK_ACCESS(dso)->binary_type; @@ -942,6 +970,16 @@ static inline void dso__set_binary_type(struct dso *dso, enum dso_binary_type bt RC_CHK_ACCESS(dso)->binary_type = bt; } +static inline bool dso__bintype_searched(const struct dso *dso) +{ + return RC_CHK_ACCESS(dso)->bintype_searched; +} + +static inline void dso__set_bintype_searched(struct dso *dso) +{ + RC_CHK_ACCESS(dso)->bintype_searched = 1; +} + static inline bool dso__is_vmlinux(const struct dso *dso) { enum dso_binary_type bt = dso__binary_type(dso); @@ -983,6 +1021,28 @@ static inline bool dso__is_kallsyms(const struct dso *dso) return is_guest_kallsyms_pid_name(name); } +static inline enum dso_binary_type dso__dbginfo_type(const struct dso *dso) +{ + return RC_CHK_ACCESS(dso)->dbginfo_type; +} + +static inline void dso__set_dbginfo_type(struct dso *dso, enum dso_binary_type bt) +{ + RC_CHK_ACCESS(dso)->dbginfo_type = bt; +} + +void dso__find_dbginfo_type(struct dso *dso); + +static inline bool dso__debuginfo_searched(const struct dso *dso) +{ + return RC_CHK_ACCESS(dso)->debuginfo_searched; +} + +static inline void dso__set_debuginfo_searched(struct dso *dso) +{ + RC_CHK_ACCESS(dso)->debuginfo_searched = 1; +} + bool dso__is_object_file(const struct dso *dso); void dso__free_a2l(struct dso *dso); diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index d7160f87ac7d..54f8b5ec74a2 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "debug.h" #include "dwarf-aux.h" #include "dwarf-regs.h" @@ -266,16 +267,27 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) return NULL; } +/* + * A DIE parsed at an offset that is not the start of a DIE can refer + * back to itself via DW_AT_type: bound these chases. + */ +#define MAX_TYPE_CHASE 32 + /* Get a type die, but skip qualifiers */ Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) { - int tag; + int tag, chase = 0; do { vr_die = die_get_type(vr_die, die_mem); if (!vr_die) - break; + return NULL; tag = dwarf_tag(vr_die); + if (++chase > MAX_TYPE_CHASE) { + pr_debug("DWARF: qualifier chase limit reached at DIE 0x%lx\n", + (unsigned long)dwarf_dieoffset(vr_die)); + return NULL; + } } while (tag == DW_TAG_const_type || tag == DW_TAG_restrict_type || tag == DW_TAG_volatile_type || @@ -296,8 +308,15 @@ Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) */ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) { + int chase = 0; + do { vr_die = __die_get_real_type(vr_die, die_mem); + if (++chase > MAX_TYPE_CHASE) { + pr_debug("DWARF: typedef chase limit reached at DIE 0x%lx\n", + vr_die ? (unsigned long)dwarf_dieoffset(vr_die) : 0); + return NULL; + } } while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef); return vr_die; @@ -314,7 +333,7 @@ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) */ Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem) { - int tag; + int tag, chase = 0; do { tag = dwarf_tag(type_die); @@ -324,6 +343,11 @@ Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem) tag != DW_TAG_restrict_type && tag != DW_TAG_volatile_type && tag != DW_TAG_shared_type) return NULL; + if (++chase > MAX_TYPE_CHASE) { + pr_debug("DWARF: pointer type chase limit reached at DIE 0x%lx\n", + (unsigned long)dwarf_dieoffset(type_die)); + return NULL; + } type_die = die_get_type(type_die, die_mem); } while (type_die); @@ -1118,17 +1142,25 @@ Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name, die_mem); } -/** - * die_get_typename_from_type - Get the name of given type DIE - * @type_die: a type DIE - * @buf: a strbuf for result type name - * - * Get the name of @type_die and stores it to @buf. Return 0 if succeeded. - * and Return -ENOENT if failed to find type name. - * Note that the result will stores typedef name if possible, and stores - * "*(function_type)" if the type is a function pointer. +/* + * The name follows DW_AT_type, so a self-referring DIE makes this + * recurse forever: bound it like the chases above. */ -int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) +static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf, + int depth); + +static int __die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf, int depth) +{ + Dwarf_Die type; + + if (__die_get_real_type(vr_die, &type) == NULL) + return -ENOENT; + + return __die_get_typename_from_type(&type, buf, depth); +} + +static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf, + int depth) { int tag, ret; const char *tmp = ""; @@ -1155,7 +1187,12 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) /* Write a base name */ return strbuf_addf(buf, "%s%s", tmp, name ?: ""); } - ret = die_get_typename(type_die, buf); + if (depth >= MAX_TYPE_CHASE) { + pr_debug("DWARF: type name recursion limit reached at DIE 0x%lx\n", + (unsigned long)dwarf_dieoffset(type_die)); + return -ENOENT; + } + ret = __die_get_typename(type_die, buf, depth + 1); if (ret < 0) { /* void pointer has no type attribute */ if (tag == DW_TAG_pointer_type && ret == -ENOENT) @@ -1166,6 +1203,19 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) return strbuf_addstr(buf, tmp); } +/** + * die_get_typename_from_type - Get the name of a type DIE + * @type_die: a type DIE + * @buf: a strbuf for the result type name + * + * Stores the typedef name if possible, "*(function_type)" for function + * pointers. Returns 0 or -ENOENT. + */ +int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) +{ + return __die_get_typename_from_type(type_die, buf, 0); +} + /** * die_get_typename - Get the name of given variable DIE * @vr_die: a variable DIE @@ -1178,12 +1228,7 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) */ int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf) { - Dwarf_Die type; - - if (__die_get_real_type(vr_die, &type) == NULL) - return -ENOENT; - - return die_get_typename_from_type(&type, buf); + return __die_get_typename(vr_die, buf, 0); } /** @@ -1632,6 +1677,15 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr, return result; } +/* + * Whether two DIEs live in the same debug file: dwarf_dieoffset() is + * relative to the file the DIE is in. + */ +bool die_same_file(Dwarf_Die *die_a, Dwarf_Die *die_b) +{ + return dwarf_cu_getdwarf(die_a->cu) == dwarf_cu_getdwarf(die_b->cu); +} + static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg) { struct die_var_type **var_types = arg; @@ -1676,6 +1730,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg) vt->is_reg_var_addr = true; vt->die_off = dwarf_dieoffset(&type_die); + vt->die_tag = dwarf_tag(&type_die); + vt->from_alt = !die_same_file(die_mem, &type_die); vt->addr = start; vt->end = end; vt->has_range = (end != 0 || start != 0); @@ -1695,7 +1751,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg) * * Save all variables and parameters in the @sc_die and save them to @var_types. * The @var_types is a singly-linked list containing type and location info. - * Actual type can be retrieved using dwarf_offdie() with 'die_off' later. + * Actual type can be retrieved using die_get_type_die() with 'die_off', + * 'die_tag' and 'from_alt' later. * * Callers should free @var_types. */ @@ -1741,6 +1798,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg) return DIE_FIND_CB_END; vt->die_off = dwarf_dieoffset(&type_die); + vt->die_tag = dwarf_tag(&type_die); + vt->from_alt = !die_same_file(die_mem, &type_die); vt->addr = ops->number; vt->end = 0; vt->has_range = false; @@ -1752,6 +1811,43 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg) return DIE_FIND_CB_SIBLING; } +/** + * die_get_type_die - Get a type DIE saved by die_collect_vars() + * @dbg: the main debug info + * @die_off: offset of the type DIE, from dwarf_dieoffset() + * @die_tag: tag that DIE had when the offset was saved + * @from_alt: whether the type DIE is in the dwz alt file + * @die_mem: where to store the resulting DIE + * + * Resolve @die_off in the file it was recorded as belonging to, with no + * fallback; @die_tag is a sanity check. + */ +Dwarf_Die *die_get_type_die(Dwarf *dbg, u64 die_off, int die_tag, bool from_alt, + Dwarf_Die *die_mem) +{ + Dwarf *target = dbg; + Dwarf_Die die; + + if (from_alt) { + /* No fallback: an alt offset here parses whatever is at it. */ + target = dwarf_getalt(dbg); + if (target == NULL) { + pr_debug("DWARF: no alt (dwz) debug file to resolve the type DIE at offset 0x%lx in\n", + (unsigned long)die_off); + return NULL; + } + } + + if (dwarf_offdie(target, die_off, &die) && dwarf_tag(&die) == die_tag) { + *die_mem = die; + return die_mem; + } + + pr_debug("DWARF: no DIE with tag %d at offset 0x%lx in the %s debug file\n", + die_tag, (unsigned long)die_off, from_alt ? "alt" : "main"); + return NULL; +} + /** * die_collect_global_vars - Save all global variables * @cu_die: a CU DIE @@ -1759,7 +1855,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg) * * Save all global variables in the @cu_die and save them to @var_types. * The @var_types is a singly-linked list containing type and location info. - * Actual type can be retrieved using dwarf_offdie() with 'die_off' later. + * Actual type can be retrieved using die_get_type_die() with 'die_off', + * 'die_tag' and 'from_alt' later. * * Callers should free @var_types. */ @@ -2180,3 +2277,109 @@ Dwarf_Die *die_deref_ptr_type(Dwarf_Die *ptr_die, int offset, return die_get_member_type(&type_die, offset, die_mem); } + +static bool is_flex_array_member(Dwarf_Die *mb_die) +{ + Dwarf_Die type_die; + Dwarf_Word size; + + /* get the type of the member */ + if (die_get_real_type(mb_die, &type_die) == NULL) + return false; + + if (dwarf_tag(&type_die) != DW_TAG_array_type) + return false; + + return dwarf_aggregate_size(&type_die, &size) < 0 || size == 0; +} + +#define MAX_FLEX_ARRAY_RECURSION 256 /* arbitrary */ + +static bool die_has_flex_array_recurse(Dwarf_Die *parent_die, int depth) +{ + Dwarf_Die die_mem, last_mb; + int tag = dwarf_tag(parent_die); + bool found = false; + Dwarf_Word loc, last_loc = 0; + + if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) + return false; + + /* prevent infinite recursion */ + if (depth > MAX_FLEX_ARRAY_RECURSION) + return false; + + if (dwarf_child(parent_die, &die_mem)) + return false; + + do { + if (dwarf_tag(&die_mem) != DW_TAG_member) + continue; + + if (tag == DW_TAG_union_type) { + if (is_flex_array_member(&die_mem)) + return true; + + if (die_get_real_type(&die_mem, &last_mb) && + die_has_flex_array_recurse(&last_mb, depth + 1)) + return true; + } + + if (tag == DW_TAG_structure_type) { + if (die_get_data_member_location(&die_mem, &loc) < 0) { + /* ignore bitfields */ + loc = 0; + } + + if (last_loc <= loc) { + memcpy(&last_mb, &die_mem, sizeof(last_mb)); + last_loc = loc; + } + } + + found = true; + } while (dwarf_siblingof(&die_mem, &die_mem) == 0); + + if (tag == DW_TAG_structure_type && found) { + if (is_flex_array_member(&last_mb)) + return true; + + if (die_get_real_type(&last_mb, &die_mem)) + return die_has_flex_array_recurse(&die_mem, depth + 1); + } + + return false; +} + +/** + * die_has_flex_array - Check if the given type has a flex-array at the end + * @type_die: a pointer to type DIE + * + * This function returns %true iff @type_die is a struct or union type and has + * an array at the end. Note that the flex-array has no element, it should have + * no size and the parent size doesn't include the flex-array. So it should + * locate at the offset of the parent size. + * + * For simplicity, it assumes the parent size of aligned with the flex-array. + */ +bool die_has_flex_array(Dwarf_Die *type_die) +{ + Dwarf_Die real_type; + + switch (dwarf_tag(type_die)) { + case DW_TAG_typedef: + case DW_TAG_const_type: + case DW_TAG_restrict_type: + case DW_TAG_volatile_type: + case DW_TAG_shared_type: + if (die_get_real_type(type_die, &real_type) == NULL) + return false; + + type_die = &real_type; + break; + default: + break; + } + + return die_has_flex_array_recurse(type_die, 0); +} diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h index 161f0bf980b6..6f9145510adc 100644 --- a/tools/perf/util/dwarf-aux.h +++ b/tools/perf/util/dwarf-aux.h @@ -152,6 +152,8 @@ int die_get_scopes(Dwarf_Die *cu_die, Dwarf_Addr pc, Dwarf_Die **scopes); struct die_var_type { struct die_var_type *next; u64 die_off; + int die_tag; + bool from_alt; /* die_off is relative to the alt (dwz) file */ u64 addr; u64 end; /* end address of location range */ int reg; @@ -183,10 +185,24 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr, /* Save all variables and parameters in this scope */ void die_collect_vars(Dwarf_Die *sc_die, struct die_var_type **var_types); +/* + * Get the type DIE saved by die_collect_vars()/die_collect_global_vars(): + * @from_alt says which file the offset must be resolved in, @die_tag is + * a sanity check. + */ +Dwarf_Die *die_get_type_die(Dwarf *dbg, u64 die_off, int die_tag, bool from_alt, + Dwarf_Die *die_mem); + +/* Whether two DIEs live in the same debug file */ +bool die_same_file(Dwarf_Die *die_a, Dwarf_Die *die_b); + /* Save all global variables in this CU */ void die_collect_global_vars(Dwarf_Die *cu_die, struct die_var_type **var_types); /* Get the frame base information from CFA */ int die_get_cfa(Dwarf *dwarf, u64 pc, int *preg, int *poffset); +/* Check whether given type has a flex array */ +bool die_has_flex_array(Dwarf_Die *parent_die); + #endif /* _DWARF_AUX_H */ diff --git a/tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c b/tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c index 593ca7d4fccc..425c8aa2e984 100644 --- a/tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c +++ b/tools/perf/util/dwarf-regs-arch/dwarf-regs-arm64.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include +#include #include #include "../../../arch/arm64/include/uapi/asm/perf_regs.h" @@ -10,3 +12,26 @@ int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum) return perf_regnum; } + +int __get_dwarf_regnum_arm64(const char *name) +{ + int reg; + + if (!strcmp(name, "sp")) + return 31; + + if (*name != 'x' && *name != 'w') + return -ENOENT; + + name++; + if (!isdigit(name[0])) + return -ENOENT; + else if (isdigit(name[1]) && name[2] == '\0') + reg = (name[0] - '0') * 10 + name[1] - '0'; + else if (name[1] == '\0') + reg = name[0] - '0'; + else + return -ENOENT; + + return reg >= 0 && reg <= 30 ? reg : -ENOENT; +} diff --git a/tools/perf/util/dwarf-regs-arch/dwarf-regs-csky.c b/tools/perf/util/dwarf-regs-arch/dwarf-regs-csky.c index cb44b774f8d9..7f976aa3cfa2 100644 --- a/tools/perf/util/dwarf-regs-arch/dwarf-regs-csky.c +++ b/tools/perf/util/dwarf-regs-arch/dwarf-regs-csky.c @@ -118,7 +118,7 @@ int __get_dwarf_regnum_for_perf_regnum_csky(int perf_regnum, unsigned int flags) if (flags & EF_CSKY_ABIV2) idx++; - if (perf_regnum < 0 || perf_regnum > (int)ARRAY_SIZE(dwarf_csky_regnums) || + if (perf_regnum < 0 || perf_regnum >= (int)ARRAY_SIZE(dwarf_csky_regnums) || dwarf_csky_regnums[perf_regnum][idx] == 0) return -ENOENT; diff --git a/tools/perf/util/dwarf-regs-arch/dwarf-regs-powerpc.c b/tools/perf/util/dwarf-regs-arch/dwarf-regs-powerpc.c index 51892a09725b..98e7f2b2791c 100644 --- a/tools/perf/util/dwarf-regs-arch/dwarf-regs-powerpc.c +++ b/tools/perf/util/dwarf-regs-arch/dwarf-regs-powerpc.c @@ -128,7 +128,7 @@ int __get_dwarf_regnum_for_perf_regnum_powerpc(int perf_regnum) if (perf_regnum == 0) return 0; - if (perf_regnum < 0 || perf_regnum > (int)ARRAY_SIZE(dwarf_powerpc_regnums) || + if (perf_regnum < 0 || perf_regnum >= (int)ARRAY_SIZE(dwarf_powerpc_regnums) || dwarf_powerpc_regnums[perf_regnum] == 0) return -ENOENT; diff --git a/tools/perf/util/dwarf-regs-arch/dwarf-regs-s390.c b/tools/perf/util/dwarf-regs-arch/dwarf-regs-s390.c index 310a37451bdc..9cef846ef9c0 100644 --- a/tools/perf/util/dwarf-regs-arch/dwarf-regs-s390.c +++ b/tools/perf/util/dwarf-regs-arch/dwarf-regs-s390.c @@ -45,7 +45,7 @@ int __get_dwarf_regnum_for_perf_regnum_s390(int perf_regnum) if (perf_regnum == 0) return 0; - if (perf_regnum < 0 || perf_regnum > (int)ARRAY_SIZE(dwarf_s390_regnums) || + if (perf_regnum < 0 || perf_regnum >= (int)ARRAY_SIZE(dwarf_s390_regnums) || dwarf_s390_regnums[perf_regnum] == 0) return -ENOENT; diff --git a/tools/perf/util/dwarf-regs-arch/dwarf-regs-x86.c b/tools/perf/util/dwarf-regs-arch/dwarf-regs-x86.c index cadef120aeb4..bb17d779036a 100644 --- a/tools/perf/util/dwarf-regs-arch/dwarf-regs-x86.c +++ b/tools/perf/util/dwarf-regs-arch/dwarf-regs-x86.c @@ -90,22 +90,22 @@ static const struct dwarf_regs_idx x86_64_regidx_table[] = { { "r14", 14 }, { "r14d", 14 }, { "r14w", 14 }, { "r14b", 14 }, { "r15", 15 }, { "r15d", 15 }, { "r15w", 15 }, { "r15b", 15 }, // 16 - Return Address RA - { "xmm0", 17}, - { "xmm1", 18}, - { "xmm2", 19}, - { "xmm3", 20}, - { "xmm4", 21}, - { "xmm5", 22}, - { "xmm6", 23}, - { "xmm7", 24}, - { "xmm8", 25}, - { "xmm9", 26}, - { "xmm10", 27}, - { "xmm11", 28}, - { "xmm12", 29}, - { "xmm13", 30}, - { "xmm14", 31}, - { "xmm15", 32}, + { "zmm0", 17 }, { "ymm0", 17 }, { "xmm0", 17 }, + { "zmm1", 18 }, { "ymm1", 18 }, { "xmm1", 18 }, + { "zmm2", 19 }, { "ymm2", 19 }, { "xmm2", 19 }, + { "zmm3", 20 }, { "ymm3", 20 }, { "xmm3", 20 }, + { "zmm4", 21 }, { "ymm4", 21 }, { "xmm4", 21 }, + { "zmm5", 22 }, { "ymm5", 22 }, { "xmm5", 22 }, + { "zmm6", 23 }, { "ymm6", 23 }, { "xmm6", 23 }, + { "zmm7", 24 }, { "ymm7", 24 }, { "xmm7", 24 }, + { "zmm8", 25 }, { "ymm8", 25 }, { "xmm8", 25 }, + { "zmm9", 26 }, { "ymm9", 26 }, { "xmm9", 26 }, + { "zmm10", 27 }, { "ymm10", 27 }, { "xmm10", 27 }, + { "zmm11", 28 }, { "ymm11", 28 }, { "xmm11", 28 }, + { "zmm12", 29 }, { "ymm12", 29 }, { "xmm12", 29 }, + { "zmm13", 30 }, { "ymm13", 30 }, { "xmm13", 30 }, + { "zmm14", 31 }, { "ymm14", 31 }, { "xmm14", 31 }, + { "zmm15", 32 }, { "ymm15", 32 }, { "xmm15", 32 }, { "st0", 33}, { "st1", 34}, { "st2", 35}, @@ -129,7 +129,7 @@ static const struct dwarf_regs_idx x86_64_regidx_table[] = { { "ds", 53}, { "fs", 54}, { "gs", 55}, - // 56-47 - reserved + // 56-57 - reserved { "fs.base", 58}, { "gs.base", 59}, // 60-61 - reserved @@ -138,6 +138,49 @@ static const struct dwarf_regs_idx x86_64_regidx_table[] = { { "mxcsr", 64}, // 128-bit Media Control and Status { "fcw", 65}, // x87 Control Word { "fsw", 66}, // x87 Status Word + // 67-82 - Upper Vector Registers 16–31 + { "zmm16", 67 }, { "ymm16", 67 }, { "xmm16", 67 }, + { "zmm17", 68 }, { "ymm17", 68 }, { "xmm17", 68 }, + { "zmm18", 69 }, { "ymm18", 69 }, { "xmm18", 69 }, + { "zmm19", 70 }, { "ymm19", 70 }, { "xmm19", 70 }, + { "zmm20", 71 }, { "ymm20", 71 }, { "xmm20", 71 }, + { "zmm21", 72 }, { "ymm21", 72 }, { "xmm21", 72 }, + { "zmm22", 73 }, { "ymm22", 73 }, { "xmm22", 73 }, + { "zmm23", 74 }, { "ymm23", 74 }, { "xmm23", 74 }, + { "zmm24", 75 }, { "ymm24", 75 }, { "xmm24", 75 }, + { "zmm25", 76 }, { "ymm25", 76 }, { "xmm25", 76 }, + { "zmm26", 77 }, { "ymm26", 77 }, { "xmm26", 77 }, + { "zmm27", 78 }, { "ymm27", 78 }, { "xmm27", 78 }, + { "zmm28", 79 }, { "ymm28", 79 }, { "xmm28", 79 }, + { "zmm29", 80 }, { "ymm29", 80 }, { "xmm29", 80 }, + { "zmm30", 81 }, { "ymm30", 81 }, { "xmm30", 81 }, + { "zmm31", 82 }, { "ymm31", 82 }, { "xmm31", 82 }, + // 118-125 - Vector Mask Registers 0–7 + { "k0", 118 }, + { "k1", 119 }, + { "k2", 120 }, + { "k3", 121 }, + { "k4", 122 }, + { "k5", 123 }, + { "k6", 124 }, + { "k7", 125 }, + // 130-145 - APX Integer Registers 16-31 + { "r16", 130 }, { "r16d", 130 }, { "r16w", 130 }, { "r16b", 130 }, + { "r17", 131 }, { "r17d", 131 }, { "r17w", 131 }, { "r17b", 131 }, + { "r18", 132 }, { "r18d", 132 }, { "r18w", 132 }, { "r18b", 132 }, + { "r19", 133 }, { "r19d", 133 }, { "r19w", 133 }, { "r19b", 133 }, + { "r20", 134 }, { "r20d", 134 }, { "r20w", 134 }, { "r20b", 134 }, + { "r21", 135 }, { "r21d", 135 }, { "r21w", 135 }, { "r21b", 135 }, + { "r22", 136 }, { "r22d", 136 }, { "r22w", 136 }, { "r22b", 136 }, + { "r23", 137 }, { "r23d", 137 }, { "r23w", 137 }, { "r23b", 137 }, + { "r24", 138 }, { "r24d", 138 }, { "r24w", 138 }, { "r24b", 138 }, + { "r25", 139 }, { "r25d", 139 }, { "r25w", 139 }, { "r25b", 139 }, + { "r26", 140 }, { "r26d", 140 }, { "r26w", 140 }, { "r26b", 140 }, + { "r27", 141 }, { "r27d", 141 }, { "r27w", 141 }, { "r27b", 141 }, + { "r28", 142 }, { "r28d", 142 }, { "r28w", 142 }, { "r28b", 142 }, + { "r29", 143 }, { "r29d", 143 }, { "r29w", 143 }, { "r29b", 143 }, + { "r30", 144 }, { "r30d", 144 }, { "r30w", 144 }, { "r30b", 144 }, + { "r31", 145 }, { "r31d", 145 }, { "r31w", 145 }, { "r31b", 145 }, // End of regular dwarf registers. { "rip", DWARF_REG_PC }, { "eip", DWARF_REG_PC }, { "ip", DWARF_REG_PC }, }; @@ -197,14 +240,14 @@ int __get_dwarf_regnum_for_perf_regnum_i386(int perf_regnum) if (perf_regnum == 0) return 0; - if (perf_regnum < 0 || perf_regnum > (int)ARRAY_SIZE(dwarf_i386_regnums) || + if (perf_regnum < 0 || perf_regnum >= (int)ARRAY_SIZE(dwarf_i386_regnums) || dwarf_i386_regnums[perf_regnum] == 0) return -ENOENT; return dwarf_i386_regnums[perf_regnum]; } -int __get_dwarf_regnum_for_perf_regnum_x86_64(int perf_regnum) +int __get_dwarf_regnum_for_perf_regnum_x86_64(int perf_regnum, int abi) { static const int dwarf_x86_64_regnums[] = { [PERF_REG_X86_AX] = 0, @@ -248,13 +291,66 @@ int __get_dwarf_regnum_for_perf_regnum_x86_64(int perf_regnum) [PERF_REG_X86_XMM14] = 31, [PERF_REG_X86_XMM15] = 32, }; + static const int dwarf_x86_64_regnums_apx[] = { + [PERF_REG_X86_AX] = 0, + [PERF_REG_X86_BX] = 3, + [PERF_REG_X86_CX] = 2, + [PERF_REG_X86_DX] = 1, + [PERF_REG_X86_SI] = 4, + [PERF_REG_X86_DI] = 5, + [PERF_REG_X86_BP] = 6, + [PERF_REG_X86_SP] = 7, + [PERF_REG_X86_IP] = 16, + [PERF_REG_X86_FLAGS] = 49, + [PERF_REG_X86_CS] = 51, + [PERF_REG_X86_SS] = 52, + [PERF_REG_X86_DS] = 53, + [PERF_REG_X86_ES] = 50, + [PERF_REG_X86_FS] = 54, + [PERF_REG_X86_GS] = 55, + [PERF_REG_X86_R8] = 8, + [PERF_REG_X86_R9] = 9, + [PERF_REG_X86_R10] = 10, + [PERF_REG_X86_R11] = 11, + [PERF_REG_X86_R12] = 12, + [PERF_REG_X86_R13] = 13, + [PERF_REG_X86_R14] = 14, + [PERF_REG_X86_R15] = 15, + [PERF_REG_X86_R16] = 130, + [PERF_REG_X86_R17] = 131, + [PERF_REG_X86_R18] = 132, + [PERF_REG_X86_R19] = 133, + [PERF_REG_X86_R20] = 134, + [PERF_REG_X86_R21] = 135, + [PERF_REG_X86_R22] = 136, + [PERF_REG_X86_R23] = 137, + [PERF_REG_X86_R24] = 138, + [PERF_REG_X86_R25] = 139, + [PERF_REG_X86_R26] = 140, + [PERF_REG_X86_R27] = 141, + [PERF_REG_X86_R28] = 142, + [PERF_REG_X86_R29] = 143, + [PERF_REG_X86_R30] = 144, + [PERF_REG_X86_R31] = 145, + }; if (perf_regnum == 0) return 0; - if (perf_regnum < 0 || perf_regnum > (int)ARRAY_SIZE(dwarf_x86_64_regnums) || - dwarf_x86_64_regnums[perf_regnum] == 0) + if (perf_regnum < 0) return -ENOENT; - return dwarf_x86_64_regnums[perf_regnum]; + if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD) && + (perf_regnum >= (int)ARRAY_SIZE(dwarf_x86_64_regnums) || + dwarf_x86_64_regnums[perf_regnum] == 0)) + return -ENOENT; + + if ((abi & PERF_SAMPLE_REGS_ABI_SIMD) && + (perf_regnum >= (int)ARRAY_SIZE(dwarf_x86_64_regnums_apx) || + dwarf_x86_64_regnums_apx[perf_regnum] == 0)) + return -ENOENT; + + return abi & PERF_SAMPLE_REGS_ABI_SIMD ? + dwarf_x86_64_regnums_apx[perf_regnum] : + dwarf_x86_64_regnums[perf_regnum]; } diff --git a/tools/perf/util/dwarf-regs.c b/tools/perf/util/dwarf-regs.c index 797f455eba0d..09aca6304002 100644 --- a/tools/perf/util/dwarf-regs.c +++ b/tools/perf/util/dwarf-regs.c @@ -114,7 +114,7 @@ int get_dwarf_regnum(const char *name, unsigned int machine, unsigned int flags) reg = _get_dwarf_regnum(arm_regstr_tbl, name); break; case EM_AARCH64: - reg = _get_dwarf_regnum(aarch64_regstr_tbl, name); + reg = __get_dwarf_regnum_arm64(name); break; case EM_CSKY: reg = __get_csky_regnum(name, flags); @@ -158,6 +158,10 @@ static int get_libdw_frame_nregs(unsigned int machine, unsigned int flags __mayb { switch (machine) { case EM_X86_64: + /* + * Since libdw doesn't support SIMD and APX eGPR register yet, + * still keep nregs to 17. + */ return 17; case EM_386: return 9; @@ -187,13 +191,14 @@ static int get_libdw_frame_nregs(unsigned int machine, unsigned int flags __mayb } int get_dwarf_regnum_for_perf_regnum(int perf_regnum, unsigned int machine, - unsigned int flags, bool only_libdw_supported) + unsigned int flags, + bool only_libdw_supported, int abi) { int reg; switch (machine) { case EM_X86_64: - reg = __get_dwarf_regnum_for_perf_regnum_x86_64(perf_regnum); + reg = __get_dwarf_regnum_for_perf_regnum_x86_64(perf_regnum, abi); break; case EM_386: reg = __get_dwarf_regnum_for_perf_regnum_i386(perf_regnum); diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c index c0e2b9d5f0b2..50993b0dedc5 100644 --- a/tools/perf/util/env.c +++ b/tools/perf/util/env.c @@ -297,6 +297,7 @@ void perf_env__exit(struct perf_env *env) zfree(&env->pmu_caps[i].pmu_name); } zfree(&env->pmu_caps); + zfree(&env->memory_ranges); free_cpu_domain_info(env->cpu_domain, env->schedstat_version, env->nr_cpus_avail); } diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h index 7acca39b42ff..23d35425efe3 100644 --- a/tools/perf/util/env.h +++ b/tools/perf/util/env.h @@ -68,6 +68,14 @@ struct cpu_domain_map { struct domain_info **domains; }; +struct memory_range { + u64 base; + u64 length; + int node; + u8 local_region_id; + u8 remote_region_id; +}; + struct perf_env { char *hostname; char *os_release; @@ -100,6 +108,7 @@ struct perf_env { int nr_cpu_pmu_caps; int nr_hybrid_nodes; int nr_pmus_with_caps; + int nr_memory_ranges; char *cmdline; const char **cmdline_argv; char *sibling_cores; @@ -122,6 +131,7 @@ struct perf_env { unsigned long long memory_bsize; struct hybrid_node *hybrid_nodes; struct pmu_caps *pmu_caps; + struct memory_range *memory_ranges; #ifdef HAVE_LIBBPF_SUPPORT /* * bpf_info_lock protects bpf rbtrees. This is needed because the diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index c3d784727810..c2402e4791b6 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -7,6 +7,7 @@ */ #include "evlist.h" +#include #include #include #include @@ -53,6 +54,7 @@ #include "event.h" #include "evsel.h" #include "evsel_fprintf.h" +#include "hist.h" #include "intel-tpebs.h" #include "metricgroup.h" #include "mmap.h" @@ -125,12 +127,24 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call if (err) goto out_err; } else { + struct evsel *leader = NULL; + + /* Fallback for cross-platform file analysis missing the topology header */ while ((pmu = perf_pmus__scan_core(pmu)) != NULL) { snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name, can_profile_kernel ? "P" : "Pu"); err = parse_event(evlist, buf); if (err) goto out_err; + + if (!leader) { + leader = evlist__last(evlist); + } else { + struct evsel *last = evlist__last(evlist); + + if (last != leader) + last->first_wildcard_match = leader; + } } } @@ -148,6 +162,175 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call return NULL; } +/** + * evlist__hybrid_matches - find events that may be merged across core PMUs. + * @evlist: The evlist to check. + * @env: The perf_env containing the PMU mapping information, NULL for the + * current machine. + * @link: Should the matches be recorded in first_wildcard_match? + * + * perf record, top, etc. set first_wildcard_match in the event parsing. The + * perf.data case (e.g. perf report) recomputes the first_wildcard_match using + * string matches for core events on hybrid systems. + */ +static bool evlist__hybrid_matches(struct evlist *evlist, struct perf_env *env, bool link) +{ + struct evsel *pos; + unsigned int nr = 0; + bool found = false; + + evlist__for_each_entry(evlist, pos) { + if (evsel__is_dummy_event(pos)) + continue; + + if (pos->core.leader != &pos->core || pos->core.nr_members > 1) + return false; + + if (pos->first_wildcard_match) + found = true; + nr++; + } + + /* No events found. */ + if (nr <= 1) + return false; + + /* Found wildcard events from parsing. */ + if (found) + return true; + + /* Try to find matching core events and set the first_wildcard_match. */ + evlist__for_each_entry(evlist, pos) { + const char *pos_name; + const char *pos_match; + struct evsel *peer; + + if (evsel__is_dummy_event(pos) || pos->first_wildcard_match) + continue; + + pos_name = evsel__name(pos); + pos_match = pos_name ? strchr(pos_name, '/') : NULL; + + if (!pos_match) + continue; + + /* If evsel->core.is_pmu_core missing in report, fallback to perf_env */ + if (!evsel__is_hybrid(pos)) { + if (!is_pmu_core_len(env, pos_name, pos_match - pos_name)) + continue; + } + + peer = pos; + list_for_each_entry_continue(peer, &evlist__core(evlist)->entries, core.node) { + const char *peer_name; + const char *peer_match; + + if (evsel__is_dummy_event(peer) || peer->first_wildcard_match) + continue; + + peer_name = evsel__name(peer); + peer_match = peer_name ? strchr(peer_name, '/') : NULL; + if (!peer_match) + continue; + + if (!evsel__is_hybrid(peer)) { + if (!is_pmu_core_len(env, peer_name, + peer_match - peer_name)) + continue; + } + + if (strcmp(pos_match, peer_match)) + continue; + + found = true; + if (!link) { + /* Just a test, don't modify the evlist. */ + return true; + } + /* + * Keep looking so that all the events of this name are + * linked, there may be more than 2 core PMUs. + */ + peer->first_wildcard_match = pos; + } + } + return found; +} + +/** + * evlist__can_merge_hybrid - can events in the evlist be merged across core + * PMUs? The evlist isn't modified. + * @evlist: The evlist to check. + * @env: The perf_env containing the PMU mapping information, NULL for the + * current machine. + */ +bool evlist__can_merge_hybrid(struct evlist *evlist, struct perf_env *env) +{ + return evlist__hybrid_matches(evlist, env, /*link=*/false); +} + +/* + * evlist__merge_hybrid - group hybrid events logically together. + * @evlist: The evlist containing events to merge. + * @env: The perf_env containing the PMU mapping information, NULL for the + * current machine. + * + * Iterates through the evlist and merges associated hybrid events by assigning + * their first_wildcard_match as their core group leader, modifying their + * presentation for a single merged histogram view. + */ +void evlist__merge_hybrid(struct evlist *evlist, struct perf_env *env) +{ + struct list_head new_list; + struct evsel *member, *mtmp; + struct evsel *pos, *tmp; + int idx = 0; + + /* Compute first_wildcard_match for events that lack it, say from a file. */ + if (!evlist__hybrid_matches(evlist, env, /*link=*/true)) + return; + + evlist__for_each_entry_safe(evlist, tmp, pos) { + struct evsel *leader, *old_leader; + + if (evsel__is_dummy_event(pos)) + continue; + + if (!pos->first_wildcard_match) + continue; + + leader = evsel__leader(pos->first_wildcard_match); + old_leader = evsel__leader(pos); + if (old_leader == leader) + continue; + + if (old_leader != pos) + old_leader->core.nr_members--; + pos->core.leader = &leader->core; + pos->merged_hybrid_group = true; + /* Base is 1 to natively represent the leader */ + if (leader->core.nr_members == 0) + leader->core.nr_members = 1; + leader->core.nr_members++; + } + + INIT_LIST_HEAD(&new_list); + + while (!list_empty(&evlist__core(evlist)->entries)) { + pos = list_first_entry(&evlist__core(evlist)->entries, struct evsel, core.node); + list_move_tail(&pos->core.node, &new_list); + + list_for_each_entry_safe(member, mtmp, &evlist__core(evlist)->entries, core.node) { + if (member->core.leader == &pos->core) + list_move_tail(&member->core.node, &new_list); + } + } + list_splice_init(&new_list, &evlist__core(evlist)->entries); + + evlist__for_each_entry(evlist, pos) + pos->core.idx = idx++; +} + struct evlist *evlist__new_dummy(void) { struct evlist *evlist = evlist__new(); @@ -570,7 +753,9 @@ static bool evlist__is_enabled(struct evlist *evlist) struct evsel *pos; evlist__for_each_entry(evlist, pos) { - if (!evsel__is_group_leader(pos) || !pos->core.fd) + if (!pos->core.fd) + continue; + if (!evsel__is_group_leader(pos) && !pos->merged_hybrid_group) continue; /* If at least one event is enabled, evlist is enabled. */ if (!pos->disabled) @@ -584,14 +769,19 @@ static void __evlist__disable(struct evlist *evlist, char *evsel_name, bool excl struct evsel *pos, *member; struct evlist_cpu_iterator evlist_cpu_itr; bool has_imm = false; + bool match; /* Disable 'immediate' events last */ for (int imm = 0; imm <= 1; imm++) { evlist__for_each_cpu(evlist_cpu_itr, evlist) { pos = evlist_cpu_itr.evsel; - if (evsel__strcmp(pos, evsel_name)) + match = !evsel__strcmp(pos, evsel_name); + if (!match && pos->merged_hybrid_group && evsel__leader(pos)) + match = !evsel__strcmp(evsel__leader(pos), evsel_name); + if (!match) continue; - if (pos->disabled || !evsel__is_group_leader(pos) || !pos->core.fd) + if (pos->disabled || (!evsel__is_group_leader(pos) && + !pos->merged_hybrid_group) || !pos->core.fd) continue; if (excl_dummy && evsel__is_dummy_event(pos)) continue; @@ -606,9 +796,12 @@ static void __evlist__disable(struct evlist *evlist, char *evsel_name, bool excl } evlist__for_each_entry(evlist, pos) { - if (evsel__strcmp(pos, evsel_name)) + match = !evsel__strcmp(pos, evsel_name); + if (!match && pos->merged_hybrid_group && evsel__leader(pos)) + match = !evsel__strcmp(evsel__leader(pos), evsel_name); + if (!match) continue; - if (!evsel__is_group_leader(pos) || !pos->core.fd) + if ((!evsel__is_group_leader(pos) && !pos->merged_hybrid_group) || !pos->core.fd) continue; if (excl_dummy && evsel__is_dummy_event(pos)) continue; @@ -644,21 +837,28 @@ static void __evlist__enable(struct evlist *evlist, char *evsel_name, bool excl_ { struct evsel *pos, *member; struct evlist_cpu_iterator evlist_cpu_itr; + bool match; evlist__for_each_cpu(evlist_cpu_itr, evlist) { pos = evlist_cpu_itr.evsel; - if (evsel__strcmp(pos, evsel_name)) + match = !evsel__strcmp(pos, evsel_name); + if (!match && pos->merged_hybrid_group && evsel__leader(pos)) + match = !evsel__strcmp(evsel__leader(pos), evsel_name); + if (!match) continue; - if (!evsel__is_group_leader(pos) || !pos->core.fd) + if ((!evsel__is_group_leader(pos) && !pos->merged_hybrid_group) || !pos->core.fd) continue; if (excl_dummy && evsel__is_dummy_event(pos)) continue; evsel__enable_cpu(pos, evlist_cpu_itr.cpu_map_idx); } evlist__for_each_entry(evlist, pos) { - if (evsel__strcmp(pos, evsel_name)) + match = !evsel__strcmp(pos, evsel_name); + if (!match && pos->merged_hybrid_group && evsel__leader(pos)) + match = !evsel__strcmp(evsel__leader(pos), evsel_name); + if (!match) continue; - if (!evsel__is_group_leader(pos) || !pos->core.fd) + if ((!evsel__is_group_leader(pos) && !pos->merged_hybrid_group) || !pos->core.fd) continue; if (excl_dummy && evsel__is_dummy_event(pos)) continue; @@ -773,13 +973,15 @@ static int evlist__event2id(struct evlist *evlist, union perf_event *event, u64 const __u64 *array = event->sample.array; ssize_t n; - n = (event->header.size - sizeof(event->header)) >> 3; - if (event->header.type == PERF_RECORD_SAMPLE) { + n = (event->header.size - sizeof(event->header)) >> 3; if (evlist__id_pos(evlist) >= n) return -1; *id = array[evlist__id_pos(evlist)]; } else { + u16 size = evsel__event_size(evlist__first(evlist), event); + + n = (size - sizeof(event->header)) >> 3; if (evlist__is_pos(evlist) > n) return -1; n -= evlist__is_pos(evlist); @@ -1232,8 +1434,12 @@ int evlist__set_tp_filter(struct evlist *evlist, const char *filter) struct evsel *evsel; int err = 0; + /* + * The only caller that passes NULL is evlist__set_tp_filter_pids(), + * where it means asprintf__tp_filter_pids() failed to allocate. + */ if (filter == NULL) - return -1; + return -ENOMEM; evlist__for_each_entry(evlist, evsel) { if (evsel->core.attr.type != PERF_TYPE_TRACEPOINT) @@ -1252,8 +1458,12 @@ int evlist__append_tp_filter(struct evlist *evlist, const char *filter) struct evsel *evsel; int err = 0; + /* + * As above, a NULL filter is asprintf__tp_filter_pids() having failed + * to allocate in evlist__append_tp_filter_pids(). + */ if (filter == NULL) - return -1; + return -ENOMEM; evlist__for_each_entry(evlist, evsel) { if (evsel->core.attr.type != PERF_TYPE_TRACEPOINT) diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index 838e263b76f3..0f380421e03d 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -331,6 +331,9 @@ static inline void evlist__set_selected(struct evlist *evlist, struct evsel *evs struct evlist *evlist__new(void); struct evlist *evlist__new_default(const struct target *target, bool sample_callchains); +struct perf_env; +bool evlist__can_merge_hybrid(struct evlist *evlist, struct perf_env *env); +void evlist__merge_hybrid(struct evlist *evlist, struct perf_env *env); struct evlist *evlist__new_dummy(void); struct evlist *evlist__get(struct evlist *evlist); void evlist__put(struct evlist *evlist); diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index d4cb455f4a7d..d060fca8090f 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -14,9 +14,12 @@ #include "evsel.h" #include +#include #include +#include #include #include +#include #include #include @@ -31,6 +34,7 @@ #include #include #include +#include #include #include @@ -551,6 +555,7 @@ struct evsel *evsel__clone(struct evsel *orig) evsel->core.system_wide = orig->core.system_wide; evsel->core.requires_cpu = orig->core.requires_cpu; evsel->core.is_pmu_core = orig->core.is_pmu_core; + evsel->core.is_pmu_uncore = orig->core.is_pmu_uncore; if (orig->name) { evsel->name = strdup(orig->name); @@ -716,10 +721,7 @@ struct tep_event *evsel__tp_format(struct evsel *evsel) else tp_format = trace_event__tp_format(evsel->tp_sys, evsel->tp_name); - if (IS_ERR(tp_format)) { - int err = -PTR_ERR(tp_format); - - errno = err; + if (!tp_format) { pr_err("Error getting tracepoint format '%s': %m\n", evsel__name(evsel)); return NULL; @@ -1092,9 +1094,9 @@ static void __evsel__config_callchain(struct evsel *evsel, const struct record_o bool function = evsel__is_function_event(evsel); struct perf_event_attr *attr = &evsel->core.attr; - if (EM_HOST == EM_S390 && param->record_mode == CALLCHAIN_FP) { - pr_warning_once( - "Framepointer unwinding lacks kernel support. Use '--call-graph dwarf'\n"); + if (EM_HOST == EM_S390 && evsel->pmu && !perf_pmu__is_software(evsel->pmu)) { + pr_warning_once("Cannot use hardware PMU 'cpum_cf' / 'cpum_sf' event with callchain. " + "Use a software 'cpu-clock' / 'task-clock' event.\n"); } evsel__set_sample_bit(evsel, CALLCHAIN); @@ -1125,19 +1127,22 @@ static void __evsel__config_callchain(struct evsel *evsel, const struct record_o } if (param->record_mode == CALLCHAIN_DWARF) { + int abi = -1; /* -1 indicates only basic GPRs are needed. */ + if (!function) { uint16_t e_machine = evsel__e_machine(evsel, /*e_flags=*/NULL); evsel__set_sample_bit(evsel, REGS_USER); evsel__set_sample_bit(evsel, STACK_USER); if (opts->sample_user_regs && - DWARF_MINIMAL_REGS(e_machine) != perf_user_reg_mask(EM_HOST)) { + DWARF_MINIMAL_REGS(e_machine) != perf_user_reg_mask(EM_HOST, &abi)) { attr->sample_regs_user |= DWARF_MINIMAL_REGS(e_machine); pr_warning("WARNING: The use of --call-graph=dwarf may require all the user registers, " "specifying a subset with --user-regs may render DWARF unwinding unreliable, " "so the minimal registers set (IP, SP) is explicitly forced.\n"); } else { - attr->sample_regs_user |= perf_user_reg_mask(EM_HOST); + abi = -1; + attr->sample_regs_user |= perf_user_reg_mask(EM_HOST, &abi); } attr->sample_stack_user = param->dump_size; attr->exclude_callchain_user = 1; @@ -1663,12 +1668,38 @@ void evsel__config(struct evsel *evsel, const struct record_opts *opts, if (opts->sample_intr_regs && !evsel->no_aux_samples && !evsel__is_dummy_event(evsel)) { attr->sample_regs_intr = opts->sample_intr_regs; + attr->sample_simd_regs_enabled = + evsel__is_non_software_event(evsel) ? + !!opts->sample_simd_regs_enabled : 0; + evsel__set_sample_bit(evsel, REGS_INTR); + } + + if ((opts->sample_intr_vec_regs || opts->sample_intr_pred_regs) && + !evsel->no_aux_samples && !evsel__is_dummy_event(evsel)) { + attr->sample_simd_regs_enabled = !!opts->sample_simd_regs_enabled; + attr->sample_simd_vec_reg_intr = opts->sample_intr_vec_regs; + attr->sample_simd_vec_reg_qwords = opts->sample_vec_reg_qwords; + attr->sample_simd_pred_reg_intr = opts->sample_intr_pred_regs; + attr->sample_simd_pred_reg_qwords = opts->sample_pred_reg_qwords; evsel__set_sample_bit(evsel, REGS_INTR); } if (opts->sample_user_regs && !evsel->no_aux_samples && !evsel__is_dummy_event(evsel)) { attr->sample_regs_user |= opts->sample_user_regs; + attr->sample_simd_regs_enabled = + evsel__is_non_software_event(evsel) ? + !!opts->sample_simd_regs_enabled : 0; + evsel__set_sample_bit(evsel, REGS_USER); + } + + if ((opts->sample_user_vec_regs || opts->sample_user_pred_regs) && + !evsel->no_aux_samples && !evsel__is_dummy_event(evsel)) { + attr->sample_simd_regs_enabled = !!opts->sample_simd_regs_enabled; + attr->sample_simd_vec_reg_user = opts->sample_user_vec_regs; + attr->sample_simd_vec_reg_qwords = opts->sample_vec_reg_qwords; + attr->sample_simd_pred_reg_user = opts->sample_user_pred_regs; + attr->sample_simd_pred_reg_qwords = opts->sample_pred_reg_qwords; evsel__set_sample_bit(evsel, REGS_USER); } @@ -1839,7 +1870,7 @@ int evsel__set_filter(struct evsel *evsel, const char *filter) return 0; } - return -1; + return -ENOMEM; } static int evsel__append_filter(struct evsel *evsel, const char *fmt, const char *filter) @@ -1855,7 +1886,7 @@ static int evsel__append_filter(struct evsel *evsel, const char *fmt, const char return 0; } - return -1; + return -ENOMEM; } int evsel__append_tp_filter(struct evsel *evsel, const char *filter) @@ -2974,6 +3005,19 @@ static bool evsel__detect_missing_features(struct evsel *evsel, struct perf_cpu return false; } +static void evsel__close_fds(struct evsel *evsel, int start_cpu_map_idx, + int cpu_map_idx, int thread, int nthreads) +{ + do { + while (--thread >= 0) { + if (FD(evsel, cpu_map_idx, thread) >= 0) + close(FD(evsel, cpu_map_idx, thread)); + FD(evsel, cpu_map_idx, thread) = -1; + } + thread = nthreads; + } while (--cpu_map_idx >= start_cpu_map_idx); +} + static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, struct perf_thread_map *threads, int start_cpu_map_idx, int end_cpu_map_idx) @@ -2983,6 +3027,12 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, enum rlimit_action set_rlimit = NO_CHANGE; struct perf_cpu cpu; + /* + * A failed attempt being retried after evsel__fallback() would + * otherwise keep group members out of their group in get_group_fd(). + */ + evsel->supported = true; + if (evsel__is_retire_lat(evsel)) { err = evsel__tpebs_open(evsel); goto out; @@ -3067,7 +3117,11 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, bpf_counter__install_pe(evsel, idx, fd); - if (unlikely(test_attr__enabled())) { + /* + * Support probes aren't what was asked for, leave them + * out of the PERF_TEST_ATTR dump. + */ + if (unlikely(test_attr__enabled() && !evsel->support_probe)) { test_attr__open(&evsel->core.attr, pid, cpu, fd, group_fd, evsel->open_flags); } @@ -3109,8 +3163,14 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, if (err == -EMFILE && rlimit__increase_nofile(&set_rlimit)) goto retry_open; - if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu)) + if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu)) { + /* + * The restart opens the events again, so close what this + * attempt opened before its entries are overwritten. + */ + evsel__close_fds(evsel, start_cpu_map_idx, idx, thread, nthreads); goto fallback_missing_features; + } if (evsel__precise_ip_fallback(evsel)) goto retry_open; @@ -3120,14 +3180,7 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, threads->err_thread = thread; old_errno = errno; - do { - while (--thread >= 0) { - if (FD(evsel, idx, thread) >= 0) - close(FD(evsel, idx, thread)); - FD(evsel, idx, thread) = -1; - } - thread = nthreads; - } while (--idx >= 0); + evsel__close_fds(evsel, start_cpu_map_idx, idx, thread, nthreads); errno = old_errno; out: if (err) @@ -3185,7 +3238,7 @@ static int perf_evsel__parse_id_sample(const union perf_event *event, const __u64 *array = event->sample.array; bool swapped = evsel->needs_swap; union u64_swap u; - int i = ((event->header.size - sizeof(event->header)) / sizeof(u64)) - 1; + int i = ((evsel__event_size(evsel, event) - sizeof(event->header)) / sizeof(u64)) - 1; if (type & PERF_SAMPLE_IDENTIFIER) { if (i < 0) @@ -3584,7 +3637,10 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, } if (type & PERF_SAMPLE_RAW) { + const __u64 *raw; + OVERFLOW_CHECK_u64(array); + raw = array; u.val64 = *array; /* @@ -3600,16 +3656,14 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, } data->raw_size = u.val32[0]; - /* - * The raw data is aligned on 64bits including the - * u32 size, so it's safe to use mem_bswap_64. - */ - if (swapped) - mem_bswap_64((void *) array, data->raw_size); - array = (void *)array + sizeof(u32); - OVERFLOW_CHECK(array, data->raw_size, max_size); + if (swapped) { + /* mem_bswap_64() accesses complete 64-bit words. */ + sz = roundup((u64)data->raw_size, sizeof(u64)); + OVERFLOW_CHECK(raw, sz, max_size); + mem_bswap_64((void *)raw, data->raw_size); + } data->raw_data = (void *)array; array = (void *)array + data->raw_size; } @@ -3639,6 +3693,8 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, e = (struct branch_entry *)&data->branch_stack->hw_idx; } + OVERFLOW_CHECK(array, sz, max_size); + if (swapped) { /* * struct branch_flag does not have endian @@ -3654,7 +3710,6 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, e->flags.value = evsel__bitfield_swap_branch_flags(e->flags.value); } - OVERFLOW_CHECK(array, sz, max_size); array = (void *)array + sz; if (evsel__has_branch_counters(evsel)) { @@ -3681,6 +3736,43 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, regs->mask = mask; regs->regs = (u64 *)array; array = (void *)array + sz; + + if (regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + u64 attr_nr_vectors = + hweight64(evsel->core.attr.sample_simd_vec_reg_user); + u64 attr_vec_qwords = + evsel->core.attr.sample_simd_vec_reg_qwords; + u64 attr_nr_pred = + hweight32(evsel->core.attr.sample_simd_pred_reg_user); + u64 attr_pred_qwords = + evsel->core.attr.sample_simd_pred_reg_qwords; + + OVERFLOW_CHECK_u64(array); + regs->nr_vectors = *(u64 *)array; + array = (void *)array + sizeof(u64); + OVERFLOW_CHECK_u64(array); + regs->vector_qwords = *(u64 *)array; + array = (void *)array + sizeof(u64); + OVERFLOW_CHECK_u64(array); + regs->nr_pred = *(u64 *)array; + array = (void *)array + sizeof(u64); + OVERFLOW_CHECK_u64(array); + regs->pred_qwords = *(u64 *)array; + array = (void *)array + sizeof(u64); + + if (regs->nr_vectors > attr_nr_vectors || + regs->vector_qwords > attr_vec_qwords || + regs->nr_pred > attr_nr_pred || + regs->pred_qwords > attr_pred_qwords) + goto out_efault; + + sz = (regs->nr_vectors * regs->vector_qwords + + regs->nr_pred * regs->pred_qwords) * sizeof(u64); + OVERFLOW_CHECK(array, sz, max_size); + + regs->simd_data = (u64 *)array; + array = (void *)array + sz; + } } } @@ -3738,6 +3830,43 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event, regs->mask = mask; regs->regs = (u64 *)array; array = (void *)array + sz; + + if (regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + u64 attr_nr_vectors = + hweight64(evsel->core.attr.sample_simd_vec_reg_intr); + u64 attr_vec_qwords = + evsel->core.attr.sample_simd_vec_reg_qwords; + u64 attr_nr_pred = + hweight32(evsel->core.attr.sample_simd_pred_reg_intr); + u64 attr_pred_qwords = + evsel->core.attr.sample_simd_pred_reg_qwords; + + OVERFLOW_CHECK_u64(array); + regs->nr_vectors = *(u64 *)array; + array = (void *)array + sizeof(u64); + OVERFLOW_CHECK_u64(array); + regs->vector_qwords = *(u64 *)array; + array = (void *)array + sizeof(u64); + OVERFLOW_CHECK_u64(array); + regs->nr_pred = *(u64 *)array; + array = (void *)array + sizeof(u64); + OVERFLOW_CHECK_u64(array); + regs->pred_qwords = *(u64 *)array; + array = (void *)array + sizeof(u64); + + if (regs->nr_vectors > attr_nr_vectors || + regs->vector_qwords > attr_vec_qwords || + regs->nr_pred > attr_nr_pred || + regs->pred_qwords > attr_pred_qwords) + goto out_efault; + + sz = (regs->nr_vectors * regs->vector_qwords + + regs->nr_pred * regs->pred_qwords) * sizeof(u64); + OVERFLOW_CHECK(array, sz, max_size); + + regs->simd_data = (u64 *)array; + array = (void *)array + sz; + } } } @@ -3859,6 +3988,63 @@ u16 evsel__id_hdr_size(const struct evsel *evsel) return size; } +/* + * Prior to kernel fix, perf_event_ksymbol_output(), perf_event_bpf_output(), + * and perf_event_text_poke_output() in kernel/events/core.c did not save and + * restore event_id.header.size across perf_iterate_sb() iterations. When + * multiple perf_events had attr.ksymbol, attr.bpf_event, or attr.text_poke + * enabled, header.size was incremented by id_header_size for each matching + * event while only a single id_sample was written immediately after the event + * payload. Clamp the effective size used to locate the trailing id_sample to + * payload + id_hdr_size so events recorded on unpatched kernels can be parsed + * without reading uninitialized ring-buffer bytes. + */ +u16 evsel__event_size(const struct evsel *evsel, const union perf_event *event) +{ + u16 size = event->header.size; + u16 id_hdr_size; + size_t payload; + + if (!evsel->core.attr.sample_id_all) + return size; + + switch (event->header.type) { + case PERF_RECORD_KSYMBOL: { + const char *name = event->ksymbol.name; + size_t fixed = offsetof(struct perf_record_ksymbol, name); + size_t max_len, len; + + if (size <= fixed) + return size; + max_len = size - fixed; + len = strnlen(name, max_len); + if (len == max_len) + return size; + payload = fixed + PERF_ALIGN(len + 1, sizeof(u64)); + break; + } + case PERF_RECORD_BPF_EVENT: + payload = sizeof(struct perf_record_bpf_event); + break; + case PERF_RECORD_TEXT_POKE: { + size_t fixed = offsetof(struct perf_record_text_poke_event, bytes); + + if (size < fixed) + return size; + payload = PERF_ALIGN(fixed + (size_t)event->text_poke.old_len + + event->text_poke.new_len, sizeof(u64)); + break; + } + default: + return size; + } + + id_hdr_size = evsel__id_hdr_size(evsel); + if (payload + id_hdr_size < size) + return payload + id_hdr_size; + return size; +} + #ifdef HAVE_LIBTRACEEVENT struct tep_format_field *evsel__field(struct evsel *evsel, const char *name) { @@ -4248,8 +4434,11 @@ static bool find_process(const char *name) return ret ? false : true; } -static int dump_perf_event_processes(char *msg, size_t size) +static int dump_perf_event_processes(const struct evsel *evsel, + char *msg, size_t size) { + const struct perf_event_attr *failed_attr = &evsel->core.attr; + u32 target_pmu_type = evsel->pmu ? evsel->pmu->type : UINT32_MAX; DIR *proc_dir; struct dirent *proc_entry; int printed = 0; @@ -4280,6 +4469,15 @@ static int dump_perf_event_processes(char *msg, size_t size) continue; } while ((fd_entry = readdir(fd_dir)) != NULL) { + const char *target_lnk = "anon_inode:[perf_event]"; + size_t target_lnk_len = sizeof("anon_inode:[perf_event]") - 1; + char fdinfo_buf[1024]; + int fdinfo_fd; + ssize_t fdinfo_size; + char *line; + char *saveptr; + u32 perf_event_type = UINT32_MAX; + u32 pmu_type = UINT32_MAX; ssize_t link_size; if (fd_entry->d_type != DT_LNK) @@ -4288,12 +4486,51 @@ static int dump_perf_event_processes(char *msg, size_t size) if (link_size < 0) continue; /* Take care as readlink doesn't null terminate the string. */ - if (!strncmp(buf, "anon_inode:[perf_event]", link_size)) { + if (link_size != (ssize_t)target_lnk_len || + strncmp(buf, target_lnk, target_lnk_len)) + continue; + + /* Let's check the PMU type reserved by this process */ + scnprintf(buf, sizeof(buf), "%s/fdinfo/%s", + proc_entry->d_name, fd_entry->d_name); + fdinfo_fd = openat(dirfd(proc_dir), buf, O_RDONLY | O_NONBLOCK); + if (fdinfo_fd == -1) + continue; + fdinfo_size = read(fdinfo_fd, fdinfo_buf, sizeof(fdinfo_buf) - 1); + close(fdinfo_fd); + if (fdinfo_size < 0) + continue; + fdinfo_buf[fdinfo_size] = '\0'; + + line = strtok_r(fdinfo_buf, "\n", &saveptr); + while (line) { + if (sscanf(line, + "perf_event_attr.type:\t%u", + &perf_event_type) == 1) { + /* continue parsing */ + } else if (sscanf(line, + "pmu_type:\t%u", + &pmu_type) == 1) { + /* continue parsing */ + } + line = strtok_r(NULL, "\n", &saveptr); + } + + /* Report the process which reserves the conflicted PMU. */ + /* If fdinfo does not contain PMU type, report it too. */ + if (perf_event_type == failed_attr->type || + pmu_type == failed_attr->type || + (target_pmu_type != UINT32_MAX && + pmu_type == target_pmu_type) || + (perf_event_type == UINT32_MAX && + pmu_type == UINT32_MAX)) { int cmdline_fd; ssize_t cmdline_size; - scnprintf(buf, sizeof(buf), "%s/cmdline", proc_entry->d_name); - cmdline_fd = openat(dirfd(proc_dir), buf, O_RDONLY); + scnprintf(buf, sizeof(buf), + "%s/cmdline", + proc_entry->d_name); + cmdline_fd = openat(dirfd(proc_dir), buf, O_RDONLY | O_NONBLOCK); if (cmdline_fd == -1) continue; cmdline_size = read(cmdline_fd, buf, sizeof(buf) - 1); @@ -4304,10 +4541,12 @@ static int dump_perf_event_processes(char *msg, size_t size) for (ssize_t i = 0; i < cmdline_size; i++) { if (buf[i] == '\0') buf[i] = ' '; + else if (!isprint((unsigned char)buf[i])) + buf[i] = '.'; } - if (printed == 0) - printed += scnprintf(msg, size, "Possible processes:\n"); + printed += scnprintf(msg, size, + "Possible processes:\n"); printed += scnprintf(msg + printed, size - printed, "%s %s\n", proc_entry->d_name, buf); @@ -4429,7 +4668,9 @@ int evsel__open_strerror(struct evsel *evsel, struct target *target, msg, size, "The PMU %s counters are busy and in use by another process.\n", evsel->pmu ? evsel->pmu->name : ""); - return printed + dump_perf_event_processes(msg + printed, size - printed); + return printed + dump_perf_event_processes(evsel, + msg + printed, + size - printed); break; case EINVAL: if (evsel->core.attr.sample_type & PERF_SAMPLE_CODE_PAGE_SIZE && perf_missing_features.code_page_size) diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index d9ecc6628217..53f3641b66e1 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -53,6 +53,7 @@ struct evsel { int id_pos; int is_pos; unsigned int sample_size; + bool merged_hybrid_group; /* * These fields can be set in the parse-events code or similar. @@ -119,6 +120,7 @@ struct evsel { u64 db_id; bool uniquified_name; bool supported; + bool support_probe; /* Opened just to check for support. */ bool needs_swap; bool disabled; bool no_aux_samples; @@ -468,6 +470,7 @@ int evsel__parse_sample_timestamp(struct evsel *evsel, union perf_event *event, u64 *timestamp); u16 evsel__id_hdr_size(const struct evsel *evsel); +u16 evsel__event_size(const struct evsel *evsel, const union perf_event *event); static inline struct evsel *evsel__next(struct evsel *evsel) { @@ -587,6 +590,13 @@ static inline bool evsel__is_dummy_event(struct evsel *evsel) (evsel->core.attr.config == PERF_COUNT_SW_DUMMY); } +static inline bool evsel__is_non_software_event(struct evsel *evsel) +{ + return (evsel->core.attr.type != PERF_TYPE_SOFTWARE) && + (evsel->core.attr.type != PERF_TYPE_TRACEPOINT) && + (evsel->core.attr.type != PERF_TYPE_BREAKPOINT); +} + struct perf_session *evsel__session(struct evsel *evsel); struct perf_env *evsel__env(struct evsel *evsel); uint16_t evsel__e_machine(struct evsel *evsel, uint32_t *e_flags); diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c index 8aef3c7418f7..f1164c4666b1 100644 --- a/tools/perf/util/expr.c +++ b/tools/perf/util/expr.c @@ -431,7 +431,10 @@ double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx } pr_debug2("literal: %s = %f\n", literal, result); - return result; + if (!ctx->is_test) + return result; + else + return isnan(result) ? 1.0 : result; } /* Does the event 'id' parse? Determine via ctx->ids if possible. */ diff --git a/tools/perf/util/genelf_debug.c b/tools/perf/util/genelf_debug.c index 8588b3e35e00..7d9ef43aa6ac 100644 --- a/tools/perf/util/genelf_debug.c +++ b/tools/perf/util/genelf_debug.c @@ -12,6 +12,8 @@ */ #include #include +#include +#include #include #include #include @@ -303,11 +305,13 @@ static ubyte get_special_opcode(struct debug_entry *ent, { unsigned int temp; unsigned long delta_addr; + int lineno = get_unaligned(&ent->lineno); + uint64_t addr = get_unaligned(&ent->addr); /* * delta from line_base */ - temp = (ent->lineno - last_line) - default_debug_line_header.line_base; + temp = (lineno - last_line) - default_debug_line_header.line_base; if (temp >= default_debug_line_header.line_range) return 0; @@ -315,7 +319,7 @@ static ubyte get_special_opcode(struct debug_entry *ent, /* * delta of addresses */ - delta_addr = (ent->addr - last_vma) / default_debug_line_header.minimum_instruction_length; + delta_addr = (addr - last_vma) / default_debug_line_header.minimum_instruction_length; /* This is not sufficient to ensure opcode will be in [0-256] but * sufficient to ensure when summing with the delta lineno we will @@ -362,6 +366,8 @@ static void emit_lineno_info(struct buffer_ext *be, for (i = 0; i < nr_entry; i++, ent = debug_entry_next(ent)) { int need_copy = 0; ubyte special_opcode; + int lineno = get_unaligned(&ent->lineno); + uint64_t addr = get_unaligned(&ent->addr); /* * check if filename changed, if so add it @@ -376,24 +382,24 @@ static void emit_lineno_info(struct buffer_ext *be, special_opcode = get_special_opcode(ent, last_line, last_vma); if (special_opcode != 0) { - last_line = ent->lineno; - last_vma = ent->addr; + last_line = lineno; + last_vma = addr; emit_opcode(be, special_opcode); } else { /* * lines differ, emit line delta */ - if (last_line != ent->lineno) { - emit_advance_lineno(be, ent->lineno - last_line); - last_line = ent->lineno; + if (last_line != lineno) { + emit_advance_lineno(be, lineno - last_line); + last_line = lineno; need_copy = 1; } /* * addresses differ, emit address delta */ - if (last_vma != ent->addr) { - emit_advance_pc(be, ent->addr - last_vma); - last_vma = ent->addr; + if (last_vma != addr) { + emit_advance_pc(be, addr - last_vma); + last_vma = addr; need_copy = 1; } /* @@ -480,7 +486,7 @@ jit_process_debug_info(uint64_t code_addr, int i; for (i = 0; i < nr_debug_entries; i++) { - ent->addr = ent->addr - code_addr; + put_unaligned(get_unaligned(&ent->addr) - code_addr, &ent->addr); ent = debug_entry_next(ent); } add_compilation_unit(di, buffer_ext_size(dl)); diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 7db7da090a1e..9eec6deee190 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -1,65 +1,69 @@ // SPDX-License-Identifier: GPL-2.0 +#include "header.h" + #include #include #include -#include "string2.h" -#include -#include -#include -#include -#include #include #include -#include -#include -#include +#include +#include + +#include +#include #include +#include +#include +#include +#include #include #include -#include -#include -#include #include -#include -#ifdef HAVE_LIBBPF_SUPPORT -#include -#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include // reallocarray +#include "../perf.h" +#include "bpf-event.h" +#include "bpf-utils.h" +#include "build-id.h" +#include "cacheline.h" +#include "clockid.h" +#include "cpumap.h" +#include "cputopo.h" +#include "data.h" +#include "debug.h" #include "dso.h" #include "evlist.h" #include "evsel.h" -#include "util/evsel_fprintf.h" -#include "header.h" +#include "evsel_fprintf.h" #include "memswap.h" -#include "trace-event.h" -#include "session.h" -#include "symbol.h" -#include "debug.h" -#include "cpumap.h" #include "pmu.h" #include "pmus.h" -#include "vdso.h" +#include "session.h" #include "strbuf.h" -#include "build-id.h" -#include "data.h" -#include -#include -#include "asm/bug.h" -#include "tool.h" -#include "../perf.h" +#include "string2.h" +#include "symbol.h" #include "time-utils.h" +#include "tool.h" +#include "trace-event.h" #include "units.h" -#include "util/util.h" // perf_exe() -#include "cputopo.h" -#include "bpf-event.h" -#include "bpf-utils.h" -#include "clockid.h" -#include "cacheline.h" +#include "util.h" // perf_exe() +#include "vdso.h" -#include -#include +#ifdef HAVE_LIBBPF_SUPPORT +#include +#endif #ifdef HAVE_LIBTRACEEVENT #include @@ -92,6 +96,7 @@ #define MAX_PMU_CAPS 512 #define MAX_PMU_MAPPINGS 4096 #define MAX_SCHED_DOMAINS 64 +#define MAX_MEMORY_RANGES 256 /* * magic2 = "PERFILE2" @@ -381,8 +386,10 @@ static int do_read_bitmap(struct feat_fd *ff, unsigned long **pset, u64 *psize) static int write_tracing_data(struct feat_fd *ff, struct evlist *evlist __maybe_unused) { - if (WARN(ff->buf, "Error: calling %s in pipe-mode.\n", __func__)) + if (ff->buf) { + pr_warning("Error: calling %s in pipe-mode.\n", __func__); return -1; + } #ifdef HAVE_LIBTRACEEVENT return read_tracing_data(ff->fd, &evlist__core(evlist)->entries); @@ -403,8 +410,10 @@ static int write_build_id(struct feat_fd *ff, if (!perf_session__read_build_ids(session, true)) return -1; - if (WARN(ff->buf, "Error: calling %s in pipe-mode.\n", __func__)) + if (ff->buf) { + pr_warning("Error: calling %s in pipe-mode.\n", __func__); return -1; + } err = perf_session__write_buildid_table(session, ff); if (err < 0) { @@ -1010,8 +1019,10 @@ static int write_auxtrace(struct feat_fd *ff, struct perf_session *session; int err; - if (WARN(ff->buf, "Error: calling %s in pipe-mode.\n", __func__)) + if (ff->buf) { + pr_warning("Error: calling %s in pipe-mode.\n", __func__); return -1; + } session = container_of(ff->ph, struct perf_session, header); @@ -1105,9 +1116,10 @@ static int write_dir_format(struct feat_fd *ff, session = container_of(ff->ph, struct perf_session, header); data = session->data; - if (WARN_ON(!perf_data__is_dir(data))) + if (!perf_data__is_dir(data)) { + pr_warning("Expected data to be a directory\n"); return -1; - + } return do_write(ff, &data->dir.version, sizeof(data->dir.version)); } @@ -1264,7 +1276,11 @@ static int cpu_cache_level__read(struct cpu_cache_level *cache, u32 cpu, u16 lev return -1; cache->type[len] = 0; - cache->type = strim(cache->type); + { + char *trimmed = strim(cache->type); + + memmove(cache->type, trimmed, strlen(trimmed) + 1); + } scnprintf(file, PATH_MAX, "%s/size", path); if (sysfs__read_str(file, &cache->size, &len)) { @@ -1273,7 +1289,11 @@ static int cpu_cache_level__read(struct cpu_cache_level *cache, u32 cpu, u16 lev } cache->size[len] = 0; - cache->size = strim(cache->size); + { + char *trimmed = strim(cache->size); + + memmove(cache->size, trimmed, strlen(trimmed) + 1); + } scnprintf(file, PATH_MAX, "%s/shared_cpu_list", path); if (sysfs__read_str(file, &cache->map, &len)) { @@ -1283,7 +1303,11 @@ static int cpu_cache_level__read(struct cpu_cache_level *cache, u32 cpu, u16 lev } cache->map[len] = 0; - cache->map = strim(cache->map); + { + char *trimmed = strim(cache->map); + + memmove(cache->map, trimmed, strlen(trimmed) + 1); + } return 0; } @@ -1892,6 +1916,132 @@ static int write_cpu_domain_info(struct feat_fd *ff, return ret; } +static int memory_range__read(struct memory_range *range, const char *path) +{ + char buf[32]; + ssize_t n; + int dfd, fd, tmp, ret = -1; + + dfd = open(path, O_RDONLY | O_DIRECTORY); + if (dfd < 0) + return -1; + +#define _R(name, dst, conv) \ + fd = openat(dfd, name, O_RDONLY); \ + if (fd < 0) \ + goto out; \ + n = read(fd, buf, sizeof(buf) - 1); \ + close(fd); \ + if (n <= 0) \ + goto out; \ + buf[n] = '\0'; \ + dst = conv(buf, NULL, 0); + + _R("base", range->base, strtoull); + _R("length", range->length, strtoull); + _R("node", range->node, strtol); + _R("local_region_id", tmp, strtol); + if (tmp < 0 || tmp > UINT8_MAX) + goto out; + range->local_region_id = tmp; + _R("remote_region_id", tmp, strtol); + if (tmp < 0 || tmp > UINT8_MAX) + goto out; + range->remote_region_id = tmp; +#undef _R + + ret = 0; +out: + close(dfd); + return ret; +} + +static int memory_range__parse(struct memory_range **ranges) +{ + const char *sysfs = sysfs__mountpoint(); + int i, err, nr_memory_ranges = 0; + char path[PATH_MAX]; + struct stat st; + + if (!sysfs) + return 0; + + scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges", sysfs); + if (stat(path, &st)) + return 0; + + while (1) { + scnprintf(path, PATH_MAX, + "%s/firmware/acpi/memory_ranges/range%d", + sysfs, nr_memory_ranges); + if (stat(path, &st)) + break; + + nr_memory_ranges++; + } + + if (nr_memory_ranges == 0) + return 0; + + *ranges = calloc(nr_memory_ranges, sizeof(struct memory_range)); + if (!(*ranges)) + return -ENOMEM; + + for (i = 0; i < nr_memory_ranges; i++) { + struct memory_range range; + + scnprintf(path, PATH_MAX, + "%s/firmware/acpi/memory_ranges/range%d", sysfs, i); + err = memory_range__read(&range, path); + if (err < 0) + goto out_error; + + (*ranges)[i] = range; + } + + return nr_memory_ranges; + +out_error: + zfree(ranges); + return -1; +} + +static int write_memory_ranges(struct feat_fd *ff, + struct evlist *evlist __maybe_unused) +{ + struct memory_range *ranges = NULL; + int nr_memory_ranges = 0, ret; + + nr_memory_ranges = memory_range__parse(&ranges); + if (nr_memory_ranges < 0) + return nr_memory_ranges; + + ret = do_write(ff, &nr_memory_ranges, sizeof(nr_memory_ranges)); + if (ret < 0) + goto out; + + for (int i = 0; i < nr_memory_ranges; i++) { + ret = do_write(ff, &ranges[i].base, sizeof(u64)); + if (ret < 0) + goto out; + ret = do_write(ff, &ranges[i].length, sizeof(u64)); + if (ret < 0) + goto out; + ret = do_write(ff, &ranges[i].node, sizeof(u32)); + if (ret < 0) + goto out; + ret = do_write(ff, &ranges[i].local_region_id, sizeof(u8)); + if (ret < 0) + goto out; + ret = do_write(ff, &ranges[i].remote_region_id, sizeof(u8)); + if (ret < 0) + goto out; + } +out: + zfree(&ranges); + return ret; +} + static void print_hostname(struct feat_fd *ff, FILE *fp) { fprintf(fp, "# hostname : %s\n", ff->ph->env.hostname); @@ -2163,7 +2313,8 @@ static void free_event_desc(struct evsel *events) static bool perf_attr_check(struct perf_event_attr *attr) { - if (attr->__reserved_1 || attr->__reserved_2 || attr->__reserved_3) { + if (attr->__reserved_1 || attr->__reserved_2 || + attr->__reserved_3 || attr->__reserved_4) { pr_warning("Reserved bits are set unexpectedly. " "Please update perf tool.\n"); return false; @@ -2629,6 +2780,23 @@ static void print_cpu_domain_info(struct feat_fd *ff, FILE *fp) } } +static void print_memory_ranges(struct feat_fd *ff, FILE *fp) +{ + struct memory_range *ranges = ff->ph->env.memory_ranges; + int nr_memory_ranges = ff->ph->env.nr_memory_ranges; + int i; + + fprintf(fp, "# memory ranges (nr %d):\n", nr_memory_ranges); + + for (i = 0; i < nr_memory_ranges; i++) { + fprintf(fp, "# range%u: [0x%016" PRIx64 "-0x%016" PRIx64, + i, ranges[i].base, ranges[i].base + ranges[i].length - 1); + fprintf(fp, "], node = %d, local_region_id = %d, remote_region_id = %d\n", + ranges[i].node, ranges[i].local_region_id, + ranges[i].remote_region_id); + } +} + static int __event_process_build_id(struct perf_record_header_build_id *bev, char *filename, struct perf_session *session) @@ -3699,9 +3867,10 @@ static int process_dir_format(struct feat_fd *ff, session = container_of(ff->ph, struct perf_session, header); data = session->data; - if (WARN_ON(!perf_data__is_dir(data))) + if (!perf_data__is_dir(data)) { + pr_warning("Expected data to be a directory\n"); return -1; - + } return do_read_u64(ff, &data->dir.version); } @@ -3903,7 +4072,8 @@ static int process_compressed(struct feat_fd *ff, * checks decomp_len + sizeof(struct decomp) against SIZE_MAX * before allocating, which handles 32-bit safety. */ - if (env->comp_mmap_len < 4096 || env->comp_mmap_len % 4096) { + if (env->comp_mmap_len && + (env->comp_mmap_len < 4096 || env->comp_mmap_len % 4096)) { pr_err("Invalid HEADER_COMPRESSED: comp_mmap_len (%u) must be a 4K-aligned value >= 4096\n", env->comp_mmap_len); return -1; @@ -4202,6 +4372,65 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused return ret; } +static int process_memory_ranges(struct feat_fd *ff, void *data __maybe_unused) +{ + struct perf_env *env = &ff->ph->env; + struct memory_range *ranges, *r; + u32 nr_memory_ranges, i; + + if (do_read_u32(ff, &nr_memory_ranges)) + return -1; + + if (!nr_memory_ranges) { + pr_debug("memory ranges not available\n"); + return 0; + } + + /* + * According to version 1.1 of the ACPI MRRM table, the maximum + * number of memory regions can be at most 255. Do a sanity check + * here to guard against a malformed perf.data file. + */ + if (nr_memory_ranges > MAX_MEMORY_RANGES) { + pr_err("Invalid memory_ranges: nr_memory_ranges (%u) > %u\n", + nr_memory_ranges, MAX_MEMORY_RANGES); + return -1; + } + + if (ff->size < sizeof(u32) + nr_memory_ranges * (2 * sizeof(u64) + sizeof(u32) + 2 * sizeof(u8))) { + pr_err("Invalid HEADER_MEMORY_RANGES: section too small (%zu) for %u range entries\n", + ff->size, nr_memory_ranges); + return -1; + } + + ranges = calloc(nr_memory_ranges, sizeof(*ranges)); + if (!ranges) + return -1; + + for (i = 0; i < nr_memory_ranges; i++) { + r = &ranges[i]; + + if (do_read_u64(ff, &r->base)) + goto error; + if (do_read_u64(ff, &r->length)) + goto error; + if (do_read_u32(ff, (u32 *) &r->node)) + goto error; + if (__do_read(ff, &r->local_region_id, sizeof(u8))) + goto error; + if (__do_read(ff, &r->remote_region_id, sizeof(u8))) + goto error; + } + + env->memory_ranges = ranges; + env->nr_memory_ranges = nr_memory_ranges; + + return 0; +error: + zfree(&ranges); + return -1; +} + #define FEAT_OPR(n, func, __full_only) \ [HEADER_##n] = { \ .name = __stringify(n), \ @@ -4266,6 +4495,7 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = { FEAT_OPR(CPU_DOMAIN_INFO, cpu_domain_info, true), FEAT_OPR(E_MACHINE, e_machine, false), FEAT_OPR(CLN_SIZE, cln_size, false), + FEAT_OPR(MEMORY_RANGES, memory_ranges, false), }; struct header_print_data { @@ -4378,8 +4608,10 @@ static int do_write_feat(struct feat_fd *ff, int type, if (!feat_ops[type].write) return -1; - if (WARN(ff->buf, "Error: calling %s in pipe-mode.\n", __func__)) + if (ff->buf) { + pr_warning("Error: calling %s in pipe-mode.\n", __func__); return -1; + } (*p)->offset = lseek(ff->fd, 0, SEEK_CUR); diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index 5e03f884b7cc..765037762758 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -56,6 +56,7 @@ enum { HEADER_CPU_DOMAIN_INFO, HEADER_E_MACHINE, HEADER_CLN_SIZE, + HEADER_MEMORY_RANGES, HEADER_LAST_FEATURE, HEADER_FEAT_BITS = 256, }; diff --git a/tools/perf/util/help-unknown-cmd.c b/tools/perf/util/help-unknown-cmd.c index a0a46e34f8d1..64dbe6e514f5 100644 --- a/tools/perf/util/help-unknown-cmd.c +++ b/tools/perf/util/help-unknown-cmd.c @@ -1,13 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 -#include "cache.h" -#include "config.h" -#include #include #include -#include -#include "../builtin.h" -#include "levenshtein.h" +#include + #include +#include + +#include + +#include "../builtin.h" +#include "config.h" +#include "levenshtein.h" static int autocorrect; @@ -36,10 +39,14 @@ static int add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) if (nr > cmds->alloc) { /* Choose bigger one to alloc */ +#define alloc_nr(x) (((x) + 16) * 3 / 2) + if (alloc_nr(cmds->alloc) < nr) cmds->alloc = nr; else cmds->alloc = alloc_nr(cmds->alloc); + +#undef alloc_nr tmp = realloc(cmds->names, cmds->alloc * sizeof(*cmds->names)); if (!tmp) return -1; diff --git a/tools/perf/util/help-unknown-cmd.h b/tools/perf/util/help-unknown-cmd.h deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 443694926f1a..e75a4fcce975 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -409,7 +409,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) u64 prev_period = he->stat.period; u64 prev_latency = he->stat.latency; - if (prev_period == 0) + if (prev_period == 0 && + (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0)) return true; he_stat__decay(&he->stat); @@ -442,7 +443,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) } } - return he->stat.period == 0 && he->stat.latency == 0; + return he->stat.period == 0 && he->stat.latency == 0 && + (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0); } static void hists__delete_entry(struct hists *hists, struct hist_entry *he) diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index b830cbe7f95b..0d08011aebce 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -130,6 +130,7 @@ struct hists { struct hists_stats stats; u64 event_stream; u16 col_len[HISTC_NR_COLS]; + bool merge_entries; bool has_callchains; int socket_filter; struct perf_hpp_list *hpp_list; @@ -338,7 +339,13 @@ static inline struct hist_entry *hist_entry__next_pair(struct hist_entry *he) static inline void hist_entry__add_pair(struct hist_entry *pair, struct hist_entry *he) { - list_add_tail(&pair->pairs.node, &he->pairs.head); + struct list_head *pos; + + list_for_each(pos, &he->pairs.head) { + if (pos == &pair->pairs.node) + return; /* Already paired */ + } + list_move_tail(&pair->pairs.node, &he->pairs.head); } struct hist_entry *hists__add_entry(struct hists *hists, @@ -433,11 +440,17 @@ void hists__match(struct hists *leader, struct hists *other); int hists__link(struct hists *leader, struct hists *other); int hists__unlink(struct hists *hists); +float hist_entry__get_percent_limit_merged(struct hist_entry *he); +void evlist__merge_hists_hybrid(struct evlist *evlist, bool refresh); + static inline float hist_entry__get_percent_limit(struct hist_entry *he) { u64 period = he->stat.period; u64 total_period = hists__total_period(he->hists); + if (he->hists->merge_entries) + return hist_entry__get_percent_limit_merged(he); + if (unlikely(total_period == 0)) return 0; @@ -714,7 +727,6 @@ struct block_hist { #define NO_ADDR 0 #ifdef HAVE_SLANG_SUPPORT -#include "../ui/keysyms.h" void attr_to_script(char *buf, struct perf_event_attr *attr); int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms, @@ -787,11 +799,6 @@ static inline int block_hists_tui_browse(struct block_hist *bh __maybe_unused, { return 0; } - -#define K_LEFT -1000 -#define K_RIGHT -2000 -#define K_SWITCH_INPUT_DATA -3000 -#define K_RELOAD -4000 #endif unsigned int hists__sort_list_width(struct hists *hists); diff --git a/tools/perf/util/hwmon_pmu.c b/tools/perf/util/hwmon_pmu.c index ed544dca70c3..96aeb379e169 100644 --- a/tools/perf/util/hwmon_pmu.c +++ b/tools/perf/util/hwmon_pmu.c @@ -803,7 +803,7 @@ int evsel__hwmon_pmu_open(struct evsel *evsel, FD(evsel, idx, thread) = -1; } thread = nthreads; - } while (--idx >= 0); + } while (--idx >= start_cpu_map_idx); close(dir); return err; } diff --git a/tools/perf/util/include/dwarf-regs.h b/tools/perf/util/include/dwarf-regs.h index 46a764cf322f..1da915c2683a 100644 --- a/tools/perf/util/include/dwarf-regs.h +++ b/tools/perf/util/include/dwarf-regs.h @@ -103,8 +103,9 @@ int __get_csky_regnum(const char *name, unsigned int flags); int __get_dwarf_regnum_i386(const char *name); int __get_dwarf_regnum_x86_64(const char *name); int __get_dwarf_regnum_for_perf_regnum_i386(int perf_regnum); -int __get_dwarf_regnum_for_perf_regnum_x86_64(int perf_regnum); +int __get_dwarf_regnum_for_perf_regnum_x86_64(int perf_regnum, int abi); +int __get_dwarf_regnum_arm64(const char *name); int __get_dwarf_regnum_for_perf_regnum_arm(int perf_regnum); int __get_dwarf_regnum_for_perf_regnum_arm64(int perf_regnum); @@ -125,8 +126,9 @@ int get_dwarf_regnum(const char *name, unsigned int machine, unsigned int flags) /* * get_dwarf_regnum - Returns DWARF regnum from perf register number. */ -int get_dwarf_regnum_for_perf_regnum(int perf_regnum, unsigned int machine, unsigned int flags, - bool only_libdw_supported); +int get_dwarf_regnum_for_perf_regnum(int perf_regnum, unsigned int machine, + unsigned int flags, + bool only_libdw_supported, int abi); void get_powerpc_regs(u32 raw_insn, int is_source, struct annotated_op_loc *op_loc); diff --git a/tools/perf/util/intel-bts.c b/tools/perf/util/intel-bts.c index 02df3e460489..4c9cca3acb6c 100644 --- a/tools/perf/util/intel-bts.c +++ b/tools/perf/util/intel-bts.c @@ -877,7 +877,7 @@ int intel_bts_process_auxtrace_info(union perf_event *event, bts->synth_opts = *session->itrace_synth_opts; } else { itrace_synth_opts__set_default(&bts->synth_opts, - session->itrace_synth_opts->default_no_sample); + session->itrace_synth_opts->default_no_sample, false); bts->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack; } diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c index 9d6628169fd9..8c21c9f52d57 100644 --- a/tools/perf/util/intel-pt.c +++ b/tools/perf/util/intel-pt.c @@ -4463,7 +4463,8 @@ int intel_pt_process_auxtrace_info(union perf_event *event, } else { struct itrace_synth_opts *opts = session->itrace_synth_opts; - itrace_synth_opts__set_default(&pt->synth_opts, opts->default_no_sample); + itrace_synth_opts__set_default(&pt->synth_opts, + opts->default_no_sample, false); if (!opts->default_no_sample && !opts->inject) { pt->synth_opts.branches = false; pt->synth_opts.callchain = true; diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c index 83005b30b9bf..45a05316b3ca 100644 --- a/tools/perf/util/jitdump.c +++ b/tools/perf/util/jitdump.c @@ -9,10 +9,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include "event.h" #include "debug.h" @@ -117,6 +120,8 @@ jit_close(struct jit_buf_desc *jd) funlockfile(jd->in); fclose(jd->in); jd->in = NULL; + zfree(&jd->debug_data); + zfree(&jd->unwinding_data); } static int @@ -143,6 +148,7 @@ jit_open(struct jit_buf_desc *jd, const char *name) ssize_t bs, bsz = 0; void *n, *buf = NULL; int ret, retval = -1; + char *dname; nsinfo__mountns_enter(jd->nsi, &nsc); jd->in = fopen(name, "r"); @@ -154,7 +160,7 @@ jit_open(struct jit_buf_desc *jd, const char *name) buf = malloc(bsz); if (!buf) - goto error; + goto error_noflock; /* * protect from writer modifying the file while we are reading it @@ -224,10 +230,12 @@ jit_open(struct jit_buf_desc *jd, const char *name) n = realloc(buf, bs); if (!n) goto error; - bsz = bs; buf = n; - /* read extra we do not know about */ - ret = fread(buf, bs - bsz, 1, jd->in); + bsz = bs; + } + if (bs > 0) { + /* consume extended header bytes from the stream */ + ret = fread(buf, bs, 1, jd->in); if (ret != 1) goto error; } @@ -236,13 +244,16 @@ jit_open(struct jit_buf_desc *jd, const char *name) */ strncpy(jd->dir, name, PATH_MAX - 1); jd->dir[PATH_MAX - 1] = '\0'; - dirname(jd->dir); + dname = dirname(jd->dir); + if (dname != jd->dir) + strlcpy(jd->dir, dname, sizeof(jd->dir)); free(buf); return 0; error: - free(buf); funlockfile(jd->in); +error_noflock: + free(buf); fclose(jd->in); return retval; } @@ -315,14 +326,37 @@ jit_get_next_entry(struct jit_buf_desc *jd) switch(id) { case JIT_CODE_DEBUG_INFO: if (jd->needs_bswap) { + void *end = (void *)jr + jr->prefix.total_size; + struct debug_entry *ent; uint64_t n; + jr->info.code_addr = bswap_64(jr->info.code_addr); jr->info.nr_entry = bswap_64(jr->info.nr_entry); - for (n = 0 ; n < jr->info.nr_entry; n++) { - jr->info.entries[n].addr = bswap_64(jr->info.entries[n].addr); - jr->info.entries[n].lineno = bswap_32(jr->info.entries[n].lineno); - jr->info.entries[n].discrim = bswap_32(jr->info.entries[n].discrim); + + /* + * debug_entry has a variable-length name[], so array + * indexing would compute wrong offsets — use + * debug_entry_next() and bounds-check each entry. + */ + ent = &jr->info.entries[0]; + for (n = 0; n < jr->info.nr_entry; n++) { + if ((void *)ent + sizeof(*ent) > end) + break; + /* name must be NUL-terminated within the record */ + if (!memchr(ent->name, '\0', (char *)end - ent->name)) + break; + /* + * debug entries are packed with a variable-length + * name[], so entries after the first may be + * unaligned: byte-swap via unaligned-safe accessors. + */ + put_unaligned(bswap_64(get_unaligned(&ent->addr)), &ent->addr); + put_unaligned(bswap_32(get_unaligned(&ent->lineno)), &ent->lineno); + put_unaligned(bswap_32(get_unaligned(&ent->discrim)), &ent->discrim); + ent = debug_entry_next(ent); } + /* clamp so downstream consumers don't overrun */ + jr->info.nr_entry = n; } break; case JIT_CODE_UNWINDING_INFO: @@ -435,12 +469,11 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) u16 idr_size; const char *sym; uint64_t count; - int ret, csize, usize; + int ret, csize; + uint64_t usize; pid_t nspid, pid, tid; - struct { - u32 pid, tid; - u64 time; - } *id; + uint64_t timestamp = 0; + unsigned long id; nspid = jr->load.pid; pid = jr_entry_pid(jd, jr); @@ -448,8 +481,25 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) csize = jr->load.code_size; usize = jd->unwinding_mapped_size; addr = jr->load.code_addr; + + /* code blob lives at the end of the record, validate it fits */ + if (jr->load.p.total_size < sizeof(jr->load) || + jr->load.code_size > jr->load.p.total_size - sizeof(jr->load) || + jr->load.code_size > INT_MAX) { + pr_warning("jitdump: invalid code_size %" PRIu64 " (total_size=%u) in code_load record\n", + (uint64_t)jr->load.code_size, jr->load.p.total_size); + return -1; + } + sym = (void *)((unsigned long)jr + sizeof(jr->load)); code = (unsigned long)jr + jr->load.p.total_size - csize; + + /* sym string lives between the load header and the code blob */ + if (!memchr(sym, '\0', code - (unsigned long)sym)) { + pr_warning("jitdump: unterminated symbol name in code_load record\n"); + return -1; + } + count = jr->load.code_index; idr_size = jd->machine->id_hdr_size; @@ -462,6 +512,9 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) jd->dir, nspid, count); + /* snprintf returns would-be length on truncation, clamp to buffer */ + if (size >= sizeof(event->mmap2.filename)) + size = sizeof(event->mmap2.filename) - 1; size++; /* for \0 */ @@ -475,7 +528,7 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) jd->nr_debug_entries = 0; } - if (jd->unwinding_data && jd->eh_frame_hdr_size) { + if (jd->unwinding_data) { zfree(&jd->unwinding_data); jd->eh_frame_hdr_size = 0; jd->unwinding_mapped_size = 0; @@ -496,7 +549,7 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) event->mmap2.pgoff = GEN_ELF_TEXT_OFFSET; event->mmap2.start = addr; - event->mmap2.len = usize ? ALIGN_8(csize) + usize : csize; + event->mmap2.len = usize ? ALIGN_8((uint64_t)csize) + usize : (uint64_t)csize; event->mmap2.pid = pid; event->mmap2.tid = tid; event->mmap2.ino = st.st_ino; @@ -506,13 +559,27 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) event->mmap2.flags = MAP_SHARED; event->mmap2.ino_generation = 1; - id = (void *)((unsigned long)event + event->mmap.header.size - idr_size); + /* + * The sample id fields are appended in the order accounted for by + * evsel__id_hdr_size(), skipping the ones not requested in + * sample_type, so they cannot be written through a fixed struct: + * with PERF_SAMPLE_TID unset, PERF_SAMPLE_TIME starts at offset 0 + * and idr_size is 8, so storing it at offset 8 runs past the end of + * the event allocation. + */ + id = (unsigned long)event + event->mmap.header.size - idr_size; if (jd->sample_type & PERF_SAMPLE_TID) { - id->pid = pid; - id->tid = tid; + struct { u32 pid, tid; } *id_tid = (void *)id; + + id_tid->pid = pid; + id_tid->tid = tid; + id += sizeof(u64); + } + if (jd->sample_type & PERF_SAMPLE_TIME) { + timestamp = convert_timestamp(jd, jr->load.p.timestamp); + *(u64 *)id = timestamp; + id += sizeof(u64); } - if (jd->sample_type & PERF_SAMPLE_TIME) - id->time = convert_timestamp(jd, jr->load.p.timestamp); /* * create pseudo sample to induce dso hit increment @@ -522,7 +589,7 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) sample.cpumode = PERF_RECORD_MISC_USER; sample.pid = pid; sample.tid = tid; - sample.time = id->time; + sample.time = timestamp; sample.ip = addr; ret = perf_event__process_mmap2(tool, event, &sample, jd->machine); @@ -565,14 +632,12 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr) char *filename; size_t size; struct stat st; - int usize; + uint64_t usize; u16 idr_size; int ret; pid_t nspid, pid, tid; - struct { - u32 pid, tid; - u64 time; - } *id; + uint64_t timestamp = 0; + unsigned long id; nspid = jr->load.pid; pid = jr_entry_pid(jd, jr); @@ -581,9 +646,10 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr) idr_size = jd->machine->id_hdr_size; /* - * +16 to account for sample_id_all (hack) + * Sample ID is written past the end of the mmap2 record; size + * the allocation to account for it instead of a hardcoded +16. */ - event = calloc(1, sizeof(*event) + 16); + event = calloc(1, sizeof(*event) + idr_size); if (!event) return -1; @@ -592,6 +658,9 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr) jd->dir, nspid, jr->move.code_index); + /* snprintf returns would-be length on truncation, clamp to buffer */ + if (size >= sizeof(event->mmap2.filename)) + size = sizeof(event->mmap2.filename) - 1; size++; /* for \0 */ @@ -617,13 +686,27 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr) event->mmap2.flags = MAP_SHARED; event->mmap2.ino_generation = 1; - id = (void *)((unsigned long)event + event->mmap.header.size - idr_size); + /* + * The sample id fields are appended in the order accounted for by + * evsel__id_hdr_size(), skipping the ones not requested in + * sample_type, so they cannot be written through a fixed struct: + * with PERF_SAMPLE_TID unset, PERF_SAMPLE_TIME starts at offset 0 + * and idr_size is 8, so storing it at offset 8 runs past the end of + * the event allocation. + */ + id = (unsigned long)event + event->mmap.header.size - idr_size; if (jd->sample_type & PERF_SAMPLE_TID) { - id->pid = pid; - id->tid = tid; + struct { u32 pid, tid; } *id_tid = (void *)id; + + id_tid->pid = pid; + id_tid->tid = tid; + id += sizeof(u64); + } + if (jd->sample_type & PERF_SAMPLE_TIME) { + timestamp = convert_timestamp(jd, jr->load.p.timestamp); + *(u64 *)id = timestamp; + id += sizeof(u64); } - if (jd->sample_type & PERF_SAMPLE_TIME) - id->time = convert_timestamp(jd, jr->load.p.timestamp); /* * create pseudo sample to induce dso hit increment @@ -633,7 +716,7 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr) sample.cpumode = PERF_RECORD_MISC_USER; sample.pid = pid; sample.tid = tid; - sample.time = id->time; + sample.time = timestamp; sample.ip = jr->move.new_code_addr; ret = perf_event__process_mmap2(tool, event, &sample, jd->machine); @@ -645,17 +728,24 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr) build_id__mark_dso_hit(tool, event, &sample, jd->machine); out: perf_sample__exit(&sample); + free(event); return ret; } static int jit_repipe_debug_info(struct jit_buf_desc *jd, union jr_entry *jr) { - void *data; - size_t sz; + struct debug_entry *ent; + void *data, *end; + size_t sz, valid; + uint64_t i; if (!(jd && jr)) return -1; + /* total_size must cover at least the fixed header */ + if (jr->prefix.total_size < sizeof(jr->info)) + return -1; + sz = jr->prefix.total_size - sizeof(jr->info); data = malloc(sz); if (!data) @@ -663,13 +753,29 @@ static int jit_repipe_debug_info(struct jit_buf_desc *jd, union jr_entry *jr) memcpy(data, &jr->info.entries, sz); + zfree(&jd->debug_data); jd->debug_data = data; /* - * we must use nr_entry instead of size here because - * we cannot distinguish actual entry from padding otherwise + * Clamp nr_debug_entries to entries that actually fit in the + * payload. The byte-swap path already does this for cross-endian + * files; validate on the native path too, since downstream + * jit_process_debug_info() iterates via debug_entry_next() which + * calls strlen() on each entry's name field. */ - jd->nr_debug_entries = jr->info.nr_entry; + end = data + sz; + ent = data; + valid = 0; + for (i = 0; i < jr->info.nr_entry; i++) { + if ((void *)ent + sizeof(*ent) > end) + break; + /* name must be NUL-terminated within the payload */ + if (!memchr(ent->name, '\0', (char *)end - ent->name)) + break; + ent = debug_entry_next(ent); + valid++; + } + jd->nr_debug_entries = valid; return 0; } @@ -683,7 +789,23 @@ jit_repipe_unwinding_info(struct jit_buf_desc *jd, union jr_entry *jr) if (!(jd && jr)) return -1; + /* total_size must cover at least the fixed header */ + if (jr->prefix.total_size < sizeof(jr->unwinding)) + return -1; + unwinding_data_size = jr->prefix.total_size - sizeof(jr->unwinding); + + /* + * Validate sizes before allocating — jit_add_eh_frame_info() + * computes unwinding_size - eh_frame_hdr_size and uses the + * result as a buffer length for libelf. + */ + if (jr->unwinding.unwinding_size > unwinding_data_size || + jr->unwinding.eh_frame_hdr_size > jr->unwinding.unwinding_size) { + pr_warning("jitdump: invalid unwinding sizes in unwinding_info record\n"); + return -1; + } + unwinding_data = malloc(unwinding_data_size); if (!unwinding_data) return -1; diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c index 33dc6158b2b1..992e2c2a7198 100644 --- a/tools/perf/util/libbfd.c +++ b/tools/perf/util/libbfd.c @@ -1,5 +1,19 @@ // SPDX-License-Identifier: GPL-2.0 #include "libbfd.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + #include "annotate.h" #include "bpf-event.h" #include "bpf-utils.h" @@ -11,15 +25,13 @@ #include "symbol.h" #include "symbol_conf.h" #include "util.h" -#include + #ifdef HAVE_LIBBPF_SUPPORT #include #include #include #endif -#include -#include -#include + #define PACKAGE "perf" #include @@ -39,13 +51,13 @@ struct a2l_data { asymbol **syms; }; -static bool perf_bfd_lock(void *bfd_mutex) +static bool perf_bfd_lock(void *bfd_mutex) NO_THREAD_SAFETY_ANALYSIS { mutex_lock(bfd_mutex); return true; } -static bool perf_bfd_unlock(void *bfd_mutex) +static bool perf_bfd_unlock(void *bfd_mutex) NO_THREAD_SAFETY_ANALYSIS { mutex_unlock(bfd_mutex); return true; @@ -72,6 +84,23 @@ static void ensure_bfd_init(void) pthread_once(&bfd_init_once, perf_bfd_init); } +/* + * Flags from libiberty's demangle.h. bfd.h declares bfd_demangle but not the + * flags to pass to it, and demangle.h isn't installed by every binutils + * package. + */ +#ifndef DMGL_PARAMS +#define DMGL_PARAMS (1 << 0) /* Include function arguments. */ +#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc. */ +#endif + +char *libbfd__demangle_sym(const char *str, bool params, bool modifiers) +{ + int flags = (params ? DMGL_PARAMS : 0) | (modifiers ? DMGL_ANSI : 0); + + return bfd_demangle(/*abfd=*/NULL, str, flags); +} + static int bfd_error(const char *string) { const char *errmsg; @@ -210,7 +239,7 @@ static int inline_list__append_dso_a2l(struct dso *dso, struct inline_node *node, struct symbol *sym) { - struct a2l_data *a2l = dso__a2l(dso); + struct a2l_data *a2l = dso__a2l_libbfd(dso); struct symbol *inline_sym = new_inline_sym(dso, sym, a2l->funcname); char *srcline = NULL; @@ -226,17 +255,22 @@ int libbfd__addr2line(const char *dso_name, u64 addr, struct symbol *sym) { int ret = 0; - struct a2l_data *a2l = dso__a2l(dso); + struct a2l_data *a2l; + + mutex_lock(dso__lock(dso)); + dso_name = dso__symsrc_filename(dso) ?: dso_name; + a2l = dso__a2l_libbfd(dso); if (!a2l) { a2l = addr2line_init(dso_name); - dso__set_a2l(dso, a2l); + dso__set_a2l_libbfd(dso, a2l); } if (a2l == NULL) { if (!symbol_conf.addr2line_disable_warn) pr_warning("addr2line_init failed for %s\n", dso_name); - return 0; + ret = -1; + goto out; } a2l->addr = addr; @@ -244,14 +278,19 @@ int libbfd__addr2line(const char *dso_name, u64 addr, bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l); - if (!a2l->found) - return 0; + if (!a2l->found) { + ret = 0; + goto out; + } if (unwind_inlines) { int cnt = 0; - if (node && inline_list__append_dso_a2l(dso, node, sym)) - return 0; + if (node && inline_list__append_dso_a2l(dso, node, sym)) { + inline_node__clear_frames(node); + ret = -ENOMEM; + goto out; + } while (bfd_find_inliner_info(a2l->abfd, &a2l->filename, &a2l->funcname, &a2l->line) && @@ -261,35 +300,48 @@ int libbfd__addr2line(const char *dso_name, u64 addr, a2l->filename = NULL; if (node != NULL) { - if (inline_list__append_dso_a2l(dso, node, sym)) - return 0; - // found at least one inline frame - ret = 1; + if (inline_list__append_dso_a2l(dso, node, sym)) { + inline_node__clear_frames(node); + ret = -ENOMEM; + goto out; + } } } } if (file) { *file = a2l->filename ? strdup(a2l->filename) : NULL; - ret = *file ? 1 : 0; + if (!*file) { + ret = a2l->filename ? -ENOMEM : 0; + goto out; + } } if (line) *line = a2l->line; + /* + * The address was found, report success so that the caller doesn't try + * another addr2line implementation that would append the inline frames + * above a second time. + */ + ret = 1; + +out: + mutex_unlock(dso__lock(dso)); return ret; } void dso__free_a2l_libbfd(struct dso *dso) { - struct a2l_data *a2l = dso__a2l(dso); + struct a2l_data *a2l = dso__a2l_libbfd(dso); if (!a2l) return; addr2line_cleanup(a2l); - dso__set_a2l(dso, NULL); + dso__set_a2l_libbfd(dso, NULL); } static int bfd_symbols__cmpvalue(const void *a, const void *b) @@ -411,7 +463,7 @@ int dso__load_bfd_symbols(struct dso *dso, const char *debugfile) symbols__fixup_duplicate(dso__symbols(dso)); dso__set_adjust_symbols(dso, true); - err = 0; + err = dso__has_symbols(dso); out_free: free(symbols); out_close: diff --git a/tools/perf/util/libbfd.h b/tools/perf/util/libbfd.h index 953886f3d62f..14171db8b112 100644 --- a/tools/perf/util/libbfd.h +++ b/tools/perf/util/libbfd.h @@ -31,6 +31,8 @@ int libbfd_filename__read_debuglink(const char *filename, char *debuglink, size_ int symbol__disassemble_bpf_libbfd(struct symbol *sym, struct annotate_args *args); +char *libbfd__demangle_sym(const char *str, bool params, bool modifiers); + #else // !defined(HAVE_LIBBFD_SUPPORT) #include "annotate.h" @@ -77,6 +79,13 @@ static inline int symbol__disassemble_bpf_libbfd(struct symbol *sym __always_unu return SYMBOL_ANNOTATE_ERRNO__NO_LIBOPCODES_FOR_BPF; } +static inline char *libbfd__demangle_sym(const char *str __always_unused, + bool params __always_unused, + bool modifiers __always_unused) +{ + return NULL; +} + #endif // defined(HAVE_LIBBFD_SUPPORT) #endif /* __PERF_LIBBFD_H */ diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c index 4ca7e7e4fbe9..e660623338d5 100644 --- a/tools/perf/util/libdw.c +++ b/tools/perf/util/libdw.c @@ -7,6 +7,7 @@ #include "callchain.h" #include #include +#include #include static const Dwfl_Callbacks offline_callbacks = { @@ -35,7 +36,7 @@ struct Dwfl *dso__libdw_dwfl(struct dso *dso) if (dwfl) return dwfl; - dso_name = dso__long_name(dso); + dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso); /* * Initialize Dwfl session. * We need to open the DSO file to report it to libdw. @@ -167,32 +168,55 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, struct dso *dso, bool unwind_inlines, struct inline_node *node, struct symbol *sym) { - Dwfl *dwfl = dso__libdw_dwfl(dso); + Dwfl *dwfl; Dwfl_Module *mod; Dwfl_Line *dwline; Dwarf_Addr bias; const char *src; int lineno = 0; + int ret = 0; - if (!dwfl) + if (!dso__has_srcline(dso)) return 0; + mutex_lock(dso__lock(dso)); + dwfl = dso__libdw_dwfl(dso); + if (!dwfl) { + ret = -1; + goto out; + } + mod = dwfl_addrmodule(dwfl, addr); - if (!mod) - return 0; + if (!mod) { + ret = 0; + goto out; + } /* * Get/ignore the dwarf information. Determine the bias, difference * between the regular ELF addr2line addresses and those to use with * libdw. */ - if (!dwfl_module_getdwarf(mod, &bias)) - return 0; + if (!dwfl_module_getdwarf(mod, &bias)) { + /* + * Free the Dwfl session so the mmap'd ELF binary is unmapped + * immediately, and mark the DSO as lacking srcline/DWARF info + * so neither libdw nor slower fallback backends (addr2line + * subprocess, libbfd) are repeatedly invoked on stripped DSOs. + */ + dso__free_libdw(dso); + dso__set_has_srcline(dso, false); + ret = 0; + goto out; + } /* Find source line information for the address. */ dwline = dwfl_module_getsrc(mod, addr + bias); - if (!dwline) - return 0; + if (!dwline) { + /* conclusively missing information -> abort fallbacks */ + ret = 0; + goto out; + } /* Get line information. */ src = dwfl_lineinfo(dwline, /*addr=*/NULL, &lineno, /*col=*/NULL, /*mtime=*/NULL, @@ -219,7 +243,8 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, free(*file); *file = NULL; } - return 0; + ret = -ENOMEM; + goto out; } /* Walk from the parent down to the leaf. */ @@ -235,8 +260,13 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, *file = NULL; } inline_node__clear_frames(node); - return 0; + ret = args.err; + goto out; } } - return 1; + ret = 1; + +out: + mutex_unlock(dso__lock(dso)); + return ret; } diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h index b12094737415..c8f319ce35df 100644 --- a/tools/perf/util/libdw.h +++ b/tools/perf/util/libdw.h @@ -23,7 +23,8 @@ struct symbol; * finds the source line information for the given address, and optionally * resolves inline function call chains. * - * Returns 1 on success (found), 0 on failure (not found). + * Returns 1 on success (found), 0 when DWARF/line info is conclusively + * missing, and < 0 on initialization or uncompiled error. */ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, struct dso *dso, @@ -47,7 +48,7 @@ static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_ struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused) { - return 0; + return -1; } static inline void dso__free_libdw(struct dso *dso __maybe_unused) diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp index 004081bd12c9..d990166100f0 100644 --- a/tools/perf/util/llvm-c-helpers.cpp +++ b/tools/perf/util/llvm-c-helpers.cpp @@ -50,20 +50,20 @@ static LLVMSymbolizer *get_symbolizer() return instance; } -/* Returns 0 on error, 1 on success. */ +/* Returns < 0 on error, 0 when line info is missing, 1 on success. */ static int extract_file_and_line(const DILineInfo &line_info, char **file, unsigned int *line) { - if (file) { - if (line_info.FileName == "") { - /* Match the convention of libbfd. */ + if (line_info.FileName == "") { + if (file) *file = nullptr; - } else { - /* The caller expects to get something it can free(). */ - *file = strdup(line_info.FileName.c_str()); - if (*file == nullptr) - return 0; - } + return 0; + } + if (file) { + /* The caller expects to get something it can free(). */ + *file = strdup(line_info.FileName.c_str()); + if (*file == nullptr) + return -ENOMEM; } if (line) *line = line_info.Line; @@ -86,30 +86,42 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected res_or_err = symbolizer->symbolizeInlinedCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } unsigned num_frames = res_or_err->getNumberOfFrames(); if (num_frames == 0) return 0; - if (extract_file_and_line(res_or_err->getFrame(0), - file, line) == 0) - return 0; + int ret = extract_file_and_line(res_or_err->getFrame(0), + file, line); + if (ret <= 0) + return ret; *inline_frames = (llvm_a2l_frame *)calloc( num_frames, sizeof(**inline_frames)); - if (*inline_frames == nullptr) - return 0; + if (*inline_frames == nullptr) { + if (file) + zfree(file); + return -ENOMEM; + } for (unsigned i = 0; i < num_frames; ++i) { const DILineInfo &src = res_or_err->getFrame(i); llvm_a2l_frame &dst = (*inline_frames)[i]; - if (src.FileName == "") - /* Match the convention of libbfd. */ - dst.filename = nullptr; - else - dst.filename = strdup(src.FileName.c_str()); + if (src.FileName == "") { + for (unsigned j = 0; j < i; ++j) { + zfree(&(*inline_frames)[j].filename); + zfree(&(*inline_frames)[j].funcname); + } + zfree(inline_frames); + if (file) + zfree(file); + return 0; + } + dst.filename = strdup(src.FileName.c_str()); dst.funcname = strdup(src.FunctionName.c_str()); dst.line = src.Line; @@ -120,7 +132,9 @@ int llvm_addr2line(const char *dso_name, u64 addr, zfree(&(*inline_frames)[j].funcname); } zfree(inline_frames); - return 0; + if (file) + zfree(file); + return -ENOMEM; } } @@ -131,8 +145,10 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected res_or_err = symbolizer->symbolizeCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } return extract_file_and_line(*res_or_err, file, line); } } diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c index a0deb742a733..0ecb1a53359e 100644 --- a/tools/perf/util/llvm.c +++ b/tools/perf/util/llvm.c @@ -9,8 +9,10 @@ #include "symbol.h" #include #include +#include #include #include +#include #ifdef HAVE_LIBLLVM_SUPPORT #include "llvm-c-helpers.h" @@ -42,8 +44,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused int num_frames = llvm_addr2line(dso_name, addr, file, line, node && unwind_inlines, &inline_frames); - if (num_frames == 0 || !inline_frames) { - /* Error, or we didn't want inlines. */ + if (num_frames <= 0 || !inline_frames) { + /* + * Error (< 0 -> iterate fallbacks), missing line info (0), or + * we resolved lines without wanting inlines (1). + */ return num_frames; } @@ -58,8 +63,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused inline_frames[i].line); } if (inline_list__append(inline_sym, srcline, node) != 0) { + if (file) + zfree(file); + inline_node__clear_frames(node); free_llvm_inline_frames(inline_frames, num_frames); - return 0; + return -1; } } free_llvm_inline_frames(inline_frames, num_frames); @@ -108,6 +116,16 @@ symbol_lookup_callback(void *disinfo, uint64_t value, storage->branch_addr = value; else if (*ref_type == LLVMDisassembler_ReferenceType_In_PCrel_Load) storage->pcrel_load_addr = value; + else if (*ref_type == LLVMDisassembler_ReferenceType_In_ARM64_ADRP) { + uint64_t adrp_imm; + + /* immhi (bits 23:5) and immlo (bits 30:29) */ + adrp_imm = ((value & 0x00ffffe0) >> 3) | ((value >> 29) & 0x3); + adrp_imm = sign_extend64(adrp_imm, 20); + + /* Calculate the target page address */ + storage->pcrel_load_addr = (address & ~0xfffULL) + (adrp_imm << 12); + } *ref_type = LLVMDisassembler_ReferenceType_InOut_None; return NULL; } @@ -228,12 +246,42 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym, } } if (storage.pcrel_load_addr != 0) { - char *name = llvm_name_for_data(dso, filename, + char *name; + + if (arch__is_arm64(args->arch)) { + /* + * For arm64, replace the immediate operand with + * the resolved address to match objdump's output. + * + * Example conversion: + * From: adrp x18, 8014 + * To: adrp x18, ffff800081f5f000 + */ + char *s = strchr(disasm_buf, ','); + + if (s == NULL) + goto err; + + s++; + *s = '\0'; + disasm_len = strlen(disasm_buf); + disasm_len += scnprintf(disasm_buf + disasm_len, + sizeof(disasm_buf) - disasm_len, + " %"PRIx64, storage.pcrel_load_addr); - disasm_len += scnprintf(disasm_buf + disasm_len, - sizeof(disasm_buf) - disasm_len, - " # %#"PRIx64, - storage.pcrel_load_addr); + } else { + /* + * For other archs, append the resolved address + * as an inline comment. + */ + disasm_len += scnprintf(disasm_buf + disasm_len, + sizeof(disasm_buf) - disasm_len, + " # %#"PRIx64, + storage.pcrel_load_addr); + } + + /* Append the resolved symbol name if available */ + name = llvm_name_for_data(dso, filename, storage.pcrel_load_addr); if (name) { disasm_len += scnprintf(disasm_buf + disasm_len, sizeof(disasm_buf) - @@ -254,8 +302,10 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym, (unsigned int *)&args->line_nr, false, NULL); dl = disasm_line__new(args); - if (dl == NULL) + if (dl == NULL) { + free(args->fileloc); goto err; + } annotation_line__add(&dl->al, ¬es->src->source); diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index f86b3b7df742..076d167380f2 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -233,10 +233,12 @@ void machine__delete(struct machine *machine) } } -int machines__init(struct machines *machines) +int machines__init(struct machines *machines, struct perf_session *session) { int err = machine__init(&machines->host, "", HOST_KERNEL_ID); + machines->session = session; + machines->host.machines = machines; machines->guests = RB_ROOT_CACHED; return err; } @@ -2858,7 +2860,7 @@ static int thread__resolve_callchain_sample(struct thread *thread, if (chain) chain_nr = chain->nr; - if (evsel__has_branch_callstack(evsel)) { + if (chain && evsel__has_branch_callstack(evsel)) { struct perf_env *env = evsel__env(evsel); err = resolve_lbr_callchain_sample(thread, cursor, sample, parent, diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h index 26f9827062f5..973a59f7c26a 100644 --- a/tools/perf/util/machine.h +++ b/tools/perf/util/machine.h @@ -15,6 +15,7 @@ struct dso; struct dso_id; struct evsel; struct perf_sample; +struct perf_session; struct symbol; struct target; struct thread; @@ -148,13 +149,20 @@ int machine__process_event(struct machine *machine, union perf_event *event, typedef void (*machine__process_t)(struct machine *machine, void *data); struct machines { + /** @session: back link to owning session if there is one. */ + struct perf_session *session; struct machine host; struct rb_root_cached guests; }; -int machines__init(struct machines *machines); +int machines__init(struct machines *machines, struct perf_session *session); void machines__exit(struct machines *machines); +static inline struct perf_session *machines__session(struct machines *machines) +{ + return machines->session; +} + void machines__process_guests(struct machines *machines, machine__process_t process, void *data); diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c index 0b49fce251fc..0cdcb47647bd 100644 --- a/tools/perf/util/mem-events.c +++ b/tools/perf/util/mem-events.c @@ -17,6 +17,7 @@ #include "symbol.h" #include "pmu.h" #include "pmus.h" +#include "session.h" unsigned int perf_mem_events__loads_ldlat = 30; @@ -391,6 +392,7 @@ static const char * const mem_lvlnum[] = { [PERF_MEM_LVLNUM_L4] = "L4", [PERF_MEM_LVLNUM_L2_MHB] = "L2 MHB", [PERF_MEM_LVLNUM_MSC] = "Memory-side Cache", + [PERF_MEM_LVLNUM_L0] = "L0", [PERF_MEM_LVLNUM_UNC] = "Uncached", [PERF_MEM_LVLNUM_CXL] = "CXL", [PERF_MEM_LVLNUM_IO] = "I/O", @@ -603,14 +605,83 @@ int perf_mem__blk_scnprintf(char *out, size_t sz, const struct mem_info *mem_inf return l; } -int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *mem_info) +static int perf_mem__region_scnprintf(char *out, size_t sz, const struct mem_info *mem_info) { + size_t l = 0; + u64 mem = PERF_MEM_REGION_NA; + + sz -= 1; /* -1 for null termination */ + out[0] = '\0'; + + if (mem_info) + mem = mem_info__const_data_src(mem_info)->mem_region; + + switch (mem) { + case PERF_MEM_REGION_NA: + case PERF_MEM_REGION_RSVD: + l += scnprintf(out + l, sz - l, "N/A"); + break; + case PERF_MEM_REGION_L_SHARE: + l += scnprintf(out + l, sz - l, "Local-shared-cache"); + break; + case PERF_MEM_REGION_L_NON_SHARE: + l += scnprintf(out + l, sz - l, "Local-non-shared-cache"); + break; + case PERF_MEM_REGION_O_IO: + l += scnprintf(out + l, sz - l, "Other-IO"); + break; + case PERF_MEM_REGION_O_SHARE: + l += scnprintf(out + l, sz - l, "Other-shared-cache"); + break; + case PERF_MEM_REGION_O_NON_SHARE: + l += scnprintf(out + l, sz - l, "Other-non-shared-cache"); + break; + case PERF_MEM_REGION_MMIO: + l += scnprintf(out + l, sz - l, "MMIO"); + break; + case PERF_MEM_REGION_MEM0: + l += scnprintf(out + l, sz - l, "Mem-0"); + break; + case PERF_MEM_REGION_MEM1: + l += scnprintf(out + l, sz - l, "Mem-1"); + break; + case PERF_MEM_REGION_MEM2: + l += scnprintf(out + l, sz - l, "Mem-2"); + break; + case PERF_MEM_REGION_MEM3: + l += scnprintf(out + l, sz - l, "Mem-3"); + break; + case PERF_MEM_REGION_MEM4: + l += scnprintf(out + l, sz - l, "Mem-4"); + break; + case PERF_MEM_REGION_MEM5: + l += scnprintf(out + l, sz - l, "Mem-5"); + break; + case PERF_MEM_REGION_MEM6: + l += scnprintf(out + l, sz - l, "Mem-6"); + break; + case PERF_MEM_REGION_MEM7: + l += scnprintf(out + l, sz - l, "Mem-7"); + break; + default: + l += scnprintf(out + l, sz - l, "N/A"); + break; + } + + return l; +} + +int perf_script__meminfo_scnprintf(char *out, size_t sz, + const struct mem_info *mem_info, + struct perf_session *session) +{ + struct perf_env *env; int i = 0; i += scnprintf(out, sz, "|OP "); i += perf_mem__op_scnprintf(out + i, sz - i, mem_info); i += scnprintf(out + i, sz - i, "|LVL "); - i += perf_mem__lvl_scnprintf(out + i, sz, mem_info); + i += perf_mem__lvl_scnprintf(out + i, sz - i, mem_info); i += scnprintf(out + i, sz - i, "|SNP "); i += perf_mem__snp_scnprintf(out + i, sz - i, mem_info); i += scnprintf(out + i, sz - i, "|TLB "); @@ -619,6 +690,21 @@ int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info * i += perf_mem__lck_scnprintf(out + i, sz - i, mem_info); i += scnprintf(out + i, sz - i, "|BLK "); i += perf_mem__blk_scnprintf(out + i, sz - i, mem_info); + if (session) { + /* + * In case the feature bits are not available, as in + * pipe mode, fallback to checking for the existence of + * memory ranges + */ + env = perf_session__env(session); + if ((env && session->data->is_pipe && env->nr_memory_ranges) || + perf_header__has_feat(&session->header, + HEADER_MEMORY_RANGES)) { + i += scnprintf(out + i, sz - i, "|Region "); + i += perf_mem__region_scnprintf(out + i, sz - i, + mem_info); + } + } return i; } @@ -831,6 +917,8 @@ int mem_stat_index(const enum mem_stat_type mst, const u64 val) } case PERF_MEM_STAT_CACHE: switch (src.mem_lvl_num) { + case PERF_MEM_LVLNUM_L0: + return MEM_STAT_CACHE_L0; case PERF_MEM_LVLNUM_L1: return MEM_STAT_CACHE_L1; case PERF_MEM_LVLNUM_L2: @@ -915,6 +1003,8 @@ const char *mem_stat_name(const enum mem_stat_type mst, const int idx) } case PERF_MEM_STAT_CACHE: switch (idx) { + case MEM_STAT_CACHE_L0: + return "L0"; case MEM_STAT_CACHE_L1: return "L1"; case MEM_STAT_CACHE_L2: diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h index 5b98076904b0..38b14c03392c 100644 --- a/tools/perf/util/mem-events.h +++ b/tools/perf/util/mem-events.h @@ -24,6 +24,7 @@ enum { struct evsel; struct mem_info; struct perf_pmu; +struct perf_session; extern unsigned int perf_mem_events__loads_ldlat; extern struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX]; @@ -47,7 +48,8 @@ int perf_mem__snp_scnprintf(char *out, size_t sz, const struct mem_info *mem_inf int perf_mem__lck_scnprintf(char *out, size_t sz, const struct mem_info *mem_info); int perf_mem__blk_scnprintf(char *out, size_t sz, const struct mem_info *mem_info); -int perf_script__meminfo_scnprintf(char *bf, size_t size, const struct mem_info *mem_info); +int perf_script__meminfo_scnprintf(char *bf, size_t size, const struct mem_info *mem_info, + struct perf_session *session); struct c2c_stats { u32 nr_entries; @@ -109,6 +111,7 @@ enum mem_stat_op { }; enum mem_stat_cache { + MEM_STAT_CACHE_L0, MEM_STAT_CACHE_L1, MEM_STAT_CACHE_L2, MEM_STAT_CACHE_L3, diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c index 5a60cb95e31c..779fe56fe8d0 100644 --- a/tools/perf/util/metricgroup.c +++ b/tools/perf/util/metricgroup.c @@ -224,7 +224,8 @@ static struct metric *metric__new(const struct pmu_metric *pm, bool metric_no_threshold, int runtime, const char *user_requested_cpu_list, - bool system_wide) + bool system_wide, + bool fake_pmu) { struct metric *m; @@ -256,6 +257,7 @@ static struct metric *metric__new(const struct pmu_metric *pm, } m->pctx->sctx.runtime = runtime; m->pctx->sctx.system_wide = system_wide; + m->pctx->sctx.is_test = fake_pmu; m->group_events = !metric_no_group && metric__group_events(pm, metric_no_threshold); m->default_show_events = pm->default_show_events; m->metric_refs = NULL; @@ -699,6 +701,7 @@ static int add_metric(struct list_head *metric_list, bool metric_no_threshold, const char *user_requested_cpu_list, bool system_wide, + bool fake_pmu, struct metric *root_metric, const struct visited_metric *visited, const struct pmu_metrics_table *table); @@ -724,6 +727,7 @@ static int metricgroup__find_metric_callback(const struct pmu_metric *pm, * user may override. * @user_requested_cpu_list: Command line specified CPUs to record on. * @system_wide: Are events for all processes recorded. + * @fake_pmu: Are PMUs and events being faked for testing? * @root_metric: Metrics may reference other metrics to form a tree. In this * case the root_metric holds all the IDs and a list of referenced * metrics. When adding a root this argument is NULL. @@ -739,6 +743,7 @@ static int resolve_metric(struct list_head *metric_list, bool metric_no_threshold, const char *user_requested_cpu_list, bool system_wide, + bool fake_pmu, struct metric *root_metric, const struct visited_metric *visited, const struct pmu_metrics_table *table) @@ -788,7 +793,7 @@ static int resolve_metric(struct list_head *metric_list, for (i = 0; i < pending_cnt; i++) { ret = add_metric(metric_list, &pending[i].pm, modifier, metric_no_group, metric_no_threshold, user_requested_cpu_list, system_wide, - root_metric, visited, table); + fake_pmu, root_metric, visited, table); if (ret) break; } @@ -809,6 +814,7 @@ static int resolve_metric(struct list_head *metric_list, * @runtime: A special argument for the parser only known at runtime. * @user_requested_cpu_list: Command line specified CPUs to record on. * @system_wide: Are events for all processes recorded. + * @fake_pmu: Are PMUs and events being faked for testing? * @root_metric: Metrics may reference other metrics to form a tree. In this * case the root_metric holds all the IDs and a list of referenced * metrics. When adding a root this argument is NULL. @@ -825,6 +831,7 @@ static int __add_metric(struct list_head *metric_list, int runtime, const char *user_requested_cpu_list, bool system_wide, + bool fake_pmu, struct metric *root_metric, const struct visited_metric *visited, const struct pmu_metrics_table *table) @@ -851,7 +858,7 @@ static int __add_metric(struct list_head *metric_list, * metrics that are added recursively. */ root_metric = metric__new(pm, modifier, metric_no_group, metric_no_threshold, - runtime, user_requested_cpu_list, system_wide); + runtime, user_requested_cpu_list, system_wide, fake_pmu); if (!root_metric) return -ENOMEM; @@ -924,7 +931,7 @@ static int __add_metric(struct list_head *metric_list, ret = resolve_metric(metric_list, pmu, modifier, metric_no_group, metric_no_threshold, user_requested_cpu_list, - system_wide, root_metric, &visited_node, + system_wide, fake_pmu, root_metric, &visited_node, table); } if (ret) { @@ -944,6 +951,7 @@ static int add_metric(struct list_head *metric_list, bool metric_no_threshold, const char *user_requested_cpu_list, bool system_wide, + bool fake_pmu, struct metric *root_metric, const struct visited_metric *visited, const struct pmu_metrics_table *table) @@ -955,7 +963,7 @@ static int add_metric(struct list_head *metric_list, if (!strstr(pm->metric_expr, "?")) { ret = __add_metric(metric_list, pm, modifier, metric_no_group, metric_no_threshold, 0, user_requested_cpu_list, - system_wide, root_metric, visited, table); + system_wide, fake_pmu, root_metric, visited, table); } else { int j, count; @@ -969,7 +977,7 @@ static int add_metric(struct list_head *metric_list, for (j = 0; j < count && !ret; j++) ret = __add_metric(metric_list, pm, modifier, metric_no_group, metric_no_threshold, j, user_requested_cpu_list, - system_wide, root_metric, visited, table); + system_wide, fake_pmu, root_metric, visited, table); } return ret; @@ -1030,6 +1038,7 @@ struct metricgroup__add_metric_data { bool metric_no_group; bool metric_no_threshold; bool system_wide; + bool fake_pmu; bool has_match; }; @@ -1047,7 +1056,7 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm, data->has_match = true; ret = add_metric(data->list, pm, data->modifier, metric_no_group, data->metric_no_threshold, data->user_requested_cpu_list, - data->system_wide, /*root_metric=*/NULL, + data->system_wide, data->fake_pmu, /*root_metric=*/NULL, /*visited_metrics=*/NULL, table); } return ret; @@ -1065,6 +1074,7 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm, * user may override. * @user_requested_cpu_list: Command line specified CPUs to record on. * @system_wide: Are events for all processes recorded. + * @fake_pmu: Are PMUs and events being faked for testing? * @metric_list: The list that the metric or metric group are added to. * @table: The table that is searched for metrics, most commonly the table for the * architecture perf is running upon. @@ -1072,7 +1082,7 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm, static int metricgroup__add_metric(const char *pmu, const char *metric_name, const char *modifier, bool metric_no_group, bool metric_no_threshold, const char *user_requested_cpu_list, - bool system_wide, + bool system_wide, bool fake_pmu, struct list_head *metric_list, const struct pmu_metrics_table *table) { @@ -1087,6 +1097,7 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con .metric_no_threshold = metric_no_threshold, .user_requested_cpu_list = user_requested_cpu_list, .system_wide = system_wide, + .fake_pmu = fake_pmu, .has_match = false, }; @@ -1118,6 +1129,7 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con * user may override. * @user_requested_cpu_list: Command line specified CPUs to record on. * @system_wide: Are events for all processes recorded. + * @fake_pmu: Are PMUs and events being faked for testing? * @metric_list: The list that metrics are added to. * @table: The table that is searched for metrics, most commonly the table for the * architecture perf is running upon. @@ -1126,7 +1138,8 @@ static int metricgroup__add_metric_list(const char *pmu, const char *list, bool metric_no_group, bool metric_no_threshold, const char *user_requested_cpu_list, - bool system_wide, struct list_head *metric_list, + bool system_wide, bool fake_pmu, + struct list_head *metric_list, const struct pmu_metrics_table *table) { char *list_itr, *list_copy, *metric_name, *modifier; @@ -1145,7 +1158,8 @@ static int metricgroup__add_metric_list(const char *pmu, const char *list, ret = metricgroup__add_metric(pmu, metric_name, modifier, metric_no_group, metric_no_threshold, user_requested_cpu_list, - system_wide, metric_list, table); + system_wide, fake_pmu, + metric_list, table); if (ret == -EINVAL) pr_err("Fail to parse metric or group `%s'\n", metric_name); else if (ret == -ENOENT) @@ -1403,7 +1417,8 @@ static int parse_groups(struct evlist *perf_evlist, ret = metricgroup__add_metric_list(pmu, str, metric_no_group, metric_no_threshold, user_requested_cpu_list, - system_wide, &metric_list, table); + system_wide, fake_pmu, + &metric_list, table); if (ret) goto out; diff --git a/tools/perf/util/mmap.c b/tools/perf/util/mmap.c index 358e70c4f3ed..6dcc4a9304b9 100644 --- a/tools/perf/util/mmap.c +++ b/tools/perf/util/mmap.c @@ -330,7 +330,7 @@ int mmap__mmap(struct mmap *map, struct mmap_params *mp, int fd, struct perf_cpu int perf_mmap__push(struct mmap *md, void *to, int push(struct mmap *map, void *to, void *buf, size_t size)) { - u64 head = perf_mmap__read_head(&md->core); + u64 head; unsigned char *data = md->core.base + page_size; unsigned long size; void *buf; @@ -340,6 +340,7 @@ int perf_mmap__push(struct mmap *md, void *to, if (rc < 0) return (rc == -EAGAIN) ? 1 : -1; + head = md->core.overwrite ? md->core.start : md->core.end; size = md->core.end - md->core.start; if ((md->core.start & md->core.mask) + size != (md->core.end & md->core.mask)) { diff --git a/tools/perf/util/mutex.c b/tools/perf/util/mutex.c index 7aa1f3f55a7d..df3b7cce3d5a 100644 --- a/tools/perf/util/mutex.c +++ b/tools/perf/util/mutex.c @@ -17,37 +17,106 @@ static void check_err(const char *fn, int err) #define CHECK_ERR(err) check_err(__func__, err) -static void __mutex_init(struct mutex *mtx, bool pshared, bool recursive) +#ifdef HAVE_PTHREAD_MUTEX_ROBUST +/* + * A robust mutex whose owner died while holding it comes back as EOWNERDEAD, + * with the caller holding it: make it consistent, which takes it over; failing + * that, a give_up caller unlocks it, leaving it ENOTRECOVERABLE, not leaked. + */ +static int mutex_take_over(struct mutex *mtx, int err, bool give_up) +{ + if (err != EOWNERDEAD) + return err; + + pr_debug("mutex: taking over one left by a dead owner\n"); + err = pthread_mutex_consistent(&mtx->lock); + if (err && give_up) + CHECK_ERR(pthread_mutex_unlock(&mtx->lock)); + + return err; +} +#else +#define mutex_take_over(mtx, err, give_up) ((void)(mtx), (void)(give_up), (err)) +#endif + +static bool __mutex_init(struct mutex *mtx, bool pshared, bool recursive, bool robust) { pthread_mutexattr_t attr; + bool ok = false, robust_unavailable = false; + int err; - CHECK_ERR(pthread_mutexattr_init(&attr)); + err = pthread_mutexattr_init(&attr); + if (err) + goto out; #ifndef NDEBUG /* In normal builds enable error checking, such as recursive usage. */ - CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)); + err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if (err) + goto out_destroy; #endif - if (recursive) - CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)); - if (pshared) - CHECK_ERR(pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); - CHECK_ERR(pthread_mutex_init(&mtx->lock, &attr)); + if (recursive) { + err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (err) + goto out_destroy; + } + if (pshared) { + err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + if (err) + goto out_destroy; + } + if (robust) { +#ifdef HAVE_PTHREAD_MUTEX_ROBUST + err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + if (err) + robust_unavailable = true; +#else + robust_unavailable = true; +#endif + if (robust_unavailable) + goto out_destroy; + } + + err = pthread_mutex_init(&mtx->lock, &attr); + if (!err) + ok = true; + else if (robust && pshared && err == ENOTSUP) + /* + * glibc refuses a robust process shared mutex with ENOTSUP when + * set_robust_list(2) isn't usable, i.e. when the machine provides + * no robust mutexes for the process shared case: an unavailable + * API, not a bad request. + */ + robust_unavailable = true; +out_destroy: CHECK_ERR(pthread_mutexattr_destroy(&attr)); +out: + if (robust_unavailable) + pr_debug("mutex: robust mutexes aren't available\n"); + else if (err) + CHECK_ERR(err); + + return ok; } void mutex_init(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/false, /*recursive=*/false); + __mutex_init(mtx, /*pshared=*/false, /*recursive=*/false, /*robust=*/false); } void mutex_init_pshared(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false); + __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/false); } void mutex_init_recursive(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/false, /*recursive=*/true); + __mutex_init(mtx, /*pshared=*/false, /*recursive=*/true, /*robust=*/false); +} + +bool mutex_init_pshared_robust(struct mutex *mtx) +{ + return __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/true); } void mutex_destroy(struct mutex *mtx) @@ -58,7 +127,8 @@ void mutex_destroy(struct mutex *mtx) void mutex_lock(struct mutex *mtx) NO_THREAD_SAFETY_ANALYSIS { - CHECK_ERR(pthread_mutex_lock(&mtx->lock)); + CHECK_ERR(mutex_take_over(mtx, pthread_mutex_lock(&mtx->lock), + /*give_up=*/false)); } void mutex_unlock(struct mutex *mtx) @@ -69,7 +139,8 @@ void mutex_unlock(struct mutex *mtx) bool mutex_trylock(struct mutex *mtx) { - int ret = pthread_mutex_trylock(&mtx->lock); + int ret = mutex_take_over(mtx, pthread_mutex_trylock(&mtx->lock), + /*give_up=*/true); if (ret == 0) return true; /* Lock acquired. */ @@ -111,7 +182,9 @@ void cond_destroy(struct cond *cnd) void cond_wait(struct cond *cnd, struct mutex *mtx) { - CHECK_ERR(pthread_cond_wait(&cnd->cond, &mtx->lock)); + CHECK_ERR(mutex_take_over(mtx, + pthread_cond_wait(&cnd->cond, &mtx->lock), + /*give_up=*/false)); } void cond_signal(struct cond *cnd) diff --git a/tools/perf/util/mutex.h b/tools/perf/util/mutex.h index 70232d8d094f..5de1065ea2b9 100644 --- a/tools/perf/util/mutex.h +++ b/tools/perf/util/mutex.h @@ -92,6 +92,42 @@ struct LOCKABLE mutex { pthread_mutex_t lock; }; +/* + * Statically initialized mutex, for the file scope ones. Error checking + * in !NDEBUG builds, like mutex_init(); the glibc-only errorcheck + * initializer falls back to the default type elsewhere. + */ +#if !defined(NDEBUG) && defined(PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) +#define __PERF_MUTEX_INITIALIZER PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +#else +#define __PERF_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#endif +#define DEFINE_MUTEX(name) struct mutex name = { .lock = __PERF_MUTEX_INITIALIZER } + +/* + * Runs fn() exactly once however many threads race to it, the rest wait + * for it to finish; per call site state, so multiple sites wanting the + * same once use a common helper, as in the kernel's DO_ONCE(). + */ +#define DO_ONCE(fn, ...) \ + ({ \ + static bool ___done; \ + static DEFINE_MUTEX(___once_lock); \ + bool ___ret = false; \ + \ + if (!__atomic_load_n(&___done, __ATOMIC_ACQUIRE)) { \ + mutex_lock(&___once_lock); \ + if (!___done) { \ + fn(__VA_ARGS__); \ + __atomic_store_n(&___done, true, \ + __ATOMIC_RELEASE); \ + ___ret = true; \ + } \ + mutex_unlock(&___once_lock); \ + } \ + ___ret; \ + }) + /* A wrapper around the condition variable implementation. */ struct cond { pthread_cond_t cond; @@ -106,6 +142,12 @@ void mutex_init(struct mutex *mtx); void mutex_init_pshared(struct mutex *mtx); /* Initializes a mutex that may be recursively held on the same thread. */ void mutex_init_recursive(struct mutex *mtx); +/* + * As mutex_init_pshared(), plus the robust attribute: a process dying while + * holding it doesn't leave it locked, but the takeover isn't reported and the + * data it protects may be mid update. Returns false when not available. + */ +bool mutex_init_pshared_robust(struct mutex *mtx); void mutex_destroy(struct mutex *mtx); void mutex_lock(struct mutex *mtx) EXCLUSIVE_LOCK_FUNCTION(*mtx); diff --git a/tools/perf/util/ordered-events.c b/tools/perf/util/ordered-events.c index a5857f9f5af2..54c85663e733 100644 --- a/tools/perf/util/ordered-events.c +++ b/tools/perf/util/ordered-events.c @@ -237,14 +237,16 @@ static int do_flush(struct ordered_events *oe, bool show_progress) ui_progress__init(&prog, oe->nr_events, "Processing time ordered events..."); list_for_each_entry_safe(iter, tmp, head, list) { - if (session_done()) - return 0; + if (session_done()) { + ret = 0; + goto out_progress; + } if (iter->timestamp > limit) break; ret = oe->deliver(oe, iter); if (ret < 0) - return ret; + goto out_progress; ordered_events__delete(oe, iter); oe->last_flush = iter->timestamp; @@ -258,10 +260,16 @@ static int do_flush(struct ordered_events *oe, bool show_progress) else if (last_ts <= limit) oe->last = list_entry(head->prev, struct ordered_event, list); + ret = 0; +out_progress: + /* + * Always pair ui_progress__init() with ui_progress__finish(), the + * stdio backend tracks the phases on a stack. + */ if (show_progress) ui_progress__finish(); - return 0; + return ret; } static int __ordered_events__flush(struct ordered_events *oe, enum oe_flush how, diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index cc7ad331a49f..f3ba70d4f76b 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include #include #include #include -#include #include #include #include "cpumap.h" @@ -269,6 +269,7 @@ __add_event(struct list_head *list, int *idx, evsel->core.pmu_cpus = pmu_cpus; evsel->core.requires_cpu = pmu ? pmu->is_uncore : false; evsel->core.is_pmu_core = is_pmu_core; + evsel->core.is_pmu_uncore = pmu ? pmu->is_uncore : false; evsel->core.reads_only_on_cpu_idx0 = perf_pmu__reads_only_on_cpu_idx0(attr); evsel->pmu = pmu; evsel->alternate_hw_config = alternate_hw_config; @@ -1683,7 +1684,7 @@ int parse_events_multi_pmu_add(struct parse_events_state *parse_state, strbuf_release(&sb); ok++; } - if (first_wildcard_match == NULL) + if (first_wildcard_match == NULL && !list_empty(list)) first_wildcard_match = container_of(list->prev, struct evsel, core.node); } @@ -1754,7 +1755,7 @@ int parse_events_multi_pmu_add_or_add_pmu(struct parse_events_state *parse_state ok++; parse_state->wild_card_pmus = true; } - if (first_wildcard_match == NULL) { + if (first_wildcard_match == NULL && !list_empty(*listp)) { first_wildcard_match = container_of((*listp)->prev, struct evsel, core.node); } diff --git a/tools/perf/util/parse-regs-options.c b/tools/perf/util/parse-regs-options.c index c93c2f0c8105..e70393388fae 100644 --- a/tools/perf/util/parse-regs-options.c +++ b/tools/perf/util/parse-regs-options.c @@ -3,43 +3,101 @@ #include #include #include +#include #include #include "util/debug.h" #include +#include #include #include "util/perf_regs.h" #include "util/parse-regs-options.h" +#include "record.h" -static void list_perf_regs(FILE *fp, uint64_t mask) +static void +__list_gp_regs(FILE *fp, uint64_t mask, int abi) { const char *last_name = NULL; - fprintf(fp, "available registers: "); for (int reg = 0; reg < 64; reg++) { const char *name; if (((1ULL << reg) & mask) == 0) continue; - name = perf_reg_name(reg, EM_HOST, EF_HOST); + name = perf_reg_name(reg, EM_HOST, EF_HOST, abi); if (name && (!last_name || strcmp(last_name, name))) fprintf(fp, "%s%s", reg > 0 ? " " : "", name); last_name = name; } +} + +static void +__list_simd_regs(FILE *fp, uint64_t mask, bool intr, bool pred) +{ + uint64_t bitmap = 0; + uint16_t qwords = 0; + const char *name; + int i = 0; + + for (int reg_c = 0; reg_c < 64; reg_c++) { + if (((1ULL << reg_c) & mask) == 0) + continue; + + name = perf_simd_reg_class_name(EM_HOST, reg_c, pred); + bitmap = intr ? + perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, &qwords, pred) : + perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, &qwords, pred); + if (name && bitmap) + fprintf(fp, "%s%s0-%d", i++ > 0 ? " " : "", + name, fls64(bitmap) - 1); + } +} + +static void +list_perf_regs(FILE *fp, uint64_t mask, uint64_t simd_mask, + uint64_t pred_mask, int abi, bool intr) +{ + bool printed = false; + + fprintf(fp, "available registers: "); + + if (mask) { + __list_gp_regs(fp, mask, abi); + printed = true; + } + + if (simd_mask) { + if (printed) + fprintf(fp, " "); + __list_simd_regs(fp, simd_mask, intr, /*pred=*/false); + printed = true; + } + + if (pred_mask) { + if (printed) + fprintf(fp, " "); + __list_simd_regs(fp, pred_mask, intr, /*pred=*/true); + printed = true; + } + fputc('\n', fp); } -static uint64_t name_to_perf_reg_mask(const char *to_match, uint64_t mask) +static uint64_t +name_to_gp_reg_mask(const char *to_match, uint64_t mask, int abi) { uint64_t reg_mask = 0; + if (!mask) + return reg_mask; + for (int reg = 0; reg < 64; reg++) { const char *name; if (((1ULL << reg) & mask) == 0) continue; - name = perf_reg_name(reg, EM_HOST, EF_HOST); + name = perf_reg_name(reg, EM_HOST, EF_HOST, abi); if (!name) continue; @@ -49,28 +107,126 @@ static uint64_t name_to_perf_reg_mask(const char *to_match, uint64_t mask) return reg_mask; } +static bool +name_to_simd_reg_mask(struct record_opts *opts, const char *to_match, + uint64_t mask, bool intr, bool pred) +{ + bool matched = false; + uint64_t bitmap; + uint16_t qwords; + int reg_c; + + if (!mask) + return false; + + for (reg_c = 0; reg_c < 64; reg_c++) { + const char *name; + + if (((1ULL << reg_c) & mask) == 0) + continue; + + name = perf_simd_reg_class_name(EM_HOST, reg_c, pred); + if (!name) + continue; + + if (!strcasecmp(to_match, name)) { + matched = true; + break; + } + } + + if (!matched) + return false; + + if (intr) { + bitmap = perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, + reg_c, &qwords, pred); + } else { + bitmap = perf_user_simd_reg_class_bitmap_qwords(EM_HOST, + reg_c, &qwords, pred); + } + + /* + * Assume higher width SIMD registers are always the superset of lower + * width SIMD registers. So only pick the largest qwords and bitmap. + */ + if (pred) { + opts->sample_pred_reg_qwords = + MAX(qwords, opts->sample_pred_reg_qwords); + if (intr && + hweight64(bitmap) > hweight32(opts->sample_intr_pred_regs)) + opts->sample_intr_pred_regs = bitmap; + if (!intr && + hweight64(bitmap) > hweight32(opts->sample_user_pred_regs)) + opts->sample_user_pred_regs = bitmap; + + /* + * sample_intr_pred_regs and sample_user_pred_regs share + * sample_pred_reg_qwords. If both are set, keep their bitmaps identical. + */ + if (opts->sample_intr_pred_regs && opts->sample_user_pred_regs) { + uint64_t max_pred_regs = MAX(opts->sample_intr_pred_regs, + opts->sample_user_pred_regs); + + opts->sample_intr_pred_regs = max_pred_regs; + opts->sample_user_pred_regs = max_pred_regs; + } + } else { + opts->sample_vec_reg_qwords = + MAX(qwords, opts->sample_vec_reg_qwords); + if (intr && + hweight64(bitmap) > hweight64(opts->sample_intr_vec_regs)) + opts->sample_intr_vec_regs = bitmap; + if (!intr && + hweight64(bitmap) > hweight64(opts->sample_user_vec_regs)) + opts->sample_user_vec_regs = bitmap; + + /* + * sample_intr_vec_regs and sample_user_vec_regs share + * sample_vec_reg_qwords. If both are set, keep their bitmaps identical. + */ + if (opts->sample_intr_vec_regs && opts->sample_user_vec_regs) { + uint64_t max_vec_regs = MAX(opts->sample_intr_vec_regs, + opts->sample_user_vec_regs); + + opts->sample_intr_vec_regs = max_vec_regs; + opts->sample_user_vec_regs = max_vec_regs; + } + } + + if (opts->sample_pred_reg_qwords || opts->sample_vec_reg_qwords) + opts->sample_simd_regs_enabled = 1; + + return true; +} + static int __parse_regs(const struct option *opt, const char *str, int unset, bool intr) { uint64_t *mode = (uint64_t *)opt->value; + struct record_opts *opts; char *s, *os = NULL, *p; - int ret = -1; + uint64_t simd_mask; + uint64_t pred_mask; uint64_t mask; + const char *warn; + bool matched; + int ret = -1; + int abi = 0; if (unset) return 0; - /* - * cannot set it twice - */ - if (*mode) - return -1; - - mask = intr ? perf_intr_reg_mask(EM_HOST) : perf_user_reg_mask(EM_HOST); + mask = intr ? perf_intr_reg_mask(EM_HOST, &abi) : + perf_user_reg_mask(EM_HOST, &abi); + opts = intr ? container_of(opt->value, struct record_opts, sample_intr_regs) : + container_of(opt->value, struct record_opts, sample_user_regs); /* str may be NULL in case no arg is passed to -I */ if (!str) { *mode = mask; + if (abi & PERF_SAMPLE_REGS_ABI_SIMD) + opts->sample_simd_regs_enabled = 1; return 0; } @@ -79,6 +235,15 @@ __parse_regs(const struct option *opt, const char *str, int unset, bool intr) if (!s) return -1; + if (intr) { + simd_mask = perf_intr_simd_reg_class_mask(EM_HOST, /*pred=*/false); + pred_mask = perf_intr_simd_reg_class_mask(EM_HOST, /*pred=*/true); + } else { + simd_mask = perf_user_simd_reg_class_mask(EM_HOST, /*pred=*/false); + pred_mask = perf_user_simd_reg_class_mask(EM_HOST, /*pred=*/true); + } + + warn = "Unknown register \"%s\", check man page or run \"perf record %s?\"\n"; for (;;) { uint64_t reg_mask; @@ -87,15 +252,23 @@ __parse_regs(const struct option *opt, const char *str, int unset, bool intr) *p = '\0'; if (!strcmp(s, "?")) { - list_perf_regs(stderr, mask); + list_perf_regs(stderr, mask, simd_mask, pred_mask, abi, intr); goto error; } - reg_mask = name_to_perf_reg_mask(s, mask); - if (reg_mask == 0) { - ui__warning("Unknown register \"%s\", check man page or run \"perf record %s?\"\n", - s, intr ? "-I" : "--user-regs="); - goto error; + reg_mask = name_to_gp_reg_mask(s, mask, abi); + if (reg_mask) { + if (abi & PERF_SAMPLE_REGS_ABI_SIMD) + opts->sample_simd_regs_enabled = 1; + } else { + matched = name_to_simd_reg_mask(opts, s, simd_mask, + intr, /*pred=*/false) || + name_to_simd_reg_mask(opts, s, pred_mask, + intr, /*pred=*/true); + if (!matched) { + ui__warning(warn, s, intr ? "-I" : "--user-regs="); + goto error; + } } *mode |= reg_mask; diff --git a/tools/perf/util/path.c b/tools/perf/util/path.c index 2e62f272fda8..b7592e16718d 100644 --- a/tools/perf/util/path.c +++ b/tools/perf/util/path.c @@ -1,14 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 #include "path.h" -#include "cache.h" -#include + #include +#include #include #include -#include -#include + #include -#include +#include +#include static char *cleanup_path(char *path) { diff --git a/tools/perf/util/path.h b/tools/perf/util/path.h index fb850fb55c60..62e86d390589 100644 --- a/tools/perf/util/path.h +++ b/tools/perf/util/path.h @@ -2,11 +2,15 @@ #ifndef _PERF_PATH_H #define _PERF_PATH_H -#include #include +#include + +#include struct dirent; +char *mkpath(char *path_buf, size_t sz, const char *fmt, ...) __printf(3, 4); + int path__join(char *bf, size_t size, const char *path1, const char *path2); int path__join3(char *bf, size_t size, const char *path1, const char *path2, const char *path3); diff --git a/tools/perf/util/perf-regs-arch/perf_regs_x86.c b/tools/perf/util/perf-regs-arch/perf_regs_x86.c index b6d20522b4e8..ea1d402b0893 100644 --- a/tools/perf/util/perf-regs-arch/perf_regs_x86.c +++ b/tools/perf/util/perf-regs-arch/perf_regs_x86.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -235,26 +237,26 @@ int __perf_sdt_arg_parse_op_x86(char *old_op, char **new_op) return SDT_ARG_VALID; } -uint64_t __perf_reg_mask_x86(bool intr) +static uint64_t __arch__reg_mask(u64 sample_type, u64 mask, bool has_simd_regs) { struct perf_event_attr attr = { - .type = PERF_TYPE_HARDWARE, - .config = PERF_COUNT_HW_CPU_CYCLES, - .sample_type = PERF_SAMPLE_REGS_INTR, - .sample_regs_intr = PERF_REG_EXTENDED_MASK, - .precise_ip = 1, - .disabled = 1, - .exclude_kernel = 1, + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CPU_CYCLES, + .sample_type = sample_type, + .precise_ip = 1, + .disabled = 1, + .exclude_kernel = 1, + .sample_simd_regs_enabled = has_simd_regs, }; int fd; - - if (!intr) - return PERF_REGS_MASK; - /* * In an unnamed union, init it here to build on older gcc versions */ attr.sample_period = 1; + if (sample_type == PERF_SAMPLE_REGS_INTR) + attr.sample_regs_intr = mask; + else + attr.sample_regs_user = mask; if (perf_pmus__num_core_pmus() > 1) { struct perf_pmu *pmu = NULL; @@ -276,13 +278,45 @@ uint64_t __perf_reg_mask_x86(bool intr) /*group_fd=*/-1, /*flags=*/0); if (fd != -1) { close(fd); - return (PERF_REG_EXTENDED_MASK | PERF_REGS_MASK); + return mask; } - return PERF_REGS_MASK; + return 0; } -const char *__perf_reg_name_x86(int id) +static bool __support_simd_sampling(bool intr); +uint64_t __perf_reg_mask_x86(bool intr, int *abi) +{ + u64 sample_type = intr ? PERF_SAMPLE_REGS_INTR : PERF_SAMPLE_REGS_USER; + uint64_t mask = PERF_REGS_MASK; + + /* -1 indicates only basic GPRs are needed. */ + if (*abi < 0) + return PERF_REGS_MASK; + + *abi = 0; + mask |= __arch__reg_mask(sample_type, + GENMASK_ULL(PERF_REG_X86_R31, PERF_REG_X86_R16), + true); + mask |= __arch__reg_mask(sample_type, BIT_ULL(PERF_REG_X86_SSP), true); + + if (mask != PERF_REGS_MASK) { + *abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } else { + /* + * Use the SIMD sampling interface for XMM registers + * when available. + */ + if (!__support_simd_sampling(intr)) { + mask |= __arch__reg_mask(sample_type, + PERF_REG_EXTENDED_MASK, false); + } + } + + return mask; +} + +static const char *__arch_reg_gpr_name(int id) { switch (id) { case PERF_REG_X86_AX: @@ -333,7 +367,60 @@ const char *__perf_reg_name_x86(int id) return "R14"; case PERF_REG_X86_R15: return "R15"; + default: + return NULL; + } + return NULL; +} + +static const char *__arch_reg_egpr_name(int id) +{ + switch (id) { + case PERF_REG_X86_R16: + return "R16"; + case PERF_REG_X86_R17: + return "R17"; + case PERF_REG_X86_R18: + return "R18"; + case PERF_REG_X86_R19: + return "R19"; + case PERF_REG_X86_R20: + return "R20"; + case PERF_REG_X86_R21: + return "R21"; + case PERF_REG_X86_R22: + return "R22"; + case PERF_REG_X86_R23: + return "R23"; + case PERF_REG_X86_R24: + return "R24"; + case PERF_REG_X86_R25: + return "R25"; + case PERF_REG_X86_R26: + return "R26"; + case PERF_REG_X86_R27: + return "R27"; + case PERF_REG_X86_R28: + return "R28"; + case PERF_REG_X86_R29: + return "R29"; + case PERF_REG_X86_R30: + return "R30"; + case PERF_REG_X86_R31: + return "R31"; + case PERF_REG_X86_SSP: + return "SSP"; + default: + return NULL; + } + + return NULL; +} + +static const char *__arch_reg_xmm_name(int id) +{ + switch (id) { #define XMM(x) \ case PERF_REG_X86_XMM ## x: \ case PERF_REG_X86_XMM ## x + 1: \ @@ -362,6 +449,22 @@ const char *__perf_reg_name_x86(int id) return NULL; } +const char *__perf_reg_name_x86(int id, int abi) +{ + const char *name; + + name = __arch_reg_gpr_name(id); + if (name) + return name; + + if (abi & PERF_SAMPLE_REGS_ABI_SIMD) + name = __arch_reg_egpr_name(id); + else + name = __arch_reg_xmm_name(id); + + return name; +} + uint64_t __perf_reg_ip_x86(void) { return PERF_REG_X86_IP; @@ -371,3 +474,308 @@ uint64_t __perf_reg_sp_x86(void) { return PERF_REG_X86_SP; } + +enum { + PERF_REG_CLASS_X86_OPMASK = 0, + PERF_REG_CLASS_X86_XMM, + PERF_REG_CLASS_X86_YMM, + PERF_REG_CLASS_X86_ZMM, + PERF_REG_X86_MAX_SIMD_CLASSES, +}; + +#define PERF_REG_CLASS_X86_PRED_MASK (BIT(PERF_REG_CLASS_X86_OPMASK)) +#define PERF_REG_CLASS_X86_SIMD_MASK (BIT(PERF_REG_CLASS_X86_XMM) | \ + BIT(PERF_REG_CLASS_X86_YMM) | \ + BIT(PERF_REG_CLASS_X86_ZMM)) + +/* + * This function is used to determine whether kernel perf subsystem + * supports which kinds of SIMD registers (OPMASK/XMM/YMM/ZMM) sampling. + * + * @sample_type: PERF_SAMPLE_REGS_INTR or PERF_SAMPLE_REGS_USER + * @qwords: the length of SIMD register, like 1/2/4/8 qwords for + * OPMASK/XMM/YMM/ZMM registers. + * @mask: the bitmask of SIMD register, like 0xffff for XMM0 ~ XMM15 + * @pred: whether It's a predicate SIMD register, like OPMASK register. + * + * Return value: true indicates support, otherwise no support. + */ +static bool +__support_simd_reg_class(uint64_t sample_type, uint16_t qwords, + uint64_t mask, bool pred) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CPU_CYCLES, + .sample_type = sample_type, + .disabled = 1, + .exclude_kernel = 1, + .sample_simd_regs_enabled = 1, + }; + int fd; + + attr.sample_period = 1; + + if (!pred) { + attr.sample_simd_vec_reg_qwords = qwords; + if (sample_type == PERF_SAMPLE_REGS_INTR) + attr.sample_simd_vec_reg_intr = mask; + else + attr.sample_simd_vec_reg_user = mask; + } else { + attr.sample_simd_pred_reg_qwords = PERF_X86_OPMASK_QWORDS; + if (sample_type == PERF_SAMPLE_REGS_INTR) + attr.sample_simd_pred_reg_intr = PERF_X86_SIMD_PRED_MASK; + else + attr.sample_simd_pred_reg_user = PERF_X86_SIMD_PRED_MASK; + } + + if (perf_pmus__num_core_pmus() > 1) { + __u64 type = perf_pmus__find_core_pmu()->type; + + attr.config |= type << PERF_PMU_TYPE_SHIFT; + } + + event_attr_init(&attr); + + fd = sys_perf_event_open(&attr, 0, -1, -1, 0); + if (fd != -1) { + close(fd); + return true; + } + + return false; +} + +#define PERF_X86_SIMD_ZMM_LOW_REGS (PERF_X86_SIMD_ZMM_REGS / 2) + +static bool __arch_has_simd_reg_class(uint64_t sample_type, int reg_class, + uint64_t *mask, uint16_t *qwords) +{ + bool supported = false; + uint64_t bits; + + *mask = 0; + *qwords = 0; + + switch (reg_class) { + case PERF_REG_CLASS_X86_OPMASK: + bits = BIT_ULL(PERF_X86_SIMD_OPMASK_REGS) - 1; + supported = __support_simd_reg_class(sample_type, + PERF_X86_OPMASK_QWORDS, + bits, true); + if (supported) { + *mask = bits; + *qwords = PERF_X86_OPMASK_QWORDS; + } + break; + case PERF_REG_CLASS_X86_XMM: + bits = BIT_ULL(PERF_X86_SIMD_XMM_REGS) - 1; + supported = __support_simd_reg_class(sample_type, + PERF_X86_XMM_QWORDS, + bits, false); + if (supported) { + *mask = bits; + *qwords = PERF_X86_XMM_QWORDS; + } + break; + case PERF_REG_CLASS_X86_YMM: + bits = BIT_ULL(PERF_X86_SIMD_YMM_REGS) - 1; + supported = __support_simd_reg_class(sample_type, + PERF_X86_YMM_QWORDS, + bits, false); + if (supported) { + *mask = bits; + *qwords = PERF_X86_YMM_QWORDS; + } + break; + case PERF_REG_CLASS_X86_ZMM: + bits = BIT_ULL(PERF_X86_SIMD_ZMM_REGS) - 1; + supported = __support_simd_reg_class(sample_type, + PERF_X86_ZMM_QWORDS, + bits, false); + if (supported) { + *mask = bits; + *qwords = PERF_X86_ZMM_QWORDS; + break; + } + + bits = BIT_ULL(PERF_X86_SIMD_ZMM_LOW_REGS) - 1; + supported = __support_simd_reg_class(sample_type, + PERF_X86_ZMM_QWORDS, + bits, false); + if (supported) { + *mask = bits; + *qwords = PERF_X86_ZMM_QWORDS; + } + break; + default: + break; + } + + return supported; +} + +static bool __support_simd_sampling(bool intr) +{ + static bool intr_simd_sampling_supported; + static bool user_simd_sampling_supported; + static bool intr_cached; + static bool user_cached; + uint64_t mask; + uint16_t qwords; + + if (intr && intr_cached) + return intr_simd_sampling_supported; + if (!intr && user_cached) + return user_simd_sampling_supported; + + if (intr) { + intr_simd_sampling_supported = + __arch_has_simd_reg_class(PERF_SAMPLE_REGS_INTR, + PERF_REG_CLASS_X86_XMM, + &mask, &qwords); + intr_cached = true; + } else { + user_simd_sampling_supported = + __arch_has_simd_reg_class(PERF_SAMPLE_REGS_USER, + PERF_REG_CLASS_X86_XMM, + &mask, &qwords); + user_cached = true; + } + + return intr ? intr_simd_sampling_supported : + user_simd_sampling_supported; +} + +/* + * @x86_intr_simd_cached: indicates the data of below 3 + * x86_intr_simd_* items has been retrieved from kernel and cached. + * @x86_intr_simd_reg_class_mask: indicates which kinds of PRED/SIMD + * registers are supported for intr-regs option. Assume kernel perf + * subsystem supports XMM/YMM sampling, then the mask is + * PERF_REG_CLASS_X86_XMM|PERF_REG_CLASS_X86_YMM. + * @x86_intr_simd_mask: indicates register bitmask for each kind of + * supported PRED/SIMD register, like + * x86_intr_simd_mask[PERF_REG_CLASS_X86_XMM] = 0xffff. + * @x86_intr_simd_qwords: indicates the register length (qwords unit) + * for each kind of supported PRED/SIMD register, like + * x86_intr_simd_qwords[PERF_REG_CLASS_X86_XMM] = 2. + */ +static bool x86_intr_simd_cached; +static uint64_t x86_intr_simd_reg_class_mask; +static uint64_t x86_intr_simd_mask[PERF_REG_X86_MAX_SIMD_CLASSES]; +static uint16_t x86_intr_simd_qwords[PERF_REG_X86_MAX_SIMD_CLASSES]; + +/* + * Similar with above x86_intr_simd_* items, the difference is these + * items are used for user-regs option. + */ +static bool x86_user_simd_cached; +static uint64_t x86_user_simd_reg_class_mask; +static uint64_t x86_user_simd_mask[PERF_REG_X86_MAX_SIMD_CLASSES]; +static uint16_t x86_user_simd_qwords[PERF_REG_X86_MAX_SIMD_CLASSES]; + +static uint64_t __arch__simd_reg_class_mask(bool intr) +{ + uint64_t mask = 0; + bool supported; + int reg_c; + + if (!__support_simd_sampling(intr)) + goto done; + + if (intr && x86_intr_simd_cached) + return x86_intr_simd_reg_class_mask; + + if (!intr && x86_user_simd_cached) + return x86_user_simd_reg_class_mask; + + for (reg_c = 0; reg_c < PERF_REG_X86_MAX_SIMD_CLASSES; reg_c++) { + supported = false; + + if (intr) { + supported = __arch_has_simd_reg_class( + PERF_SAMPLE_REGS_INTR, + reg_c, + &x86_intr_simd_mask[reg_c], + &x86_intr_simd_qwords[reg_c]); + } else { + supported = __arch_has_simd_reg_class( + PERF_SAMPLE_REGS_USER, + reg_c, + &x86_user_simd_mask[reg_c], + &x86_user_simd_qwords[reg_c]); + } + if (supported) + mask |= BIT_ULL(reg_c); + } + +done: + if (intr) { + x86_intr_simd_reg_class_mask = mask; + x86_intr_simd_cached = true; + } else { + x86_user_simd_reg_class_mask = mask; + x86_user_simd_cached = true; + } + + return mask; +} + +static uint64_t +__arch__simd_reg_class_bitmap_qwords(bool intr, int reg_c, uint16_t *qwords) +{ + uint64_t mask = 0; + uint64_t class_mask; + + *qwords = 0; + class_mask = intr ? x86_intr_simd_reg_class_mask : + x86_user_simd_reg_class_mask; + if (!(class_mask & BIT_ULL(reg_c))) + return 0; + + if (intr) { + mask = x86_intr_simd_mask[reg_c]; + *qwords = x86_intr_simd_qwords[reg_c]; + } else { + mask = x86_user_simd_mask[reg_c]; + *qwords = x86_user_simd_qwords[reg_c]; + } + + return mask; +} + +uint64_t __perf_simd_reg_class_mask_x86(bool intr, bool pred) +{ + uint64_t mask = __arch__simd_reg_class_mask(intr); + + return pred ? mask & PERF_REG_CLASS_X86_PRED_MASK : + mask & PERF_REG_CLASS_X86_SIMD_MASK; +} + +uint64_t __perf_simd_reg_class_bitmap_qwords_x86(int reg_c, uint16_t *qwords, + bool intr, bool pred) +{ + if (intr ? !x86_intr_simd_cached : !x86_user_simd_cached) + __perf_simd_reg_class_mask_x86(intr, pred); + return __arch__simd_reg_class_bitmap_qwords(intr, reg_c, qwords); +} + +const char *__perf_simd_reg_class_name_x86(int id, bool pred __maybe_unused) +{ + switch (id) { + case PERF_REG_CLASS_X86_OPMASK: + return "OPMASK"; + case PERF_REG_CLASS_X86_XMM: + return "XMM"; + case PERF_REG_CLASS_X86_YMM: + return "YMM"; + case PERF_REG_CLASS_X86_ZMM: + return "ZMM"; + default: + return NULL; + } + + return NULL; +} diff --git a/tools/perf/util/perf_event_attr_fprintf.c b/tools/perf/util/perf_event_attr_fprintf.c index 3933639d76c5..2e8be9a357dc 100644 --- a/tools/perf/util/perf_event_attr_fprintf.c +++ b/tools/perf/util/perf_event_attr_fprintf.c @@ -405,6 +405,19 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr, PRINT_ATTRf_bf(aux_pause, p_unsigned); PRINT_ATTRf_bf(aux_resume, p_unsigned); } + if (attr_size >= offsetof(struct perf_event_attr, config4)) + PRINT_ATTRf(config3, p_hex); + if (attr_size >= offsetof(struct perf_event_attr, sample_simd_regs_enabled)) + PRINT_ATTRf(config4, p_hex); + if (attr_size > offsetof(struct perf_event_attr, sample_simd_vec_reg_user)) { + PRINT_ATTRf(sample_simd_regs_enabled, p_unsigned); + PRINT_ATTRf(sample_simd_pred_reg_qwords, p_unsigned); + PRINT_ATTRf(sample_simd_pred_reg_intr, p_hex); + PRINT_ATTRf(sample_simd_pred_reg_user, p_hex); + PRINT_ATTRf(sample_simd_vec_reg_qwords, p_unsigned); + PRINT_ATTRf(sample_simd_vec_reg_intr, p_hex); + PRINT_ATTRf(sample_simd_vec_reg_user, p_hex); + } return ret; } diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index 558c143abbab..c8242b7e1db5 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c @@ -38,7 +38,7 @@ int perf_sdt_arg_parse_op(uint16_t e_machine, char *old_op, char **new_op) return ret; } -uint64_t perf_intr_reg_mask(uint16_t e_machine) +uint64_t perf_intr_reg_mask(uint16_t e_machine, int *abi /*inout*/) { uint64_t mask = 0; @@ -70,7 +70,7 @@ uint64_t perf_intr_reg_mask(uint16_t e_machine) break; case EM_386: case EM_X86_64: - mask = __perf_reg_mask_x86(/*intr=*/true); + mask = __perf_reg_mask_x86(/*intr=*/true, abi); break; default: pr_debug("Unknown ELF machine %d, interrupt sampling register mask will be empty.\n", @@ -81,7 +81,7 @@ uint64_t perf_intr_reg_mask(uint16_t e_machine) return mask; } -uint64_t perf_user_reg_mask(uint16_t e_machine) +uint64_t perf_user_reg_mask(uint16_t e_machine, int *abi /*inout*/) { uint64_t mask = 0; @@ -113,7 +113,7 @@ uint64_t perf_user_reg_mask(uint16_t e_machine) break; case EM_386: case EM_X86_64: - mask = __perf_reg_mask_x86(/*intr=*/false); + mask = __perf_reg_mask_x86(/*intr=*/false, abi); break; default: pr_debug("Unknown ELF machine %d, user sampling register mask will be empty.\n", @@ -124,7 +124,7 @@ uint64_t perf_user_reg_mask(uint16_t e_machine) return mask; } -const char *perf_reg_name(int id, uint16_t e_machine, uint32_t e_flags) +const char *perf_reg_name(int id, uint16_t e_machine, uint32_t e_flags, int abi) { const char *reg_name = NULL; @@ -156,7 +156,7 @@ const char *perf_reg_name(int id, uint16_t e_machine, uint32_t e_flags) break; case EM_386: case EM_X86_64: - reg_name = __perf_reg_name_x86(id); + reg_name = __perf_reg_name_x86(id, abi); break; default: break; @@ -252,3 +252,75 @@ uint64_t perf_arch_reg_sp(uint16_t e_machine) return 0; } } + +uint64_t perf_intr_simd_reg_class_mask(uint16_t e_machine, bool pred) +{ + switch (e_machine) { + case EM_386: + case EM_X86_64: + return __perf_simd_reg_class_mask_x86(/*intr=*/true, pred); + default: + return 0; + } +} + +uint64_t perf_user_simd_reg_class_mask(uint16_t e_machine, bool pred) +{ + switch (e_machine) { + case EM_386: + case EM_X86_64: + return __perf_simd_reg_class_mask_x86(/*intr=*/false, pred); + default: + return 0; + } +} + +uint64_t perf_intr_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c, + uint16_t *qwords, bool pred) +{ + switch (e_machine) { + case EM_386: + case EM_X86_64: + return __perf_simd_reg_class_bitmap_qwords_x86(reg_c, qwords, + /*intr=*/true, + pred); + default: + *qwords = 0; + return 0; + } +} + +uint64_t perf_user_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c, + uint16_t *qwords, bool pred) +{ + switch (e_machine) { + case EM_386: + case EM_X86_64: + return __perf_simd_reg_class_bitmap_qwords_x86(reg_c, qwords, + /*intr=*/false, + pred); + default: + *qwords = 0; + return 0; + } +} + +const char *perf_simd_reg_class_name(uint16_t e_machine, int id, bool pred) +{ + const char *name = NULL; + + switch (e_machine) { + case EM_386: + case EM_X86_64: + name = __perf_simd_reg_class_name_x86(id, pred); + break; + default: + break; + } + if (name) + return name; + + pr_debug("Failed to find %s register %d for ELF machine type %u\n", + pred ? "PRED" : "SIMD", id, e_machine); + return "unknown"; +} diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index 79be2b791509..c9f461b6f11f 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h @@ -13,13 +13,20 @@ enum { }; int perf_sdt_arg_parse_op(uint16_t e_machine, char *old_op, char **new_op); -uint64_t perf_intr_reg_mask(uint16_t e_machine); -uint64_t perf_user_reg_mask(uint16_t e_machine); +uint64_t perf_intr_reg_mask(uint16_t e_machine, int *abi /*inout*/); +uint64_t perf_user_reg_mask(uint16_t e_machine, int *abi /*inout*/); -const char *perf_reg_name(int id, uint16_t e_machine, uint32_t e_flags); +const char *perf_reg_name(int id, uint16_t e_machine, uint32_t e_flags, int abi); int perf_reg_value(u64 *valp, struct regs_dump *regs, int id); uint64_t perf_arch_reg_ip(uint16_t e_machine); uint64_t perf_arch_reg_sp(uint16_t e_machine); +uint64_t perf_intr_simd_reg_class_mask(uint16_t e_machine, bool pred); +uint64_t perf_user_simd_reg_class_mask(uint16_t e_machine, bool pred); +uint64_t perf_intr_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c, + uint16_t *qwords, bool pred); +uint64_t perf_user_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c, + uint16_t *qwords, bool pred); +const char *perf_simd_reg_class_name(uint16_t e_machine, int id, bool pred); int __perf_sdt_arg_parse_op_arm64(char *old_op, char **new_op); uint64_t __perf_reg_mask_arm64(bool intr); @@ -66,10 +73,14 @@ uint64_t __perf_reg_sp_s390(void); int __perf_sdt_arg_parse_op_s390(char *old_op, char **new_op); int __perf_sdt_arg_parse_op_x86(char *old_op, char **new_op); -uint64_t __perf_reg_mask_x86(bool intr); -const char *__perf_reg_name_x86(int id); +uint64_t __perf_reg_mask_x86(bool intr, int *abi); +const char *__perf_reg_name_x86(int id, int abi); uint64_t __perf_reg_ip_x86(void); uint64_t __perf_reg_sp_x86(void); +uint64_t __perf_simd_reg_class_mask_x86(bool intr, bool pred); +uint64_t __perf_simd_reg_class_bitmap_qwords_x86(int reg_c, uint16_t *qwords, + bool intr, bool pred); +const char *__perf_simd_reg_class_name_x86(int id, bool pred); static inline uint64_t DWARF_MINIMAL_REGS(uint16_t e_machine) { diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c index f80d6b0df47a..ad9bc481da70 100644 --- a/tools/perf/util/pfm.c +++ b/tools/perf/util/pfm.c @@ -141,6 +141,7 @@ static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpu return false; evsel->is_libpfm_event = true; + evsel->support_probe = true; ret = evsel__open(evsel, cpus, threads); if (ret == -EACCES) { diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 836e3b5615cd..01753d5c69d1 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -8,8 +8,11 @@ #include #include #include -#include +#include #include +#include +#include +#include #include #include #include @@ -2036,7 +2039,7 @@ int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_call } /** - * is_pmu_core() - Check if the given PMU name corresponds to a core CPU PMU. + * is_pmu_core() - Runtime check if the given PMU name corresponds to a core CPU PMU. * @name: The PMU name to check. * * Core PMUs can be identified by: @@ -2057,6 +2060,47 @@ bool is_pmu_core(const char *name) is_sysfs_pmu_core(name); } +/** + * is_pmu_core_len - Perf env based check if a given string prefix matches a core PMU name. + * @env: The perf_env to check within, NULL for the current machine. + * @name: The PMU name to check. + * @len: The length of the PMU name prefix in the string. + */ +bool is_pmu_core_len(struct perf_env *env, const char *name, size_t len) +{ + struct perf_pmu *pmu = NULL; + + if (env && env->hybrid_nodes) { + for (int i = 0; i < env->nr_hybrid_nodes; i++) { + const char *pmu_name = env->hybrid_nodes[i].pmu_name; + + if (strlen(pmu_name) == len && !strncmp(name, pmu_name, len)) + return true; + } + return false; + } + + /* + * No hybrid topology, either the machine isn't hybrid or the perf.data + * file was written by a perf lacking HEADER_HYBRID_TOPOLOGY. Fall back + * to the PMUs of this machine, which is only meaningful when the data + * was recorded on this architecture. Guessing PMU names from a prefix + * isn't portable, for example, big.LITTLE ARM and Apple core PMUs are + * named after their CPU rather than "cpu_core" and "cpu_atom". + */ + if (env && strcmp(perf_env__arch(env), perf_env__arch(/*env=*/NULL))) { + pr_debug("Can't identify core PMUs of a %s perf.data file recorded without hybrid topology\n", + perf_env__arch(env)); + return false; + } + + while ((pmu = perf_pmus__scan_core(pmu)) != NULL) { + if (strlen(pmu->name) == len && !strncmp(name, pmu->name, len)) + return true; + } + return false; +} + bool perf_pmu__supports_legacy_cache(const struct perf_pmu *pmu) { return pmu->is_core; diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 0d9f3c57e8e8..2c8916ad073e 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -16,6 +16,7 @@ struct evsel_config_term; struct hashmap; struct perf_cpu_map; +struct perf_env; struct print_callbacks; enum { @@ -289,6 +290,8 @@ int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_call u64 perf_pmu__format_unpack(unsigned long *format, u64 config_val); bool is_pmu_core(const char *name); +bool is_pmu_core_len(struct perf_env *env, const char *name, size_t len); + bool perf_pmu__supports_legacy_cache(const struct perf_pmu *pmu); bool perf_pmu__auto_merge_stats(const struct perf_pmu *pmu); bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name); diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c index e0a4cb2428ca..1355b317f3ed 100644 --- a/tools/perf/util/pmus.c +++ b/tools/perf/util/pmus.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include "cpumap.h" @@ -571,7 +572,7 @@ static int cmp_sevent(const void *a, const void *b) } /* Order by event name. */ - return strcmp(as->name, bs->name); + return strcmp(as->name ?: "", bs->name ?: ""); } static bool pmu_alias_is_duplicate(struct sevent *a, struct sevent *b) @@ -581,7 +582,7 @@ static bool pmu_alias_is_duplicate(struct sevent *a, struct sevent *b) return false; /* Don't remove duplicates for different PMUs */ - return strcmp(a->pmu_name, b->pmu_name) == 0; + return strcmp(a->pmu_name ?: "", b->pmu_name ?: "") == 0; } struct events_callback_state { @@ -597,8 +598,18 @@ static int perf_pmus__print_pmu_events__callback(void *vstate, struct sevent *s; if (state->index >= state->aliases_len) { - pr_err("Unexpected event %s/%s/\n", info->pmu->name, info->name); - return 1; + size_t new_len = max_t(size_t, 16, state->aliases_len * 2); + struct sevent *new_aliases; + + new_aliases = realloc(state->aliases, new_len * sizeof(struct sevent)); + if (!new_aliases) { + pr_err("Unexpected event %s/%s/\n", info->pmu->name, info->name); + return 1; + } + memset(&new_aliases[state->aliases_len], 0, + (new_len - state->aliases_len) * sizeof(struct sevent)); + state->aliases = new_aliases; + state->aliases_len = new_len; } assert(info->pmu != NULL || info->name != NULL); s = &state->aliases[state->index]; @@ -654,6 +665,8 @@ void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *p perf_pmu__for_each_event(pmu, skip_duplicate_pmus, &state, perf_pmus__print_pmu_events__callback); } + aliases = state.aliases; + len = state.index; qsort(aliases, len, sizeof(struct sevent), cmp_sevent); for (int j = 0; j < len; j++) { /* Skip duplicates */ diff --git a/tools/perf/util/powerpc-vpadtl.c b/tools/perf/util/powerpc-vpadtl.c index af6783cfdb53..032864e3884a 100644 --- a/tools/perf/util/powerpc-vpadtl.c +++ b/tools/perf/util/powerpc-vpadtl.c @@ -683,7 +683,7 @@ int powerpc_vpadtl_process_auxtrace_info(union perf_event *event, struct perf_session *session) { struct perf_record_auxtrace_info *auxtrace_info = &event->auxtrace_info; - size_t min_sz = sizeof(u64) * POWERPC_VPADTL_TYPE; + size_t min_sz = sizeof(u64) * (POWERPC_VPADTL_TYPE + 1); struct powerpc_vpadtl *vpa; int err; diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c index 0242243681b6..77428d9e73c4 100644 --- a/tools/perf/util/print-events.c +++ b/tools/perf/util/print-events.c @@ -147,6 +147,7 @@ bool is_event_supported(u8 type, u64 config) evsel = evsel__new(&attr); if (evsel) { + evsel->support_probe = true; ret = evsel__open(evsel, NULL, tmap) >= 0; if (!ret) { diff --git a/tools/perf/util/print_insn.h b/tools/perf/util/print_insn.h index cefa5c5f246e..3de9438af9cd 100644 --- a/tools/perf/util/print_insn.h +++ b/tools/perf/util/print_insn.h @@ -4,6 +4,7 @@ #include #include +#include #include diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 11ae4a09412c..1534280c0c1d 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -519,7 +519,7 @@ static struct debuginfo *open_from_debuginfod(struct dso *dso, struct nsinfo *ns pr_debug("Load debuginfo from debuginfod (%s)\n", path); nsinfo__mountns_enter(nsi, &nsc); - ret = debuginfo__new((const char *)path); + ret = debuginfo__from_path((const char *)path); nsinfo__mountns_exit(&nsc); return ret; } @@ -567,7 +567,7 @@ static struct debuginfo *open_debuginfo(const char *module, struct nsinfo *nsi, path = dso__long_name(dso); } nsinfo__mountns_enter(nsi, &nsc); - ret = debuginfo__new(path); + ret = debuginfo__from_path(path); if (!ret && !silent) { pr_warning("The %s file has no debug information.\n", path); if (!module || !strtailcmp(path, ".ko")) diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index d8a842621cda..60dbe2dad5eb 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -2,11 +2,18 @@ #define PY_SSIZE_T_CLEAN #include +#include +#include #include +#include +#include +#include #include +#include #include #include +#include #include #include @@ -22,12 +29,17 @@ #include "counts.h" #include "data.h" #include "debug.h" +#include "call-path.h" +#include "db-export.h" +#include "thread-stack.h" #include "dso.h" #include "dwarf-regs.h" #include "event.h" +#include "env.h" #include "branch.h" #include "evlist.h" #include "evsel.h" +#include "symbol.h" #include "expr.h" #include "map.h" #include "metricgroup.h" @@ -40,6 +52,7 @@ #include "srccode.h" #include "srcline.h" #include "strbuf.h" +#include "strlist.h" #include "symbol.h" #include "stat.h" #include "header.h" @@ -58,16 +71,6 @@ PyMODINIT_FUNC PyInit_perf(void); static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel); -#define member_def(type, member, ptype, help) \ - { #member, ptype, \ - offsetof(struct pyrf_event, event) + offsetof(struct type, member), \ - 0, help } - -#define sample_member_def(name, member, ptype, help) \ - { #name, ptype, \ - offsetof(struct pyrf_event, sample) + offsetof(struct perf_sample, member), \ - 0, help } - #define CHECK_INITIALIZED(ptr, msg) \ do { \ if (!(ptr)) { \ @@ -86,48 +89,110 @@ static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel); struct pyrf_event { PyObject_HEAD - /** @sample: The parsed sample from the event. */ - struct perf_sample sample; + /** @sample: The parsed sample, pointing to @sample_storage or a borrowed sample. */ + struct perf_sample *sample; + /** @sample_storage: Storage for @sample when owned by this object. */ + struct perf_sample sample_storage; + /** @evsel: The evsel associated with the event. */ + struct evsel *evsel; + /** @machine: The machine associated with the sample/event. */ + struct machine *machine; /** @al: The address location from machine__resolve, lazily computed. */ struct addr_location al; /** @al_resolved: True when machine__resolve been called. */ bool al_resolved; - /** @callchain: Resolved callchain, eagerly computed if requested. */ + /** @callchain: Resolved callchain, lazily computed. */ PyObject *callchain; /** @brstack: Resolved branch stack, eagerly computed if requested. */ PyObject *brstack; - /** @event: The underlying perf_event that may be in a file or ring buffer. */ - union perf_event event; + /** @event: The underlying perf_event, pointing to @event_copy or a borrowed event. */ + const union perf_event *event; + /** @event_copy: Copy of @event when owned by this object. */ + union perf_event *event_copy; }; -#define sample_members \ - sample_member_def(sample_pid, pid, T_INT, "event pid"), \ - sample_member_def(sample_tid, tid, T_INT, "event tid"), \ - sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \ - sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \ - sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \ - sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \ - sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"), +static const union perf_event zero_event; + +static PyObject *pyrf_event__get_member(PyObject *self, void *closure) +{ + struct pyrf_event *pevent = (void *)self; + const union perf_event *event = pevent->event ?: &zero_event; + + return PyMember_GetOne((const char *)event, closure); +} + +static PyObject *pyrf_event__get_sample_member(PyObject *self, void *closure) +{ + struct pyrf_event *pevent = (void *)self; + const struct perf_sample *sample = pevent->sample ?: &pevent->sample_storage; + + return PyMember_GetOne((const char *)sample, closure); +} + +#define named_member_def(field, type, member, ptype, help) \ + { .name = #field, .get = pyrf_event__get_member, .doc = help, \ + .closure = &(struct PyMemberDef){ #field, ptype, \ + offsetof(struct type, member), READONLY, help } } + +#define member_def(type, member, ptype, help) \ + named_member_def(member, type, member, ptype, help) + +struct pyrf_string_member { + size_t offset; + size_t maxlen; +}; + +static size_t pyrf_event__str_len(const struct pyrf_event *pevent, size_t offset, size_t maxlen) +{ + const char *base = (const char *)(pevent->event ?: &zero_event); + size_t event_size = pevent->event ? pevent->event->header.size : 0; + + if (event_size <= offset) + return 0; + return strnlen(base + offset, min(event_size - offset, maxlen)); +} + +static PyObject *pyrf_event__get_string_member(PyObject *self, void *closure) +{ + struct pyrf_event *pevent = (void *)self; + const struct pyrf_string_member *sm = closure; + const char *base = (const char *)(pevent->event ?: &zero_event); + + return PyUnicode_FromStringAndSize(base + sm->offset, + pyrf_event__str_len(pevent, sm->offset, sm->maxlen)); +} + +#define string_member_def(type, member, help) \ + { .name = #member, .get = pyrf_event__get_string_member, .doc = help, \ + .closure = &(struct pyrf_string_member){ offsetof(struct type, member), \ + sizeof(((struct type *)0)->member) } } + +#define sample_member_def(field, member, ptype, help) \ + { .name = #field, .get = pyrf_event__get_sample_member, .doc = help, \ + .closure = &(struct PyMemberDef){ #field, ptype, \ + offsetof(struct perf_sample, member), READONLY, help } } static PyObject *pyrf_event__get_evsel(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (!pevent->sample.evsel) + if (!pevent->sample || !pevent->sample->evsel) Py_RETURN_NONE; - return pyrf_evsel__from_evsel(pevent->sample.evsel); + return pyrf_evsel__from_evsel(pevent->sample->evsel); } -static PyGetSetDef pyrf_event__getset[] = { - { - .name = "evsel", - .get = pyrf_event__get_evsel, - .set = NULL, - .doc = "tracking event.", - }, - { .name = NULL, }, -}; +#define sample_members \ + { .name = "evsel", .get = pyrf_event__get_evsel, .doc = "tracking event." }, \ + sample_member_def(sample_pid, pid, T_INT, "event pid"), \ + sample_member_def(sample_tid, tid, T_INT, "event tid"), \ + sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \ + sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \ + sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \ + sample_member_def(machine_pid, machine_pid, T_UINT, "event machine pid"), \ + sample_member_def(vcpu, vcpu, T_UINT, "event vcpu"), \ + sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \ + sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"), static void pyrf_event__delete(struct pyrf_event *pevent) { @@ -135,13 +200,15 @@ static void pyrf_event__delete(struct pyrf_event *pevent) addr_location__exit(&pevent->al); Py_XDECREF(pevent->callchain); Py_XDECREF(pevent->brstack); - perf_sample__exit(&pevent->sample); + perf_sample__exit(&pevent->sample_storage); + evsel__put(pevent->evsel); + free(pevent->event_copy); Py_TYPE(pevent)->tp_free((PyObject *)pevent); } static const char pyrf_mmap_event__doc[] = PyDoc_STR("perf mmap event object."); -static PyMemberDef pyrf_mmap_event__members[] = { +static PyGetSetDef pyrf_mmap_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_event_header, misc, T_USHORT, "event misc"), @@ -150,7 +217,7 @@ static PyMemberDef pyrf_mmap_event__members[] = { member_def(perf_record_mmap, start, T_ULONGLONG, "start of the map"), member_def(perf_record_mmap, len, T_ULONGLONG, "map length"), member_def(perf_record_mmap, pgoff, T_ULONGLONG, "page offset"), - member_def(perf_record_mmap, filename, T_STRING_INPLACE, "backing store"), + string_member_def(perf_record_mmap, filename, "backing store"), { .name = NULL, }, }; @@ -158,13 +225,15 @@ static PyObject *pyrf_mmap_event__repr(const struct pyrf_event *pevent) { PyObject *ret; char *s; + int fn_len = pyrf_event__str_len(pevent, offsetof(struct perf_record_mmap, filename), + sizeof(pevent->event->mmap.filename)); if (asprintf(&s, "{ type: mmap, pid: %u, tid: %u, start: %#" PRI_lx64 ", " "length: %#" PRI_lx64 ", offset: %#" PRI_lx64 ", " - "filename: %s }", - pevent->event.mmap.pid, pevent->event.mmap.tid, - pevent->event.mmap.start, pevent->event.mmap.len, - pevent->event.mmap.pgoff, pevent->event.mmap.filename) < 0) { + "filename: %.*s }", + pevent->event->mmap.pid, pevent->event->mmap.tid, + pevent->event->mmap.start, pevent->event->mmap.len, + pevent->event->mmap.pgoff, fn_len, pevent->event->mmap.filename) < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -180,8 +249,7 @@ static PyTypeObject pyrf_mmap_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_mmap_event__doc, - .tp_members = pyrf_mmap_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_mmap_event__getset, .tp_repr = (reprfunc)pyrf_mmap_event__repr, }; @@ -191,64 +259,69 @@ static PyObject *pyrf_mmap2_event__get_maj(PyObject *self, void *closure __maybe { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLong(pevent->event.mmap2.maj); + return PyLong_FromUnsignedLong(pevent->event->mmap2.maj); } static PyObject *pyrf_mmap2_event__get_min(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLong(pevent->event.mmap2.min); + return PyLong_FromUnsignedLong(pevent->event->mmap2.min); } static PyObject *pyrf_mmap2_event__get_ino(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLongLong(pevent->event.mmap2.ino); + return PyLong_FromUnsignedLongLong(pevent->event->mmap2.ino); } static PyObject *pyrf_mmap2_event__get_ino_generation(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLongLong(pevent->event.mmap2.ino_generation); + return PyLong_FromUnsignedLongLong(pevent->event->mmap2.ino_generation); } static PyObject *pyrf_mmap2_event__get_build_id(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (!(pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID)) + if (!(pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID)) Py_RETURN_NONE; - int size = pevent->event.mmap2.build_id_size; + int size = pevent->event->mmap2.build_id_size; if (size > 20) size = 20; - return PyBytes_FromStringAndSize((const char *)pevent->event.mmap2.build_id, size); + return PyBytes_FromStringAndSize((const char *)pevent->event->mmap2.build_id, size); } static PyGetSetDef pyrf_mmap2_event__getset[] = { - { - .name = "evsel", - .get = pyrf_event__get_evsel, - .set = NULL, - .doc = "tracking event.", - }, + sample_members + member_def(perf_event_header, type, T_UINT, "event type"), + member_def(perf_event_header, misc, T_USHORT, "event misc"), + member_def(perf_record_mmap2, pid, T_UINT, "event pid"), + member_def(perf_record_mmap2, tid, T_UINT, "event tid"), + member_def(perf_record_mmap2, start, T_ULONGLONG, "start of the map"), + member_def(perf_record_mmap2, len, T_ULONGLONG, "map length"), + member_def(perf_record_mmap2, pgoff, T_ULONGLONG, "page offset"), + member_def(perf_record_mmap2, prot, T_UINT, "protection"), + member_def(perf_record_mmap2, flags, T_UINT, "flags"), + string_member_def(perf_record_mmap2, filename, "backing store"), { .name = "maj", .get = pyrf_mmap2_event__get_maj, @@ -282,31 +355,18 @@ static PyGetSetDef pyrf_mmap2_event__getset[] = { { .name = NULL, }, }; -static PyMemberDef pyrf_mmap2_event__members[] = { - sample_members - member_def(perf_event_header, type, T_UINT, "event type"), - member_def(perf_event_header, misc, T_USHORT, "event misc"), - member_def(perf_record_mmap2, pid, T_UINT, "event pid"), - member_def(perf_record_mmap2, tid, T_UINT, "event tid"), - member_def(perf_record_mmap2, start, T_ULONGLONG, "start of the map"), - member_def(perf_record_mmap2, len, T_ULONGLONG, "map length"), - member_def(perf_record_mmap2, pgoff, T_ULONGLONG, "page offset"), - member_def(perf_record_mmap2, prot, T_UINT, "protection"), - member_def(perf_record_mmap2, flags, T_UINT, "flags"), - member_def(perf_record_mmap2, filename, T_STRING_INPLACE, "backing store"), - { .name = NULL, }, -}; - static PyObject *pyrf_mmap2_event__repr(const struct pyrf_event *pevent) { PyObject *ret; char *s; + int fn_len = pyrf_event__str_len(pevent, offsetof(struct perf_record_mmap2, filename), + sizeof(pevent->event->mmap2.filename)); - if (asprintf(&s, "{ type: mmap2, pid: %u, tid: %u, start: %#" PRI_lx64 ", length: %#" PRI_lx64 ", offset: %#" PRI_lx64 ", flags: %#x, prot: %#x, filename: %s }", - pevent->event.mmap2.pid, pevent->event.mmap2.tid, - pevent->event.mmap2.start, pevent->event.mmap2.len, - pevent->event.mmap2.pgoff, pevent->event.mmap2.flags, - pevent->event.mmap2.prot, pevent->event.mmap2.filename) < 0) + if (asprintf(&s, "{ type: mmap2, pid: %u, tid: %u, start: %#" PRI_lx64 ", length: %#" PRI_lx64 ", offset: %#" PRI_lx64 ", flags: %#x, prot: %#x, filename: %.*s }", + pevent->event->mmap2.pid, pevent->event->mmap2.tid, + pevent->event->mmap2.start, pevent->event->mmap2.len, + pevent->event->mmap2.pgoff, pevent->event->mmap2.flags, + pevent->event->mmap2.prot, fn_len, pevent->event->mmap2.filename) < 0) return PyErr_NoMemory(); ret = PyUnicode_FromString(s); @@ -321,14 +381,13 @@ static PyTypeObject pyrf_mmap2_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_mmap2_event__doc, - .tp_members = pyrf_mmap2_event__members, .tp_getset = pyrf_mmap2_event__getset, .tp_repr = (reprfunc)pyrf_mmap2_event__repr, }; static const char pyrf_task_event__doc[] = PyDoc_STR("perf task (fork/exit) event object."); -static PyMemberDef pyrf_task_event__members[] = { +static PyGetSetDef pyrf_task_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_fork, pid, T_UINT, "event pid"), @@ -343,12 +402,12 @@ static PyObject *pyrf_task_event__repr(const struct pyrf_event *pevent) { return PyUnicode_FromFormat("{ type: %s, pid: %u, ppid: %u, tid: %u, " "ptid: %u, time: %" PRI_lu64 "}", - pevent->event.header.type == PERF_RECORD_FORK ? "fork" : "exit", - pevent->event.fork.pid, - pevent->event.fork.ppid, - pevent->event.fork.tid, - pevent->event.fork.ptid, - pevent->event.fork.time); + pevent->event->header.type == PERF_RECORD_FORK ? "fork" : "exit", + pevent->event->fork.pid, + pevent->event->fork.ppid, + pevent->event->fork.tid, + pevent->event->fork.ptid, + pevent->event->fork.time); } static PyTypeObject pyrf_task_event__type = { @@ -358,28 +417,36 @@ static PyTypeObject pyrf_task_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_task_event__doc, - .tp_members = pyrf_task_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_task_event__getset, .tp_repr = (reprfunc)pyrf_task_event__repr, }; static const char pyrf_comm_event__doc[] = PyDoc_STR("perf comm event object."); -static PyMemberDef pyrf_comm_event__members[] = { +static PyGetSetDef pyrf_comm_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_comm, pid, T_UINT, "event pid"), member_def(perf_record_comm, tid, T_UINT, "event tid"), - member_def(perf_record_comm, comm, T_STRING_INPLACE, "process name"), + string_member_def(perf_record_comm, comm, "process name"), { .name = NULL, }, }; static PyObject *pyrf_comm_event__repr(const struct pyrf_event *pevent) { - return PyUnicode_FromFormat("{ type: comm, pid: %u, tid: %u, comm: %s }", - pevent->event.comm.pid, - pevent->event.comm.tid, - pevent->event.comm.comm); + PyObject *ret; + char *s; + int comm_len = pyrf_event__str_len(pevent, offsetof(struct perf_record_comm, comm), + sizeof(pevent->event->comm.comm)); + + if (asprintf(&s, "{ type: comm, pid: %u, tid: %u, comm: %.*s }", + pevent->event->comm.pid, pevent->event->comm.tid, + comm_len, pevent->event->comm.comm) < 0) + return PyErr_NoMemory(); + + ret = PyUnicode_FromString(s); + free(s); + return ret; } static PyTypeObject pyrf_comm_event__type = { @@ -389,14 +456,13 @@ static PyTypeObject pyrf_comm_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_comm_event__doc, - .tp_members = pyrf_comm_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_comm_event__getset, .tp_repr = (reprfunc)pyrf_comm_event__repr, }; static const char pyrf_throttle_event__doc[] = PyDoc_STR("perf throttle event object."); -static PyMemberDef pyrf_throttle_event__members[] = { +static PyGetSetDef pyrf_throttle_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_throttle, time, T_ULONGLONG, "timestamp"), @@ -408,11 +474,11 @@ static PyMemberDef pyrf_throttle_event__members[] = { static PyObject *pyrf_throttle_event__repr(const struct pyrf_event *pevent) { const struct perf_record_throttle *te = (const struct perf_record_throttle *) - (&pevent->event.header + 1); + (&pevent->event->header + 1); return PyUnicode_FromFormat("{ type: %sthrottle, time: %" PRI_lu64 ", id: %" PRI_lu64 ", stream_id: %" PRI_lu64 " }", - pevent->event.header.type == PERF_RECORD_THROTTLE ? "" : "un", + pevent->event->header.type == PERF_RECORD_THROTTLE ? "" : "un", te->time, te->id, te->stream_id); } @@ -423,14 +489,13 @@ static PyTypeObject pyrf_throttle_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_throttle_event__doc, - .tp_members = pyrf_throttle_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_throttle_event__getset, .tp_repr = (reprfunc)pyrf_throttle_event__repr, }; static const char pyrf_lost_event__doc[] = PyDoc_STR("perf lost event object."); -static PyMemberDef pyrf_lost_event__members[] = { +static PyGetSetDef pyrf_lost_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_lost, id, T_ULONGLONG, "event id"), @@ -445,7 +510,7 @@ static PyObject *pyrf_lost_event__repr(const struct pyrf_event *pevent) if (asprintf(&s, "{ type: lost, id: %#" PRI_lx64 ", " "lost: %#" PRI_lx64 " }", - pevent->event.lost.id, pevent->event.lost.lost) < 0) { + pevent->event->lost.id, pevent->event->lost.lost) < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -461,14 +526,13 @@ static PyTypeObject pyrf_lost_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_lost_event__doc, - .tp_members = pyrf_lost_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_lost_event__getset, .tp_repr = (reprfunc)pyrf_lost_event__repr, }; static const char pyrf_stat_event__doc[] = PyDoc_STR("perf stat event object."); -static PyMemberDef pyrf_stat_event__members[] = { +static PyGetSetDef pyrf_stat_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_stat, id, T_ULONGLONG, "event id"), @@ -482,14 +546,16 @@ static PyMemberDef pyrf_stat_event__members[] = { static PyObject *pyrf_stat_event__repr(const struct pyrf_event *pevent) { + const union perf_event *event = pevent->event ?: &zero_event; + return PyUnicode_FromFormat( "{ type: stat, id: %llu, cpu: %u, thread: %u, val: %llu, ena: %llu, run: %llu }", - pevent->event.stat.id, - pevent->event.stat.cpu, - pevent->event.stat.thread, - pevent->event.stat.val, - pevent->event.stat.ena, - pevent->event.stat.run); + event->stat.id, + event->stat.cpu, + event->stat.thread, + event->stat.val, + event->stat.ena, + event->stat.run); } static PyTypeObject pyrf_stat_event__type = { @@ -500,28 +566,27 @@ static PyTypeObject pyrf_stat_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_stat_event__doc, - .tp_members = pyrf_stat_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_stat_event__getset, .tp_repr = (reprfunc)pyrf_stat_event__repr, }; static const char pyrf_stat_round_event__doc[] = PyDoc_STR("perf stat round event object."); -static PyMemberDef pyrf_stat_round_event__members[] = { +static PyGetSetDef pyrf_stat_round_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), - { .name = "stat_round_type", .type = T_ULONGLONG, - .offset = offsetof(struct pyrf_event, event) + offsetof(struct perf_record_stat_round, type), - .doc = "round type" }, + named_member_def(stat_round_type, perf_record_stat_round, type, T_ULONGLONG, "round type"), member_def(perf_record_stat_round, time, T_ULONGLONG, "round time"), { .name = NULL, }, }; static PyObject *pyrf_stat_round_event__repr(const struct pyrf_event *pevent) { + const union perf_event *event = pevent->event ?: &zero_event; + return PyUnicode_FromFormat("{ type: stat_round, type: %llu, time: %llu }", - pevent->event.stat_round.type, - pevent->event.stat_round.time); + event->stat_round.type, + event->stat_round.time); } static PyTypeObject pyrf_stat_round_event__type = { @@ -532,14 +597,13 @@ static PyTypeObject pyrf_stat_round_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_stat_round_event__doc, - .tp_members = pyrf_stat_round_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_stat_round_event__getset, .tp_repr = (reprfunc)pyrf_stat_round_event__repr, }; static const char pyrf_read_event__doc[] = PyDoc_STR("perf read event object."); -static PyMemberDef pyrf_read_event__members[] = { +static PyGetSetDef pyrf_read_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_read, pid, T_UINT, "event pid"), @@ -550,8 +614,8 @@ static PyMemberDef pyrf_read_event__members[] = { static PyObject *pyrf_read_event__repr(const struct pyrf_event *pevent) { return PyUnicode_FromFormat("{ type: read, pid: %u, tid: %u }", - pevent->event.read.pid, - pevent->event.read.tid); + pevent->event->read.pid, + pevent->event->read.tid); /* * FIXME: return the array of read values, * making this method useful ;-) @@ -565,26 +629,12 @@ static PyTypeObject pyrf_read_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_read_event__doc, - .tp_members = pyrf_read_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_read_event__getset, .tp_repr = (reprfunc)pyrf_read_event__repr, }; static const char pyrf_sample_event__doc[] = PyDoc_STR("perf sample event object."); -static PyMemberDef pyrf_sample_event__members[] = { - sample_members - sample_member_def(sample_ip, ip, T_ULONGLONG, "event ip"), - sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"), - sample_member_def(sample_phys_addr, phys_addr, T_ULONGLONG, "event physical addr"), - sample_member_def(sample_weight, weight, T_ULONGLONG, "event weight"), - sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"), - sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"), - sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"), - member_def(perf_event_header, type, T_UINT, "event type"), - { .name = NULL, }, -}; - static PyObject *pyrf_sample_event__repr(const struct pyrf_event *pevent) { PyObject *ret; @@ -602,16 +652,18 @@ static PyObject *pyrf_sample_event__repr(const struct pyrf_event *pevent) #ifdef HAVE_LIBTRACEEVENT static bool is_tracepoint(const struct pyrf_event *pevent) { - if (!pevent->sample.evsel) + struct evsel *evsel = pevent->sample->evsel ?: pevent->evsel; + + if (!evsel) return false; - return pevent->sample.evsel->core.attr.type == PERF_TYPE_TRACEPOINT; + return evsel->core.attr.type == PERF_TYPE_TRACEPOINT; } static PyObject* tracepoint_field(const struct pyrf_event *pe, struct tep_format_field *field) { struct tep_handle *pevent = field->event->tep; - void *data = pe->sample.raw_data; + void *data = pe->sample->raw_data; PyObject *ret = NULL; unsigned long long val; unsigned int offset, len; @@ -651,7 +703,7 @@ tracepoint_field(const struct pyrf_event *pe, struct tep_format_field *field) static PyObject* get_tracepoint_field(struct pyrf_event *pevent, PyObject *attr_name) { - struct evsel *evsel = pevent->sample.evsel; + struct evsel *evsel = pevent->sample->evsel ?: pevent->evsel; struct tep_event *tp_format = evsel__tp_format(evsel); struct tep_format_field *field; @@ -676,18 +728,19 @@ get_tracepoint_field(struct pyrf_event *pevent, PyObject *attr_name) static int pyrf_sample_event__resolve_al(struct pyrf_event *pevent) { - struct evsel *evsel = pevent->sample.evsel; - struct evlist *evlist = evsel ? evsel->evlist : NULL; - struct perf_session *session = evlist ? evlist__session(evlist) : NULL; - if (pevent->al_resolved) return 0; - if (!session) + /* + * Use pevent->machine, which pyrf_event__new() initializes while the + * session callback is active and pyrf_event__copy() clears after + * resolving pevent->al if a reference is retained beyond the callback. + */ + if (!pevent->machine) return -1; addr_location__init(&pevent->al); - if (machine__resolve(&session->machines.host, &pevent->al, &pevent->sample) < 0) { + if (machine__resolve(pevent->machine, &pevent->al, pevent->sample) < 0) { addr_location__exit(&pevent->al); return -1; } @@ -771,6 +824,15 @@ static PyObject *pyrf_sample_event__get_sym_start(struct pyrf_event *pevent, return PyLong_FromUnsignedLongLong(pevent->al.sym->start); } +static PyObject *pyrf_sample_event__get_sym_offset(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.sym) + Py_RETURN_NONE; + + return PyLong_FromUnsignedLongLong(pevent->al.addr - pevent->al.sym->start); +} + static PyObject *pyrf_sample_event__get_sym_end(struct pyrf_event *pevent, void *closure __maybe_unused) { @@ -783,17 +845,17 @@ static PyObject *pyrf_sample_event__get_sym_end(struct pyrf_event *pevent, static PyObject *pyrf_sample_event__get_raw_buf(struct pyrf_event *pevent, void *closure __maybe_unused) { - if (pevent->event.header.type != PERF_RECORD_SAMPLE) + if (pevent->event->header.type != PERF_RECORD_SAMPLE) Py_RETURN_NONE; - return PyBytes_FromStringAndSize((const char *)pevent->sample.raw_data, - pevent->sample.raw_size); + return PyBytes_FromStringAndSize((const char *)pevent->sample->raw_data, + pevent->sample->raw_size); } static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) { struct pyrf_event *pevent = (void *)self; - u64 addr = pevent->sample.ip; + u64 addr = pevent->sample->ip; char *srcfile = NULL; char *srccode = NULL; unsigned int line = 0; @@ -801,15 +863,21 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) PyObject *result; struct addr_location al; + /* + * addr defaults to pevent->sample->ip when omitted, and when + * addr != pevent->sample->ip a local addr_location is resolved so + * callers can inspect callchain/branch addresses without mutating + * the sample's cached pevent->al. + */ if (!PyArg_ParseTuple(args, "|K", &addr)) return NULL; if (pyrf_sample_event__resolve_al(pevent) < 0) Py_RETURN_NONE; - if (addr != pevent->sample.ip) { + if (addr != pevent->sample->ip) { addr_location__init(&al); - thread__find_symbol_fb(pevent->al.thread, pevent->sample.cpumode, addr, &al); + thread__find_symbol_fb(pevent->al.thread, pevent->sample->cpumode, addr, &al); } else { addr_location__init(&al); al.thread = thread__get(pevent->al.thread); @@ -829,6 +897,12 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) addr_location__exit(&al); if (srcfile) { + /* + * PyUnicode_FromStringAndSize(line, len) (via Py_BuildValue "s#") + * must copy the source line before free_srccode() frees the + * buffer, and because the line returned by find_sourceline() is + * bounded by len rather than NUL-terminated. + */ srccode = find_sourceline(srcfile, line, &len); result = Py_BuildValue("(sIs#)", srcfile, line, srccode, (Py_ssize_t)len); free(srcfile); @@ -842,29 +916,21 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) static PyObject *pyrf_sample_event__insn(PyObject *self, PyObject *args __maybe_unused) { struct pyrf_event *pevent = (void *)self; - struct thread *thread; - struct machine *machine; - if (pyrf_sample_event__resolve_al(pevent) < 0) + if (pevent->sample->ip && !pevent->sample->insn_len) { + if (pyrf_sample_event__resolve_al(pevent) < 0 || + !pevent->al.thread || !pevent->machine) + Py_RETURN_NONE; + + perf_sample__fetch_insn(pevent->sample, pevent->al.thread, + pevent->machine); + } + + if (!pevent->sample->insn_len) Py_RETURN_NONE; - thread = pevent->al.thread; - - if (!thread || !thread__maps(thread)) - Py_RETURN_NONE; - - machine = maps__machine(thread__maps(thread)); - if (!machine) - Py_RETURN_NONE; - - if (pevent->sample.ip && !pevent->sample.insn_len) - perf_sample__fetch_insn(&pevent->sample, thread, machine); - - if (!pevent->sample.insn_len) - Py_RETURN_NONE; - - return PyBytes_FromStringAndSize((const char *)pevent->sample.insn, - pevent->sample.insn_len); + return PyBytes_FromStringAndSize((const char *)pevent->sample->insn, + pevent->sample->insn_len); } struct pyrf_callchain_node { @@ -872,11 +938,13 @@ struct pyrf_callchain_node { u64 ip; struct map *map; struct symbol *sym; + char *sym_name; }; static void pyrf_callchain_node__delete(struct pyrf_callchain_node *pnode) { map__put(pnode->map); + free(pnode->sym_name); Py_TYPE(pnode)->tp_free((PyObject *)pnode); } @@ -889,6 +957,8 @@ static PyObject *pyrf_callchain_node__get_ip(struct pyrf_callchain_node *pnode, static PyObject *pyrf_callchain_node__get_symbol(struct pyrf_callchain_node *pnode, void *closure __maybe_unused) { + if (pnode->sym_name) + return PyUnicode_FromString(pnode->sym_name); if (pnode->sym) return PyUnicode_FromString(pnode->sym->name); return PyUnicode_FromString("[unknown]"); @@ -912,10 +982,23 @@ static PyObject *pyrf_callchain_node__get_dso(struct pyrf_callchain_node *pnode, return PyUnicode_FromString(dsoname); } +static PyObject *pyrf_callchain_node__get_sym_offset(struct pyrf_callchain_node *pnode, + void *closure __maybe_unused) +{ + u64 addr; + + if (!pnode->sym) + Py_RETURN_NONE; + + addr = pnode->map ? map__map_ip(pnode->map, pnode->ip) : pnode->ip; + return PyLong_FromUnsignedLongLong(addr - pnode->sym->start); +} + static PyGetSetDef pyrf_callchain_node__getset[] = { - { .name = "ip", .get = (getter)pyrf_callchain_node__get_ip, }, - { .name = "symbol", .get = (getter)pyrf_callchain_node__get_symbol, }, - { .name = "dso", .get = (getter)pyrf_callchain_node__get_dso, }, + { .name = "ip", .get = (getter)pyrf_callchain_node__get_ip, }, + { .name = "symbol", .get = (getter)pyrf_callchain_node__get_symbol, }, + { .name = "dso", .get = (getter)pyrf_callchain_node__get_dso, }, + { .name = "sym_offset", .get = (getter)pyrf_callchain_node__get_sym_offset, }, { .name = NULL, }, }; @@ -933,6 +1016,7 @@ struct pyrf_callchain_frame { u64 ip; struct map *map; struct symbol *sym; + char *sym_name; }; struct pyrf_callchain { @@ -944,8 +1028,10 @@ struct pyrf_callchain { static void pyrf_callchain__delete(struct pyrf_callchain *pchain) { if (pchain->frames) { - for (u64 i = 0; i < pchain->nr_frames; i++) + for (u64 i = 0; i < pchain->nr_frames; i++) { map__put(pchain->frames[i].map); + free(pchain->frames[i].sym_name); + } free(pchain->frames); } Py_TYPE(pchain)->tp_free((PyObject *)pchain); @@ -975,6 +1061,7 @@ static PyObject *pyrf_callchain__item(PyObject *obj, Py_ssize_t i) pnode->ip = pchain->frames[i].ip; pnode->map = map__get(pchain->frames[i].map); pnode->sym = pchain->frames[i].sym; + pnode->sym_name = pchain->frames[i].sym_name ? strdup(pchain->frames[i].sym_name) : NULL; return (PyObject *)pnode; } @@ -994,10 +1081,53 @@ static PyTypeObject pyrf_callchain__type = { .tp_as_sequence = &pyrf_callchain__sequence_methods, }; +static int pyrf_sample_event__resolve_callchain(struct pyrf_event *pevent) +{ + struct callchain_cursor *cursor; + struct pyrf_callchain *pchain; + struct callchain_cursor_node *node; + + if (pevent->callchain || !pevent->sample->callchain || !pevent->machine) + return 0; + + if (pyrf_sample_event__resolve_al(pevent) < 0) + return 0; + + cursor = get_tls_callchain_cursor(); + if (thread__resolve_callchain(pevent->al.thread, cursor, pevent->sample, + NULL, NULL, PERF_MAX_STACK_DEPTH) != 0) + return 0; + + callchain_cursor_commit(cursor); + pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type); + if (!pchain) + return -ENOMEM; + + pchain->nr_frames = cursor->nr; + pchain->frames = calloc(pchain->nr_frames, sizeof(*pchain->frames)); + if (!pchain->frames) { + Py_DECREF(pchain); + PyErr_NoMemory(); + return -ENOMEM; + } + for (u64 i = 0; i < pchain->nr_frames; i++) { + node = callchain_cursor_current(cursor); + pchain->frames[i].ip = node->ip; + pchain->frames[i].map = map__get(node->ms.map); + pchain->frames[i].sym = node->ms.sym; + callchain_cursor_advance(cursor); + } + pevent->callchain = (PyObject *)pchain; + return 0; +} + static PyObject *pyrf_sample_event__get_callchain(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; + if (pyrf_sample_event__resolve_callchain(pevent) < 0) + return NULL; + if (!pevent->callchain) Py_RETURN_NONE; @@ -1166,7 +1296,123 @@ pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name) return obj ?: PyObject_GenericGetAttr((PyObject *) pevent, attr_name); } +static int pyrf_sample_event__resolve_addr_al(struct pyrf_event *pevent, + struct addr_location *addr_al) +{ + addr_location__init(addr_al); + if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.thread) + return -1; + + thread__find_symbol_fb(pevent->al.thread, pevent->sample->cpumode, + pevent->sample->addr, addr_al); + return 0; +} + +static PyObject *pyrf_sample_event__get_addr_dso(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.map) + ret = PyUnicode_FromString(dso__name(map__dso(addr_al.map))); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_addr_symbol(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.sym) + ret = PyUnicode_FromString(addr_al.sym->name); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_addr_sym_offset(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.sym) + ret = PyLong_FromUnsignedLongLong(addr_al.addr - addr_al.sym->start); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_branch_type(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLong(pevent->sample->flags & PERF_BRANCH_MASK); +} + +static PyObject *pyrf_sample_event__get_in_tx(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLong(!!(pevent->sample->flags & PERF_IP_FLAG_IN_TX)); +} + +static PyObject *pyrf_sample_event__get_transaction(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pevent->sample->transaction); +} + static PyGetSetDef pyrf_sample_event__getset[] = { + sample_members + sample_member_def(sample_ip, ip, T_ULONGLONG, "event ip"), + sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"), + sample_member_def(sample_phys_addr, phys_addr, T_ULONGLONG, "event physical addr"), + sample_member_def(sample_weight, weight, T_ULONGLONG, "event weight"), + sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"), + sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"), + sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"), + sample_member_def(flags, flags, T_UINT, "event flags"), + member_def(perf_event_header, type, T_UINT, "event type"), + { + .name = "addr_dso", + .get = (getter)pyrf_sample_event__get_addr_dso, + .doc = "event destination dso.", + }, + { + .name = "addr_symbol", + .get = (getter)pyrf_sample_event__get_addr_symbol, + .doc = "event destination symbol.", + }, + { + .name = "addr_sym_offset", + .get = (getter)pyrf_sample_event__get_addr_sym_offset, + .doc = "event destination symbol offset.", + }, + { + .name = "branch_type", + .get = (getter)pyrf_sample_event__get_branch_type, + .doc = "branch type.", + }, + { + .name = "in_tx", + .get = (getter)pyrf_sample_event__get_in_tx, + .doc = "in transaction flag.", + }, + { + .name = "transaction", + .get = (getter)pyrf_sample_event__get_transaction, + .doc = "transaction execution.", + }, + { .name = "callchain", .get = pyrf_sample_event__get_callchain, @@ -1185,12 +1431,6 @@ static PyGetSetDef pyrf_sample_event__getset[] = { .set = NULL, .doc = "event raw buffer.", }, - { - .name = "evsel", - .get = pyrf_event__get_evsel, - .set = NULL, - .doc = "tracking event.", - }, { .name = "dso", .get = (getter)pyrf_sample_event__get_dso, @@ -1227,6 +1467,12 @@ static PyGetSetDef pyrf_sample_event__getset[] = { .set = NULL, .doc = "event map page offset.", }, + { + .name = "sym_offset", + .get = (getter)pyrf_sample_event__get_sym_offset, + .set = NULL, + .doc = "event symbol offset.", + }, { .name = "symbol", .get = (getter)pyrf_sample_event__get_symbol, @@ -1271,7 +1517,6 @@ static PyTypeObject pyrf_sample_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_sample_event__doc, - .tp_members = pyrf_sample_event__members, .tp_getset = pyrf_sample_event__getset, .tp_methods = pyrf_sample_event__methods, .tp_repr = (reprfunc)pyrf_sample_event__repr, @@ -1280,11 +1525,38 @@ static PyTypeObject pyrf_sample_event__type = { static const char pyrf_context_switch_event__doc[] = PyDoc_STR("perf context_switch event object."); -static PyMemberDef pyrf_context_switch_event__members[] = { +static PyObject *pyrf_context_switch_event__get_next_prev_pid(const struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pevent->event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) + return PyLong_FromUnsignedLong(pevent->event->context_switch.next_prev_pid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_context_switch_event__get_next_prev_tid(const struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pevent->event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) + return PyLong_FromUnsignedLong(pevent->event->context_switch.next_prev_tid); + Py_RETURN_NONE; +} + +static PyGetSetDef pyrf_context_switch_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), - member_def(perf_record_switch, next_prev_pid, T_UINT, "next/prev pid"), - member_def(perf_record_switch, next_prev_tid, T_UINT, "next/prev tid"), + member_def(perf_event_header, misc, T_USHORT, "event misc"), + { + .name = "next_prev_pid", + .get = (getter)pyrf_context_switch_event__get_next_prev_pid, + .set = NULL, + .doc = "next/prev pid for CPU-wide switch, or None.", + }, + { + .name = "next_prev_tid", + .get = (getter)pyrf_context_switch_event__get_next_prev_tid, + .set = NULL, + .doc = "next/prev tid for CPU-wide switch, or None.", + }, { .name = NULL, }, }; @@ -1292,11 +1564,19 @@ static PyObject *pyrf_context_switch_event__repr(const struct pyrf_event *pevent { PyObject *ret; char *s; + int res; - if (asprintf(&s, "{ type: context_switch, next_prev_pid: %u, next_prev_tid: %u, switch_out: %u }", - pevent->event.context_switch.next_prev_pid, - pevent->event.context_switch.next_prev_tid, - !!(pevent->event.header.misc & PERF_RECORD_MISC_SWITCH_OUT)) < 0) { + if (pevent->event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) { + res = asprintf(&s, + "{ type: context_switch, next_prev_pid: %u, next_prev_tid: %u, switch_out: %u }", + pevent->event->context_switch.next_prev_pid, + pevent->event->context_switch.next_prev_tid, + !!(pevent->event->header.misc & PERF_RECORD_MISC_SWITCH_OUT)); + } else { + res = asprintf(&s, "{ type: context_switch, switch_out: %u }", + !!(pevent->event->header.misc & PERF_RECORD_MISC_SWITCH_OUT)); + } + if (res < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -1312,8 +1592,7 @@ static PyTypeObject pyrf_context_switch_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_context_switch_event__doc, - .tp_members = pyrf_context_switch_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_context_switch_event__getset, .tp_repr = (reprfunc)pyrf_context_switch_event__repr, }; @@ -1387,18 +1666,120 @@ static PyTypeObject *pyrf_event__type[] = { [PERF_RECORD_STAT_ROUND] = &pyrf_stat_round_event__type, }; +static int pyrf_event__copy(struct pyrf_event *pevent) +{ + const union perf_event *orig_event = pevent->event; + const struct perf_sample *orig_sample = pevent->sample; + size_t copy_size = orig_event->header.size; + int err = -EINVAL; + + /* + * If a session callback retained a reference to pevent, resolve its + * address location and callchain now while pevent->machine is still + * live and thread->maps reflects the point in time of the sample. + */ + if (pevent->machine && pevent->evsel) { + pyrf_sample_event__resolve_al(pevent); + if (pyrf_sample_event__resolve_callchain(pevent) < 0) { + pevent->machine = NULL; + if (!pevent->event_copy) { + pevent->event = &zero_event; + pevent->sample = &pevent->sample_storage; + } + return -ENOMEM; + } + } + pevent->machine = NULL; + + if (pevent->event_copy) + return 0; + + /* + * Clear borrowed pointers immediately so that even if copying fails, + * pevent does not retain dangling pointers to the caller's stack, + * ring buffer, or session. + */ + pevent->event = &zero_event; + pevent->sample = &pevent->sample_storage; + + /* + * Allocate an extra zero byte so string fields such as mmap/mmap2 + * filenames or comm strings are always NUL-terminated. + */ + pevent->event_copy = calloc(1, copy_size + 1); + if (!pevent->event_copy) { + PyErr_NoMemory(); + return -ENOMEM; + } + + memcpy(pevent->event_copy, orig_event, copy_size); + pevent->event = pevent->event_copy; + + if (pevent->evsel) { + bool needs_swap = pevent->evsel->needs_swap; + + pevent->evsel->needs_swap = false; + err = evsel__parse_sample(pevent->evsel, pevent->event_copy, + &pevent->sample_storage); + pevent->evsel->needs_swap = needs_swap; + } + + if (err == 0) { + if (orig_sample != &pevent->sample_storage) { + if (orig_sample->id) + pevent->sample_storage.id = orig_sample->id; + if (orig_sample->period) + pevent->sample_storage.period = orig_sample->period; + if (orig_sample->machine_pid) + pevent->sample_storage.machine_pid = + orig_sample->machine_pid; + if (orig_sample->vcpu != (u32)-1) + pevent->sample_storage.vcpu = orig_sample->vcpu; + if (orig_sample->insn_len && !pevent->sample_storage.insn_len) { + memcpy(pevent->sample_storage.insn, orig_sample->insn, + sizeof(pevent->sample_storage.insn)); + pevent->sample_storage.insn_len = orig_sample->insn_len; + } + } + pevent->sample = &pevent->sample_storage; + } else if (orig_sample != &pevent->sample_storage) { + /* + * Synthesized events (e.g. Intel PT itrace) or events without + * an evsel may not have raw sample buffers for + * evsel__parse_sample(); copy the scalar fields from the + * borrowed sample. + */ + perf_sample__exit(&pevent->sample_storage); + pevent->sample_storage = *orig_sample; + if (pevent->sample_storage.evsel) + pevent->sample_storage.evsel = + evsel__get(pevent->sample_storage.evsel); + pevent->sample_storage.callchain = NULL; + pevent->sample_storage.merged_callchain = false; + pevent->sample_storage.raw_data = NULL; + pevent->sample_storage.raw_size = 0; + pevent->sample_storage.branch_stack = NULL; + pevent->sample_storage.user_regs = NULL; + pevent->sample_storage.intr_regs = NULL; + pevent->sample_storage.user_stack.size = 0; + pevent->sample_storage.user_stack.data = NULL; + pevent->sample_storage.aux_sample.size = 0; + pevent->sample_storage.aux_sample.data = NULL; + pevent->sample = &pevent->sample_storage; + } else if (pevent->evsel) { + PyErr_Format(PyExc_OSError, "perf: can't parse sample, err=%d", err); + return err; + } + return 0; +} + static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *evsel, - struct perf_session *session, - struct machine *machine) + struct perf_session *session, struct machine *machine, + struct perf_sample *sample_arg) { struct pyrf_event *pevent; struct perf_sample *sample; - int err; u32 min_size; - bool needs_swap; - - if (!machine) - machine = session ? &session->machines.host : NULL; if (event->header.type >= ARRAY_SIZE(pyrf_event__type) || pyrf_event__type[event->header.type] == NULL) { @@ -1411,89 +1792,58 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev event->header.size, event->header.type); } - size_t copy_size = event->header.size; - - if (copy_size > sizeof(pevent->event)) { - return PyErr_Format(PyExc_TypeError, "Unexpected event size: %zd < %zu", - sizeof(pevent->event), copy_size); + if (event->header.size > sizeof(union perf_event)) { + return PyErr_Format(PyExc_TypeError, "Unexpected event size: %zu < %u", + sizeof(union perf_event), event->header.size); } pevent = PyObject_New(struct pyrf_event, pyrf_event__type[event->header.type]); if (pevent == NULL) return PyErr_NoMemory(); - /* Copy the event for memory safety and initialize variables. */ - memcpy(&pevent->event, event, copy_size); - if (copy_size < sizeof(pevent->event)) - memset((char *)&pevent->event + copy_size, 0, sizeof(pevent->event) - copy_size); - - if (event->header.type == PERF_RECORD_MMAP2) - pevent->event.mmap2.filename[sizeof(pevent->event.mmap2.filename) - 1] = '\0'; - - perf_sample__init(&pevent->sample, /*all=*/true); + perf_sample__init(&pevent->sample_storage, /*all=*/true); + pevent->sample = sample_arg ?: &pevent->sample_storage; + pevent->evsel = evsel ? evsel__get(evsel) : NULL; + pevent->machine = NULL; pevent->callchain = NULL; pevent->brstack = NULL; pevent->al_resolved = false; addr_location__init(&pevent->al); + pevent->event = event; + pevent->event_copy = NULL; + + /* + * When called outside a session callback (e.g. pyrf_evlist__read_on_cpu) + * or without a pre-parsed sample for an evsel, copy the event and parse + * its sample into sample_storage immediately. + */ + if ((!session || (evsel && !sample_arg)) && pyrf_event__copy(pevent) < 0) { + Py_DECREF(pevent); + return NULL; + } if (!evsel) return (PyObject *)pevent; - /* Parse the sample again so that pointers are within the copied event. */ - needs_swap = evsel->needs_swap; + sample = pevent->sample; + if (session && session->evlist && perf_guest && sample->id) { + struct perf_sample_id *sid = evlist__id2sid(session->evlist, sample->id); - evsel->needs_swap = false; - err = evsel__parse_sample(evsel, &pevent->event, &pevent->sample); - evsel->needs_swap = needs_swap; - if (err < 0) { - Py_DECREF(pevent); - return PyErr_Format(PyExc_OSError, - "perf: can't parse sample, err=%d", err); - } - sample = &pevent->sample; - if (machine && sample->callchain) { - struct addr_location al; - struct callchain_cursor *cursor; - u64 i; - struct pyrf_callchain *pchain; - - addr_location__init(&al); - if (machine__resolve(machine, &al, sample) >= 0) { - cursor = get_tls_callchain_cursor(); - if (thread__resolve_callchain(al.thread, cursor, sample, - NULL, NULL, PERF_MAX_STACK_DEPTH) == 0) { - callchain_cursor_commit(cursor); - - pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type); - if (!pchain) { - addr_location__exit(&al); - Py_DECREF(pevent); - return NULL; - } - pchain->nr_frames = cursor->nr; - pchain->frames = calloc(pchain->nr_frames, - sizeof(*pchain->frames)); - if (!pchain->frames) { - Py_DECREF(pchain); - addr_location__exit(&al); - Py_DECREF(pevent); - return PyErr_NoMemory(); - } - struct callchain_cursor_node *node; - - for (i = 0; i < pchain->nr_frames; i++) { - node = callchain_cursor_current(cursor); - pchain->frames[i].ip = node->ip; - pchain->frames[i].map = - map__get(node->ms.map); - pchain->frames[i].sym = node->ms.sym; - callchain_cursor_advance(cursor); - } - pevent->callchain = (PyObject *)pchain; - } - addr_location__exit(&al); + if (sid) { + sample->machine_pid = sid->machine_pid; + sample->vcpu = sid->vcpu.cpu; } } + if (!machine && session) { + machine = sample->machine_pid ? + machines__find(&session->machines, sample->machine_pid) : + &session->machines.host; + if (!machine) + machine = &session->machines.host; + } + pevent->machine = machine; + if (machine && machine->pid > 0 && !sample->machine_pid) + sample->machine_pid = machine->pid; if (sample->branch_stack) { struct branch_stack *bs = sample->branch_stack; struct branch_entry *entries = perf_sample__branch_entries(sample); @@ -1795,7 +2145,6 @@ static int pyrf_pmu__setup_types(void) return PyType_Ready(&pyrf_pmu__type); } - /** A python iterator for pmus that has no equivalent in the C code. */ struct pyrf_pmu_iterator { PyObject_HEAD @@ -2206,12 +2555,13 @@ static PyObject *pyrf_evsel__read(struct pyrf_evsel *pevsel, { struct evsel *evsel = pevsel->evsel; int cpu = 0, cpu_idx, thread = 0, thread_idx; + static char *kwlist[] = { "cpu", "thread", NULL }; struct perf_counts_values *old_count, *new_count; struct pyrf_counts_values *count_values; CHECK_INITIALIZED(evsel, "evsel"); - if (!PyArg_ParseTuple(args, "ii", &cpu, &thread)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", kwlist, &cpu, &thread)) return NULL; cpu_idx = perf_cpu_map__idx(evsel->core.cpus, (struct perf_cpu){.cpu = cpu}); @@ -2736,10 +3086,6 @@ static int prepare_metric(const struct metric_expr *mexp, double val, ena, run; int ret, source_count = 0; struct perf_counts_values *old_count, *new_count; - char *n = strdup(evsel__metric_id(cur)); - - if (!n) - return -ENOMEM; /* * If there are multiple uncore PMUs and we're not reading the @@ -2781,9 +3127,15 @@ static int prepare_metric(const struct metric_expr *mexp, if (ena != run && run != 0) val = val * ena / run; + char *n = strdup(evsel__metric_id(cur)); + + if (!n) + return -ENOMEM; ret = expr__add_id_val_source_count(pctx, n, val, source_count); - if (ret) + if (ret) { + free(n); return ret; + } } for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) { @@ -2801,6 +3153,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist, { int ret, cpu = 0, cpu_idx = 0, thread = 0, thread_idx = 0; const char *metric; + static char *kwlist[] = { "metric", "cpu", "thread", NULL }; struct rb_node *node; struct metric_expr *mexp = NULL; struct expr_parse_ctx *pctx; @@ -2809,7 +3162,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist, CHECK_INITIALIZED(pevlist->evlist, "evlist"); - if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sii", kwlist, &metric, &cpu, &thread)) return NULL; for (node = rb_first_cached(&evlist__metric_events(pevlist->evlist)->entries); @@ -2951,10 +3304,7 @@ static PyObject *pyrf_evlist__get_pollfd(struct pyrf_evlist *pevlist, return PyErr_NoMemory(); } - -static PyObject *pyrf_evlist__add(struct pyrf_evlist *pevlist, - PyObject *args, - PyObject *kwargs __maybe_unused) +static PyObject *pyrf_evlist__add(struct pyrf_evlist *pevlist, PyObject *args) { struct evlist *evlist; PyObject *pevsel; @@ -3032,7 +3382,7 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist, perf_mmap__consume(&md->core); Py_RETURN_NONE; } - pyevent = pyrf_event__new(event, evsel, evlist__session(evlist), /*machine=*/NULL); + pyevent = pyrf_event__new(event, evsel, evlist__session(evlist), /*machine=*/NULL, NULL); perf_mmap__consume(&md->core); if (pyevent == NULL) return PyErr_Occurred() ? NULL : PyErr_NoMemory(); @@ -3166,7 +3516,7 @@ static PyMethodDef pyrf_evlist__methods[] = { { .ml_name = "add", .ml_meth = (PyCFunction)pyrf_evlist__add, - .ml_flags = METH_VARARGS | METH_KEYWORDS, + .ml_flags = METH_VARARGS, .ml_doc = PyDoc_STR("adds an event selector to the list.") }, { @@ -3400,6 +3750,11 @@ static const struct perf_constant perf__constants[] = { PERF_CONST(RECORD_STAT_ROUND), PERF_CONST(RECORD_MISC_SWITCH_OUT), + + /* Values for the flags of a perf.call_return, see thread-stack.h. */ + { "CALL_RETURN_NO_CALL", CALL_RETURN_NO_CALL }, + { "CALL_RETURN_NO_RETURN", CALL_RETURN_NO_RETURN }, + { "CALL_RETURN_NON_CALL", CALL_RETURN_NON_CALL }, { .name = NULL, }, }; @@ -3410,7 +3765,7 @@ static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel, char *sys = NULL; char *name = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ss", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss", kwlist, &sys, &name)) return NULL; @@ -3469,10 +3824,12 @@ static PyObject *pyrf__parse_events(PyObject *self, PyObject *args) perf_evlist__set_maps(evlist__core(evlist), cpus, threads); if (parse_events(evlist, input, &err)) { parse_events_error__print(&err, input); + parse_events_error__exit(&err); PyErr_SetFromErrno(PyExc_OSError); evlist__put(evlist); return NULL; } + parse_events_error__exit(&err); result = pyrf_evlist__from_evlist(evlist); evlist__put(evlist); return result; @@ -3678,6 +4035,10 @@ static int pyrf_data__init(struct pyrf_data *pdata, PyObject *args, PyObject *kw pdata->data.mode = PERF_DATA_MODE_READ; pdata->data.file.fd = fd; if (perf_data__open(&pdata->data) < 0) { + if (fd >= 0) { + close(fd); + pdata->data.file.fd = -1; + } PyErr_Format(PyExc_IOError, "Failed to open perf data: %s", pdata->data.path ? pdata->data.path : "perf.data"); return -1; @@ -3746,16 +4107,38 @@ static void pyrf_thread__delete(struct pyrf_thread *pthread) static PyObject *pyrf_thread__comm(PyObject *obj) { struct pyrf_thread *pthread = (void *)obj; - const char *str; + struct thread *thread = pthread->thread; + bool has_comm = false; + char *str = NULL; + PyObject *ret; - CHECK_INITIALIZED(pthread->thread, "perf.thread"); + CHECK_INITIALIZED(thread, "perf.thread"); - str = thread__comm_str(pthread->thread); + /* + * Copy the comm string under comm_lock so a concurrent PERF_RECORD_COMM + * in perf_session__process_events() (which runs with the GIL released) + * cannot free the old comm_str via comm__override() while it is being + * read, and drop comm_lock before calling into the Python allocator. + */ + down_read(thread__comm_lock(thread)); + if (!list_empty(thread__comm_list(thread))) { + const struct comm *comm; - if (!str) + comm = list_first_entry(thread__comm_list(thread), struct comm, list); + if (comm && comm__str(comm)) { + has_comm = true; + str = strdup(comm__str(comm)); + } + } + up_read(thread__comm_lock(thread)); + + if (!has_comm) Py_RETURN_NONE; - - return PyUnicode_FromString(str); + if (!str) + return PyErr_NoMemory(); + ret = PyUnicode_FromString(str); + free(str); + return ret; } static PyMethodDef pyrf_thread__methods[] = { @@ -3834,31 +4217,675 @@ struct pyrf_session { struct perf_session *session; struct perf_tool tool; + struct itrace_synth_opts itrace_opts; struct pyrf_data *pdata; PyObject *sample; PyObject *stat; + PyObject *context_switch; + PyObject *call_return; + struct call_return_processor *crp; + /** + * @call_return_last_db_id: Counter used to give each perf.call_return a + * unique db_id. A call's db_id may be allocated before the call + * returns, so the identifiers cannot be generated by the Python script. + */ + u64 call_return_last_db_id; + u64 sample_last_db_id; + /** + * @callback_raised: A python callback raised an exception and requested + * that event processing stops, see pyrf_session__callback_raised(). + */ + bool callback_raised; + unsigned int callback_depth; + unsigned long callback_thread; + char *vmlinux_name; + char *kallsyms_name; + char *symfs; }; +struct pyrf_call_return { + PyObject_HEAD + struct call_return cr; + /** + * @call_path: perf.callchain of the call path, ordered from the + * outermost caller to the callee. Resolved eagerly as the underlying + * struct call_path is owned by the call return processor. + */ + PyObject *call_path; + char *comm; + pid_t machine_pid; + pid_t pid; + pid_t tid; +}; + +static PyObject *pyrf_call_return__repr(struct pyrf_call_return *pcr) +{ + return PyUnicode_FromString("{ type: call_return }"); +} + +static PyObject *pyrf_call_return__get_comm(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + if (pcr->comm) + return PyUnicode_FromString(pcr->comm); + Py_RETURN_NONE; +} + +static PyObject *pyrf_call_return__get_machine_pid(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromLong(pcr->machine_pid); +} + +static PyObject *pyrf_call_return__get_pid(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + if (pcr->pid != -1) + return PyLong_FromLong(pcr->pid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_call_return__get_tid(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + if (pcr->tid != -1) + return PyLong_FromLong(pcr->tid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_call_return__get_call_time(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->call_time); +} + +static PyObject *pyrf_call_return__get_return_time(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->return_time); +} + +static PyObject *pyrf_call_return__get_branch_count(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->branch_count); +} + +static PyObject *pyrf_call_return__get_call_ref(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->call_ref); +} + +static PyObject *pyrf_call_return__get_return_ref(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->return_ref); +} + +static PyObject *pyrf_call_return__get_flags(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromLong(cr->flags); +} + +static PyObject *pyrf_call_return__get_call_path(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + PyObject *call_path = pcr->call_path ?: Py_None; + + Py_INCREF(call_path); + return call_path; +} + +static PyObject *pyrf_call_return__get_parent_id(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.parent_db_id); +} + +static PyObject *pyrf_call_return__get_insn_count(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.insn_count); +} + +static PyObject *pyrf_call_return__get_cyc_count(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.cyc_count); +} + +static PyObject *pyrf_call_return__get_db_id(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.db_id); +} + +static PyGetSetDef pyrf_call_return__getset[] = { + { + .name = "db_id", + .get = (getter)pyrf_call_return__get_db_id, + .doc = "Unique identifier of this call return.", + }, + { + .name = "parent_id", + .get = (getter)pyrf_call_return__get_parent_id, + .doc = "db_id of the calling call return, 0 if there is none.", + }, + { + .name = "insn_count", + .get = (getter)pyrf_call_return__get_insn_count, + .doc = "Instructions executed between the call and the return.", + }, + { + .name = "cyc_count", + .get = (getter)pyrf_call_return__get_cyc_count, + .doc = "Cycles elapsed between the call and the return.", + }, + { + .name = "comm", + .get = (getter)pyrf_call_return__get_comm, + .doc = "Thread comm at the time of the call/return, or None.", + }, + { + .name = "machine_pid", + .get = (getter)pyrf_call_return__get_machine_pid, + .doc = "Machine PID (0 for host).", + }, + { + .name = "pid", + .get = (getter)pyrf_call_return__get_pid, + .doc = "Process ID of the thread, or None.", + }, + { + .name = "tid", + .get = (getter)pyrf_call_return__get_tid, + .doc = "Thread identifier, None if unknown.", + }, + { + .name = "call_time", + .get = (getter)pyrf_call_return__get_call_time, + .doc = "Timestamp of the call.", + }, + { + .name = "return_time", + .get = (getter)pyrf_call_return__get_return_time, + .doc = "Timestamp of the return.", + }, + { + .name = "branch_count", + .get = (getter)pyrf_call_return__get_branch_count, + .doc = "Branches taken between the call and the return.", + }, + { + .name = "call_ref", + .get = (getter)pyrf_call_return__get_call_ref, + .doc = "Reference to the sample of the call.", + }, + { + .name = "return_ref", + .get = (getter)pyrf_call_return__get_return_ref, + .doc = "Reference to the sample of the return.", + }, + { + .name = "flags", + .get = (getter)pyrf_call_return__get_flags, + .doc = "Bitmask of perf.CALL_RETURN_* values.", + }, + { + .name = "call_path", + .get = (getter)pyrf_call_return__get_call_path, + .doc = "perf.callchain from outermost caller to callee.", + }, + { .name = NULL, }, +}; + +static void pyrf_call_return__delete(struct pyrf_call_return *pevent) +{ + Py_XDECREF(pevent->call_path); + free(pevent->comm); + Py_TYPE(pevent)->tp_free((PyObject *)pevent); +} + +static PyTypeObject pyrf_call_return__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.call_return", + .tp_basicsize = sizeof(struct pyrf_call_return), + .tp_dealloc = (destructor)pyrf_call_return__delete, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_doc = "perf call_return object.", + .tp_getset = pyrf_call_return__getset, + .tp_repr = (reprfunc)pyrf_call_return__repr, +}; + +/* + * Turn a struct call_path, that is a leaf with parent pointers up to the + * outermost caller, into a perf.callchain ordered from the outermost caller to + * the leaf. The struct call_path is owned by the call return processor and so + * must be resolved before returning to Python. + */ +static PyObject *pyrf_call_path__to_callchain(const struct call_path *cp, + struct thread *thread) +{ + struct pyrf_callchain *pchain; + const struct call_path *pos; + u64 nr_frames = 0; + + /* The root call path is a sentinel with no symbol, skip it. */ + for (pos = cp; pos && pos->parent; pos = pos->parent) + nr_frames++; + + pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type); + if (!pchain) + return NULL; + + pchain->nr_frames = nr_frames; + pchain->frames = nr_frames ? calloc(nr_frames, sizeof(*pchain->frames)) : NULL; + if (nr_frames && !pchain->frames) { + pchain->nr_frames = 0; + Py_DECREF(pchain); + return PyErr_NoMemory(); + } + /* Fill in reverse so that the outermost caller is first. */ + for (pos = cp; nr_frames > 0; pos = pos->parent) { + struct pyrf_callchain_frame *frame = &pchain->frames[--nr_frames]; + + frame->ip = pos->ip; + frame->sym = NULL; + frame->sym_name = pos->sym ? strdup(pos->sym->name) : NULL; + frame->map = NULL; + if (thread) { + struct addr_location al; + u8 cpumode = pos->in_kernel ? PERF_RECORD_MISC_KERNEL + : PERF_RECORD_MISC_USER; + + addr_location__init(&al); + thread__find_map(thread, cpumode, pos->ip, &al); + frame->map = map__get(al.map); + if (frame->map) + frame->sym = pos->sym; + addr_location__exit(&al); + } + } + return (PyObject *)pchain; +} + +static PyObject *pyrf_call_return__new(struct call_return *cr) +{ + struct pyrf_call_return *pevent; + + pevent = PyObject_New(struct pyrf_call_return, &pyrf_call_return__type); + if (!pevent) + return NULL; + + pevent->cr = *cr; + /* Avoid lifetime issues with thread by caching tid/pid/machine_pid */ + if (cr->thread) { + struct maps *maps = thread__maps(cr->thread); + + pevent->machine_pid = maps && maps__machine(maps) ? maps__machine(maps)->pid : 0; + pevent->pid = thread__pid(cr->thread); + pevent->tid = thread__tid(cr->thread); + } else { + pevent->machine_pid = 0; + pevent->pid = -1; + pevent->tid = -1; + } + pevent->cr.thread = NULL; + pevent->comm = cr->comm ? strdup(comm__str(cr->comm)) : NULL; + pevent->call_path = NULL; + if (cr->cp) { + pevent->call_path = pyrf_call_path__to_callchain(cr->cp, cr->thread); + if (!pevent->call_path) { + Py_DECREF(pevent); + return NULL; + } + } + return (PyObject *)pevent; +} + +/* + * Number of times a python callback has requested that event processing + * stops, see pyrf_session__callback_raised(). + */ +static unsigned int pyrf_session__stop_requests; + +/* + * A python callback raised an exception. + * + * Returning an error to the session code would cause reader__read_event() to + * print a "processing failed for event of type: ..." error to stderr. This is + * noise, and in the case of a TUI corrupts the display, when a callback raises + * to deliberately stop processing (e.g. cancelling a background load) and the + * error message is redundant with the python exception that is raised anyway. + * + * Instead request that event processing stops by setting session_done and + * return success. The exception remains pending (PyErr_Occurred()) and + * pyrf_session__find_thread_events() returns NULL so python raises it. + * + * Note, session_done is a process-global rather than per-session. Were + * multiple sessions processing events concurrently in different python + * threads then an exception in one would stop all of them. Only the session + * whose callback raised clears session_done, and the other sessions detect + * the stop request, using pyrf_session__stop_requests, and raise an error + * rather than silently dropping events. + */ +static int pyrf_session__callback_raised(struct pyrf_session *psession) +{ + psession->callback_raised = true; + pyrf_session__stop_requests++; + session_done = 1; + return 0; +} + +/* + * session_done stops the reader loops, the ordered_events and deferred sample + * flushes, and auxtrace__flush_events(), but some callbacks may still happen + * after a callback has raised. For example, a single sample may generate + * multiple call_return callbacks, and perf_session__flush_thread_stacks() + * delivers call returns for the remaining stack entries (skipping it would + * only defer those callbacks until the threads are deleted). Calling into + * python with an exception pending is an error, and the events would be + * discarded anyway, so callbacks check this and skip calling python. + */ +static bool pyrf_session__exception_pending(void) +{ + return PyErr_Occurred() != NULL; +} + +static int __pyrf_session__call_return_process(struct call_return *cr, + u64 *parent_db_id, + struct pyrf_session *psession) +{ + PyObject *pyevent, *ret; + + if (!psession->call_return || pyrf_session__exception_pending()) + return 0; + + if (!cr->db_id) + cr->db_id = ++psession->call_return_last_db_id; + + if (parent_db_id) { + if (!*parent_db_id) + *parent_db_id = ++psession->call_return_last_db_id; + cr->parent_db_id = *parent_db_id; + } + + pyevent = pyrf_call_return__new(cr); + if (!pyevent) + return -1; + + ret = PyObject_CallFunctionObjArgs(psession->call_return, pyevent, NULL); + Py_DECREF(pyevent); + if (!ret) + return pyrf_session__callback_raised(psession); + Py_DECREF(ret); + return 0; +} + +static PyGILState_STATE pyrf_session__callback_enter(struct pyrf_session *psession) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + + psession->callback_thread = PyThread_get_thread_ident(); + psession->callback_depth++; + return gstate; +} + +static void pyrf_session__callback_leave(struct pyrf_session *psession, + PyGILState_STATE gstate) +{ + psession->callback_depth--; + PyGILState_Release(gstate); +} + +/* + * Call returns may be processed during a sample callback, or when thread + * stacks are flushed, so acquire the GIL, see pyrf_session_tool__sample(). + */ +static int pyrf_session__call_return_process(struct call_return *cr, + u64 *parent_db_id, + void *data) +{ + struct pyrf_session *psession = data; + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session__call_return_process(cr, parent_db_id, psession); + + pyrf_session__callback_leave(psession, gstate); + return ret; +} + +static int pyrf_session__call_event(struct pyrf_session *psession, PyObject *callback, + PyObject *pyevent, const char *extra_arg, bool has_extra_arg) +{ + PyObject *ret; + int err = 0; + + if (has_extra_arg) + ret = PyObject_CallFunction(callback, "Oz", pyevent, extra_arg); + else + ret = PyObject_CallFunctionObjArgs(callback, pyevent, NULL); + + Py_XDECREF(ret); + if (Py_REFCNT(pyevent) > 1) { + PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL; + + /* + * The Python callback retained a reference to the event + * beyond the callback's return (or in an exception frame); + * copy the underlying event and sample before process_events + * reuses or unmaps its buffer. + */ + if (!ret) + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + err = pyrf_event__copy((struct pyrf_event *)pyevent); + if (!ret) { + PyErr_Clear(); + PyErr_Restore(exc_type, exc_value, exc_tb); + } + } + Py_DECREF(pyevent); + if (!ret || err < 0) + return pyrf_session__callback_raised(psession); + return 0; +} + +static int __pyrf_session_tool__sample(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + u64 sample_db_id = ++psession->sample_last_db_id; + + if (pyrf_session__exception_pending()) + return 0; + + if (psession->crp) { + struct addr_location al, addr_al; + struct thread *thread = NULL; + + addr_location__init(&al); + addr_location__init(&addr_al); + + if (machine__resolve(machine, &al, sample) >= 0) { + thread = al.thread; + if (sample->flags & (PERF_IP_FLAG_CALL | + PERF_IP_FLAG_TRACE_BEGIN | + PERF_IP_FLAG_RETURN | + PERF_IP_FLAG_BRANCH | + PERF_IP_FLAG_TRACE_END)) { + thread__resolve(thread, &addr_al, sample); + } + + int err; + + err = thread_stack__process(thread, thread__comm(thread), sample, + &al, &addr_al, sample_db_id, psession->crp); + if (err) { + addr_location__exit(&addr_al); + addr_location__exit(&al); + return err; + } + } + + addr_location__exit(&addr_al); + addr_location__exit(&al); + + /* A call_return callback may have raised. */ + if (pyrf_session__exception_pending()) + return 0; + } + + if (psession->sample) { + PyObject *pyevent = pyrf_event__new(event, sample->evsel, + psession->session, + machine, sample); + + if (pyevent == NULL) + return -ENOMEM; + + return pyrf_session__call_event(psession, psession->sample, pyevent, + /*extra_arg=*/NULL, /*has_extra_arg=*/false); + } + return 0; +} + +static int __pyrf_session_tool__context_switch(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyObject *pyevent; + + if (pyrf_session__exception_pending()) { + /* + * Skip calling python, but keep the machine's thread state + * consistent as perf_event__process_switch() would have been + * called had no python callback been registered. + */ + return perf_event__process_switch(tool, event, sample, machine); + } + + pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine, sample); + if (perf_event__process_switch(tool, event, sample, machine) < 0) { + Py_XDECREF(pyevent); + return -1; + } + + if (pyevent == NULL) + return -ENOMEM; + + return pyrf_session__call_event(psession, psession->context_switch, pyevent, + /*extra_arg=*/NULL, /*has_extra_arg=*/false); +} + +static int __pyrf_session_tool__stat(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + struct evsel *evsel = evlist__id2evsel(session->evlist, event->stat.id); + const char *name = evsel ? evsel__name(evsel) : "unknown"; + PyObject *pyevent; + + if (pyrf_session__exception_pending()) + return 0; + + pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, + /*machine=*/NULL, NULL); + if (pyevent == NULL) + return -ENOMEM; + + return pyrf_session__call_event(psession, psession->stat, pyevent, + name, /*has_extra_arg=*/true); +} + +static int __pyrf_session_tool__stat_round(const struct perf_tool *tool, + struct perf_session *session __maybe_unused, + union perf_event *event) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyObject *pyevent; + + if (pyrf_session__exception_pending()) + return 0; + + pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, + /*machine=*/NULL, NULL); + if (pyevent == NULL) + return -ENOMEM; + + return pyrf_session__call_event(psession, psession->stat, pyevent, + /*extra_arg=*/NULL, /*has_extra_arg=*/true); +} + +/* + * The session currently inside perf_session__new() or + * perf_session__process_events() with the GIL released. Because symbol_conf + * and session_done are process-globals and perf_session is not thread-safe, + * only one session may be active at a time. Modified with the GIL held. + */ +static struct pyrf_session *pyrf_session__active; + +static bool pyrf_session__check_not_busy(struct pyrf_session *psession) +{ + if (pyrf_session__active == psession && + (!psession->callback_depth || + psession->callback_thread != PyThread_get_thread_ident())) { + PyErr_SetString(PyExc_RuntimeError, + "perf.session is busy processing events in another thread"); + return false; + } + return true; +} + +/* + * The GIL is released while events are processed, see + * pyrf_session__find_thread_events(), so that other python threads can run, + * such as a UI thread while a session reads from a pipe that may block. The + * tool callbacks that use python acquire the GIL, PyGILState_Ensure() is + * reentrant so nested callbacks, like call returns during a sample, work. + */ + static int pyrf_session_tool__sample(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine) { struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); - PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine); - PyObject *ret; + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__sample(tool, event, sample, machine); - if (pyevent == NULL) - return -ENOMEM; + pyrf_session__callback_leave(psession, gstate); + return ret; +} - ret = PyObject_CallFunction(psession->sample, "O", pyevent); - if (!ret) { - Py_DECREF(pyevent); - return -1; - } - Py_DECREF(ret); - Py_DECREF(pyevent); - return 0; +static int pyrf_session_tool__context_switch(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__context_switch(tool, event, sample, machine); + + pyrf_session__callback_leave(psession, gstate); + return ret; } static int pyrf_session_tool__stat(const struct perf_tool *tool, @@ -3866,45 +4893,23 @@ static int pyrf_session_tool__stat(const struct perf_tool *tool, union perf_event *event) { struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); - struct evsel *evsel = evlist__id2evsel(session->evlist, event->stat.id); - PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, - /*machine=*/NULL); - const char *name = evsel ? evsel__name(evsel) : "unknown"; - PyObject *ret; + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__stat(tool, session, event); - if (pyevent == NULL) - return -ENOMEM; - - ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, name); - if (!ret) { - Py_DECREF(pyevent); - return -1; - } - Py_DECREF(ret); - Py_DECREF(pyevent); - return 0; + pyrf_session__callback_leave(psession, gstate); + return ret; } static int pyrf_session_tool__stat_round(const struct perf_tool *tool, - struct perf_session *session __maybe_unused, + struct perf_session *session, union perf_event *event) { struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); - PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, - /*machine=*/NULL); - PyObject *ret; + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__stat_round(tool, session, event); - if (pyevent == NULL) - return -ENOMEM; - - ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, NULL); - if (!ret) { - Py_DECREF(pyevent); - return -1; - } - Py_DECREF(ret); - Py_DECREF(pyevent); - return 0; + pyrf_session__callback_leave(psession, gstate); + return ret; } static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObject *args) @@ -3912,26 +4917,42 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje struct machine *machine; struct thread *thread = NULL; PyObject *result; - int pid; + int pid, tid = -1; CHECK_INITIALIZED(psession->session, "session"); - - if (!PyArg_ParseTuple(args, "i", &pid)) + if (!pyrf_session__check_not_busy(psession)) return NULL; + if (!PyArg_ParseTuple(args, "i|i", &pid, &tid)) + return NULL; + + if (tid == -1) { + tid = pid; + pid = -1; + } + + /* + * When host lookup fails, guest machines (default_guest and the guests + * rb_root) are searched. + */ machine = &psession->session->machines.host; - thread = machine__find_thread(machine, pid, pid); + thread = machine__find_thread(machine, pid, tid); if (!thread) { - machine = perf_session__find_machine(psession->session, pid); + machine = perf_session__find_machine(psession->session, pid != -1 ? pid : tid); if (machine) - thread = machine__find_thread(machine, pid, pid); + thread = machine__find_thread(machine, pid, tid); + if (!thread) { + machine = machines__find(&psession->session->machines, + DEFAULT_GUEST_KERNEL_ID); + if (machine) + thread = machine__find_thread(machine, pid, tid); + } } - if (!thread) { - PyErr_Format(PyExc_TypeError, "Failed to find thread %d", pid); - return NULL; - } + /* Return None rather than raising TypeError when a PID/TID is not known. */ + if (!thread) + Py_RETURN_NONE; result = pyrf_thread__from_thread(thread); thread__put(thread); return result; @@ -3940,23 +4961,44 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { struct pyrf_data *pdata; - PyObject *sample = NULL, *stat = NULL; - static char *kwlist[] = { "data", "sample", "stat", NULL }; + PyObject *sample = NULL, *stat = NULL, *context_switch = NULL; + PyObject *call_return = NULL; + char *itrace_str = NULL; + char *vmlinux_str = NULL, *kallsyms_str = NULL, *symfs_str = NULL; + static char *kwlist[] = { "data", "sample", "stat", "context_switch", + "call_return", "itrace", "vmlinux", "kallsyms", + "symfs", NULL }; struct pyrf_session *psession; struct perf_session *session; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OO", kwlist, &pyrf_data__type, &pdata, - &sample, &stat)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OOOOzzzz", kwlist, + &pyrf_data__type, &pdata, + &sample, &stat, &context_switch, + &call_return, &itrace_str, + &vmlinux_str, &kallsyms_str, &symfs_str)) return NULL; psession = PyObject_New(struct pyrf_session, type); if (!psession) return NULL; + /* PyObject_New doesn't zero the memory, initialize every member. */ psession->session = NULL; psession->sample = NULL; psession->stat = NULL; + psession->context_switch = NULL; + psession->call_return = NULL; + psession->crp = NULL; + psession->call_return_last_db_id = 0; + psession->sample_last_db_id = 0; + psession->callback_raised = false; + psession->callback_depth = 0; + psession->callback_thread = 0; + psession->vmlinux_name = NULL; + psession->kallsyms_name = NULL; + psession->symfs = NULL; psession->pdata = NULL; + memset(&psession->itrace_opts, 0, sizeof(psession->itrace_opts)); Py_INCREF(pdata); psession->pdata = pdata; @@ -3966,7 +5008,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject #define ADD_TOOL(name) \ do { \ - if (name) { \ + if (name && name != Py_None) { \ if (!PyCallable_Check(name)) { \ PyErr_SetString(PyExc_TypeError, #name " must be callable"); \ goto err_out; \ @@ -3978,14 +5020,38 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject } while (0) ADD_TOOL(sample); + if (call_return && call_return != Py_None && (!sample || sample == Py_None)) + psession->tool.sample = pyrf_session_tool__sample; ADD_TOOL(stat); + ADD_TOOL(context_switch); + + if (call_return && call_return != Py_None) { + if (!PyCallable_Check(call_return)) { + PyErr_SetString(PyExc_TypeError, "call_return must be callable"); + Py_DECREF(psession); + return NULL; + } + Py_INCREF(call_return); + psession->call_return = call_return; + psession->crp = call_return_processor__new(pyrf_session__call_return_process, + psession); + if (!psession->crp) { + PyErr_SetString(PyExc_MemoryError, + "Failed to allocate call_return_processor"); + Py_DECREF(psession); + return NULL; + } + } #undef ADD_TOOL - if (stat) + if (stat && stat != Py_None) psession->tool.stat_round = pyrf_session_tool__stat_round; - psession->tool.comm = perf_event__process_comm; + psession->tool.auxtrace_info = perf_event__process_auxtrace_info; + psession->tool.auxtrace = perf_event__process_auxtrace; + psession->tool.auxtrace_error = perf_event__process_auxtrace_error; + psession->tool.id_index = perf_event__process_id_index; psession->tool.mmap = perf_event__process_mmap; psession->tool.mmap2 = perf_event__process_mmap2; psession->tool.namespaces = perf_event__process_namespaces; @@ -3996,24 +5062,85 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject psession->tool.text_poke = perf_event__process_text_poke; psession->tool.build_id = perf_event__process_build_id; psession->tool.attr = perf_event__process_attr; + psession->tool.event_update = perf_event__process_event_update; psession->tool.feature = perf_event__process_feature; +#ifdef HAVE_LIBTRACEEVENT + psession->tool.tracing_data = perf_event__process_tracing_data; +#endif + if (!vmlinux_str) + vmlinux_str = getenv("PERF_SYMBOL_VMLINUX"); + if (vmlinux_str && vmlinux_str[0]) { + psession->vmlinux_name = strdup(vmlinux_str); + if (!psession->vmlinux_name) { + PyErr_NoMemory(); + goto err_out; + } + } + + if (!kallsyms_str) + kallsyms_str = getenv("PERF_SYMBOL_KALLSYMS"); + if (kallsyms_str && kallsyms_str[0]) { + psession->kallsyms_name = strdup(kallsyms_str); + if (!psession->kallsyms_name) { + PyErr_NoMemory(); + goto err_out; + } + } + + if (!symfs_str) + symfs_str = getenv("PERF_SYMBOL_SYMFS"); + if (symfs_str && symfs_str[0]) { + psession->symfs = strdup(symfs_str); + if (!psession->symfs) { + PyErr_NoMemory(); + goto err_out; + } + } + + if (!itrace_str) + itrace_str = getenv("PERF_ITRACE"); + if (itrace_str) { + memset(&psession->itrace_opts, 0, sizeof(psession->itrace_opts)); + if (itrace_do_parse_synth_opts(&psession->itrace_opts, itrace_str, 0) < 0) { + PyErr_SetString(PyExc_ValueError, "Failed to parse itrace options"); + goto err_out; + } + } + + if (pyrf_session__active) { + PyErr_SetString(PyExc_RuntimeError, + "Another perf.session is currently active"); + goto err_out; + } + + symbol_conf.vmlinux_name = psession->vmlinux_name; + symbol_conf.kallsyms_name = psession->kallsyms_name; + symbol_conf.symfs = psession->symfs ?: ""; + symbol_conf.use_callchain = true; + symbol_conf.show_kernel_path = true; + symbol_conf.inline_name = false; + + /* Reading the header of a pipe may block, let other python threads run. */ + pyrf_session__active = psession; + Py_BEGIN_ALLOW_THREADS session = perf_session__new(&pdata->data, &psession->tool); + Py_END_ALLOW_THREADS + pyrf_session__active = NULL; + if (!psession->kallsyms_name && symbol_conf.kallsyms_name) + psession->kallsyms_name = (char *)symbol_conf.kallsyms_name; if (IS_ERR(session)) { PyErr_Format(PyExc_IOError, "failed to create session: %ld", PTR_ERR(session)); goto err_out; } psession->session = session; - - symbol_conf.use_callchain = true; - symbol_conf.show_kernel_path = true; - symbol_conf.inline_name = false; + psession->session->itrace_synth_opts = &psession->itrace_opts; if (symbol__init(perf_session__env(session)) < 0) { PyErr_SetString(PyExc_OSError, "perf: symbol__init failed"); goto err_out; } - - + if (psession->symfs && !symbol_conf.symfs[0]) + zfree(&psession->symfs); return (PyObject *)psession; err_out: @@ -4024,19 +5151,74 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject static void pyrf_session__delete(struct pyrf_session *psession) { perf_session__delete(psession->session); + if (!pyrf_session__active) { + if (symbol_conf.vmlinux_name == psession->vmlinux_name) + symbol_conf.vmlinux_name = NULL; + if (symbol_conf.kallsyms_name == psession->kallsyms_name) + symbol_conf.kallsyms_name = NULL; + if (symbol_conf.symfs == psession->symfs) + symbol_conf.symfs = ""; + } + free(psession->vmlinux_name); + free(psession->kallsyms_name); + free(psession->symfs); + free(psession->itrace_opts.vm_tm_corr_args); + free(psession->itrace_opts.cpu_bitmap); + free(psession->itrace_opts.ptime_range); Py_XDECREF(psession->pdata); Py_XDECREF(psession->sample); Py_XDECREF(psession->stat); + Py_XDECREF(psession->context_switch); + Py_XDECREF(psession->call_return); + if (psession->crp) + call_return_processor__free(psession->crp); Py_TYPE(psession)->tp_free((PyObject *)psession); } static PyObject *pyrf_session__find_thread_events(struct pyrf_session *psession) { + unsigned int stop_requests = pyrf_session__stop_requests; int err; CHECK_INITIALIZED(psession->session, "session"); + if (pyrf_session__active) { + PyErr_SetString(PyExc_RuntimeError, + "Another perf.session is currently active"); + return NULL; + } + symbol_conf.vmlinux_name = psession->vmlinux_name; + symbol_conf.kallsyms_name = psession->kallsyms_name; + symbol_conf.symfs = psession->symfs ?: ""; + + psession->callback_raised = false; + /* Callbacks that use python acquire the GIL, see pyrf_session_tool__sample(). */ + pyrf_session__active = psession; + Py_BEGIN_ALLOW_THREADS err = perf_session__process_events(psession->session); + Py_END_ALLOW_THREADS + pyrf_session__active = NULL; + if (psession->callback_raised) { + /* + * Clear the early stop requested by this session's callback, in + * pyrf_session__callback_raised(), so that later sessions process + * all their events. That function is the only writer of + * session_done in the python module, so resetting it here after + * processing is sufficient and there is no need to also reset it + * before processing. + */ + session_done = 0; + } else if (stop_requests != pyrf_session__stop_requests && !PyErr_Occurred()) { + /* + * A callback of a session being processed concurrently, in + * another thread, raised and set session_done which may have + * stopped processing this session's events early. Raise an error + * rather than silently dropping events. + */ + PyErr_SetString(PyExc_RuntimeError, + "Processing events may have stopped early as a callback of a concurrently processed session raised an exception"); + return NULL; + } if (PyErr_Occurred()) return NULL; @@ -4069,10 +5251,56 @@ static const char pyrf_session__doc[] = PyDoc_STR("perf session object."); static PyObject *pyrf_session__getattro(struct pyrf_session *psession, PyObject *attr_name) { + const char *name_str = PyUnicode_AsUTF8(attr_name); + + if (!name_str) + return NULL; if (!psession->session) { PyErr_SetString(PyExc_ValueError, "session not initialized"); return NULL; } + if (!pyrf_session__check_not_busy(psession)) + return NULL; + if (!strcmp(name_str, "e_machine")) + return PyLong_FromLong(perf_session__e_machine(psession->session, + /*e_flags=*/NULL)); + + if (!strcmp(name_str, "is_64_bit")) { + /* + * Prefer the machine of the recorded threads over the perf_env, + * as in pipe mode the perf_env may not be populated yet and + * perf_env__kernel_is_64_bit would cache the host's bitness. + */ + switch (perf_session__e_machine(psession->session, /*e_flags=*/NULL)) { + case EM_AARCH64: + case EM_ALPHA: + case EM_IA_64: + case EM_LOONGARCH: + case EM_PPC64: + case EM_SPARCV9: + case EM_X86_64: + Py_RETURN_TRUE; + case EM_386: + case EM_ARM: + case EM_PPC: + case EM_SPARC: + Py_RETURN_FALSE; + default: + /* + * Machines like EM_MIPS, EM_PARISC, EM_RISCV and + * EM_S390 are used for both word sizes, fall back on + * the recorded arch string if available. + */ + break; + } + { + struct perf_env *env = perf_session__env(psession->session); + + if (env && env->arch) + return PyBool_FromLong(perf_env__kernel_is_64_bit(env) == 1); + return PyBool_FromLong(sizeof(void *) == 8); + } + } return PyObject_GenericGetAttr((PyObject *) psession, attr_name); } @@ -4083,6 +5311,8 @@ static int pyrf_session__setattro(struct pyrf_session *psession, PyObject *attr_ PyErr_SetString(PyExc_ValueError, "session not initialized"); return -1; } + if (!pyrf_session__check_not_busy(psession)) + return -1; return PyObject_GenericSetAttr((PyObject *) psession, attr_name, value); } @@ -4104,14 +5334,35 @@ static int pyrf_session__setup_types(void) return PyType_Ready(&pyrf_session__type); } +static int pyrf__parse_elf_machine(PyObject *obj, int *elf_machine) +{ + long val; + + if (!obj || obj == Py_None) { + *elf_machine = EM_HOST; + return 0; + } + val = PyLong_AsLong(obj); + if (val == -1 && PyErr_Occurred()) + return -1; + if (val <= 0 || val > UINT16_MAX) + *elf_machine = EM_HOST; + else + *elf_machine = (int)val; + return 0; +} + static PyObject *pyrf__syscall_name(PyObject *self, PyObject *args, PyObject *kwargs) { const char *name; int id; int elf_machine = EM_HOST; + PyObject *elf_machine_obj = NULL; static char *kwlist[] = { "id", "elf_machine", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|$i", kwlist, &id, &elf_machine)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O", kwlist, &id, &elf_machine_obj)) + return NULL; + if (pyrf__parse_elf_machine(elf_machine_obj, &elf_machine) < 0) return NULL; name = syscalltbl__name(elf_machine, id); @@ -4125,9 +5376,12 @@ static PyObject *pyrf__syscall_id(PyObject *self, PyObject *args, PyObject *kwar const char *name; int id; int elf_machine = EM_HOST; + PyObject *elf_machine_obj = NULL; static char *kwlist[] = { "name", "elf_machine", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|$i", kwlist, &name, &elf_machine)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", kwlist, &name, &elf_machine_obj)) + return NULL; + if (pyrf__parse_elf_machine(elf_machine_obj, &elf_machine) < 0) return NULL; id = syscalltbl__id(elf_machine, name); @@ -4138,6 +5392,30 @@ static PyObject *pyrf__syscall_id(PyObject *self, PyObject *args, PyObject *kwar return PyLong_FromLong(id); } +static PyObject *pyrf__arch_strerrno(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *name; + int err; + int elf_machine = EM_HOST; + PyObject *elf_machine_obj = NULL; + static char *kwlist[] = { "err", "elf_machine", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O", kwlist, &err, &elf_machine_obj)) + return NULL; + if (pyrf__parse_elf_machine(elf_machine_obj, &elf_machine) < 0) + return NULL; + + if (err == INT_MIN) + Py_RETURN_NONE; + if (err < 0) + err = -err; + + name = perf_env__arch_strerrno((uint16_t)elf_machine, err); + if (!name || !strcmp(name, "(unknown)")) + Py_RETURN_NONE; + return PyUnicode_FromString(name); +} + static PyObject *pyrf__config_get(PyObject *self, PyObject *args) { const char *config_name, *val; @@ -4202,6 +5480,12 @@ static PyMethodDef perf__methods[] = { .ml_flags = METH_VARARGS | METH_KEYWORDS, .ml_doc = PyDoc_STR("Turns a syscall name to a number.") }, + { + .ml_name = "arch_strerrno", + .ml_meth = (PyCFunction) pyrf__arch_strerrno, + .ml_flags = METH_VARARGS | METH_KEYWORDS, + .ml_doc = PyDoc_STR("Turns an errno number to a string.") + }, { .ml_name = NULL, } }; @@ -4226,7 +5510,8 @@ PyMODINIT_FUNC PyInit_perf(void) if (module == NULL) return NULL; - if (pyrf_event__setup_types() < 0 || + if (PyType_Ready(&pyrf_call_return__type) < 0 || + pyrf_event__setup_types() < 0 || pyrf_evlist__setup_types() < 0 || pyrf_evsel__setup_types() < 0 || pyrf_thread_map__setup_types() < 0 || @@ -4243,6 +5528,7 @@ PyMODINIT_FUNC PyInit_perf(void) /* The page_size is placed in util object. */ page_size = sysconf(_SC_PAGE_SIZE); + perf_debug_setup(); Py_INCREF(&pyrf_evlist__type); PyModule_AddObject(module, "evlist", (PyObject *)&pyrf_evlist__type); @@ -4277,9 +5563,6 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_throttle_event__type); PyModule_AddObject(module, "throttle_event", (PyObject *)&pyrf_throttle_event__type); - Py_INCREF(&pyrf_task_event__type); - PyModule_AddObject(module, "task_event", (PyObject *)&pyrf_task_event__type); - Py_INCREF(&pyrf_read_event__type); PyModule_AddObject(module, "read_event", (PyObject *)&pyrf_read_event__type); @@ -4309,6 +5592,8 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_session__type); PyModule_AddObject(module, "session", (PyObject *)&pyrf_session__type); + Py_INCREF(&pyrf_call_return__type); + PyModule_AddObject(module, "call_return", (PyObject *)&pyrf_call_return__type); Py_INCREF(&pyrf_branch_entry__type); if (PyModule_AddObject(module, "branch_entry", (PyObject *)&pyrf_branch_entry__type) < 0) { diff --git a/tools/perf/util/record.h b/tools/perf/util/record.h index 93627c9a7338..411bb7276ad7 100644 --- a/tools/perf/util/record.h +++ b/tools/perf/util/record.h @@ -62,6 +62,13 @@ struct record_opts { u64 branch_stack; u64 sample_intr_regs; u64 sample_user_regs; + u16 sample_simd_regs_enabled; + u16 sample_vec_reg_qwords; + u16 sample_pred_reg_qwords; + u32 sample_intr_pred_regs; + u32 sample_user_pred_regs; + u64 sample_intr_vec_regs; + u64 sample_user_vec_regs; u64 default_interval; u64 user_interval; size_t auxtrace_snapshot_size; diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h index c4eae8b2fd06..cb4b16654876 100644 --- a/tools/perf/util/sample.h +++ b/tools/perf/util/sample.h @@ -16,6 +16,11 @@ struct regs_dump { u64 abi; u64 mask; u64 *regs; + u64 nr_vectors; + u64 vector_qwords; + u64 nr_pred; + u64 pred_qwords; + u64 *simd_data; /* Cached values/mask filled by first register access. */ u64 cache_regs[PERF_SAMPLE_REGS_CACHE_SIZE]; diff --git a/tools/perf/util/scripting-engines/Build b/tools/perf/util/scripting-engines/Build deleted file mode 100644 index 24f087b0cd11..000000000000 --- a/tools/perf/util/scripting-engines/Build +++ /dev/null @@ -1,9 +0,0 @@ -ifeq ($(CONFIG_LIBTRACEEVENT),y) - perf-util-$(CONFIG_LIBPERL) += trace-event-perl.o -endif -perf-util-$(CONFIG_LIBPYTHON) += trace-event-python.o - -CFLAGS_trace-event-perl.o += $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow -Wno-nested-externs -Wno-undef -Wno-switch-default -Wno-bad-function-cast -Wno-declaration-after-statement -Wno-switch-enum -Wno-thread-safety-analysis - -# -Wno-declaration-after-statement: The python headers have mixed code with declarations (decls after asserts, for instance) -CFLAGS_trace-event-python.o += $(PYTHON_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow -Wno-deprecated-declarations -Wno-switch-enum -Wno-declaration-after-statement diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c deleted file mode 100644 index 410dc4cd0600..000000000000 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ /dev/null @@ -1,770 +0,0 @@ -/* - * trace-event-perl. Feed perf script events to an embedded Perl interpreter. - * - * Copyright (C) 2009 Tom Zanussi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -/* perl needs the following define, right after including stdbool.h */ -#define HAS_BOOL -#include -#include - -#include "../callchain.h" -#include "../dso.h" -#include "../machine.h" -#include "../map.h" -#include "../symbol.h" -#include "../thread.h" -#include "../event.h" -#include "../trace-event.h" -#include "../evsel.h" -#include "../debug.h" - -void boot_Perf__Trace__Context(pTHX_ CV *cv); -void boot_DynaLoader(pTHX_ CV *cv); -typedef PerlInterpreter * INTERP; - -void xs_init(pTHX); - -void xs_init(pTHX) -{ - const char *file = __FILE__; - dXSUB_SYS; - - newXS("Perf::Trace::Context::bootstrap", boot_Perf__Trace__Context, - file); - newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); -} - -INTERP my_perl; - -#define TRACE_EVENT_TYPE_MAX \ - ((1 << (sizeof(unsigned short) * 8)) - 1) - -extern struct scripting_context *scripting_context; - -static char *cur_field_name; -static int zero_flag_atom; - -static void define_symbolic_value(const char *ev_name, - const char *field_name, - const char *field_value, - const char *field_str) -{ - unsigned long long value; - dSP; - - value = eval_flag(field_value); - - ENTER; - SAVETMPS; - PUSHMARK(SP); - - XPUSHs(sv_2mortal(newSVpv(ev_name, 0))); - XPUSHs(sv_2mortal(newSVpv(field_name, 0))); - XPUSHs(sv_2mortal(newSVuv(value))); - XPUSHs(sv_2mortal(newSVpv(field_str, 0))); - - PUTBACK; - if (get_cv("main::define_symbolic_value", 0)) - call_pv("main::define_symbolic_value", G_SCALAR); - SPAGAIN; - PUTBACK; - FREETMPS; - LEAVE; -} - -static void define_symbolic_values(struct tep_print_flag_sym *field, - const char *ev_name, - const char *field_name) -{ - define_symbolic_value(ev_name, field_name, field->value, field->str); - if (field->next) - define_symbolic_values(field->next, ev_name, field_name); -} - -static void define_symbolic_field(const char *ev_name, - const char *field_name) -{ - dSP; - - ENTER; - SAVETMPS; - PUSHMARK(SP); - - XPUSHs(sv_2mortal(newSVpv(ev_name, 0))); - XPUSHs(sv_2mortal(newSVpv(field_name, 0))); - - PUTBACK; - if (get_cv("main::define_symbolic_field", 0)) - call_pv("main::define_symbolic_field", G_SCALAR); - SPAGAIN; - PUTBACK; - FREETMPS; - LEAVE; -} - -static void define_flag_value(const char *ev_name, - const char *field_name, - const char *field_value, - const char *field_str) -{ - unsigned long long value; - dSP; - - value = eval_flag(field_value); - - ENTER; - SAVETMPS; - PUSHMARK(SP); - - XPUSHs(sv_2mortal(newSVpv(ev_name, 0))); - XPUSHs(sv_2mortal(newSVpv(field_name, 0))); - XPUSHs(sv_2mortal(newSVuv(value))); - XPUSHs(sv_2mortal(newSVpv(field_str, 0))); - - PUTBACK; - if (get_cv("main::define_flag_value", 0)) - call_pv("main::define_flag_value", G_SCALAR); - SPAGAIN; - PUTBACK; - FREETMPS; - LEAVE; -} - -static void define_flag_values(struct tep_print_flag_sym *field, - const char *ev_name, - const char *field_name) -{ - define_flag_value(ev_name, field_name, field->value, field->str); - if (field->next) - define_flag_values(field->next, ev_name, field_name); -} - -static void define_flag_field(const char *ev_name, - const char *field_name, - const char *delim) -{ - dSP; - - ENTER; - SAVETMPS; - PUSHMARK(SP); - - XPUSHs(sv_2mortal(newSVpv(ev_name, 0))); - XPUSHs(sv_2mortal(newSVpv(field_name, 0))); - XPUSHs(sv_2mortal(newSVpv(delim, 0))); - - PUTBACK; - if (get_cv("main::define_flag_field", 0)) - call_pv("main::define_flag_field", G_SCALAR); - SPAGAIN; - PUTBACK; - FREETMPS; - LEAVE; -} - -static void define_event_symbols(struct tep_event *event, - const char *ev_name, - struct tep_print_arg *args) -{ - if (args == NULL) - return; - - switch (args->type) { - case TEP_PRINT_NULL: - break; - case TEP_PRINT_ATOM: - define_flag_value(ev_name, cur_field_name, "0", - args->atom.atom); - zero_flag_atom = 0; - break; - case TEP_PRINT_FIELD: - free(cur_field_name); - cur_field_name = strdup(args->field.name); - break; - case TEP_PRINT_FLAGS: - define_event_symbols(event, ev_name, args->flags.field); - define_flag_field(ev_name, cur_field_name, args->flags.delim); - define_flag_values(args->flags.flags, ev_name, cur_field_name); - break; - case TEP_PRINT_SYMBOL: - define_event_symbols(event, ev_name, args->symbol.field); - define_symbolic_field(ev_name, cur_field_name); - define_symbolic_values(args->symbol.symbols, ev_name, - cur_field_name); - break; - case TEP_PRINT_HEX: - case TEP_PRINT_HEX_STR: - define_event_symbols(event, ev_name, args->hex.field); - define_event_symbols(event, ev_name, args->hex.size); - break; - case TEP_PRINT_INT_ARRAY: - define_event_symbols(event, ev_name, args->int_array.field); - define_event_symbols(event, ev_name, args->int_array.count); - define_event_symbols(event, ev_name, args->int_array.el_size); - break; - case TEP_PRINT_BSTRING: - case TEP_PRINT_DYNAMIC_ARRAY: - case TEP_PRINT_DYNAMIC_ARRAY_LEN: - case TEP_PRINT_STRING: - case TEP_PRINT_BITMASK: - break; - case TEP_PRINT_TYPE: - define_event_symbols(event, ev_name, args->typecast.item); - break; - case TEP_PRINT_OP: - if (strcmp(args->op.op, ":") == 0) - zero_flag_atom = 1; - define_event_symbols(event, ev_name, args->op.left); - define_event_symbols(event, ev_name, args->op.right); - break; - case TEP_PRINT_FUNC: - default: - pr_err("Unsupported print arg type\n"); - /* we should warn... */ - return; - } - - if (args->next) - define_event_symbols(event, ev_name, args->next); -} - -static SV *perl_process_callchain(struct perf_sample *sample, - struct addr_location *al) -{ - struct callchain_cursor *cursor; - AV *list; - - list = newAV(); - if (!list) - goto exit; - - if (!symbol_conf.use_callchain || !sample->callchain) - goto exit; - - cursor = get_tls_callchain_cursor(); - - if (thread__resolve_callchain(al->thread, cursor, - sample, NULL, NULL, scripting_max_stack) != 0) { - pr_err("Failed to resolve callchain. Skipping\n"); - goto exit; - } - callchain_cursor_commit(cursor); - - - while (1) { - HV *elem; - struct callchain_cursor_node *node; - node = callchain_cursor_current(cursor); - if (!node) - break; - - elem = newHV(); - if (!elem) - goto exit; - - if (!hv_stores(elem, "ip", newSVuv(node->ip))) { - hv_undef(elem); - goto exit; - } - - if (node->ms.sym) { - HV *sym = newHV(); - if (!sym) { - hv_undef(elem); - goto exit; - } - if (!hv_stores(sym, "start", newSVuv(node->ms.sym->start)) || - !hv_stores(sym, "end", newSVuv(node->ms.sym->end)) || - !hv_stores(sym, "binding", newSVuv(symbol__binding(node->ms.sym))) || - !hv_stores(sym, "name", newSVpvn(node->ms.sym->name, - node->ms.sym->namelen)) || - !hv_stores(elem, "sym", newRV_noinc((SV*)sym))) { - hv_undef(sym); - hv_undef(elem); - goto exit; - } - } - - if (node->ms.map) { - struct map *map = node->ms.map; - struct dso *dso = map ? map__dso(map) : NULL; - const char *dsoname = "[unknown]"; - - if (dso) { - if (symbol_conf.show_kernel_path && dso__long_name(dso)) - dsoname = dso__long_name(dso); - else - dsoname = dso__name(dso); - } - if (!hv_stores(elem, "dso", newSVpv(dsoname,0))) { - hv_undef(elem); - goto exit; - } - } - - callchain_cursor_advance(cursor); - av_push(list, newRV_noinc((SV*)elem)); - } - -exit: - return newRV_noinc((SV*)list); -} - -static void perl_process_tracepoint(struct perf_sample *sample, - struct addr_location *al) -{ - struct thread *thread = al->thread; - struct tep_event *event; - struct tep_format_field *field; - static char handler[256]; - unsigned long long val; - unsigned long s, ns; - int pid; - int cpu = sample->cpu; - void *data = sample->raw_data; - unsigned long long nsecs = sample->time; - const char *comm = thread__comm_str(thread); - DECLARE_BITMAP(events_defined, TRACE_EVENT_TYPE_MAX); - struct evsel *evsel = sample->evsel; - - bitmap_zero(events_defined, TRACE_EVENT_TYPE_MAX); - dSP; - - if (evsel->core.attr.type != PERF_TYPE_TRACEPOINT) - return; - - event = evsel__tp_format(evsel); - if (!event) { - pr_debug("ug! no event found for type %" PRIu64, (u64)evsel->core.attr.config); - return; - } - - pid = raw_field_value(event, "common_pid", data); - - sprintf(handler, "%s::%s", event->system, event->name); - - if (!__test_and_set_bit(event->id, events_defined)) - define_event_symbols(event, handler, event->print_fmt.args); - - s = nsecs / NSEC_PER_SEC; - ns = nsecs - s * NSEC_PER_SEC; - - ENTER; - SAVETMPS; - PUSHMARK(SP); - - XPUSHs(sv_2mortal(newSVpv(handler, 0))); - XPUSHs(sv_2mortal(newSViv(PTR2IV(scripting_context)))); - XPUSHs(sv_2mortal(newSVuv(cpu))); - XPUSHs(sv_2mortal(newSVuv(s))); - XPUSHs(sv_2mortal(newSVuv(ns))); - XPUSHs(sv_2mortal(newSViv(pid))); - XPUSHs(sv_2mortal(newSVpv(comm, 0))); - XPUSHs(sv_2mortal(perl_process_callchain(sample, al))); - - /* common fields other than pid can be accessed via xsub fns */ - - for (field = event->format.fields; field; field = field->next) { - if (field->flags & TEP_FIELD_IS_STRING) { - int offset; - if (field->flags & TEP_FIELD_IS_DYNAMIC) { - offset = *(int *)(data + field->offset); - offset &= 0xffff; - if (tep_field_is_relative(field->flags)) - offset += field->offset + field->size; - } else - offset = field->offset; - XPUSHs(sv_2mortal(newSVpv((char *)data + offset, 0))); - } else { /* FIELD_IS_NUMERIC */ - val = read_size(event, data + field->offset, - field->size); - if (field->flags & TEP_FIELD_IS_SIGNED) { - XPUSHs(sv_2mortal(newSViv(val))); - } else { - XPUSHs(sv_2mortal(newSVuv(val))); - } - } - } - - PUTBACK; - - if (get_cv(handler, 0)) - call_pv(handler, G_SCALAR); - else if (get_cv("main::trace_unhandled", 0)) { - XPUSHs(sv_2mortal(newSVpv(handler, 0))); - XPUSHs(sv_2mortal(newSViv(PTR2IV(scripting_context)))); - XPUSHs(sv_2mortal(newSVuv(cpu))); - XPUSHs(sv_2mortal(newSVuv(nsecs))); - XPUSHs(sv_2mortal(newSViv(pid))); - XPUSHs(sv_2mortal(newSVpv(comm, 0))); - XPUSHs(sv_2mortal(perl_process_callchain(sample, al))); - call_pv("main::trace_unhandled", G_SCALAR); - } - SPAGAIN; - PUTBACK; - FREETMPS; - LEAVE; -} - -static void perl_process_event_generic(union perf_event *event, struct perf_sample *sample) -{ - dSP; - - if (!get_cv("process_event", 0)) - return; - - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSVpvn((const char *)event, event->header.size))); - XPUSHs(sv_2mortal(newSVpvn((const char *)&sample->evsel->core.attr, - sizeof(sample->evsel->core.attr)))); - XPUSHs(sv_2mortal(newSVpvn((const char *)sample, sizeof(*sample)))); - XPUSHs(sv_2mortal(newSVpvn((const char *)sample->raw_data, sample->raw_size))); - PUTBACK; - call_pv("process_event", G_SCALAR); - SPAGAIN; - PUTBACK; - FREETMPS; - LEAVE; -} - -static void perl_process_event(union perf_event *event, - struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al) -{ - scripting_context__update(scripting_context, event, sample, al, addr_al); - perl_process_tracepoint(sample, al); - perl_process_event_generic(event, sample); -} - -static void run_start_sub(void) -{ - dSP; /* access to Perl stack */ - PUSHMARK(SP); - - if (get_cv("main::trace_begin", 0)) - call_pv("main::trace_begin", G_DISCARD | G_NOARGS); -} - -/* - * Start trace script - */ -static int perl_start_script(const char *script, int argc, const char **argv, - struct perf_session *session) -{ - const char **command_line; - int i, err = 0; - - scripting_context->session = session; - - command_line = malloc((argc + 2) * sizeof(const char *)); - if (!command_line) - return -ENOMEM; - - command_line[0] = ""; - command_line[1] = script; - for (i = 2; i < argc + 2; i++) - command_line[i] = argv[i - 2]; - - my_perl = perl_alloc(); - perl_construct(my_perl); - - if (perl_parse(my_perl, xs_init, argc + 2, (char **)command_line, - (char **)NULL)) { - err = -1; - goto error; - } - - if (perl_run(my_perl)) { - err = -1; - goto error; - } - - if (SvTRUE(ERRSV)) { - err = -1; - goto error; - } - - run_start_sub(); - - free(command_line); - return 0; -error: - perl_free(my_perl); - free(command_line); - - return err; -} - -static int perl_flush_script(void) -{ - return 0; -} - -/* - * Stop trace script - */ -static int perl_stop_script(void) -{ - dSP; /* access to Perl stack */ - PUSHMARK(SP); - - if (get_cv("main::trace_end", 0)) - call_pv("main::trace_end", G_DISCARD | G_NOARGS); - - perl_destruct(my_perl); - perl_free(my_perl); - - return 0; -} - -static int perl_generate_script(struct tep_handle *pevent, const char *outfile) -{ - int i, not_first, count, nr_events; - struct tep_event **all_events; - struct tep_event *event = NULL; - struct tep_format_field *f; - char fname[PATH_MAX]; - FILE *ofp; - - sprintf(fname, "%s.pl", outfile); - ofp = fopen(fname, "w"); - if (ofp == NULL) { - fprintf(stderr, "couldn't open %s\n", fname); - return -1; - } - - fprintf(ofp, "# perf script event handlers, " - "generated by perf script -g perl\n"); - - fprintf(ofp, "# Licensed under the terms of the GNU GPL" - " License version 2\n\n"); - - fprintf(ofp, "# The common_* event handler fields are the most useful " - "fields common to\n"); - - fprintf(ofp, "# all events. They don't necessarily correspond to " - "the 'common_*' fields\n"); - - fprintf(ofp, "# in the format files. Those fields not available as " - "handler params can\n"); - - fprintf(ofp, "# be retrieved using Perl functions of the form " - "common_*($context).\n"); - - fprintf(ofp, "# See Context.pm for the list of available " - "functions.\n\n"); - - fprintf(ofp, "use lib \"$ENV{'PERF_EXEC_PATH'}/scripts/perl/" - "Perf-Trace-Util/lib\";\n"); - - fprintf(ofp, "use lib \"./Perf-Trace-Util/lib\";\n"); - fprintf(ofp, "use Perf::Trace::Core;\n"); - fprintf(ofp, "use Perf::Trace::Context;\n"); - fprintf(ofp, "use Perf::Trace::Util;\n\n"); - - fprintf(ofp, "sub trace_begin\n{\n\t# optional\n}\n\n"); - fprintf(ofp, "sub trace_end\n{\n\t# optional\n}\n"); - - - fprintf(ofp, "\n\ -sub print_backtrace\n\ -{\n\ - my $callchain = shift;\n\ - for my $node (@$callchain)\n\ - {\n\ - if(exists $node->{sym})\n\ - {\n\ - printf( \"\\t[\\%%x] \\%%s\\n\", $node->{ip}, $node->{sym}{name});\n\ - }\n\ - else\n\ - {\n\ - printf( \"\\t[\\%%x]\\n\", $node{ip});\n\ - }\n\ - }\n\ -}\n\n\ -"); - - nr_events = tep_get_events_count(pevent); - all_events = tep_list_events(pevent, TEP_EVENT_SORT_ID); - - for (i = 0; all_events && i < nr_events; i++) { - event = all_events[i]; - fprintf(ofp, "sub %s::%s\n{\n", event->system, event->name); - fprintf(ofp, "\tmy ("); - - fprintf(ofp, "$event_name, "); - fprintf(ofp, "$context, "); - fprintf(ofp, "$common_cpu, "); - fprintf(ofp, "$common_secs, "); - fprintf(ofp, "$common_nsecs,\n"); - fprintf(ofp, "\t $common_pid, "); - fprintf(ofp, "$common_comm, "); - fprintf(ofp, "$common_callchain,\n\t "); - - not_first = 0; - count = 0; - - for (f = event->format.fields; f; f = f->next) { - if (not_first++) - fprintf(ofp, ", "); - if (++count % 5 == 0) - fprintf(ofp, "\n\t "); - - fprintf(ofp, "$%s", f->name); - } - fprintf(ofp, ") = @_;\n\n"); - - fprintf(ofp, "\tprint_header($event_name, $common_cpu, " - "$common_secs, $common_nsecs,\n\t " - "$common_pid, $common_comm, $common_callchain);\n\n"); - - fprintf(ofp, "\tprintf(\""); - - not_first = 0; - count = 0; - - for (f = event->format.fields; f; f = f->next) { - if (not_first++) - fprintf(ofp, ", "); - if (count && count % 4 == 0) { - fprintf(ofp, "\".\n\t \""); - } - count++; - - fprintf(ofp, "%s=", f->name); - if (f->flags & TEP_FIELD_IS_STRING || - f->flags & TEP_FIELD_IS_FLAG || - f->flags & TEP_FIELD_IS_SYMBOLIC) - fprintf(ofp, "%%s"); - else if (f->flags & TEP_FIELD_IS_SIGNED) - fprintf(ofp, "%%d"); - else - fprintf(ofp, "%%u"); - } - - fprintf(ofp, "\\n\",\n\t "); - - not_first = 0; - count = 0; - - for (f = event->format.fields; f; f = f->next) { - if (not_first++) - fprintf(ofp, ", "); - - if (++count % 5 == 0) - fprintf(ofp, "\n\t "); - - if (f->flags & TEP_FIELD_IS_FLAG) { - if ((count - 1) % 5 != 0) { - fprintf(ofp, "\n\t "); - count = 4; - } - fprintf(ofp, "flag_str(\""); - fprintf(ofp, "%s::%s\", ", event->system, - event->name); - fprintf(ofp, "\"%s\", $%s)", f->name, - f->name); - } else if (f->flags & TEP_FIELD_IS_SYMBOLIC) { - if ((count - 1) % 5 != 0) { - fprintf(ofp, "\n\t "); - count = 4; - } - fprintf(ofp, "symbol_str(\""); - fprintf(ofp, "%s::%s\", ", event->system, - event->name); - fprintf(ofp, "\"%s\", $%s)", f->name, - f->name); - } else - fprintf(ofp, "$%s", f->name); - } - - fprintf(ofp, ");\n\n"); - - fprintf(ofp, "\tprint_backtrace($common_callchain);\n"); - - fprintf(ofp, "}\n\n"); - } - - fprintf(ofp, "sub trace_unhandled\n{\n\tmy ($event_name, $context, " - "$common_cpu, $common_secs, $common_nsecs,\n\t " - "$common_pid, $common_comm, $common_callchain) = @_;\n\n"); - - fprintf(ofp, "\tprint_header($event_name, $common_cpu, " - "$common_secs, $common_nsecs,\n\t $common_pid, " - "$common_comm, $common_callchain);\n"); - fprintf(ofp, "\tprint_backtrace($common_callchain);\n"); - fprintf(ofp, "}\n\n"); - - fprintf(ofp, "sub print_header\n{\n" - "\tmy ($event_name, $cpu, $secs, $nsecs, $pid, $comm) = @_;\n\n" - "\tprintf(\"%%-20s %%5u %%05u.%%09u %%8u %%-20s \",\n\t " - "$event_name, $cpu, $secs, $nsecs, $pid, $comm);\n}\n"); - - fprintf(ofp, - "\n# Packed byte string args of process_event():\n" - "#\n" - "# $event:\tunion perf_event\tutil/event.h\n" - "# $attr:\tstruct perf_event_attr\tlinux/perf_event.h\n" - "# $sample:\tstruct perf_sample\tutil/event.h\n" - "# $raw_data:\tperf_sample->raw_data\tutil/event.h\n" - "\n" - "sub process_event\n" - "{\n" - "\tmy ($event, $attr, $sample, $raw_data) = @_;\n" - "\n" - "\tmy @event\t= unpack(\"LSS\", $event);\n" - "\tmy @attr\t= unpack(\"LLQQQQQLLQQ\", $attr);\n" - "\tmy @sample\t= unpack(\"QLLQQQQQLL\", $sample);\n" - "\tmy @raw_data\t= unpack(\"C*\", $raw_data);\n" - "\n" - "\tuse Data::Dumper;\n" - "\tprint Dumper \\@event, \\@attr, \\@sample, \\@raw_data;\n" - "}\n"); - - fclose(ofp); - - fprintf(stderr, "generated Perl script: %s\n", fname); - - return 0; -} - -struct scripting_ops perl_scripting_ops = { - .name = "Perl", - .dirname = "perl", - .start_script = perl_start_script, - .flush_script = perl_flush_script, - .stop_script = perl_stop_script, - .process_event = perl_process_event, - .generate_script = perl_generate_script, -}; diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c deleted file mode 100644 index 8f832ae316ca..000000000000 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ /dev/null @@ -1,2224 +0,0 @@ -/* - * trace-event-python. Feed trace events to an embedded Python interpreter. - * - * Copyright (C) 2010 Tom Zanussi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_LIBTRACEEVENT -#include -#endif - -#include "../build-id.h" -#include "../counts.h" -#include "../debug.h" -#include "../dso.h" -#include "../callchain.h" -#include "../env.h" -#include "../evsel.h" -#include "../event.h" -#include "../thread.h" -#include "../comm.h" -#include "../machine.h" -#include "../mem-info.h" -#include "../db-export.h" -#include "../thread-stack.h" -#include "../trace-event.h" -#include "../call-path.h" -#include "dwarf-regs.h" -#include "map.h" -#include "symbol.h" -#include "thread_map.h" -#include "print_binary.h" -#include "stat.h" -#include "mem-events.h" -#include "util/perf_regs.h" - -#define _PyUnicode_FromString(arg) \ - PyUnicode_FromString(arg) -#define _PyUnicode_FromStringAndSize(arg1, arg2) \ - PyUnicode_FromStringAndSize((arg1), (arg2)) -#define _PyBytes_FromStringAndSize(arg1, arg2) \ - PyBytes_FromStringAndSize((arg1), (arg2)) -#define _PyLong_FromLong(arg) \ - PyLong_FromLong(arg) -#define _PyLong_AsLong(arg) \ - PyLong_AsLong(arg) -#define _PyCapsule_New(arg1, arg2, arg3) \ - PyCapsule_New((arg1), (arg2), (arg3)) - -PyMODINIT_FUNC PyInit_perf_trace_context(void); - -#ifdef HAVE_LIBTRACEEVENT -#define TRACE_EVENT_TYPE_MAX \ - ((1 << (sizeof(unsigned short) * 8)) - 1) - -#define N_COMMON_FIELDS 7 - -static char *cur_field_name; -static int zero_flag_atom; -#endif - -#define MAX_FIELDS 64 - -extern struct scripting_context *scripting_context; - -static PyObject *main_module, *main_dict; - -struct tables { - struct db_export dbe; - PyObject *evsel_handler; - PyObject *machine_handler; - PyObject *thread_handler; - PyObject *comm_handler; - PyObject *comm_thread_handler; - PyObject *dso_handler; - PyObject *symbol_handler; - PyObject *branch_type_handler; - PyObject *sample_handler; - PyObject *call_path_handler; - PyObject *call_return_handler; - PyObject *synth_handler; - PyObject *context_switch_handler; - bool db_export_mode; -}; - -static struct tables tables_global; - -static void handler_call_die(const char *handler_name) __noreturn; -static void handler_call_die(const char *handler_name) -{ - PyErr_Print(); - Py_FatalError("problem in Python trace event handler"); - // Py_FatalError does not return - // but we have to make the compiler happy - abort(); -} - -/* - * Insert val into the dictionary and decrement the reference counter. - * This is necessary for dictionaries since PyDict_SetItemString() does not - * steal a reference, as opposed to PyTuple_SetItem(). - */ -static void pydict_set_item_string_decref(PyObject *dict, const char *key, PyObject *val) -{ - PyDict_SetItemString(dict, key, val); - Py_DECREF(val); -} - -static PyObject *get_handler(const char *handler_name) -{ - PyObject *handler; - - handler = PyDict_GetItemString(main_dict, handler_name); - if (handler && !PyCallable_Check(handler)) - return NULL; - return handler; -} - -static void call_object(PyObject *handler, PyObject *args, const char *die_msg) -{ - PyObject *retval; - - retval = PyObject_CallObject(handler, args); - if (retval == NULL) - handler_call_die(die_msg); - Py_DECREF(retval); -} - -static void try_call_object(const char *handler_name, PyObject *args) -{ - PyObject *handler; - - handler = get_handler(handler_name); - if (handler) - call_object(handler, args, handler_name); -} - -#ifdef HAVE_LIBTRACEEVENT -static int get_argument_count(PyObject *handler) -{ - int arg_count = 0; - - PyObject *code_obj = code_obj = PyObject_GetAttrString(handler, "__code__"); - PyErr_Clear(); - if (code_obj) { - PyObject *arg_count_obj = PyObject_GetAttrString(code_obj, - "co_argcount"); - if (arg_count_obj) { - arg_count = (int) _PyLong_AsLong(arg_count_obj); - Py_DECREF(arg_count_obj); - } - Py_DECREF(code_obj); - } - return arg_count; -} - -static void define_value(enum tep_print_arg_type field_type, - const char *ev_name, - const char *field_name, - const char *field_value, - const char *field_str) -{ - const char *handler_name = "define_flag_value"; - PyObject *t; - unsigned long long value; - unsigned n = 0; - - if (field_type == TEP_PRINT_SYMBOL) - handler_name = "define_symbolic_value"; - - t = PyTuple_New(4); - if (!t) - Py_FatalError("couldn't create Python tuple"); - - value = eval_flag(field_value); - - PyTuple_SetItem(t, n++, _PyUnicode_FromString(ev_name)); - PyTuple_SetItem(t, n++, _PyUnicode_FromString(field_name)); - PyTuple_SetItem(t, n++, _PyLong_FromLong(value)); - PyTuple_SetItem(t, n++, _PyUnicode_FromString(field_str)); - - try_call_object(handler_name, t); - - Py_DECREF(t); -} - -static void define_values(enum tep_print_arg_type field_type, - struct tep_print_flag_sym *field, - const char *ev_name, - const char *field_name) -{ - define_value(field_type, ev_name, field_name, field->value, - field->str); - - if (field->next) - define_values(field_type, field->next, ev_name, field_name); -} - -static void define_field(enum tep_print_arg_type field_type, - const char *ev_name, - const char *field_name, - const char *delim) -{ - const char *handler_name = "define_flag_field"; - PyObject *t; - unsigned n = 0; - - if (field_type == TEP_PRINT_SYMBOL) - handler_name = "define_symbolic_field"; - - if (field_type == TEP_PRINT_FLAGS) - t = PyTuple_New(3); - else - t = PyTuple_New(2); - if (!t) - Py_FatalError("couldn't create Python tuple"); - - PyTuple_SetItem(t, n++, _PyUnicode_FromString(ev_name)); - PyTuple_SetItem(t, n++, _PyUnicode_FromString(field_name)); - if (field_type == TEP_PRINT_FLAGS) - PyTuple_SetItem(t, n++, _PyUnicode_FromString(delim)); - - try_call_object(handler_name, t); - - Py_DECREF(t); -} - -static void define_event_symbols(struct tep_event *event, - const char *ev_name, - struct tep_print_arg *args) -{ - if (args == NULL) - return; - - switch (args->type) { - case TEP_PRINT_NULL: - break; - case TEP_PRINT_ATOM: - define_value(TEP_PRINT_FLAGS, ev_name, cur_field_name, "0", - args->atom.atom); - zero_flag_atom = 0; - break; - case TEP_PRINT_FIELD: - free(cur_field_name); - cur_field_name = strdup(args->field.name); - break; - case TEP_PRINT_FLAGS: - define_event_symbols(event, ev_name, args->flags.field); - define_field(TEP_PRINT_FLAGS, ev_name, cur_field_name, - args->flags.delim); - define_values(TEP_PRINT_FLAGS, args->flags.flags, ev_name, - cur_field_name); - break; - case TEP_PRINT_SYMBOL: - define_event_symbols(event, ev_name, args->symbol.field); - define_field(TEP_PRINT_SYMBOL, ev_name, cur_field_name, NULL); - define_values(TEP_PRINT_SYMBOL, args->symbol.symbols, ev_name, - cur_field_name); - break; - case TEP_PRINT_HEX: - case TEP_PRINT_HEX_STR: - define_event_symbols(event, ev_name, args->hex.field); - define_event_symbols(event, ev_name, args->hex.size); - break; - case TEP_PRINT_INT_ARRAY: - define_event_symbols(event, ev_name, args->int_array.field); - define_event_symbols(event, ev_name, args->int_array.count); - define_event_symbols(event, ev_name, args->int_array.el_size); - break; - case TEP_PRINT_STRING: - break; - case TEP_PRINT_TYPE: - define_event_symbols(event, ev_name, args->typecast.item); - break; - case TEP_PRINT_OP: - if (strcmp(args->op.op, ":") == 0) - zero_flag_atom = 1; - define_event_symbols(event, ev_name, args->op.left); - define_event_symbols(event, ev_name, args->op.right); - break; - default: - /* gcc warns for these? */ - case TEP_PRINT_BSTRING: - case TEP_PRINT_DYNAMIC_ARRAY: - case TEP_PRINT_DYNAMIC_ARRAY_LEN: - case TEP_PRINT_FUNC: - case TEP_PRINT_BITMASK: - /* we should warn... */ - return; - } - - if (args->next) - define_event_symbols(event, ev_name, args->next); -} - -static PyObject *get_field_numeric_entry(struct tep_event *event, - struct tep_format_field *field, void *data) -{ - bool is_array = field->flags & TEP_FIELD_IS_ARRAY; - PyObject *obj = NULL, *list = NULL; - unsigned long long val; - unsigned int item_size, n_items, i; - - if (is_array) { - list = PyList_New(field->arraylen); - if (!list) - Py_FatalError("couldn't create Python list"); - item_size = field->size / field->arraylen; - n_items = field->arraylen; - } else { - item_size = field->size; - n_items = 1; - } - - for (i = 0; i < n_items; i++) { - - val = read_size(event, data + field->offset + i * item_size, - item_size); - if (field->flags & TEP_FIELD_IS_SIGNED) { - if ((long long)val >= LONG_MIN && - (long long)val <= LONG_MAX) - obj = _PyLong_FromLong(val); - else - obj = PyLong_FromLongLong(val); - } else { - if (val <= LONG_MAX) - obj = _PyLong_FromLong(val); - else - obj = PyLong_FromUnsignedLongLong(val); - } - if (is_array) - PyList_SET_ITEM(list, i, obj); - } - if (is_array) - obj = list; - return obj; -} -#endif - -static const char *get_dsoname(struct map *map) -{ - const char *dsoname = "[unknown]"; - struct dso *dso = map ? map__dso(map) : NULL; - - if (dso) { - if (symbol_conf.show_kernel_path && dso__long_name(dso)) - dsoname = dso__long_name(dso); - else - dsoname = dso__name(dso); - } - - return dsoname; -} - -static unsigned long get_offset(struct symbol *sym, struct addr_location *al) -{ - unsigned long offset; - - if (al->addr < sym->end) - offset = al->addr - sym->start; - else - offset = al->addr - map__start(al->map) - sym->start; - - return offset; -} - -static PyObject *python_process_callchain(struct perf_sample *sample, - struct addr_location *al) -{ - PyObject *pylist; - struct callchain_cursor *cursor; - - pylist = PyList_New(0); - if (!pylist) - Py_FatalError("couldn't create Python list"); - - if (!symbol_conf.use_callchain || !sample->callchain) - goto exit; - - cursor = get_tls_callchain_cursor(); - if (thread__resolve_callchain(al->thread, cursor, - sample, NULL, NULL, - scripting_max_stack) != 0) { - pr_err("Failed to resolve callchain. Skipping\n"); - goto exit; - } - callchain_cursor_commit(cursor); - - - while (1) { - PyObject *pyelem; - struct callchain_cursor_node *node; - node = callchain_cursor_current(cursor); - if (!node) - break; - - pyelem = PyDict_New(); - if (!pyelem) - Py_FatalError("couldn't create Python dictionary"); - - - pydict_set_item_string_decref(pyelem, "ip", - PyLong_FromUnsignedLongLong(node->ip)); - - if (node->ms.sym) { - PyObject *pysym = PyDict_New(); - if (!pysym) - Py_FatalError("couldn't create Python dictionary"); - pydict_set_item_string_decref(pysym, "start", - PyLong_FromUnsignedLongLong(node->ms.sym->start)); - pydict_set_item_string_decref(pysym, "end", - PyLong_FromUnsignedLongLong(node->ms.sym->end)); - pydict_set_item_string_decref(pysym, "binding", - _PyLong_FromLong(symbol__binding(node->ms.sym))); - pydict_set_item_string_decref(pysym, "name", - _PyUnicode_FromStringAndSize(node->ms.sym->name, - node->ms.sym->namelen)); - pydict_set_item_string_decref(pyelem, "sym", pysym); - - if (node->ms.map) { - struct map *map = node->ms.map; - struct addr_location node_al; - unsigned long offset; - - addr_location__init(&node_al); - node_al.addr = map__map_ip(map, node->ip); - node_al.map = map__get(map); - offset = get_offset(node->ms.sym, &node_al); - addr_location__exit(&node_al); - - pydict_set_item_string_decref( - pyelem, "sym_off", - PyLong_FromUnsignedLongLong(offset)); - } - if (node->srcline && strcmp(":0", node->srcline)) { - pydict_set_item_string_decref( - pyelem, "sym_srcline", - _PyUnicode_FromString(node->srcline)); - } - } - - if (node->ms.map) { - const char *dsoname = get_dsoname(node->ms.map); - - pydict_set_item_string_decref(pyelem, "dso", - _PyUnicode_FromString(dsoname)); - } - - callchain_cursor_advance(cursor); - PyList_Append(pylist, pyelem); - Py_DECREF(pyelem); - } - -exit: - return pylist; -} - -static PyObject *python_process_brstack(struct perf_sample *sample, - struct thread *thread) -{ - struct branch_stack *br = sample->branch_stack; - struct branch_entry *entries = perf_sample__branch_entries(sample); - PyObject *pylist; - u64 i; - - pylist = PyList_New(0); - if (!pylist) - Py_FatalError("couldn't create Python list"); - - if (!(br && br->nr)) - goto exit; - - for (i = 0; i < br->nr; i++) { - PyObject *pyelem; - struct addr_location al; - const char *dsoname; - - pyelem = PyDict_New(); - if (!pyelem) - Py_FatalError("couldn't create Python dictionary"); - - pydict_set_item_string_decref(pyelem, "from", - PyLong_FromUnsignedLongLong(entries[i].from)); - pydict_set_item_string_decref(pyelem, "to", - PyLong_FromUnsignedLongLong(entries[i].to)); - pydict_set_item_string_decref(pyelem, "mispred", - PyBool_FromLong(entries[i].flags.mispred)); - pydict_set_item_string_decref(pyelem, "predicted", - PyBool_FromLong(entries[i].flags.predicted)); - pydict_set_item_string_decref(pyelem, "in_tx", - PyBool_FromLong(entries[i].flags.in_tx)); - pydict_set_item_string_decref(pyelem, "abort", - PyBool_FromLong(entries[i].flags.abort)); - pydict_set_item_string_decref(pyelem, "cycles", - PyLong_FromUnsignedLongLong(entries[i].flags.cycles)); - - addr_location__init(&al); - thread__find_map_fb(thread, sample->cpumode, - entries[i].from, &al); - dsoname = get_dsoname(al.map); - pydict_set_item_string_decref(pyelem, "from_dsoname", - _PyUnicode_FromString(dsoname)); - - thread__find_map_fb(thread, sample->cpumode, - entries[i].to, &al); - dsoname = get_dsoname(al.map); - pydict_set_item_string_decref(pyelem, "to_dsoname", - _PyUnicode_FromString(dsoname)); - - addr_location__exit(&al); - PyList_Append(pylist, pyelem); - Py_DECREF(pyelem); - } - -exit: - return pylist; -} - -static int get_symoff(struct symbol *sym, struct addr_location *al, - bool print_off, char *bf, int size) -{ - unsigned long offset; - - if (!sym || !sym->name[0]) - return scnprintf(bf, size, "%s", "[unknown]"); - - if (!print_off) - return scnprintf(bf, size, "%s", sym->name); - - offset = get_offset(sym, al); - - return scnprintf(bf, size, "%s+0x%x", sym->name, offset); -} - -static int get_br_mspred(struct branch_flags *flags, char *bf, int size) -{ - if (!flags->mispred && !flags->predicted) - return scnprintf(bf, size, "%s", "-"); - - if (flags->mispred) - return scnprintf(bf, size, "%s", "M"); - - return scnprintf(bf, size, "%s", "P"); -} - -static PyObject *python_process_brstacksym(struct perf_sample *sample, - struct thread *thread) -{ - struct branch_stack *br = sample->branch_stack; - struct branch_entry *entries = perf_sample__branch_entries(sample); - PyObject *pylist; - u64 i; - char bf[512]; - - pylist = PyList_New(0); - if (!pylist) - Py_FatalError("couldn't create Python list"); - - if (!(br && br->nr)) - goto exit; - - for (i = 0; i < br->nr; i++) { - PyObject *pyelem; - struct addr_location al; - - addr_location__init(&al); - pyelem = PyDict_New(); - if (!pyelem) - Py_FatalError("couldn't create Python dictionary"); - - thread__find_symbol_fb(thread, sample->cpumode, - entries[i].from, &al); - get_symoff(al.sym, &al, true, bf, sizeof(bf)); - pydict_set_item_string_decref(pyelem, "from", - _PyUnicode_FromString(bf)); - - thread__find_symbol_fb(thread, sample->cpumode, - entries[i].to, &al); - get_symoff(al.sym, &al, true, bf, sizeof(bf)); - pydict_set_item_string_decref(pyelem, "to", - _PyUnicode_FromString(bf)); - - get_br_mspred(&entries[i].flags, bf, sizeof(bf)); - pydict_set_item_string_decref(pyelem, "pred", - _PyUnicode_FromString(bf)); - - if (entries[i].flags.in_tx) { - pydict_set_item_string_decref(pyelem, "in_tx", - _PyUnicode_FromString("X")); - } else { - pydict_set_item_string_decref(pyelem, "in_tx", - _PyUnicode_FromString("-")); - } - - if (entries[i].flags.abort) { - pydict_set_item_string_decref(pyelem, "abort", - _PyUnicode_FromString("A")); - } else { - pydict_set_item_string_decref(pyelem, "abort", - _PyUnicode_FromString("-")); - } - - PyList_Append(pylist, pyelem); - Py_DECREF(pyelem); - addr_location__exit(&al); - } - -exit: - return pylist; -} - -static PyObject *get_sample_value_as_tuple(struct sample_read_value *value, - u64 read_format) -{ - PyObject *t; - - t = PyTuple_New(3); - if (!t) - Py_FatalError("couldn't create Python tuple"); - PyTuple_SetItem(t, 0, PyLong_FromUnsignedLongLong(value->id)); - PyTuple_SetItem(t, 1, PyLong_FromUnsignedLongLong(value->value)); - if (read_format & PERF_FORMAT_LOST) - PyTuple_SetItem(t, 2, PyLong_FromUnsignedLongLong(value->lost)); - - return t; -} - -static void set_sample_read_in_dict(PyObject *dict_sample, struct perf_sample *sample) -{ - u64 read_format = sample->evsel->core.attr.read_format; - PyObject *values; - unsigned int i; - - if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) { - pydict_set_item_string_decref(dict_sample, "time_enabled", - PyLong_FromUnsignedLongLong(sample->read.time_enabled)); - } - - if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) { - pydict_set_item_string_decref(dict_sample, "time_running", - PyLong_FromUnsignedLongLong(sample->read.time_running)); - } - - if (read_format & PERF_FORMAT_GROUP) - values = PyList_New(sample->read.group.nr); - else - values = PyList_New(1); - - if (!values) - Py_FatalError("couldn't create Python list"); - - if (read_format & PERF_FORMAT_GROUP) { - struct sample_read_value *v = sample->read.group.values; - - i = 0; - sample_read_group__for_each(v, sample->read.group.nr, read_format) { - PyObject *t = get_sample_value_as_tuple(v, read_format); - PyList_SET_ITEM(values, i, t); - i++; - } - } else { - PyObject *t = get_sample_value_as_tuple(&sample->read.one, - read_format); - PyList_SET_ITEM(values, 0, t); - } - pydict_set_item_string_decref(dict_sample, "values", values); -} - -static void set_sample_datasrc_in_dict(PyObject *dict, - struct perf_sample *sample) -{ - struct mem_info *mi = mem_info__new(); - char decode[100]; - - if (!mi) - Py_FatalError("couldn't create mem-info"); - - pydict_set_item_string_decref(dict, "datasrc", - PyLong_FromUnsignedLongLong(sample->data_src)); - - mem_info__data_src(mi)->val = sample->data_src; - perf_script__meminfo_scnprintf(decode, 100, mi); - mem_info__put(mi); - - pydict_set_item_string_decref(dict, "datasrc_decode", - _PyUnicode_FromString(decode)); -} - -static void regs_map(struct regs_dump *regs, uint64_t mask, uint16_t e_machine, uint32_t e_flags, - char *bf, int size) -{ - unsigned int i = 0, r; - int printed = 0; - - bf[0] = 0; - - if (size <= 0) - return; - - if (!regs || !regs->regs) - return; - - for_each_set_bit(r, (unsigned long *) &mask, sizeof(mask) * 8) { - u64 val = regs->regs[i++]; - - printed += scnprintf(bf + printed, size - printed, - "%5s:0x%" PRIx64 " ", - perf_reg_name(r, e_machine, e_flags), val); - } -} - -#define MAX_REG_SIZE 128 - -static int set_regs_in_dict(PyObject *dict, - struct perf_sample *sample, - uint16_t e_machine, - uint32_t e_flags) -{ - struct perf_event_attr *attr = &sample->evsel->core.attr; - - int size = (__sw_hweight64(attr->sample_regs_intr) * MAX_REG_SIZE) + 1; - char *bf = NULL; - - if (sample->intr_regs) { - bf = malloc(size); - if (!bf) - return -1; - - regs_map(sample->intr_regs, attr->sample_regs_intr, e_machine, e_flags, bf, size); - - pydict_set_item_string_decref(dict, "iregs", - _PyUnicode_FromString(bf)); - } - - if (sample->user_regs) { - if (!bf) { - bf = malloc(size); - if (!bf) - return -1; - } - regs_map(sample->user_regs, attr->sample_regs_user, e_machine, e_flags, bf, size); - - pydict_set_item_string_decref(dict, "uregs", - _PyUnicode_FromString(bf)); - } - free(bf); - - return 0; -} - -static void set_sym_in_dict(PyObject *dict, struct addr_location *al, - const char *dso_field, const char *dso_bid_field, - const char *dso_map_start, const char *dso_map_end, - const char *sym_field, const char *symoff_field, - const char *map_pgoff) -{ - if (al->map) { - char sbuild_id[SBUILD_ID_SIZE]; - struct dso *dso = map__dso(al->map); - - pydict_set_item_string_decref(dict, dso_field, - _PyUnicode_FromString(dso__name(dso))); - build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id)); - pydict_set_item_string_decref(dict, dso_bid_field, - _PyUnicode_FromString(sbuild_id)); - pydict_set_item_string_decref(dict, dso_map_start, - PyLong_FromUnsignedLong(map__start(al->map))); - pydict_set_item_string_decref(dict, dso_map_end, - PyLong_FromUnsignedLong(map__end(al->map))); - pydict_set_item_string_decref(dict, map_pgoff, - PyLong_FromUnsignedLongLong(map__pgoff(al->map))); - } - if (al->sym) { - pydict_set_item_string_decref(dict, sym_field, - _PyUnicode_FromString(al->sym->name)); - pydict_set_item_string_decref(dict, symoff_field, - PyLong_FromUnsignedLong(get_offset(al->sym, al))); - } -} - -static void set_sample_flags(PyObject *dict, u32 flags) -{ - const char *ch = PERF_IP_FLAG_CHARS; - char *p, str[33]; - - for (p = str; *ch; ch++, flags >>= 1) { - if (flags & 1) - *p++ = *ch; - } - *p = 0; - pydict_set_item_string_decref(dict, "flags", _PyUnicode_FromString(str)); -} - -static void python_process_sample_flags(struct perf_sample *sample, PyObject *dict_sample) -{ - char flags_disp[SAMPLE_FLAGS_BUF_SIZE]; - - set_sample_flags(dict_sample, sample->flags); - perf_sample__sprintf_flags(sample->flags, flags_disp, sizeof(flags_disp)); - pydict_set_item_string_decref(dict_sample, "flags_disp", - _PyUnicode_FromString(flags_disp)); -} - -static PyObject *get_perf_sample_dict(struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al, - PyObject *callchain) -{ - PyObject *dict, *dict_sample, *brstack, *brstacksym; - uint16_t e_machine = EM_HOST; - uint32_t e_flags = EF_HOST; - struct evsel *evsel = sample->evsel; - - dict = PyDict_New(); - if (!dict) - Py_FatalError("couldn't create Python dictionary"); - - dict_sample = PyDict_New(); - if (!dict_sample) - Py_FatalError("couldn't create Python dictionary"); - - pydict_set_item_string_decref(dict, "ev_name", _PyUnicode_FromString(evsel__name(evsel))); - pydict_set_item_string_decref(dict, "attr", _PyBytes_FromStringAndSize((const char *)&evsel->core.attr, sizeof(evsel->core.attr))); - - pydict_set_item_string_decref(dict_sample, "id", - PyLong_FromUnsignedLongLong(sample->id)); - pydict_set_item_string_decref(dict_sample, "stream_id", - PyLong_FromUnsignedLongLong(sample->stream_id)); - pydict_set_item_string_decref(dict_sample, "pid", - _PyLong_FromLong(sample->pid)); - pydict_set_item_string_decref(dict_sample, "tid", - _PyLong_FromLong(sample->tid)); - pydict_set_item_string_decref(dict_sample, "cpu", - _PyLong_FromLong(sample->cpu)); - pydict_set_item_string_decref(dict_sample, "ip", - PyLong_FromUnsignedLongLong(sample->ip)); - pydict_set_item_string_decref(dict_sample, "time", - PyLong_FromUnsignedLongLong(sample->time)); - pydict_set_item_string_decref(dict_sample, "period", - PyLong_FromUnsignedLongLong(sample->period)); - pydict_set_item_string_decref(dict_sample, "phys_addr", - PyLong_FromUnsignedLongLong(sample->phys_addr)); - pydict_set_item_string_decref(dict_sample, "addr", - PyLong_FromUnsignedLongLong(sample->addr)); - set_sample_read_in_dict(dict_sample, sample); - pydict_set_item_string_decref(dict_sample, "weight", - PyLong_FromUnsignedLongLong(sample->weight)); - pydict_set_item_string_decref(dict_sample, "ins_lat", - PyLong_FromUnsignedLong(sample->ins_lat)); - pydict_set_item_string_decref(dict_sample, "transaction", - PyLong_FromUnsignedLongLong(sample->transaction)); - set_sample_datasrc_in_dict(dict_sample, sample); - pydict_set_item_string_decref(dict, "sample", dict_sample); - - pydict_set_item_string_decref(dict, "raw_buf", _PyBytes_FromStringAndSize( - (const char *)sample->raw_data, sample->raw_size)); - pydict_set_item_string_decref(dict, "comm", - _PyUnicode_FromString(thread__comm_str(al->thread))); - set_sym_in_dict(dict, al, "dso", "dso_bid", "dso_map_start", "dso_map_end", - "symbol", "symoff", "map_pgoff"); - - pydict_set_item_string_decref(dict, "callchain", callchain); - - brstack = python_process_brstack(sample, al->thread); - pydict_set_item_string_decref(dict, "brstack", brstack); - - brstacksym = python_process_brstacksym(sample, al->thread); - pydict_set_item_string_decref(dict, "brstacksym", brstacksym); - - if (sample->machine_pid) { - pydict_set_item_string_decref(dict_sample, "machine_pid", - _PyLong_FromLong(sample->machine_pid)); - pydict_set_item_string_decref(dict_sample, "vcpu", - _PyLong_FromLong(sample->vcpu)); - } - - pydict_set_item_string_decref(dict_sample, "cpumode", - _PyLong_FromLong((unsigned long)sample->cpumode)); - - if (addr_al) { - pydict_set_item_string_decref(dict_sample, "addr_correlates_sym", - PyBool_FromLong(1)); - set_sym_in_dict(dict_sample, addr_al, "addr_dso", "addr_dso_bid", - "addr_dso_map_start", "addr_dso_map_end", - "addr_symbol", "addr_symoff", "addr_map_pgoff"); - } - - if (sample->flags) - python_process_sample_flags(sample, dict_sample); - - /* Instructions per cycle (IPC) */ - if (sample->insn_cnt && sample->cyc_cnt) { - pydict_set_item_string_decref(dict_sample, "insn_cnt", - PyLong_FromUnsignedLongLong(sample->insn_cnt)); - pydict_set_item_string_decref(dict_sample, "cyc_cnt", - PyLong_FromUnsignedLongLong(sample->cyc_cnt)); - } - - if (al->thread) - e_machine = thread__e_machine(al->thread, /*machine=*/NULL, &e_flags); - - if (set_regs_in_dict(dict, sample, e_machine, e_flags)) - Py_FatalError("Failed to setting regs in dict"); - - return dict; -} - -#ifdef HAVE_LIBTRACEEVENT -static void python_process_tracepoint(struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al) -{ - struct tep_event *event; - PyObject *handler, *context, *t, *obj = NULL, *callchain; - PyObject *dict = NULL, *all_entries_dict = NULL; - static char handler_name[256]; - struct tep_format_field *field; - unsigned long s, ns; - unsigned n = 0; - int pid; - int cpu = sample->cpu; - void *data = sample->raw_data; - unsigned long long nsecs = sample->time; - const char *comm = thread__comm_str(al->thread); - const char *default_handler_name = "trace_unhandled"; - DECLARE_BITMAP(events_defined, TRACE_EVENT_TYPE_MAX); - struct evsel *evsel = sample->evsel; - - bitmap_zero(events_defined, TRACE_EVENT_TYPE_MAX); - - event = evsel__tp_format(evsel); - if (!event) { - snprintf(handler_name, sizeof(handler_name), - "ug! no event found for type %" PRIu64, (u64)evsel->core.attr.config); - Py_FatalError(handler_name); - } - - pid = raw_field_value(event, "common_pid", data); - - sprintf(handler_name, "%s__%s", event->system, event->name); - - if (!__test_and_set_bit(event->id, events_defined)) - define_event_symbols(event, handler_name, event->print_fmt.args); - - handler = get_handler(handler_name); - if (!handler) { - handler = get_handler(default_handler_name); - if (!handler) - return; - dict = PyDict_New(); - if (!dict) - Py_FatalError("couldn't create Python dict"); - } - - t = PyTuple_New(MAX_FIELDS); - if (!t) - Py_FatalError("couldn't create Python tuple"); - - - s = nsecs / NSEC_PER_SEC; - ns = nsecs - s * NSEC_PER_SEC; - - context = _PyCapsule_New(scripting_context, NULL, NULL); - - PyTuple_SetItem(t, n++, _PyUnicode_FromString(handler_name)); - PyTuple_SetItem(t, n++, context); - - /* ip unwinding */ - callchain = python_process_callchain(sample, al); - /* Need an additional reference for the perf_sample dict */ - Py_INCREF(callchain); - - if (!dict) { - PyTuple_SetItem(t, n++, _PyLong_FromLong(cpu)); - PyTuple_SetItem(t, n++, _PyLong_FromLong(s)); - PyTuple_SetItem(t, n++, _PyLong_FromLong(ns)); - PyTuple_SetItem(t, n++, _PyLong_FromLong(pid)); - PyTuple_SetItem(t, n++, _PyUnicode_FromString(comm)); - PyTuple_SetItem(t, n++, callchain); - } else { - pydict_set_item_string_decref(dict, "common_cpu", _PyLong_FromLong(cpu)); - pydict_set_item_string_decref(dict, "common_s", _PyLong_FromLong(s)); - pydict_set_item_string_decref(dict, "common_ns", _PyLong_FromLong(ns)); - pydict_set_item_string_decref(dict, "common_pid", _PyLong_FromLong(pid)); - pydict_set_item_string_decref(dict, "common_comm", _PyUnicode_FromString(comm)); - pydict_set_item_string_decref(dict, "common_callchain", callchain); - } - for (field = event->format.fields; field; field = field->next) { - unsigned int offset, len; - unsigned long long val; - - if (field->flags & TEP_FIELD_IS_ARRAY) { - offset = field->offset; - len = field->size; - if (field->flags & TEP_FIELD_IS_DYNAMIC) { - val = tep_read_number(scripting_context->pevent, - data + offset, len); - offset = val; - len = offset >> 16; - offset &= 0xffff; - if (tep_field_is_relative(field->flags)) - offset += field->offset + field->size; - } - if (field->flags & TEP_FIELD_IS_STRING && - is_printable_array(data + offset, len)) { - obj = _PyUnicode_FromString((char *) data + offset); - } else { - obj = PyByteArray_FromStringAndSize((const char *) data + offset, len); - field->flags &= ~TEP_FIELD_IS_STRING; - } - } else { /* FIELD_IS_NUMERIC */ - obj = get_field_numeric_entry(event, field, data); - } - if (!dict) - PyTuple_SetItem(t, n++, obj); - else - pydict_set_item_string_decref(dict, field->name, obj); - - } - - if (dict) - PyTuple_SetItem(t, n++, dict); - - if (get_argument_count(handler) == (int) n + 1) { - all_entries_dict = get_perf_sample_dict(sample, al, addr_al, - callchain); - PyTuple_SetItem(t, n++, all_entries_dict); - } else { - Py_DECREF(callchain); - } - - if (_PyTuple_Resize(&t, n) == -1) - Py_FatalError("error resizing Python tuple"); - - if (!dict) - call_object(handler, t, handler_name); - else - call_object(handler, t, default_handler_name); - - Py_DECREF(t); -} -#else -static void python_process_tracepoint(struct perf_sample *sample __maybe_unused, - struct addr_location *al __maybe_unused, - struct addr_location *addr_al __maybe_unused) -{ - fprintf(stderr, "Tracepoint events are not supported because " - "perf is not linked with libtraceevent.\n"); -} -#endif - -static PyObject *tuple_new(unsigned int sz) -{ - PyObject *t; - - t = PyTuple_New(sz); - if (!t) - Py_FatalError("couldn't create Python tuple"); - return t; -} - -static int tuple_set_s64(PyObject *t, unsigned int pos, s64 val) -{ -#if BITS_PER_LONG == 64 - return PyTuple_SetItem(t, pos, _PyLong_FromLong(val)); -#endif -#if BITS_PER_LONG == 32 - return PyTuple_SetItem(t, pos, PyLong_FromLongLong(val)); -#endif -} - -/* - * Databases support only signed 64-bit numbers, so even though we are - * exporting a u64, it must be as s64. - */ -#define tuple_set_d64 tuple_set_s64 - -static int tuple_set_u64(PyObject *t, unsigned int pos, u64 val) -{ -#if BITS_PER_LONG == 64 - return PyTuple_SetItem(t, pos, PyLong_FromUnsignedLong(val)); -#endif -#if BITS_PER_LONG == 32 - return PyTuple_SetItem(t, pos, PyLong_FromUnsignedLongLong(val)); -#endif -} - -static int tuple_set_u32(PyObject *t, unsigned int pos, u32 val) -{ - return PyTuple_SetItem(t, pos, PyLong_FromUnsignedLong(val)); -} - -static int tuple_set_s32(PyObject *t, unsigned int pos, s32 val) -{ - return PyTuple_SetItem(t, pos, _PyLong_FromLong(val)); -} - -static int tuple_set_bool(PyObject *t, unsigned int pos, bool val) -{ - return PyTuple_SetItem(t, pos, PyBool_FromLong(val)); -} - -static int tuple_set_string(PyObject *t, unsigned int pos, const char *s) -{ - return PyTuple_SetItem(t, pos, _PyUnicode_FromString(s)); -} - -static int tuple_set_bytes(PyObject *t, unsigned int pos, void *bytes, - unsigned int sz) -{ - return PyTuple_SetItem(t, pos, _PyBytes_FromStringAndSize(bytes, sz)); -} - -static int python_export_evsel(struct db_export *dbe, struct evsel *evsel) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(2); - - tuple_set_d64(t, 0, evsel->db_id); - tuple_set_string(t, 1, evsel__name(evsel)); - - call_object(tables->evsel_handler, t, "evsel_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_machine(struct db_export *dbe, - struct machine *machine) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(3); - - tuple_set_d64(t, 0, machine->db_id); - tuple_set_s32(t, 1, machine->pid); - tuple_set_string(t, 2, machine->root_dir ? machine->root_dir : ""); - - call_object(tables->machine_handler, t, "machine_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_thread(struct db_export *dbe, struct thread *thread, - u64 main_thread_db_id, struct machine *machine) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(5); - - tuple_set_d64(t, 0, thread__db_id(thread)); - tuple_set_d64(t, 1, machine->db_id); - tuple_set_d64(t, 2, main_thread_db_id); - tuple_set_s32(t, 3, thread__pid(thread)); - tuple_set_s32(t, 4, thread__tid(thread)); - - call_object(tables->thread_handler, t, "thread_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_comm(struct db_export *dbe, struct comm *comm, - struct thread *thread) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(5); - - tuple_set_d64(t, 0, comm->db_id); - tuple_set_string(t, 1, comm__str(comm)); - tuple_set_d64(t, 2, thread__db_id(thread)); - tuple_set_d64(t, 3, comm->start); - tuple_set_s32(t, 4, comm->exec); - - call_object(tables->comm_handler, t, "comm_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_comm_thread(struct db_export *dbe, u64 db_id, - struct comm *comm, struct thread *thread) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(3); - - tuple_set_d64(t, 0, db_id); - tuple_set_d64(t, 1, comm->db_id); - tuple_set_d64(t, 2, thread__db_id(thread)); - - call_object(tables->comm_thread_handler, t, "comm_thread_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_dso(struct db_export *dbe, struct dso *dso, - struct machine *machine) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - char sbuild_id[SBUILD_ID_SIZE]; - PyObject *t; - - build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id)); - - t = tuple_new(5); - - tuple_set_d64(t, 0, dso__db_id(dso)); - tuple_set_d64(t, 1, machine->db_id); - tuple_set_string(t, 2, dso__short_name(dso)); - tuple_set_string(t, 3, dso__long_name(dso)); - tuple_set_string(t, 4, sbuild_id); - - call_object(tables->dso_handler, t, "dso_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_symbol(struct db_export *dbe, struct symbol *sym, - struct dso *dso) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - u64 *sym_db_id = symbol__priv(sym); - PyObject *t; - - t = tuple_new(6); - - tuple_set_d64(t, 0, *sym_db_id); - tuple_set_d64(t, 1, dso__db_id(dso)); - tuple_set_d64(t, 2, sym->start); - tuple_set_d64(t, 3, sym->end); - tuple_set_s32(t, 4, symbol__binding(sym)); - tuple_set_string(t, 5, sym->name); - - call_object(tables->symbol_handler, t, "symbol_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_branch_type(struct db_export *dbe, u32 branch_type, - const char *name) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(2); - - tuple_set_s32(t, 0, branch_type); - tuple_set_string(t, 1, name); - - call_object(tables->branch_type_handler, t, "branch_type_table"); - - Py_DECREF(t); - - return 0; -} - -static void python_export_sample_table(struct db_export *dbe, - struct export_sample *es) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(28); - - tuple_set_d64(t, 0, es->db_id); - tuple_set_d64(t, 1, es->sample->evsel->db_id); - tuple_set_d64(t, 2, maps__machine(thread__maps(es->al->thread))->db_id); - tuple_set_d64(t, 3, thread__db_id(es->al->thread)); - tuple_set_d64(t, 4, es->comm_db_id); - tuple_set_d64(t, 5, es->dso_db_id); - tuple_set_d64(t, 6, es->sym_db_id); - tuple_set_d64(t, 7, es->offset); - tuple_set_d64(t, 8, es->sample->ip); - tuple_set_d64(t, 9, es->sample->time); - tuple_set_s32(t, 10, es->sample->cpu); - tuple_set_d64(t, 11, es->addr_dso_db_id); - tuple_set_d64(t, 12, es->addr_sym_db_id); - tuple_set_d64(t, 13, es->addr_offset); - tuple_set_d64(t, 14, es->sample->addr); - tuple_set_d64(t, 15, es->sample->period); - tuple_set_d64(t, 16, es->sample->weight); - tuple_set_d64(t, 17, es->sample->transaction); - tuple_set_d64(t, 18, es->sample->data_src); - tuple_set_s32(t, 19, es->sample->flags & PERF_BRANCH_MASK); - tuple_set_s32(t, 20, !!(es->sample->flags & PERF_IP_FLAG_IN_TX)); - tuple_set_d64(t, 21, es->call_path_id); - tuple_set_d64(t, 22, es->sample->insn_cnt); - tuple_set_d64(t, 23, es->sample->cyc_cnt); - tuple_set_s32(t, 24, es->sample->flags); - tuple_set_d64(t, 25, es->sample->id); - tuple_set_d64(t, 26, es->sample->stream_id); - tuple_set_u32(t, 27, es->sample->ins_lat); - - call_object(tables->sample_handler, t, "sample_table"); - - Py_DECREF(t); -} - -static void python_export_synth(struct db_export *dbe, struct export_sample *es) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(3); - - tuple_set_d64(t, 0, es->db_id); - tuple_set_d64(t, 1, es->sample->evsel->core.attr.config); - tuple_set_bytes(t, 2, es->sample->raw_data, es->sample->raw_size); - - call_object(tables->synth_handler, t, "synth_data"); - - Py_DECREF(t); -} - -static int python_export_sample(struct db_export *dbe, - struct export_sample *es) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - - python_export_sample_table(dbe, es); - - if (es->sample->evsel->core.attr.type == PERF_TYPE_SYNTH && tables->synth_handler) - python_export_synth(dbe, es); - - return 0; -} - -static int python_export_call_path(struct db_export *dbe, struct call_path *cp) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - u64 parent_db_id, sym_db_id; - - parent_db_id = cp->parent ? cp->parent->db_id : 0; - sym_db_id = cp->sym ? *(u64 *)symbol__priv(cp->sym) : 0; - - t = tuple_new(4); - - tuple_set_d64(t, 0, cp->db_id); - tuple_set_d64(t, 1, parent_db_id); - tuple_set_d64(t, 2, sym_db_id); - tuple_set_d64(t, 3, cp->ip); - - call_object(tables->call_path_handler, t, "call_path_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_call_return(struct db_export *dbe, - struct call_return *cr) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - u64 comm_db_id = cr->comm ? cr->comm->db_id : 0; - PyObject *t; - - t = tuple_new(14); - - tuple_set_d64(t, 0, cr->db_id); - tuple_set_d64(t, 1, thread__db_id(cr->thread)); - tuple_set_d64(t, 2, comm_db_id); - tuple_set_d64(t, 3, cr->cp->db_id); - tuple_set_d64(t, 4, cr->call_time); - tuple_set_d64(t, 5, cr->return_time); - tuple_set_d64(t, 6, cr->branch_count); - tuple_set_d64(t, 7, cr->call_ref); - tuple_set_d64(t, 8, cr->return_ref); - tuple_set_d64(t, 9, cr->cp->parent->db_id); - tuple_set_s32(t, 10, cr->flags); - tuple_set_d64(t, 11, cr->parent_db_id); - tuple_set_d64(t, 12, cr->insn_count); - tuple_set_d64(t, 13, cr->cyc_count); - - call_object(tables->call_return_handler, t, "call_return_table"); - - Py_DECREF(t); - - return 0; -} - -static int python_export_context_switch(struct db_export *dbe, u64 db_id, - struct machine *machine, - struct perf_sample *sample, - u64 th_out_id, u64 comm_out_id, - u64 th_in_id, u64 comm_in_id, int flags) -{ - struct tables *tables = container_of(dbe, struct tables, dbe); - PyObject *t; - - t = tuple_new(9); - - tuple_set_d64(t, 0, db_id); - tuple_set_d64(t, 1, machine->db_id); - tuple_set_d64(t, 2, sample->time); - tuple_set_s32(t, 3, sample->cpu); - tuple_set_d64(t, 4, th_out_id); - tuple_set_d64(t, 5, comm_out_id); - tuple_set_d64(t, 6, th_in_id); - tuple_set_d64(t, 7, comm_in_id); - tuple_set_s32(t, 8, flags); - - call_object(tables->context_switch_handler, t, "context_switch"); - - Py_DECREF(t); - - return 0; -} - -static int python_process_call_return(struct call_return *cr, u64 *parent_db_id, - void *data) -{ - struct db_export *dbe = data; - - return db_export__call_return(dbe, cr, parent_db_id); -} - -static void python_process_general_event(struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al) -{ - PyObject *handler, *t, *dict, *callchain; - static char handler_name[64]; - unsigned n = 0; - - snprintf(handler_name, sizeof(handler_name), "%s", "process_event"); - - handler = get_handler(handler_name); - if (!handler) - return; - - /* - * Use the MAX_FIELDS to make the function expandable, though - * currently there is only one item for the tuple. - */ - t = PyTuple_New(MAX_FIELDS); - if (!t) - Py_FatalError("couldn't create Python tuple"); - - /* ip unwinding */ - callchain = python_process_callchain(sample, al); - dict = get_perf_sample_dict(sample, al, addr_al, callchain); - - PyTuple_SetItem(t, n++, dict); - if (_PyTuple_Resize(&t, n) == -1) - Py_FatalError("error resizing Python tuple"); - - call_object(handler, t, handler_name); - - Py_DECREF(t); -} - -static void python_process_event(union perf_event *event, - struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al) -{ - struct tables *tables = &tables_global; - - scripting_context__update(scripting_context, event, sample, al, addr_al); - - switch (sample->evsel->core.attr.type) { - case PERF_TYPE_TRACEPOINT: - python_process_tracepoint(sample, al, addr_al); - break; - /* Reserve for future process_hw/sw/raw APIs */ - default: - if (tables->db_export_mode) - db_export__sample(&tables->dbe, event, sample, al, addr_al); - else - python_process_general_event(sample, al, addr_al); - } -} - -static void python_process_throttle(union perf_event *event, - struct perf_sample *sample, - struct machine *machine) -{ - const char *handler_name; - PyObject *handler, *t; - - if (event->header.type == PERF_RECORD_THROTTLE) - handler_name = "throttle"; - else - handler_name = "unthrottle"; - handler = get_handler(handler_name); - if (!handler) - return; - - t = tuple_new(6); - if (!t) - return; - - tuple_set_u64(t, 0, event->throttle.time); - tuple_set_u64(t, 1, event->throttle.id); - tuple_set_u64(t, 2, event->throttle.stream_id); - tuple_set_s32(t, 3, sample->cpu); - tuple_set_s32(t, 4, sample->pid); - tuple_set_s32(t, 5, sample->tid); - - call_object(handler, t, handler_name); - - Py_DECREF(t); -} - -static void python_do_process_switch(union perf_event *event, - struct perf_sample *sample, - struct machine *machine) -{ - const char *handler_name = "context_switch"; - bool out = event->header.misc & PERF_RECORD_MISC_SWITCH_OUT; - bool out_preempt = out && (event->header.misc & PERF_RECORD_MISC_SWITCH_OUT_PREEMPT); - pid_t np_pid = -1, np_tid = -1; - PyObject *handler, *t; - - handler = get_handler(handler_name); - if (!handler) - return; - - if (event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) { - np_pid = event->context_switch.next_prev_pid; - np_tid = event->context_switch.next_prev_tid; - } - - t = tuple_new(11); - if (!t) - return; - - tuple_set_u64(t, 0, sample->time); - tuple_set_s32(t, 1, sample->cpu); - tuple_set_s32(t, 2, sample->pid); - tuple_set_s32(t, 3, sample->tid); - tuple_set_s32(t, 4, np_pid); - tuple_set_s32(t, 5, np_tid); - tuple_set_s32(t, 6, machine->pid); - tuple_set_bool(t, 7, out); - tuple_set_bool(t, 8, out_preempt); - tuple_set_s32(t, 9, sample->machine_pid); - tuple_set_s32(t, 10, sample->vcpu); - - call_object(handler, t, handler_name); - - Py_DECREF(t); -} - -static void python_process_switch(union perf_event *event, - struct perf_sample *sample, - struct machine *machine) -{ - struct tables *tables = &tables_global; - - if (tables->db_export_mode) - db_export__switch(&tables->dbe, event, sample, machine); - else - python_do_process_switch(event, sample, machine); -} - -static void python_process_auxtrace_error(struct perf_session *session __maybe_unused, - union perf_event *event) -{ - struct perf_record_auxtrace_error *e = &event->auxtrace_error; - u8 cpumode = e->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - const char *handler_name = "auxtrace_error"; - unsigned long long tm = e->time; - const char *msg = e->msg; - s32 machine_pid = 0, vcpu = 0; - char msg_buf[MAX_AUXTRACE_ERROR_MSG + 1]; - int msg_max; - PyObject *handler, *t; - - handler = get_handler(handler_name); - if (!handler) - return; - - if (!e->fmt) { - tm = 0; - msg = (const char *)&e->time; - } - - /* Bound msg to the bytes within the event, ensure NUL-termination */ - msg_max = (int)((void *)event + event->header.size - (void *)msg); - if (msg_max <= 0) { - msg_buf[0] = '\0'; - } else { - if (msg_max > (int)sizeof(msg_buf) - 1) - msg_max = sizeof(msg_buf) - 1; - memcpy(msg_buf, msg, msg_max); - msg_buf[msg_max] = '\0'; - } - - /* Only access fmt >= 2 fields if the event is large enough */ - if (e->fmt >= 2 && - event->header.size >= offsetof(typeof(event->auxtrace_error), vcpu) + - sizeof(event->auxtrace_error.vcpu)) { - machine_pid = e->machine_pid; - vcpu = e->vcpu; - } - - t = tuple_new(11); - - tuple_set_u32(t, 0, e->type); - tuple_set_u32(t, 1, e->code); - tuple_set_s32(t, 2, e->cpu); - tuple_set_s32(t, 3, e->pid); - tuple_set_s32(t, 4, e->tid); - tuple_set_u64(t, 5, e->ip); - tuple_set_u64(t, 6, tm); - tuple_set_string(t, 7, msg_buf); - tuple_set_u32(t, 8, cpumode); - tuple_set_s32(t, 9, machine_pid); - tuple_set_s32(t, 10, vcpu); - - call_object(handler, t, handler_name); - - Py_DECREF(t); -} - -static void get_handler_name(char *str, size_t size, - struct evsel *evsel) -{ - char *p = str; - - scnprintf(str, size, "stat__%s", evsel__name(evsel)); - - while ((p = strchr(p, ':'))) { - *p = '_'; - p++; - } -} - -static void -process_stat(struct evsel *counter, struct perf_cpu cpu, int thread, u64 tstamp, - struct perf_counts_values *count) -{ - PyObject *handler, *t; - static char handler_name[256]; - int n = 0; - - t = PyTuple_New(MAX_FIELDS); - if (!t) - Py_FatalError("couldn't create Python tuple"); - - get_handler_name(handler_name, sizeof(handler_name), - counter); - - handler = get_handler(handler_name); - if (!handler) { - pr_debug("can't find python handler %s\n", handler_name); - return; - } - - PyTuple_SetItem(t, n++, _PyLong_FromLong(cpu.cpu)); - PyTuple_SetItem(t, n++, _PyLong_FromLong(thread)); - - tuple_set_u64(t, n++, tstamp); - tuple_set_u64(t, n++, count->val); - tuple_set_u64(t, n++, count->ena); - tuple_set_u64(t, n++, count->run); - - if (_PyTuple_Resize(&t, n) == -1) - Py_FatalError("error resizing Python tuple"); - - call_object(handler, t, handler_name); - - Py_DECREF(t); -} - -static void python_process_stat(struct perf_stat_config *config, - struct evsel *counter, u64 tstamp) -{ - struct perf_thread_map *threads = counter->core.threads; - struct perf_cpu_map *cpus = counter->core.cpus; - - for (int thread = 0; thread < perf_thread_map__nr(threads); thread++) { - unsigned int idx; - struct perf_cpu cpu; - - perf_cpu_map__for_each_cpu(cpu, idx, cpus) { - process_stat(counter, cpu, - perf_thread_map__pid(threads, thread), tstamp, - perf_counts(counter->counts, idx, thread)); - } - } -} - -static void python_process_stat_interval(u64 tstamp) -{ - PyObject *handler, *t; - static const char handler_name[] = "stat__interval"; - int n = 0; - - t = PyTuple_New(MAX_FIELDS); - if (!t) - Py_FatalError("couldn't create Python tuple"); - - handler = get_handler(handler_name); - if (!handler) { - pr_debug("can't find python handler %s\n", handler_name); - return; - } - - tuple_set_u64(t, n++, tstamp); - - if (_PyTuple_Resize(&t, n) == -1) - Py_FatalError("error resizing Python tuple"); - - call_object(handler, t, handler_name); - - Py_DECREF(t); -} - -static int perf_script_context_init(void) -{ - PyObject *perf_script_context; - PyObject *perf_trace_context; - PyObject *dict; - int ret; - - perf_trace_context = PyImport_AddModule("perf_trace_context"); - if (!perf_trace_context) - return -1; - dict = PyModule_GetDict(perf_trace_context); - if (!dict) - return -1; - - perf_script_context = _PyCapsule_New(scripting_context, NULL, NULL); - if (!perf_script_context) - return -1; - - ret = PyDict_SetItemString(dict, "perf_script_context", perf_script_context); - if (!ret) - ret = PyDict_SetItemString(main_dict, "perf_script_context", perf_script_context); - Py_DECREF(perf_script_context); - return ret; -} - -static int run_start_sub(void) -{ - main_module = PyImport_AddModule("__main__"); - if (main_module == NULL) - return -1; - Py_INCREF(main_module); - - main_dict = PyModule_GetDict(main_module); - if (main_dict == NULL) - goto error; - Py_INCREF(main_dict); - - if (perf_script_context_init()) - goto error; - - try_call_object("trace_begin", NULL); - - return 0; - -error: - Py_XDECREF(main_dict); - Py_XDECREF(main_module); - return -1; -} - -#define SET_TABLE_HANDLER_(name, handler_name, table_name) do { \ - tables->handler_name = get_handler(#table_name); \ - if (tables->handler_name) \ - tables->dbe.export_ ## name = python_export_ ## name; \ -} while (0) - -#define SET_TABLE_HANDLER(name) \ - SET_TABLE_HANDLER_(name, name ## _handler, name ## _table) - -static void set_table_handlers(struct tables *tables) -{ - const char *perf_db_export_mode = "perf_db_export_mode"; - const char *perf_db_export_calls = "perf_db_export_calls"; - const char *perf_db_export_callchains = "perf_db_export_callchains"; - PyObject *db_export_mode, *db_export_calls, *db_export_callchains; - bool export_calls = false; - bool export_callchains = false; - int ret; - - memset(tables, 0, sizeof(struct tables)); - if (db_export__init(&tables->dbe)) - Py_FatalError("failed to initialize export"); - - db_export_mode = PyDict_GetItemString(main_dict, perf_db_export_mode); - if (!db_export_mode) - return; - - ret = PyObject_IsTrue(db_export_mode); - if (ret == -1) - handler_call_die(perf_db_export_mode); - if (!ret) - return; - - /* handle export calls */ - tables->dbe.crp = NULL; - db_export_calls = PyDict_GetItemString(main_dict, perf_db_export_calls); - if (db_export_calls) { - ret = PyObject_IsTrue(db_export_calls); - if (ret == -1) - handler_call_die(perf_db_export_calls); - export_calls = !!ret; - } - - if (export_calls) { - tables->dbe.crp = - call_return_processor__new(python_process_call_return, - &tables->dbe); - if (!tables->dbe.crp) - Py_FatalError("failed to create calls processor"); - } - - /* handle export callchains */ - tables->dbe.cpr = NULL; - db_export_callchains = PyDict_GetItemString(main_dict, - perf_db_export_callchains); - if (db_export_callchains) { - ret = PyObject_IsTrue(db_export_callchains); - if (ret == -1) - handler_call_die(perf_db_export_callchains); - export_callchains = !!ret; - } - - if (export_callchains) { - /* - * Attempt to use the call path root from the call return - * processor, if the call return processor is in use. Otherwise, - * we allocate a new call path root. This prevents exporting - * duplicate call path ids when both are in use simultaneously. - */ - if (tables->dbe.crp) - tables->dbe.cpr = tables->dbe.crp->cpr; - else - tables->dbe.cpr = call_path_root__new(); - - if (!tables->dbe.cpr) - Py_FatalError("failed to create call path root"); - } - - tables->db_export_mode = true; - /* - * Reserve per symbol space for symbol->db_id via symbol__priv() - */ - symbol_conf.priv_size = sizeof(u64); - - SET_TABLE_HANDLER(evsel); - SET_TABLE_HANDLER(machine); - SET_TABLE_HANDLER(thread); - SET_TABLE_HANDLER(comm); - SET_TABLE_HANDLER(comm_thread); - SET_TABLE_HANDLER(dso); - SET_TABLE_HANDLER(symbol); - SET_TABLE_HANDLER(branch_type); - SET_TABLE_HANDLER(sample); - SET_TABLE_HANDLER(call_path); - SET_TABLE_HANDLER(call_return); - SET_TABLE_HANDLER(context_switch); - - /* - * Synthesized events are samples but with architecture-specific data - * stored in sample->raw_data. They are exported via - * python_export_sample() and consequently do not need a separate export - * callback. - */ - tables->synth_handler = get_handler("synth_data"); -} - -static void _free_command_line(wchar_t **command_line, int num) -{ - int i; - for (i = 0; i < num; i++) - PyMem_RawFree(command_line[i]); - free(command_line); -} - - -/* - * Start trace script - */ -static int python_start_script(const char *script, int argc, const char **argv, - struct perf_session *session) -{ - struct tables *tables = &tables_global; - wchar_t **command_line; - char buf[PATH_MAX]; - int i, err = 0; - FILE *fp; - - scripting_context->session = session; - command_line = malloc((argc + 1) * sizeof(wchar_t *)); - if (!command_line) - return -1; - - command_line[0] = Py_DecodeLocale(script, NULL); - for (i = 1; i < argc + 1; i++) - command_line[i] = Py_DecodeLocale(argv[i - 1], NULL); - PyImport_AppendInittab("perf_trace_context", PyInit_perf_trace_context); - Py_Initialize(); - - PySys_SetArgv(argc + 1, command_line); - - fp = fopen(script, "r"); - if (!fp) { - sprintf(buf, "Can't open python script \"%s\"", script); - perror(buf); - err = -1; - goto error; - } - - err = PyRun_SimpleFile(fp, script); - if (err) { - fprintf(stderr, "Error running python script %s\n", script); - goto error; - } - - err = run_start_sub(); - if (err) { - fprintf(stderr, "Error starting python script %s\n", script); - goto error; - } - - set_table_handlers(tables); - - if (tables->db_export_mode) { - err = db_export__branch_types(&tables->dbe); - if (err) - goto error; - } - - _free_command_line(command_line, argc + 1); - - return err; -error: - Py_Finalize(); - _free_command_line(command_line, argc + 1); - - return err; -} - -static int python_flush_script(void) -{ - return 0; -} - -/* - * Stop trace script - */ -static int python_stop_script(void) -{ - struct tables *tables = &tables_global; - - try_call_object("trace_end", NULL); - - db_export__exit(&tables->dbe); - - Py_XDECREF(main_dict); - Py_XDECREF(main_module); - Py_Finalize(); - - return 0; -} - -#ifdef HAVE_LIBTRACEEVENT -static int python_generate_script(struct tep_handle *pevent, const char *outfile) -{ - int i, not_first, count, nr_events; - struct tep_event **all_events; - struct tep_event *event = NULL; - struct tep_format_field *f; - char fname[PATH_MAX]; - FILE *ofp; - - sprintf(fname, "%s.py", outfile); - ofp = fopen(fname, "w"); - if (ofp == NULL) { - fprintf(stderr, "couldn't open %s\n", fname); - return -1; - } - fprintf(ofp, "# perf script event handlers, " - "generated by perf script -g python\n"); - - fprintf(ofp, "# Licensed under the terms of the GNU GPL" - " License version 2\n\n"); - - fprintf(ofp, "# The common_* event handler fields are the most useful " - "fields common to\n"); - - fprintf(ofp, "# all events. They don't necessarily correspond to " - "the 'common_*' fields\n"); - - fprintf(ofp, "# in the format files. Those fields not available as " - "handler params can\n"); - - fprintf(ofp, "# be retrieved using Python functions of the form " - "common_*(context).\n"); - - fprintf(ofp, "# See the perf-script-python Documentation for the list " - "of available functions.\n\n"); - - fprintf(ofp, "from __future__ import print_function\n\n"); - fprintf(ofp, "import os\n"); - fprintf(ofp, "import sys\n\n"); - - fprintf(ofp, "sys.path.append(os.environ['PERF_EXEC_PATH'] + \\\n"); - fprintf(ofp, "\t'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')\n"); - fprintf(ofp, "\nfrom perf_trace_context import *\n"); - fprintf(ofp, "from Core import *\n\n\n"); - - fprintf(ofp, "def trace_begin():\n"); - fprintf(ofp, "\tprint(\"in trace_begin\")\n\n"); - - fprintf(ofp, "def trace_end():\n"); - fprintf(ofp, "\tprint(\"in trace_end\")\n\n"); - - nr_events = tep_get_events_count(pevent); - all_events = tep_list_events(pevent, TEP_EVENT_SORT_ID); - - for (i = 0; all_events && i < nr_events; i++) { - event = all_events[i]; - fprintf(ofp, "def %s__%s(", event->system, event->name); - fprintf(ofp, "event_name, "); - fprintf(ofp, "context, "); - fprintf(ofp, "common_cpu,\n"); - fprintf(ofp, "\tcommon_secs, "); - fprintf(ofp, "common_nsecs, "); - fprintf(ofp, "common_pid, "); - fprintf(ofp, "common_comm,\n\t"); - fprintf(ofp, "common_callchain, "); - - not_first = 0; - count = 0; - - for (f = event->format.fields; f; f = f->next) { - if (not_first++) - fprintf(ofp, ", "); - if (++count % 5 == 0) - fprintf(ofp, "\n\t"); - - fprintf(ofp, "%s", f->name); - } - if (not_first++) - fprintf(ofp, ", "); - if (++count % 5 == 0) - fprintf(ofp, "\n\t\t"); - fprintf(ofp, "perf_sample_dict"); - - fprintf(ofp, "):\n"); - - fprintf(ofp, "\t\tprint_header(event_name, common_cpu, " - "common_secs, common_nsecs,\n\t\t\t" - "common_pid, common_comm)\n\n"); - - fprintf(ofp, "\t\tprint(\""); - - not_first = 0; - count = 0; - - for (f = event->format.fields; f; f = f->next) { - if (not_first++) - fprintf(ofp, ", "); - if (count && count % 3 == 0) { - fprintf(ofp, "\" \\\n\t\t\""); - } - count++; - - fprintf(ofp, "%s=", f->name); - if (f->flags & TEP_FIELD_IS_STRING || - f->flags & TEP_FIELD_IS_FLAG || - f->flags & TEP_FIELD_IS_ARRAY || - f->flags & TEP_FIELD_IS_SYMBOLIC) - fprintf(ofp, "%%s"); - else if (f->flags & TEP_FIELD_IS_SIGNED) - fprintf(ofp, "%%d"); - else - fprintf(ofp, "%%u"); - } - - fprintf(ofp, "\" %% \\\n\t\t("); - - not_first = 0; - count = 0; - - for (f = event->format.fields; f; f = f->next) { - if (not_first++) - fprintf(ofp, ", "); - - if (++count % 5 == 0) - fprintf(ofp, "\n\t\t"); - - if (f->flags & TEP_FIELD_IS_FLAG) { - if ((count - 1) % 5 != 0) { - fprintf(ofp, "\n\t\t"); - count = 4; - } - fprintf(ofp, "flag_str(\""); - fprintf(ofp, "%s__%s\", ", event->system, - event->name); - fprintf(ofp, "\"%s\", %s)", f->name, - f->name); - } else if (f->flags & TEP_FIELD_IS_SYMBOLIC) { - if ((count - 1) % 5 != 0) { - fprintf(ofp, "\n\t\t"); - count = 4; - } - fprintf(ofp, "symbol_str(\""); - fprintf(ofp, "%s__%s\", ", event->system, - event->name); - fprintf(ofp, "\"%s\", %s)", f->name, - f->name); - } else - fprintf(ofp, "%s", f->name); - } - - fprintf(ofp, "))\n\n"); - - fprintf(ofp, "\t\tprint('Sample: {'+" - "get_dict_as_string(perf_sample_dict['sample'], ', ')+'}')\n\n"); - - fprintf(ofp, "\t\tfor node in common_callchain:"); - fprintf(ofp, "\n\t\t\tif 'sym' in node:"); - fprintf(ofp, "\n\t\t\t\tprint(\"\t[%%x] %%s%%s%%s%%s\" %% ("); - fprintf(ofp, "\n\t\t\t\t\tnode['ip'], node['sym']['name'],"); - fprintf(ofp, "\n\t\t\t\t\t\"+0x{:x}\".format(node['sym_off']) if 'sym_off' in node else \"\","); - fprintf(ofp, "\n\t\t\t\t\t\" ({})\".format(node['dso']) if 'dso' in node else \"\","); - fprintf(ofp, "\n\t\t\t\t\t\" \" + node['sym_srcline'] if 'sym_srcline' in node else \"\"))"); - fprintf(ofp, "\n\t\t\telse:"); - fprintf(ofp, "\n\t\t\t\tprint(\"\t[%%x]\" %% (node['ip']))\n\n"); - fprintf(ofp, "\t\tprint()\n\n"); - - } - - fprintf(ofp, "def trace_unhandled(event_name, context, " - "event_fields_dict, perf_sample_dict):\n"); - - fprintf(ofp, "\t\tprint(get_dict_as_string(event_fields_dict))\n"); - fprintf(ofp, "\t\tprint('Sample: {'+" - "get_dict_as_string(perf_sample_dict['sample'], ', ')+'}')\n\n"); - - fprintf(ofp, "def print_header(" - "event_name, cpu, secs, nsecs, pid, comm):\n" - "\tprint(\"%%-20s %%5u %%05u.%%09u %%8u %%-20s \" %% \\\n\t" - "(event_name, cpu, secs, nsecs, pid, comm), end=\"\")\n\n"); - - fprintf(ofp, "def get_dict_as_string(a_dict, delimiter=' '):\n" - "\treturn delimiter.join" - "(['%%s=%%s'%%(k,str(v))for k,v in sorted(a_dict.items())])\n"); - - fclose(ofp); - - fprintf(stderr, "generated Python script: %s\n", fname); - - return 0; -} -#else -static int python_generate_script(struct tep_handle *pevent __maybe_unused, - const char *outfile __maybe_unused) -{ - fprintf(stderr, "Generating Python perf-script is not supported." - " Install libtraceevent and rebuild perf to enable it.\n" - "For example:\n # apt install libtraceevent-dev (ubuntu)" - "\n # yum install libtraceevent-devel (Fedora)" - "\n etc.\n"); - return -1; -} -#endif - -struct scripting_ops python_scripting_ops = { - .name = "Python", - .dirname = "python", - .start_script = python_start_script, - .flush_script = python_flush_script, - .stop_script = python_stop_script, - .process_event = python_process_event, - .process_switch = python_process_switch, - .process_auxtrace_error = python_process_auxtrace_error, - .process_stat = python_process_stat, - .process_stat_interval = python_process_stat_interval, - .process_throttle = python_process_throttle, - .generate_script = python_generate_script, -}; diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 3237870a1a34..56a5e57ec512 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1,4 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include #include #include #include @@ -161,7 +166,7 @@ struct perf_session *__perf_session__new(struct perf_data *data, session->active_decomp = &session->decomp_data; INIT_LIST_HEAD(&session->auxtrace_index); perf_env__init(&session->header.env); - if (machines__init(&session->machines)) + if (machines__init(&session->machines, session)) goto out_delete; ordered_events__init(&session->ordered_events, @@ -629,6 +634,23 @@ do { \ bswap_field_32(aux_watermark); bswap_field_16(sample_max_stack); bswap_field_32(aux_sample_size); + /* + * aux_action aliases bitfields (e.g. aux_start_paused) in a union. + * Bitfields and plain u32 fields follow different endian rules, so + * both views cannot be swapped correctly at the same time. + * Leave aux_action unswapped until the ABI is represented in a + * byte-order-safe form. + */ + bswap_field_64(sig_data); + bswap_field_64(config3); + bswap_field_64(config4); + bswap_field_16(sample_simd_regs_enabled); + bswap_field_16(sample_simd_pred_reg_qwords); + bswap_field_16(sample_simd_vec_reg_qwords); + bswap_field_32(sample_simd_pred_reg_intr); + bswap_field_32(sample_simd_pred_reg_user); + bswap_field_64(sample_simd_vec_reg_intr); + bswap_field_64(sample_simd_vec_reg_user); /* * After read_format are bitfields. Check read_format because @@ -1416,15 +1438,96 @@ static void branch_stack__printf(struct perf_sample *sample, } } -static void regs_dump__printf(u64 mask, u64 *regs, uint16_t e_machine, uint32_t e_flags) +static void regs_dump__printf(u64 mask, struct regs_dump *regs, + uint16_t e_machine, uint32_t e_flags) { unsigned rid, i = 0; for_each_set_bit(rid, (unsigned long *) &mask, sizeof(mask) * 8) { - u64 val = regs[i++]; + u64 val = regs->regs[i++]; printf(".... %-5s 0x%016" PRIx64 "\n", - perf_reg_name(rid, e_machine, e_flags), val); + perf_reg_name(rid, e_machine, e_flags, regs->abi), val); + } +} + +static void simd_regs_dump__printf(uint16_t e_machine, struct regs_dump *regs, bool intr) +{ + const char *name = "unknown"; + const char *simd_header; + u32 i, j, idx, pred_base; + uint16_t qwords; + int reg_c; + + if (!(regs->abi & PERF_SAMPLE_REGS_ABI_SIMD)) + return; + + if (!regs->nr_vectors && !regs->nr_pred) + return; + + simd_header = "... SIMD ABI nr_vectors %" PRIu64 " vector_qwords %" PRIu64 \ + " nr_pred %" PRIu64 " pred_qwords %" PRIu64 "\n"; + printf(simd_header, regs->nr_vectors, regs->vector_qwords, + regs->nr_pred, regs->pred_qwords); + + for (reg_c = 0; reg_c < 64; reg_c++) { + if (!regs->nr_vectors) + break; + if (intr) { + perf_intr_simd_reg_class_bitmap_qwords(e_machine, reg_c, + &qwords, /*pred=*/false); + } else { + perf_user_simd_reg_class_bitmap_qwords(e_machine, reg_c, + &qwords, /*pred=*/false); + } + if (regs->vector_qwords == qwords) { + name = perf_simd_reg_class_name(e_machine, reg_c, /*pred=*/false); + break; + } + } + + for (i = 0; i < regs->nr_vectors; i++) { + for (j = 0; j < regs->vector_qwords; j++) { + idx = i * regs->vector_qwords + j; + if (regs->vector_qwords > 1) { + printf(".... %3s[%d][%d] 0x%016" PRIx64 "\n", + name, i, j, regs->simd_data[idx]); + } else { + printf(".... %3s[%d] 0x%016" PRIx64 "\n", + name, i, regs->simd_data[idx]); + } + } + } + + name = "unknown"; + for (reg_c = 0; reg_c < 64; reg_c++) { + if (!regs->nr_pred) + break; + if (intr) { + perf_intr_simd_reg_class_bitmap_qwords(e_machine, reg_c, + &qwords, /*pred=*/true); + } else { + perf_user_simd_reg_class_bitmap_qwords(e_machine, reg_c, + &qwords, /*pred=*/true); + } + if (regs->pred_qwords == qwords) { + name = perf_simd_reg_class_name(e_machine, reg_c, /*pred=*/true); + break; + } + } + + pred_base = regs->nr_vectors * regs->vector_qwords; + for (i = 0; i < regs->nr_pred; i++) { + for (j = 0; j < regs->pred_qwords; j++) { + idx = pred_base + i * regs->pred_qwords + j; + if (regs->pred_qwords > 1) { + printf(".... %3s[%d][%d] 0x%016" PRIx64 "\n", + name, i, j, regs->simd_data[idx]); + } else { + printf(".... %3s[%d] 0x%016" PRIx64 "\n", + name, i, regs->simd_data[idx]); + } + } } } @@ -1432,11 +1535,13 @@ static const char *regs_abi[] = { [PERF_SAMPLE_REGS_ABI_NONE] = "none", [PERF_SAMPLE_REGS_ABI_32] = "32-bit", [PERF_SAMPLE_REGS_ABI_64] = "64-bit", + [PERF_SAMPLE_REGS_ABI_SIMD | PERF_SAMPLE_REGS_ABI_32] = "32-bit SIMD", + [PERF_SAMPLE_REGS_ABI_SIMD | PERF_SAMPLE_REGS_ABI_64] = "64-bit SIMD", }; static inline const char *regs_dump_abi(struct regs_dump *d) { - if (d->abi > PERF_SAMPLE_REGS_ABI_64) + if (d->abi >= ARRAY_SIZE(regs_abi) || !regs_abi[d->abi]) return "unknown"; return regs_abi[d->abi]; @@ -1452,7 +1557,7 @@ static void regs__printf(const char *type, struct regs_dump *regs, mask, regs_dump_abi(regs)); - regs_dump__printf(mask, regs->regs, e_machine, e_flags); + regs_dump__printf(mask, regs, e_machine, e_flags); } static void regs_user__printf(struct perf_sample *sample, uint16_t e_machine, uint32_t e_flags) @@ -1466,6 +1571,7 @@ static void regs_user__printf(struct perf_sample *sample, uint16_t e_machine, ui if (user_regs->regs) regs__printf("user", user_regs, e_machine, e_flags); + simd_regs_dump__printf(e_machine, user_regs, /*intr=*/false); } static void regs_intr__printf(struct perf_sample *sample, uint16_t e_machine, uint32_t e_flags) @@ -1479,6 +1585,7 @@ static void regs_intr__printf(struct perf_sample *sample, uint16_t e_machine, ui if (intr_regs->regs) regs__printf("intr", intr_regs, e_machine, e_flags); + simd_regs_dump__printf(e_machine, intr_regs, /*intr=*/true); } static void stack_user__printf(struct stack_dump *dump) @@ -1911,6 +2018,14 @@ static int session__flush_deferred_samples(struct perf_session *session, struct perf_sample sample; struct evsel *new_evsel; + if (session_done()) { + /* Processing was stopped early, discard the sample. */ + list_del(&de->list); + free(de->event); + free(de); + continue; + } + perf_sample__init(&sample, /*all=*/false); ret = evlist__parse_sample(evlist, de->event, &sample); if (ret < 0) { @@ -3146,8 +3261,12 @@ static int __perf_session__process_pipe_events(struct perf_session *session) cur_size = sizeof(union perf_event); buf = malloc(cur_size); - if (!buf) - return -errno; + if (!buf) { + err = -errno; + if (update_prog) + ui_progress__finish(); + return err; + } ordered_events__set_copy_on_queue(oe, true); more: event = buf; @@ -3646,8 +3765,10 @@ static int __perf_session__process_dir_events(struct perf_session *session) } rd = calloc(nr_readers, sizeof(struct reader)); - if (!rd) + if (!rd) { + ui_progress__finish(); return -ENOMEM; + } rd[0] = (struct reader) { .fd = perf_data__fd(session->data), diff --git a/tools/perf/util/setup.py b/tools/perf/util/setup.py index 8380980ade4c..3b6a59f66944 100644 --- a/tools/perf/util/setup.py +++ b/tools/perf/util/setup.py @@ -173,6 +173,7 @@ def main() -> None: perf = Extension( 'perf', sources=[os.path.join(src_perf, 'util/python.c')], + depends=shlex.split(os.getenv('PYTHON_EXT_DEPS', '')), include_dirs=['util/include'], extra_compile_args=cflags, ) diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index b082178c279b..23951c35fb66 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -1,40 +1,49 @@ // SPDX-License-Identifier: GPL-2.0 #include "srcline.h" -#include "addr2line.h" -#include "dso.h" -#include "callchain.h" -#include "libbfd.h" -#include "llvm.h" -#include "symbol.h" -#include "libdw.h" -#include "debug.h" -#include "util.h" #include +#include +#include #include + #include #include +#include "addr2line.h" +#include "callchain.h" +#include "debug.h" +#include "dso.h" +#include "libbfd.h" +#include "libdw.h" +#include "llvm.h" +#include "symbol.h" +#include "util.h" + bool srcline_full_filename; char *srcline__unknown = (char *)"??:0"; -static const char *srcline_dso_name(struct dso *dso) +static char *srcline_dso_name(struct dso *dso) { const char *dso_name; + char *ret = NULL; + mutex_lock(dso__lock(dso)); if (dso__symsrc_filename(dso)) dso_name = dso__symsrc_filename(dso); else dso_name = dso__long_name(dso); if (dso_name[0] == '[') - return NULL; + goto out; if (is_perf_pid_map_name(dso_name)) - return NULL; + goto out; - return dso_name; + ret = strdup(dso_name); +out: + mutex_unlock(dso__lock(dso)); + return ret; } int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node) @@ -172,9 +181,9 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * break; case A2L_STYLE_UNKNOWN: default: - break; + return 0; } - if (ret > 0) + if (ret >= 0) return ret; } @@ -241,6 +250,10 @@ static struct inline_node *addr2inlines(const char *dso_name, u64 addr, addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso, /*unwind_inlines=*/true, node, sym); + if (!dso__has_srcline(dso) && list_empty(&node->val)) { + inline_node__delete(node); + return NULL; + } return node; } @@ -258,7 +271,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, char *file = NULL; unsigned line = 0; char *srcline; - const char *dso_name; + char *dso_name; if (!dso__has_srcline(dso)) goto out; @@ -267,9 +280,12 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, &line, dso, - unwind_inlines, /*node=*/NULL, sym)) + if (addr2line(dso_name, addr, &file, &line, dso, + unwind_inlines, /*node=*/NULL, sym) <= 0) { + free(dso_name); goto out_err; + } + free(dso_name); srcline = srcline_from_fileline(file, line); free(file); @@ -277,16 +293,22 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, if (!srcline) goto out_err; + mutex_lock(dso__lock(dso)); dso__set_a2l_fails(dso, 0); + mutex_unlock(dso__lock(dso)); return srcline; out_err: + mutex_lock(dso__lock(dso)); dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1); if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) { dso__set_has_srcline(dso, false); dso__free_a2l(dso); + dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } + mutex_unlock(dso__lock(dso)); out: if (!show_addr) return (show_sym && sym) ? @@ -305,7 +327,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) { char *file = NULL; - const char *dso_name; + char *dso_name; if (!dso__has_srcline(dso)) return NULL; @@ -314,19 +336,28 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, - /*node=*/NULL, /*sym=*/NULL)) + if (addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, + /*node=*/NULL, /*sym=*/NULL) <= 0) { + free(dso_name); goto out_err; + } + free(dso_name); + mutex_lock(dso__lock(dso)); dso__set_a2l_fails(dso, 0); + mutex_unlock(dso__lock(dso)); return file; out_err: + mutex_lock(dso__lock(dso)); dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1); if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) { dso__set_has_srcline(dso, false); dso__free_a2l(dso); + dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } + mutex_unlock(dso__lock(dso)); return NULL; } @@ -420,13 +451,19 @@ void srcline__tree_delete(struct rb_root_cached *tree) struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr, struct symbol *sym) { - const char *dso_name; + char *dso_name; + struct inline_node *node; + + if (!dso__has_srcline(dso)) + return NULL; dso_name = srcline_dso_name(dso); if (dso_name == NULL) return NULL; - return addr2inlines(dso_name, addr, dso, sym); + node = addr2inlines(dso_name, addr, dso, sym); + free(dso_name); + return node; } void inline_node__clear_frames(struct inline_node *node) diff --git a/tools/perf/util/strbuf.c b/tools/perf/util/strbuf.c index a64a37628f12..4080bc02a533 100644 --- a/tools/perf/util/strbuf.c +++ b/tools/perf/util/strbuf.c @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 -#include "cache.h" -#include "debug.h" #include "strbuf.h" -#include -#include -#include + #include #include #include +#include + +#include #include +#include "debug.h" + /* * Used as the default ->buf value, so that people can always assume * buf is non NULL and ->buf is NUL terminated even for a freshly @@ -54,9 +55,12 @@ int strbuf_grow(struct strbuf *sb, size_t extra) if (nr <= sb->len) return -E2BIG; +#define alloc_nr(x) (((x) + 16) * 3 / 2) + if (alloc_nr(sb->alloc) > nr) nr = alloc_nr(sb->alloc); +#undef alloc_nr /* * Note that sb->buf == strbuf_slopbuf if sb->alloc == 0, and it is * a static variable. Thus we have to avoid passing it to realloc. diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index e955c3feddcd..1e2716c6af45 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -228,7 +228,8 @@ bool filename__has_section(const char *filename, const char *sec) if (gelf_getehdr(elf, &ehdr) == NULL) goto elf_out; - found = !!elf_section_by_name(elf, &ehdr, &shdr, sec, NULL); + found = !!elf_section_by_name(elf, &ehdr, &shdr, sec, NULL) && + shdr.sh_type == SHT_PROGBITS; elf_out: elf_end(elf); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 3587ad243159..b61a574ce9a3 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -27,6 +27,7 @@ #include "dso.h" #include "util.h" // lsdir() #include "event.h" +#include "libbfd.h" #include "machine.h" #include "map.h" #include "symbol.h" @@ -73,6 +74,7 @@ struct symbol_conf symbol_conf = { .symfs = "", .symfs_layout_flat = false, .event_group = true, + .hybrid_merge = false, .inline_name = true, .res_sample = 0, .addr2line_timeout_ms = 5 * 1000, @@ -300,6 +302,10 @@ void symbols__fixup_end(struct rb_root_cached *symbols, bool is_kallsyms) else prev->end = curr->start; + /* The next module can start within that page */ + if (prev->end > curr->start) + prev->end = curr->start; + pr_debug4("%s sym:%s end:%#" PRIx64 "\n", __func__, prev->name, prev->end); } @@ -961,23 +967,6 @@ static int maps__split_kallsyms_for_kcore(struct maps *kmaps, struct dso *dso) return count; } -static uint16_t machine_or_dso_e_machine(struct machine *machine, struct dso *dso) -{ - uint16_t e_machine = EM_NONE; - /* DSO should be most accurate */ - if (dso) - e_machine = dso__e_machine(dso, machine, /*e_flags=*/NULL); - - if (e_machine != EM_NONE) - return e_machine; - - /* Check the global environment next. */ - if (machine && machine->env && machine->env->e_machine != EM_NONE) - return machine->env->e_machine; - - return perf_env__e_machine(machine ? machine->env : NULL, /*e_flags=*/NULL); -} - /* * Split the symbols into maps, making sure there are no overlaps, i.e. the * kernel range is broken in several maps, named [kernel].N, as we don't have @@ -999,7 +988,7 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; machine = maps__machine(kmaps); - e_machine = machine_or_dso_e_machine(machine, dso); + e_machine = perf_env__e_machine(machine->env, /*e_flags=*/NULL); while (next) { char *module; @@ -1093,6 +1082,13 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; dso__set_kernel(ndso, dso__kernel(dso)); + if (dso__kernel(dso) == DSO_SPACE__KERNEL_GUEST) { + dso__set_binary_type(ndso, DSO_BINARY_TYPE__GUEST_KALLSYMS); + dso__set_symtab_type(ndso, DSO_BINARY_TYPE__GUEST_KALLSYMS); + } else { + dso__set_binary_type(ndso, DSO_BINARY_TYPE__KALLSYMS); + dso__set_symtab_type(ndso, DSO_BINARY_TYPE__KALLSYMS); + } dso__set_loaded(ndso); curr_map = map__new2(pos->start, ndso); @@ -1554,7 +1550,7 @@ static int dso__load_kcore(struct dso *dso, struct map *map, free(new_node); } - if (machine_or_dso_e_machine(machine, dso) == EM_X86_64) { + if (perf_env__e_machine(machine->env, /*e_flags=*/NULL) == EM_X86_64) { u64 addr; /* @@ -1842,7 +1838,7 @@ int dso__load(struct dso *dso, struct map *map) ret = dso__load_guest_kernel_sym(dso, map); machine = maps__machine(map__kmaps(map)); - if (machine && machine_or_dso_e_machine(machine, dso) == EM_X86_64) + if (perf_env__e_machine(machine->env, /*e_flags=*/NULL) == EM_X86_64) machine__map_x86_64_entry_trampolines(machine, dso); goto out; } @@ -1851,6 +1847,7 @@ int dso__load(struct dso *dso, struct map *map) if (perfmap) { ret = dso__load_perf_map(map_path, dso); + dso__set_binary_type(dso, DSO_BINARY_TYPE__JAVA_JIT); dso__set_symtab_type(dso, ret > 0 ? DSO_BINARY_TYPE__JAVA_JIT : DSO_BINARY_TYPE__NOT_FOUND); @@ -1924,9 +1921,10 @@ int dso__load(struct dso *dso, struct map *map) if (nsexit) nsinfo__mountns_enter(dso__nsinfo(dso), &nsc); - if (bfdrc == 0) { - ret = 0; - break; + if (bfdrc > 0) { + dso__set_symtab_type(dso, symtab_type); + ret = 1; + goto out_destroy; } if (!is_reg || sirc < 0) @@ -1946,19 +1944,6 @@ int dso__load(struct dso *dso, struct map *map) if (next_slot) { ss_pos++; - - /* - * The binary type is used to find the file containing - * the executed instructions, so prefer the types that - * refer to the actual object over debug-only files such - * as DSO_BINARY_TYPE__DEBUGLINK. - */ - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND || - symtab_type == DSO_BINARY_TYPE__BUILD_ID_CACHE || - (symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_DSO && - dso__binary_type(dso) != DSO_BINARY_TYPE__BUILD_ID_CACHE)) - dso__set_binary_type(dso, symtab_type); - if (syms_ss && runtime_ss) break; } else { @@ -1991,6 +1976,7 @@ int dso__load(struct dso *dso, struct map *map) ret += nr_plt; } +out_destroy: for (; ss_pos > 0; ss_pos--) symsrc__destroy(&ss_[ss_pos - 1]); out_free: @@ -2073,7 +2059,7 @@ int dso__load_vmlinux_path(struct dso *dso, struct map *map) if (filename != NULL) { err = dso__load_vmlinux(dso, map, filename, true); if (err > 0) - goto out; + dso__set_symtab_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); } out: return err; @@ -2229,8 +2215,10 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map) filename = __dso__build_id_filename(dso, NULL, 0, false, false); if (filename != NULL) { err = dso__load_vmlinux(dso, map, filename, true); - if (err > 0) + if (err > 0) { + dso__set_symtab_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); return err; + } } if (!symbol_conf.ignore_vmlinux && vmlinux_path != NULL) { @@ -2707,13 +2695,24 @@ static bool want_demangle(bool is_kernel_sym) * version. */ #ifndef HAVE_CXA_DEMANGLE_SUPPORT +#ifdef HAVE_CPLUS_DEMANGLE_SUPPORT +/* + * Declarations from libiberty's demangle.h, the header isn't installed by + * every binutils package. + */ +#ifndef DMGL_PARAMS +#define DMGL_PARAMS (1 << 0) /* Include function arguments. */ +#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc. */ +#endif + +char *cplus_demangle(const char *mangled, int options); +#endif + char *cxx_demangle_sym(const char *str __maybe_unused, bool params __maybe_unused, bool modifiers __maybe_unused) { #ifdef HAVE_LIBBFD_SUPPORT - int flags = (params ? DMGL_PARAMS : 0) | (modifiers ? DMGL_ANSI : 0); - - return bfd_demangle(NULL, str, flags); + return libbfd__demangle_sym(str, params, modifiers); #elif defined(HAVE_CPLUS_DEMANGLE_SUPPORT) int flags = (params ? DMGL_PARAMS : 0) | (modifiers ? DMGL_ANSI : 0); @@ -2749,17 +2748,16 @@ char *dso__demangle_sym(struct dso *dso, int kmodule, const char *elf_name) buf_len < 1024 * 1024; buf_len += 32) { char *tmp = realloc(demangled, buf_len); - if (!tmp) { - /* Failure to grow output buffer, return what is there. */ - return demangled; - } + if (!tmp) + break; demangled = tmp; if (rust_demangle_display_demangle(&rust_demangle, demangled, buf_len, /*alternate=*/true) == OverflowOk) return demangled; } - /* Buffer exceeded sensible bounds, return what is there. */ - return demangled; + /* Failure to grow output buffer or buffer exceeded sensible bounds. */ + free(demangled); + return NULL; } demangled = cxx_demangle_sym(elf_name, verbose > 0, verbose > 0); diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h index 0dee5aa6a534..37d35f42dcc1 100644 --- a/tools/perf/util/symbol_conf.h +++ b/tools/perf/util/symbol_conf.h @@ -27,7 +27,20 @@ enum a2l_style { }; #define MAX_A2L_STYLE (A2L_STYLE_CMD + 1) +enum symbol__weight_mode { + WEIGHT_NONE = 0, + WEIGHT_WEIGHT, + WEIGHT_INSNLAT, + WEIGHT_WEIGHT3, +}; + +#define for_each_weight(_weight) \ + for ((_weight) = WEIGHT_WEIGHT; (_weight) <= WEIGHT_WEIGHT3; (_weight)++) + struct symbol_conf { + bool hybrid_merge; + /* Was hybrid_merge given on the command line rather than in a config file? */ + bool hybrid_merge_set; bool nanosecs; unsigned short priv_size; bool try_vmlinux_path, @@ -69,7 +82,8 @@ struct symbol_conf { annotate_data_sample, skip_empty, enable_latency, - prefer_latency; + prefer_latency, + annotate_weight; const char *vmlinux_name, *kallsyms_name, *source_prefix, diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c index 0c150193cca8..b9dbd105cdc3 100644 --- a/tools/perf/util/synthetic-events.c +++ b/tools/perf/util/synthetic-events.c @@ -1644,6 +1644,13 @@ size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type, if (sample->user_regs && sample->user_regs->abi) { result += sizeof(u64); sz = hweight64(sample->user_regs->mask) * sizeof(u64); + if (sample->user_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + sz += 4 * sizeof(u64); + sz += (sample->user_regs->nr_vectors * + sample->user_regs->vector_qwords + + sample->user_regs->nr_pred * + sample->user_regs->pred_qwords) * sizeof(u64); + } result += sz; } else { result += sizeof(u64); @@ -1672,6 +1679,13 @@ size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type, if (sample->intr_regs && sample->intr_regs->abi) { result += sizeof(u64); sz = hweight64(sample->intr_regs->mask) * sizeof(u64); + if (sample->intr_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + sz += 4 * sizeof(u64); + sz += (sample->intr_regs->nr_vectors * + sample->intr_regs->vector_qwords + + sample->intr_regs->nr_pred * + sample->intr_regs->pred_qwords) * sizeof(u64); + } result += sz; } else { result += sizeof(u64); @@ -1859,6 +1873,18 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type, u64 read_fo sz = hweight64(sample->user_regs->mask) * sizeof(u64); memcpy(array, sample->user_regs->regs, sz); array = (void *)array + sz; + if (sample->user_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + *array++ = sample->user_regs->nr_vectors; + *array++ = sample->user_regs->vector_qwords; + *array++ = sample->user_regs->nr_pred; + *array++ = sample->user_regs->pred_qwords; + sz = (sample->user_regs->nr_vectors * + sample->user_regs->vector_qwords + + sample->user_regs->nr_pred * + sample->user_regs->pred_qwords) * sizeof(u64); + memcpy(array, sample->user_regs->simd_data, sz); + array = (void *)array + sz; + } } else { *array++ = 0; } @@ -1895,6 +1921,18 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type, u64 read_fo sz = hweight64(sample->intr_regs->mask) * sizeof(u64); memcpy(array, sample->intr_regs->regs, sz); array = (void *)array + sz; + if (sample->intr_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + *array++ = sample->intr_regs->nr_vectors; + *array++ = sample->intr_regs->vector_qwords; + *array++ = sample->intr_regs->nr_pred; + *array++ = sample->intr_regs->pred_qwords; + sz = (sample->intr_regs->nr_vectors * + sample->intr_regs->vector_qwords + + sample->intr_regs->nr_pred * + sample->intr_regs->pred_qwords) * sizeof(u64); + memcpy(array, sample->intr_regs->simd_data, sz); + array = (void *)array + sz; + } } else { *array++ = 0; } @@ -2266,7 +2304,7 @@ int perf_event__synthesize_extra_attr(const struct perf_tool *tool, struct evlis if (has_scale(evsel)) { err = perf_event__synthesize_event_update_scale(tool, evsel, process); if (err < 0) { - pr_err("Couldn't synthesize evsel evsel.\n"); + pr_err("Couldn't synthesize evsel.\n"); return err; } } @@ -2776,6 +2814,8 @@ static union perf_event *__synthesize_schedstat_domain(struct io *io, __u16 vers size = sizeof(*ds); size = PERF_ALIGN(size, sizeof(u64)); event = zalloc(size); + if (!event) + return NULL; ds = &event->schedstat_domain; ds->header.type = PERF_RECORD_SCHEDSTAT_DOMAIN; @@ -2817,7 +2857,6 @@ int perf_event__synthesize_schedstat(const struct perf_tool *tool, struct perf_cpu_map *user_requested_cpus) { char *line = NULL, path[PATH_MAX]; - union perf_event *event = NULL; size_t line_len = 0; char bf[BUFSIZ]; __u64 timestamp; @@ -2858,6 +2897,7 @@ int perf_event__synthesize_schedstat(const struct perf_tool *tool, * for filtered out cpus. */ for (ch = io__get_char(&io); !io.eof; ch = io__get_char(&io)) { + union perf_event *event = NULL; struct perf_cpu this_cpu; if (ch == 'c') { @@ -2872,12 +2912,12 @@ int perf_event__synthesize_schedstat(const struct perf_tool *tool, this_cpu.cpu = cpu; - if (user_requested_cpus && !perf_cpu_map__has(user_requested_cpus, this_cpu)) - continue; - - if (process(tool, event, NULL, NULL) < 0) { - free(event); - goto out_free_line; + if (!user_requested_cpus || + perf_cpu_map__has(user_requested_cpus, this_cpu)) { + if (process(tool, event, NULL, NULL) < 0) { + free(event); + goto out_free_line; + } } free(event); diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index e483ffcb5d93..91118c301913 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -296,12 +296,12 @@ int thread__set_comm_from_proc(struct thread *thread) thread__pid(thread), thread__tid(thread)) >= (int)sizeof(path)) && procfs__read_str(path, &comm, &sz) == 0) { /* sz==0: read got nothing, e.g. race during exit teardown */ - if (sz == 0) { - free(comm); - return -1; + if (sz > 0) { + comm[sz - 1] = '\0'; + err = thread__set_comm(thread, comm, 0); } - comm[sz - 1] = '\0'; - err = thread__set_comm(thread, comm, 0); + /* thread__set_comm() copies the string, so release the buffer. */ + free(comm); } return err; @@ -531,12 +531,10 @@ uint16_t thread__e_machine_endian(struct thread *thread, struct machine *machine bool is_live = machine->machines == NULL; if (!is_live) { - /* Check if the session has a data file. */ - struct perf_session *session = container_of(machine->machines, - struct perf_session, - machines); + /* Check if the session has a data file (assume no session is a test). */ + struct perf_session *session = machines__session(machine->machines); - is_live = !!session->data; + is_live = !session || !session->data; } /* Read from /proc/pid/exe if live. */ if (is_live) { diff --git a/tools/perf/util/thread_map.c b/tools/perf/util/thread_map.c index 48c70f149e92..918ab3180232 100644 --- a/tools/perf/util/thread_map.c +++ b/tools/perf/util/thread_map.c @@ -395,3 +395,29 @@ int thread_map__remove(struct perf_thread_map *threads, int idx) threads->nr--; return 0; } + +/* The tgid of the thread at idx, read from /proc, or -1 if it has exited. */ +pid_t thread_map__tgid(struct perf_thread_map *threads, int idx) +{ + char path[128]; + char *buf, *p, *q; + pid_t tgid; + size_t len; + + snprintf(path, sizeof(path), "%d/status", perf_thread_map__pid(threads, idx)); + if (procfs__read_str(path, &buf, &len) < 0) + return -1; + + /* From a line start, as the Name: line before it can contain "Tgid:". */ + p = strstr(buf, "\nTgid:"); + if (p == NULL) { + free(buf); + return -1; + } + + tgid = strtol(p + 6, &q, 0); + if (*q != '\n') + tgid = -1; + free(buf); + return tgid; +} diff --git a/tools/perf/util/thread_map.h b/tools/perf/util/thread_map.h index fc16d87f32fb..d700a22b7ae4 100644 --- a/tools/perf/util/thread_map.h +++ b/tools/perf/util/thread_map.h @@ -24,4 +24,5 @@ size_t thread_map__fprintf(struct perf_thread_map *threads, FILE *fp); void thread_map__read_comms(struct perf_thread_map *threads); bool thread_map__has(struct perf_thread_map *threads, pid_t pid); int thread_map__remove(struct perf_thread_map *threads, int idx); +pid_t thread_map__tgid(struct perf_thread_map *threads, int idx); #endif /* __PERF_THREAD_MAP_H */ diff --git a/tools/perf/util/tool_pmu.c b/tools/perf/util/tool_pmu.c index 5c30854b4644..d2f7591747d9 100644 --- a/tools/perf/util/tool_pmu.c +++ b/tools/perf/util/tool_pmu.c @@ -349,7 +349,7 @@ int evsel__tool_pmu_open(struct evsel *evsel, FD(evsel, idx, thread) = -1; } thread = nthreads; - } while (--idx >= 0); + } while (--idx >= start_cpu_map_idx); errno = old_errno; return err; } diff --git a/tools/perf/util/tp_pmu.c b/tools/perf/util/tp_pmu.c index c2be8c9f9084..5ac732e06841 100644 --- a/tools/perf/util/tp_pmu.c +++ b/tools/perf/util/tp_pmu.c @@ -151,7 +151,9 @@ static int for_each_event_cb(void *state, const char *sys_name, const char *evt_ static int for_each_event_sys_cb(void *state, const char *sys_name) { - return tp_pmu__for_each_tp_event(sys_name, state, for_each_event_cb); + int ret = tp_pmu__for_each_tp_event(sys_name, state, for_each_event_cb); + + return ret == -ENOENT ? 0 : ret; } int tp_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callback cb) @@ -176,7 +178,9 @@ static int num_events_cb(void *state, const char *sys_name __maybe_unused, static int num_events_sys_cb(void *state, const char *sys_name) { - return tp_pmu__for_each_tp_event(sys_name, state, num_events_cb); + int ret = tp_pmu__for_each_tp_event(sys_name, state, num_events_cb); + + return ret == -ENOENT ? 0 : ret; } size_t tp_pmu__num_events(struct perf_pmu *pmu __maybe_unused) diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c index 9c015fc2bcfb..374cf82fd86e 100644 --- a/tools/perf/util/trace-event-parse.c +++ b/tools/perf/util/trace-event-parse.c @@ -14,71 +14,6 @@ #include #include -static int get_common_field(struct scripting_context *context, - int *offset, int *size, const char *type) -{ - struct tep_handle *pevent = context->pevent; - struct tep_event *event; - struct tep_format_field *field; - - if (!*size) { - - event = tep_get_first_event(pevent); - if (!event) - return 0; - - field = tep_find_common_field(event, type); - if (!field) - return 0; - *offset = field->offset; - *size = field->size; - } - - return tep_read_number(pevent, context->event_data + *offset, *size); -} - -int common_lock_depth(struct scripting_context *context) -{ - static int offset; - static int size; - int ret; - - ret = get_common_field(context, &size, &offset, - "common_lock_depth"); - if (ret < 0) - return -1; - - return ret; -} - -int common_flags(struct scripting_context *context) -{ - static int offset; - static int size; - int ret; - - ret = get_common_field(context, &size, &offset, - "common_flags"); - if (ret < 0) - return -1; - - return ret; -} - -int common_pc(struct scripting_context *context) -{ - static int offset; - static int size; - int ret; - - ret = get_common_field(context, &size, &offset, - "common_preempt_count"); - if (ret < 0) - return -1; - - return ret; -} - unsigned long long raw_field_value(struct tep_event *event, const char *name, void *data) { diff --git a/tools/perf/util/trace-event-scripting.c b/tools/perf/util/trace-event-scripting.c deleted file mode 100644 index dc584ac316a3..000000000000 --- a/tools/perf/util/trace-event-scripting.c +++ /dev/null @@ -1,407 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * trace-event-scripting. Scripting engine common and initialization code. - * - * Copyright (C) 2009-2010 Tom Zanussi - */ - -#include -#include -#include -#include -#ifdef HAVE_LIBTRACEEVENT -#include -#endif - -#include "debug.h" -#include "event.h" -#include "trace-event.h" -#include "evsel.h" -#include -#include -#include "util/sample.h" - -unsigned int scripting_max_stack = PERF_MAX_STACK_DEPTH; - -struct scripting_context *scripting_context; - -struct script_spec { - struct list_head node; - struct scripting_ops *ops; - char spec[]; -}; - -static LIST_HEAD(script_specs); - -static struct script_spec *script_spec__new(const char *spec, - struct scripting_ops *ops) -{ - struct script_spec *s = malloc(sizeof(*s) + strlen(spec) + 1); - - if (s != NULL) { - strcpy(s->spec, spec); - s->ops = ops; - } - - return s; -} - -static void script_spec__add(struct script_spec *s) -{ - list_add_tail(&s->node, &script_specs); -} - -static struct script_spec *script_spec__find(const char *spec) -{ - struct script_spec *s; - - list_for_each_entry(s, &script_specs, node) - if (strcasecmp(s->spec, spec) == 0) - return s; - return NULL; -} - -static int script_spec_register(const char *spec, struct scripting_ops *ops) -{ - struct script_spec *s; - - s = script_spec__find(spec); - if (s) - return -1; - - s = script_spec__new(spec, ops); - if (!s) - return -1; - - script_spec__add(s); - return 0; -} - -struct scripting_ops *script_spec__lookup(const char *spec) -{ - struct script_spec *s = script_spec__find(spec); - - if (!s) - return NULL; - - return s->ops; -} - -int script_spec__for_each(int (*cb)(struct scripting_ops *ops, const char *spec)) -{ - struct script_spec *s; - int ret = 0; - - list_for_each_entry(s, &script_specs, node) { - ret = cb(s->ops, s->spec); - if (ret) - break; - } - return ret; -} - -void scripting_context__update(struct scripting_context *c, - union perf_event *event, - struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al) -{ -#ifdef HAVE_LIBTRACEEVENT - const struct tep_event *tp_format = evsel__tp_format(sample->evsel); - - c->pevent = tp_format ? tp_format->tep : NULL; -#else - c->pevent = NULL; -#endif - c->event_data = sample->raw_data; - c->event = event; - c->sample = sample; - c->al = al; - c->addr_al = addr_al; -} - -static int flush_script_unsupported(void) -{ - return 0; -} - -static int stop_script_unsupported(void) -{ - return 0; -} - -static void process_event_unsupported(union perf_event *event __maybe_unused, - struct perf_sample *sample __maybe_unused, - struct addr_location *al __maybe_unused, - struct addr_location *addr_al __maybe_unused) -{ -} - -static void print_python_unsupported_msg(void) -{ - fprintf(stderr, "Python scripting not supported." - " Install libpython and rebuild perf to enable it.\n" - "For example:\n # apt-get install python-dev (ubuntu)" - "\n # yum install python-devel (Fedora)" - "\n etc.\n"); -} - -static int python_start_script_unsupported(const char *script __maybe_unused, - int argc __maybe_unused, - const char **argv __maybe_unused, - struct perf_session *session __maybe_unused) -{ - print_python_unsupported_msg(); - - return -1; -} - -static int python_generate_script_unsupported(struct tep_handle *pevent - __maybe_unused, - const char *outfile - __maybe_unused) -{ - print_python_unsupported_msg(); - - return -1; -} - -struct scripting_ops python_scripting_unsupported_ops = { - .name = "Python", - .dirname = "python", - .start_script = python_start_script_unsupported, - .flush_script = flush_script_unsupported, - .stop_script = stop_script_unsupported, - .process_event = process_event_unsupported, - .generate_script = python_generate_script_unsupported, -}; - -static void register_python_scripting(struct scripting_ops *scripting_ops) -{ - if (scripting_context == NULL) - scripting_context = malloc(sizeof(*scripting_context)); - - if (scripting_context == NULL || - script_spec_register("Python", scripting_ops) || - script_spec_register("py", scripting_ops)) { - pr_err("Error registering Python script extension: disabling it\n"); - zfree(&scripting_context); - } -} - -#ifndef HAVE_LIBPYTHON_SUPPORT -void setup_python_scripting(void) -{ - register_python_scripting(&python_scripting_unsupported_ops); -} -#else -extern struct scripting_ops python_scripting_ops; - -void setup_python_scripting(void) -{ - register_python_scripting(&python_scripting_ops); -} -#endif - -#ifdef HAVE_LIBTRACEEVENT -static void print_perl_unsupported_msg(void) -{ - fprintf(stderr, "Perl scripting not supported." - " Install libperl and rebuild perf to enable it.\n" - "For example:\n # apt-get install libperl-dev (ubuntu)" - "\n # yum install 'perl(ExtUtils::Embed)' (Fedora)" - "\n etc.\n"); -} - -static int perl_start_script_unsupported(const char *script __maybe_unused, - int argc __maybe_unused, - const char **argv __maybe_unused, - struct perf_session *session __maybe_unused) -{ - print_perl_unsupported_msg(); - - return -1; -} - -static int perl_generate_script_unsupported(struct tep_handle *pevent - __maybe_unused, - const char *outfile __maybe_unused) -{ - print_perl_unsupported_msg(); - - return -1; -} - -struct scripting_ops perl_scripting_unsupported_ops = { - .name = "Perl", - .dirname = "perl", - .start_script = perl_start_script_unsupported, - .flush_script = flush_script_unsupported, - .stop_script = stop_script_unsupported, - .process_event = process_event_unsupported, - .generate_script = perl_generate_script_unsupported, -}; - -static void register_perl_scripting(struct scripting_ops *scripting_ops) -{ - if (scripting_context == NULL) - scripting_context = malloc(sizeof(*scripting_context)); - - if (scripting_context == NULL || - script_spec_register("Perl", scripting_ops) || - script_spec_register("pl", scripting_ops)) { - pr_err("Error registering Perl script extension: disabling it\n"); - zfree(&scripting_context); - } -} - -#ifndef HAVE_LIBPERL_SUPPORT -void setup_perl_scripting(void) -{ - register_perl_scripting(&perl_scripting_unsupported_ops); -} -#else -extern struct scripting_ops perl_scripting_ops; - -void setup_perl_scripting(void) -{ - register_perl_scripting(&perl_scripting_ops); -} -#endif -#endif - -static const struct { - u32 flags; - const char *name; -} sample_flags[] = { - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL, "call"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN, "return"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CONDITIONAL, "jcc"}, - {PERF_IP_FLAG_BRANCH, "jmp"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_INTERRUPT, "int"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN | PERF_IP_FLAG_INTERRUPT, "iret"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_SYSCALLRET, "syscall"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN | PERF_IP_FLAG_SYSCALLRET, "sysret"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_ASYNC, "async"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_ASYNC | PERF_IP_FLAG_INTERRUPT, - "hw int"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TX_ABORT, "tx abrt"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TRACE_BEGIN, "tr strt"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TRACE_END, "tr end"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_VMENTRY, "vmentry"}, - {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_VMEXIT, "vmexit"}, - {0, NULL} -}; - -static const struct { - u32 flags; - const char *name; -} branch_events[] = { - {PERF_IP_FLAG_BRANCH_MISS, "miss"}, - {PERF_IP_FLAG_NOT_TAKEN, "not_taken"}, - {0, NULL} -}; - -static int sample_flags_to_name(u32 flags, char *str, size_t size) -{ - int i; - const char *prefix; - int pos = 0, ret, ev_idx = 0; - u32 xf = flags & PERF_ADDITIONAL_STATE_MASK; - u32 types, events; - char xs[16] = { 0 }; - - /* Clear additional state bits */ - flags &= ~PERF_ADDITIONAL_STATE_MASK; - - if (flags & PERF_IP_FLAG_TRACE_BEGIN) - prefix = "tr strt "; - else if (flags & PERF_IP_FLAG_TRACE_END) - prefix = "tr end "; - else - prefix = ""; - - ret = snprintf(str + pos, size - pos, "%s", prefix); - if (ret < 0) - return ret; - pos += ret; - - flags &= ~(PERF_IP_FLAG_TRACE_BEGIN | PERF_IP_FLAG_TRACE_END); - - types = flags & ~PERF_IP_FLAG_BRANCH_EVENT_MASK; - for (i = 0; sample_flags[i].name; i++) { - if (sample_flags[i].flags != types) - continue; - - ret = snprintf(str + pos, size - pos, "%s", sample_flags[i].name); - if (ret < 0) - return ret; - pos += ret; - break; - } - - events = flags & PERF_IP_FLAG_BRANCH_EVENT_MASK; - for (i = 0; branch_events[i].name; i++) { - if (!(branch_events[i].flags & events)) - continue; - - ret = snprintf(str + pos, size - pos, !ev_idx ? "/%s" : ",%s", - branch_events[i].name); - if (ret < 0) - return ret; - pos += ret; - ev_idx++; - } - - /* Add an end character '/' for events */ - if (ev_idx) { - ret = snprintf(str + pos, size - pos, "/"); - if (ret < 0) - return ret; - pos += ret; - } - - if (!xf) - return pos; - - snprintf(xs, sizeof(xs), "(%s%s%s)", - flags & PERF_IP_FLAG_IN_TX ? "x" : "", - flags & PERF_IP_FLAG_INTR_DISABLE ? "D" : "", - flags & PERF_IP_FLAG_INTR_TOGGLE ? "t" : ""); - - /* Right align the string if its length is less than the limit */ - if ((pos + strlen(xs)) < SAMPLE_FLAGS_STR_ALIGNED_SIZE) - ret = snprintf(str + pos, size - pos, "%*s", - (int)(SAMPLE_FLAGS_STR_ALIGNED_SIZE - ret), xs); - else - ret = snprintf(str + pos, size - pos, " %s", xs); - if (ret < 0) - return ret; - - return pos + ret; -} - -int perf_sample__sprintf_flags(u32 flags, char *str, size_t sz) -{ - const char *chars = PERF_IP_FLAG_CHARS; - const size_t n = strlen(PERF_IP_FLAG_CHARS); - size_t i, pos = 0; - int ret; - - ret = sample_flags_to_name(flags, str, sz); - if (ret > 0) - return ret; - - for (i = 0; i < n; i++, flags >>= 1) { - if ((flags & 1) && pos < sz) - str[pos++] = chars[i]; - } - for (; i < 32; i++, flags >>= 1) { - if ((flags & 1) && pos < sz) - str[pos++] = '?'; - } - if (pos < sz) - str[pos] = 0; - - return pos; -} diff --git a/tools/perf/util/trace-event.c b/tools/perf/util/trace-event.c index 000c1e1d68c1..819542a73f55 100644 --- a/tools/perf/util/trace-event.c +++ b/tools/perf/util/trace-event.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -15,11 +14,9 @@ #include "machine.h" /* - * global trace_event object used by trace_event__tp_format - * - * TODO There's no cleanup call for this. Add some sort of - * __exit function support and call trace_event__cleanup - * there. + * Global trace_event object used by trace_event__tp_format. It caches the + * tracepoint formats of the running kernel for the lifetime of the command + * and is released by trace_event__exit. */ static struct trace_event tevent; static bool tevent_initialized; @@ -77,51 +74,104 @@ void trace_event__cleanup(struct trace_event *t) } /* - * Returns pointer with encoded error via interface. + * Release the global trace_event. Called once the command is done, when the + * tep_event pointers handed out by trace_event__tp_format are no longer in + * use. + */ +void trace_event__exit(void) +{ + if (!tevent_initialized) + return; + + trace_event__cleanup(&tevent); + tevent_initialized = false; +} + +/* + * Returns NULL and sets errno on failure. */ static struct tep_event* tp_format(const char *sys, const char *name) { - char *tp_dir = get_events_file(sys); struct tep_handle *pevent = tevent.pevent; - struct tep_event *event = NULL; + struct tep_event *event; + char *tp_dir; char path[PATH_MAX]; size_t size; char *data; int err; - if (!tp_dir) - return ERR_PTR(-errno); + /* + * Each parse adds an event to the tep handle that can only be freed + * by freeing the whole handle, so re-reading a format file both + * repeats the work and grows the handle with a duplicate. Reuse the + * event if it was already parsed. + */ + event = tep_find_event_by_name(pevent, sys, name); + if (event) + return event; + + tp_dir = get_events_file(sys); + if (!tp_dir) { + errno = ENOMEM; + return NULL; + } scnprintf(path, PATH_MAX, "%s/%s/format", tp_dir, name); put_events_file(tp_dir); err = filename__read_str(path, &data, &size); - if (err) - return ERR_PTR(err); + if (err) { + errno = -err; + return NULL; + } - tep_parse_format(pevent, &event, data, size, sys); + event = NULL; + err = tep_parse_format(pevent, &event, data, size, sys); free(data); + + /* + * A parse failure leaves no event behind, report it rather than + * letting a NULL be mistaken for a successfully parsed format. + */ + if (err != TEP_ERRNO__SUCCESS || !event) { + errno = EINVAL; + return NULL; + } + return event; } /* - * Returns pointer with encoded error via interface. + * Returns NULL and sets errno on failure. */ struct tep_event* trace_event__tp_format(const char *sys, const char *name) { - if (!tevent_initialized && trace_event__init2()) - return ERR_PTR(-ENOMEM); + if (!tevent_initialized && trace_event__init2()) { + errno = ENOMEM; + return NULL; + } return tp_format(sys, name); } +/* + * Returns NULL and sets errno on failure. + */ struct tep_event *trace_event__tp_format_id(int id) { - if (!tevent_initialized && trace_event__init2()) - return ERR_PTR(-ENOMEM); + struct tep_event *event; - return tep_find_event(tevent.pevent, id); + if (!tevent_initialized && trace_event__init2()) { + errno = ENOMEM; + return NULL; + } + + event = tep_find_event(tevent.pevent, id); + if (!event) + errno = ENOENT; + + return event; } diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index 720121c74f1d..fa3824cf39fa 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -7,15 +7,9 @@ #include #include -struct evlist; struct machine; -struct perf_sample; -union perf_event; -struct perf_tool; -struct thread; -struct tep_plugin_list; -struct evsel; struct tep_format_field; +struct tep_plugin_list; struct trace_event { struct tep_handle *pevent; @@ -32,6 +26,7 @@ bool have_tracepoints(struct list_head *evlist); int trace_event__init(struct trace_event *t); void trace_event__cleanup(struct trace_event *t); +void trace_event__exit(void); int trace_event__register_resolver(struct machine *machine, tep_func_resolver_t *func); struct tep_event* @@ -79,70 +74,6 @@ struct tracing_data *tracing_data_get(struct list_head *pattrs, int fd, bool temp); int tracing_data_put(struct tracing_data *tdata); - -struct addr_location; - -struct perf_session; -struct perf_stat_config; - -struct scripting_ops { - const char *name; - const char *dirname; /* For script path .../scripts//... */ - int (*start_script)(const char *script, int argc, const char **argv, - struct perf_session *session); - int (*flush_script) (void); - int (*stop_script) (void); - void (*process_event) (union perf_event *event, - struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al); - void (*process_switch)(union perf_event *event, - struct perf_sample *sample, - struct machine *machine); - void (*process_auxtrace_error)(struct perf_session *session, - union perf_event *event); - void (*process_stat)(struct perf_stat_config *config, - struct evsel *evsel, u64 tstamp); - void (*process_stat_interval)(u64 tstamp); - void (*process_throttle)(union perf_event *event, - struct perf_sample *sample, - struct machine *machine); - int (*generate_script) (struct tep_handle *pevent, const char *outfile); -}; - -extern unsigned int scripting_max_stack; - -struct scripting_ops *script_spec__lookup(const char *spec); -int script_spec__for_each(int (*cb)(struct scripting_ops *ops, const char *spec)); - -void setup_perl_scripting(void); -void setup_python_scripting(void); - -struct scripting_context { - struct tep_handle *pevent; - void *event_data; - union perf_event *event; - struct perf_sample *sample; - struct addr_location *al; - struct addr_location *addr_al; - struct perf_session *session; -}; - -void scripting_context__update(struct scripting_context *scripting_context, - union perf_event *event, - struct perf_sample *sample, - struct addr_location *al, - struct addr_location *addr_al); - -int common_pc(struct scripting_context *context); -int common_flags(struct scripting_context *context); -int common_lock_depth(struct scripting_context *context); - -#define SAMPLE_FLAGS_BUF_SIZE 64 -#define SAMPLE_FLAGS_STR_ALIGNED_SIZE 21 - -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 diff --git a/tools/perf/util/trace_augment.h b/tools/perf/util/trace_augment.h index 4f729bc67753..423f16221963 100644 --- a/tools/perf/util/trace_augment.h +++ b/tools/perf/util/trace_augment.h @@ -2,19 +2,27 @@ #define TRACE_AUGMENT_H #include +#include +#include struct bpf_program; struct evlist; +struct perf_thread_map; #ifdef HAVE_BPF_SKEL int augmented_syscalls__prepare(void); +int augmented_syscalls__attach(void); int augmented_syscalls__create_bpf_output(struct evlist *evlist); void augmented_syscalls__setup_bpf_output(void); int augmented_syscalls__set_filter_pids(unsigned int nr, pid_t *pids); +void augmented_syscalls__set_target_pids(struct perf_thread_map *threads, bool inherit, + bool uses_tgid, bool on_exec); +int augmented_syscalls__lost_tasks(void); int augmented_syscalls__get_map_fds(int *enter_fd, int *exit_fd, int *beauty_fd); struct bpf_program *augmented_syscalls__find_by_title(const char *name); -struct bpf_program *augmented_syscalls__unaugmented(void); +struct bpf_program *augmented_syscalls__unaugmented_enter(void); +struct bpf_program *augmented_syscalls__unaugmented_exit(void); void augmented_syscalls__cleanup(void); #else /* !HAVE_BPF_SKEL */ @@ -24,6 +32,11 @@ static inline int augmented_syscalls__prepare(void) return -1; } +static inline int augmented_syscalls__attach(void) +{ + return 0; +} + static inline int augmented_syscalls__create_bpf_output(struct evlist *evlist __maybe_unused) { return -1; @@ -39,6 +52,19 @@ static inline int augmented_syscalls__set_filter_pids(unsigned int nr __maybe_un return 0; } +static inline void +augmented_syscalls__set_target_pids(struct perf_thread_map *threads __maybe_unused, + bool inherit __maybe_unused, + bool uses_tgid __maybe_unused, + bool on_exec __maybe_unused) +{ +} + +static inline int augmented_syscalls__lost_tasks(void) +{ + return 0; +} + static inline int augmented_syscalls__get_map_fds(int *enter_fd __maybe_unused, int *exit_fd __maybe_unused, int *beauty_fd __maybe_unused) @@ -52,7 +78,12 @@ augmented_syscalls__find_by_title(const char *name __maybe_unused) return NULL; } -static inline struct bpf_program *augmented_syscalls__unaugmented(void) +static inline struct bpf_program *augmented_syscalls__unaugmented_enter(void) +{ + return NULL; +} + +static inline struct bpf_program *augmented_syscalls__unaugmented_exit(void) { return NULL; } diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c index 63a5c2253174..d49901b1571e 100644 --- a/tools/perf/util/unwind-libdw.c +++ b/tools/perf/util/unwind-libdw.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "debug.h" #include "dso.h" @@ -17,6 +18,8 @@ #include #include #include "event.h" +#include "evsel.h" +#include "memswap.h" #include "perf_regs.h" #include "callchain.h" #include "util/env.h" @@ -42,8 +45,10 @@ static int __find_debuginfo(Dwfl_Module *mod __maybe_unused, void **userdata, const struct dso *dso = *userdata; assert(dso); + mutex_lock(dso__lock((struct dso *)dso)); if (dso__symsrc_filename(dso) && strcmp(file_name, dso__symsrc_filename(dso))) *debuginfo_file_name = strdup(dso__symsrc_filename(dso)); + mutex_unlock(dso__lock((struct dso *)dso)); return -1; } @@ -201,9 +206,10 @@ static bool get_thread(Dwfl *dwfl __maybe_unused, pid_t tid, void *arg, } static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr, - Dwarf_Word *data) + Dwarf_Word *data, size_t len) { struct addr_location al; + union u64_swap u; ssize_t size; struct dso *dso; @@ -216,15 +222,38 @@ static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr, if (!dso) goto out_fail; - size = dso__data_read_addr(dso, al.map, ui->machine, addr, (u8 *) data, sizeof(*data)); + size = dso__data_read_addr(dso, al.map, ui->machine, addr, (u8 *)&u, len); addr_location__exit(&al); - return !(size == sizeof(*data)); + if (size != (ssize_t)len) + return 1; + *data = len == sizeof(u32) ? u.val32[0] : u.val64; + return 0; out_fail: addr_location__exit(&al); return -1; } +/* + * libdw expects a 32-bit task's words zero-extended. A recording of the + * other byte order was swapped in 8-byte units when it was read (see + * perf_event__all64_swap()), so a 4-byte word is picked out of its unit with + * that swap undone, as for PERF_SAMPLE_CPU in __evsel__parse_sample(). + */ +static Dwarf_Word stack_word(struct stack_dump *stack, int offset, size_t len, + bool swapped) +{ + union u64_swap u; + + if (len == sizeof(u64)) + return *(Dwarf_Word *)&stack->data[offset]; + if (!swapped) + return *(u32 *)&stack->data[offset]; + + u.val64 = bswap_64(*(u64 *)&stack->data[offset & ~7]); + return bswap_32(u.val32[(offset & 4) / 4]); +} + static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word *result, void *arg) { @@ -232,11 +261,16 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word * struct unwind_info *ui = dwfl_ui_ti->ui; struct stack_dump *stack = &ui->sample->user_stack; u64 start, end; + bool swapped; + size_t len; int offset; int ret; if (!ui->sample->user_regs) return false; + len = ui->sample->user_regs->abi == PERF_SAMPLE_REGS_ABI_32 ? + sizeof(u32) : sizeof(u64); + swapped = ui->sample->evsel && ui->sample->evsel->needs_swap; ret = perf_reg_value(&start, ui->sample->user_regs, perf_arch_reg_sp(ui->e_machine)); @@ -246,11 +280,11 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word * end = start + stack->size; /* Check overflow. */ - if (addr + sizeof(Dwarf_Word) < addr) + if (addr + len < addr) return false; - if (addr < start || addr + sizeof(Dwarf_Word) > end) { - ret = access_dso_mem(ui, addr, result); + if (addr < start || addr + len > end) { + ret = access_dso_mem(ui, addr, result, len); if (ret) { pr_debug("unwind: access_mem 0x%" PRIx64 " not inside range" " 0x%" PRIx64 "-0x%" PRIx64 "\n", @@ -261,7 +295,7 @@ static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word * } offset = addr - start; - *result = *(Dwarf_Word *)&stack->data[offset]; + *result = stack_word(stack, offset, len, swapped); pr_debug("unwind: access_mem addr 0x%" PRIx64 ", val %lx, offset %d\n", addr, (unsigned long)*result, offset); return true; @@ -290,7 +324,8 @@ static bool libdw_set_initial_registers(Dwfl_Thread *thread, void *arg) int dwarf_reg = get_dwarf_regnum_for_perf_regnum(perf_reg, e_machine, e_flags, - /*only_libdw_supported=*/true); + /*only_libdw_supported=*/true, + user_regs->abi); if (dwarf_reg > max_dwarf_reg) max_dwarf_reg = dwarf_reg; } @@ -305,7 +340,8 @@ static bool libdw_set_initial_registers(Dwfl_Thread *thread, void *arg) int dwarf_reg = get_dwarf_regnum_for_perf_regnum(perf_reg, e_machine, e_flags, - /*only_libdw_supported=*/true); + /*only_libdw_supported=*/true, + user_regs->abi); if (dwarf_reg >= 0) { val = 0; if (perf_reg_value(&val, user_regs, perf_reg) == 0) @@ -397,6 +433,11 @@ int libdw__get_entries(unwind_entry_cb_t cb, void *arg, dwfl = dwfl_ui_ti->dwfl; } else { dwfl_ui_ti = zalloc(sizeof(*dwfl_ui_ti)); + if (!dwfl_ui_ti) { + free(ui); + return -ENOMEM; + } + dwfl = dwfl_begin(&offline_callbacks); if (!dwfl) goto out; diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c index 73d191ce51a5..6052383e862b 100644 --- a/tools/perf/util/unwind-libunwind.c +++ b/tools/perf/util/unwind-libunwind.c @@ -1,20 +1,28 @@ // SPDX-License-Identifier: GPL-2.0 +#include "unwind.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + #include "callchain.h" #include "debug.h" #include "dso.h" #include "env.h" +#include "libunwind-arch/libunwind-arch.h" #include "map.h" #include "perf_regs.h" #include "session.h" #include "symbol.h" #include "thread.h" -#include "unwind.h" -#include "libunwind-arch/libunwind-arch.h" -#include -#include -#include -#include -#include #define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */ #define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */ @@ -293,10 +301,19 @@ static int read_unwind_spec_debug_frame(struct dso *dso, } if (ofs <= 0) { - fd = open(dso__symsrc_filename(dso), O_RDONLY); - if (fd >= 0) { - ofs = elf_section_offset(fd, ".debug_frame"); - close(fd); + char *alloc_name; + + mutex_lock(dso__lock(dso)); + alloc_name = dso__symsrc_filename(dso) ? + strdup(dso__symsrc_filename(dso)) : NULL; + mutex_unlock(dso__lock(dso)); + if (alloc_name) { + fd = open(alloc_name, O_RDONLY); + if (fd >= 0) { + ofs = elf_section_offset(fd, ".debug_frame"); + close(fd); + } + free(alloc_name); } } @@ -321,6 +338,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso, } } if (ofs > 0) { + mutex_lock(dso__lock(dso)); if (dso__symsrc_filename(dso) != NULL) { pr_warning( "%s: overwrite symsrc(%s,%s)\n", @@ -330,6 +348,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso, dso__free_symsrc_filename(dso); } dso__set_symsrc_filename(dso, debuglink); + mutex_unlock(dso__lock(dso)); } else { free(debuglink); } @@ -429,12 +448,23 @@ int __libunwind__find_proc_info(void *as, uint64_t ip, void *pi, int need_unwind dso__data_put_fd(dso); } - symfile = dso__symsrc_filename(dso) ?: dso__name(dso); + mutex_lock(dso__lock(dso)); + symfile = dso__symsrc_filename(dso) ? + strdup(dso__symsrc_filename(dso)) : + strdup(dso__name(dso)); + mutex_unlock(dso__lock(dso)); - if (libunwind_arch__dwarf_find_debug_frame(ui->e_machine, /*found=*/0, &di, ip, - base, symfile, start, map__end(map))) { - ret = libunwind_arch__dwarf_search_unwind_table(ui->e_machine, as, ip, &di, pi, - need_unwind_info, arg); + if (symfile) { + if (libunwind_arch__dwarf_find_debug_frame(ui->e_machine, + /*found=*/0, &di, ip, + base, symfile, start, + map__end(map))) { + ret = libunwind_arch__dwarf_search_unwind_table(ui->e_machine, as, + ip, &di, pi, + need_unwind_info, + arg); + } + free((char *)symfile); } } map__put(map); diff --git a/tools/perf/util/usage.c b/tools/perf/util/usage.c deleted file mode 100644 index 4c8ffbad2323..000000000000 --- a/tools/perf/util/usage.c +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * usage.c - * - * Various reporting routines. - * Originally copied from GIT source. - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "util.h" -#include -#include -#include - -const char perf_usage_string[] = - "perf [--version] [--help] [OPTIONS] COMMAND [ARGS]"; - -const char perf_more_info_string[] = - "See 'perf help COMMAND' for more information on a specific command."; - -static __noreturn void usage_builtin(const char *err) -{ - fprintf(stderr, "\n Usage: %s\n", err); - exit(129); -} - -/* If we are in a dlopen()ed .so write to a global variable would segfault - * (ugh), so keep things static. */ -static void (*usage_routine)(const char *err) __noreturn = usage_builtin; - -void usage(const char *err) -{ - usage_routine(err); -} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 87a0818a8c76..f8f1ff603c72 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -17,9 +17,6 @@ #include #endif -extern const char perf_usage_string[]; -extern const char perf_more_info_string[]; - extern const char *input_name; /* This will control if perf_{host,guest} will set attr.exclude_{host,guest}. */ @@ -29,7 +26,6 @@ extern bool perf_host; extern bool perf_guest; /* General helper functions */ -void usage(const char *err) __noreturn; struct dirent; struct strlist;