forked from Minki/linux
Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull perf fixes from Thomas Gleixner: "A bunch of perf tooling fixes: - Make the Intel PT SQL viewer more robust - Make the Intel PT debug log more useful - Support weak groups in perf record so it's behaving the same way as perf stat - Display the LBR stats in callchain entries properly in perf top - Handle different PMu names with common prefix properlin in pert stat - Start syscall augmenting in perf trace. Preparation for architecture independent eBPF instrumentation of syscalls. - Fix build breakage in JVMTI perf lib - Fix arm64 tools build failure wrt smp_load_{acquire,release}" * 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: perf tools: Do not zero sample_id_all for group members perf tools: Fix undefined symbol scnprintf in libperf-jvmti.so perf beauty: Use SRCARCH, ARCH=x86_64 must map to "x86" to find the headers perf intel-pt: Add MTC and CYC timestamps to debug log perf intel-pt: Add more event information to debug log perf scripts python: exported-sql-viewer.py: Fix table find when table re-ordered perf scripts python: exported-sql-viewer.py: Add help window perf scripts python: exported-sql-viewer.py: Add Selected branches report perf scripts python: exported-sql-viewer.py: Fall back to /usr/local/lib/libxed.so perf top: Display the LBR stats in callchain entry perf stat: Handle different PMU names with common prefix perf record: Support weak groups perf evlist: Move perf_evsel__reset_weak_group into evlist perf augmented_syscalls: Start collecting pathnames in the BPF program perf trace: Fix setting of augmented payload when using eBPF + raw_syscalls perf trace: When augmenting raw_syscalls plug raw_syscalls:sys_exit too perf examples bpf: Start augmenting raw_syscalls:sys_{start,exit} tools headers barrier: Fix arm64 tools build failure wrt smp_load_{acquire,release}
This commit is contained in:
commit
655c6b9777
@ -14,74 +14,75 @@
|
|||||||
#define wmb() asm volatile("dmb ishst" ::: "memory")
|
#define wmb() asm volatile("dmb ishst" ::: "memory")
|
||||||
#define rmb() asm volatile("dmb ishld" ::: "memory")
|
#define rmb() asm volatile("dmb ishld" ::: "memory")
|
||||||
|
|
||||||
#define smp_store_release(p, v) \
|
#define smp_store_release(p, v) \
|
||||||
do { \
|
do { \
|
||||||
union { typeof(*p) __val; char __c[1]; } __u = \
|
union { typeof(*p) __val; char __c[1]; } __u = \
|
||||||
{ .__val = (__force typeof(*p)) (v) }; \
|
{ .__val = (v) }; \
|
||||||
\
|
\
|
||||||
switch (sizeof(*p)) { \
|
switch (sizeof(*p)) { \
|
||||||
case 1: \
|
case 1: \
|
||||||
asm volatile ("stlrb %w1, %0" \
|
asm volatile ("stlrb %w1, %0" \
|
||||||
: "=Q" (*p) \
|
: "=Q" (*p) \
|
||||||
: "r" (*(__u8 *)__u.__c) \
|
: "r" (*(__u8_alias_t *)__u.__c) \
|
||||||
: "memory"); \
|
: "memory"); \
|
||||||
break; \
|
break; \
|
||||||
case 2: \
|
case 2: \
|
||||||
asm volatile ("stlrh %w1, %0" \
|
asm volatile ("stlrh %w1, %0" \
|
||||||
: "=Q" (*p) \
|
: "=Q" (*p) \
|
||||||
: "r" (*(__u16 *)__u.__c) \
|
: "r" (*(__u16_alias_t *)__u.__c) \
|
||||||
: "memory"); \
|
: "memory"); \
|
||||||
break; \
|
break; \
|
||||||
case 4: \
|
case 4: \
|
||||||
asm volatile ("stlr %w1, %0" \
|
asm volatile ("stlr %w1, %0" \
|
||||||
: "=Q" (*p) \
|
: "=Q" (*p) \
|
||||||
: "r" (*(__u32 *)__u.__c) \
|
: "r" (*(__u32_alias_t *)__u.__c) \
|
||||||
: "memory"); \
|
: "memory"); \
|
||||||
break; \
|
break; \
|
||||||
case 8: \
|
case 8: \
|
||||||
asm volatile ("stlr %1, %0" \
|
asm volatile ("stlr %1, %0" \
|
||||||
: "=Q" (*p) \
|
: "=Q" (*p) \
|
||||||
: "r" (*(__u64 *)__u.__c) \
|
: "r" (*(__u64_alias_t *)__u.__c) \
|
||||||
: "memory"); \
|
: "memory"); \
|
||||||
break; \
|
break; \
|
||||||
default: \
|
default: \
|
||||||
/* Only to shut up gcc ... */ \
|
/* Only to shut up gcc ... */ \
|
||||||
mb(); \
|
mb(); \
|
||||||
break; \
|
break; \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define smp_load_acquire(p) \
|
#define smp_load_acquire(p) \
|
||||||
({ \
|
({ \
|
||||||
union { typeof(*p) __val; char __c[1]; } __u; \
|
union { typeof(*p) __val; char __c[1]; } __u = \
|
||||||
\
|
{ .__c = { 0 } }; \
|
||||||
switch (sizeof(*p)) { \
|
\
|
||||||
case 1: \
|
switch (sizeof(*p)) { \
|
||||||
asm volatile ("ldarb %w0, %1" \
|
case 1: \
|
||||||
: "=r" (*(__u8 *)__u.__c) \
|
asm volatile ("ldarb %w0, %1" \
|
||||||
: "Q" (*p) : "memory"); \
|
: "=r" (*(__u8_alias_t *)__u.__c) \
|
||||||
break; \
|
: "Q" (*p) : "memory"); \
|
||||||
case 2: \
|
break; \
|
||||||
asm volatile ("ldarh %w0, %1" \
|
case 2: \
|
||||||
: "=r" (*(__u16 *)__u.__c) \
|
asm volatile ("ldarh %w0, %1" \
|
||||||
: "Q" (*p) : "memory"); \
|
: "=r" (*(__u16_alias_t *)__u.__c) \
|
||||||
break; \
|
: "Q" (*p) : "memory"); \
|
||||||
case 4: \
|
break; \
|
||||||
asm volatile ("ldar %w0, %1" \
|
case 4: \
|
||||||
: "=r" (*(__u32 *)__u.__c) \
|
asm volatile ("ldar %w0, %1" \
|
||||||
: "Q" (*p) : "memory"); \
|
: "=r" (*(__u32_alias_t *)__u.__c) \
|
||||||
break; \
|
: "Q" (*p) : "memory"); \
|
||||||
case 8: \
|
break; \
|
||||||
asm volatile ("ldar %0, %1" \
|
case 8: \
|
||||||
: "=r" (*(__u64 *)__u.__c) \
|
asm volatile ("ldar %0, %1" \
|
||||||
: "Q" (*p) : "memory"); \
|
: "=r" (*(__u64_alias_t *)__u.__c) \
|
||||||
break; \
|
: "Q" (*p) : "memory"); \
|
||||||
default: \
|
break; \
|
||||||
/* Only to shut up gcc ... */ \
|
default: \
|
||||||
mb(); \
|
/* Only to shut up gcc ... */ \
|
||||||
break; \
|
mb(); \
|
||||||
} \
|
break; \
|
||||||
__u.__val; \
|
} \
|
||||||
|
__u.__val; \
|
||||||
})
|
})
|
||||||
|
|
||||||
#endif /* _TOOLS_LINUX_ASM_AARCH64_BARRIER_H */
|
#endif /* _TOOLS_LINUX_ASM_AARCH64_BARRIER_H */
|
||||||
|
@ -55,7 +55,6 @@ counted. The following modifiers exist:
|
|||||||
S - read sample value (PERF_SAMPLE_READ)
|
S - read sample value (PERF_SAMPLE_READ)
|
||||||
D - pin the event to the PMU
|
D - pin the event to the PMU
|
||||||
W - group is weak and will fallback to non-group if not schedulable,
|
W - group is weak and will fallback to non-group if not schedulable,
|
||||||
only supported in 'perf stat' for now.
|
|
||||||
|
|
||||||
The 'p' modifier can be used for specifying how precise the instruction
|
The 'p' modifier can be used for specifying how precise the instruction
|
||||||
address should be. The 'p' modifier can be specified multiple times:
|
address should be. The 'p' modifier can be specified multiple times:
|
||||||
|
@ -387,7 +387,7 @@ SHELL = $(SHELL_PATH)
|
|||||||
|
|
||||||
linux_uapi_dir := $(srctree)/tools/include/uapi/linux
|
linux_uapi_dir := $(srctree)/tools/include/uapi/linux
|
||||||
asm_generic_uapi_dir := $(srctree)/tools/include/uapi/asm-generic
|
asm_generic_uapi_dir := $(srctree)/tools/include/uapi/asm-generic
|
||||||
arch_asm_uapi_dir := $(srctree)/tools/arch/$(ARCH)/include/uapi/asm/
|
arch_asm_uapi_dir := $(srctree)/tools/arch/$(SRCARCH)/include/uapi/asm/
|
||||||
|
|
||||||
beauty_outdir := $(OUTPUT)trace/beauty/generated
|
beauty_outdir := $(OUTPUT)trace/beauty/generated
|
||||||
beauty_ioctl_outdir := $(beauty_outdir)/ioctl
|
beauty_ioctl_outdir := $(beauty_outdir)/ioctl
|
||||||
|
@ -391,7 +391,12 @@ try_again:
|
|||||||
ui__warning("%s\n", msg);
|
ui__warning("%s\n", msg);
|
||||||
goto try_again;
|
goto try_again;
|
||||||
}
|
}
|
||||||
|
if ((errno == EINVAL || errno == EBADF) &&
|
||||||
|
pos->leader != pos &&
|
||||||
|
pos->weak_group) {
|
||||||
|
pos = perf_evlist__reset_weak_group(evlist, pos);
|
||||||
|
goto try_again;
|
||||||
|
}
|
||||||
rc = -errno;
|
rc = -errno;
|
||||||
perf_evsel__open_strerror(pos, &opts->target,
|
perf_evsel__open_strerror(pos, &opts->target,
|
||||||
errno, msg, sizeof(msg));
|
errno, msg, sizeof(msg));
|
||||||
|
@ -383,32 +383,6 @@ static bool perf_evsel__should_store_id(struct perf_evsel *counter)
|
|||||||
return STAT_RECORD || counter->attr.read_format & PERF_FORMAT_ID;
|
return STAT_RECORD || counter->attr.read_format & PERF_FORMAT_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct perf_evsel *perf_evsel__reset_weak_group(struct perf_evsel *evsel)
|
|
||||||
{
|
|
||||||
struct perf_evsel *c2, *leader;
|
|
||||||
bool is_open = true;
|
|
||||||
|
|
||||||
leader = evsel->leader;
|
|
||||||
pr_debug("Weak group for %s/%d failed\n",
|
|
||||||
leader->name, leader->nr_members);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* for_each_group_member doesn't work here because it doesn't
|
|
||||||
* include the first entry.
|
|
||||||
*/
|
|
||||||
evlist__for_each_entry(evsel_list, c2) {
|
|
||||||
if (c2 == evsel)
|
|
||||||
is_open = false;
|
|
||||||
if (c2->leader == leader) {
|
|
||||||
if (is_open)
|
|
||||||
perf_evsel__close(c2);
|
|
||||||
c2->leader = c2;
|
|
||||||
c2->nr_members = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return leader;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_target_alive(struct target *_target,
|
static bool is_target_alive(struct target *_target,
|
||||||
struct thread_map *threads)
|
struct thread_map *threads)
|
||||||
{
|
{
|
||||||
@ -477,7 +451,7 @@ try_again:
|
|||||||
if ((errno == EINVAL || errno == EBADF) &&
|
if ((errno == EINVAL || errno == EBADF) &&
|
||||||
counter->leader != counter &&
|
counter->leader != counter &&
|
||||||
counter->weak_group) {
|
counter->weak_group) {
|
||||||
counter = perf_evsel__reset_weak_group(counter);
|
counter = perf_evlist__reset_weak_group(evsel_list, counter);
|
||||||
goto try_again;
|
goto try_again;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1429,6 +1429,9 @@ int cmd_top(int argc, const char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->branch_stack && callchain_param.enabled)
|
||||||
|
symbol_conf.show_branchflag_count = true;
|
||||||
|
|
||||||
sort__mode = SORT_MODE__TOP;
|
sort__mode = SORT_MODE__TOP;
|
||||||
/* display thread wants entries to be collapsed in a different tree */
|
/* display thread wants entries to be collapsed in a different tree */
|
||||||
perf_hpp_list.need_collapse = 1;
|
perf_hpp_list.need_collapse = 1;
|
||||||
|
@ -108,6 +108,7 @@ struct trace {
|
|||||||
} stats;
|
} stats;
|
||||||
unsigned int max_stack;
|
unsigned int max_stack;
|
||||||
unsigned int min_stack;
|
unsigned int min_stack;
|
||||||
|
bool raw_augmented_syscalls;
|
||||||
bool not_ev_qualifier;
|
bool not_ev_qualifier;
|
||||||
bool live;
|
bool live;
|
||||||
bool full_time;
|
bool full_time;
|
||||||
@ -1724,13 +1725,28 @@ static int trace__fprintf_sample(struct trace *trace, struct perf_evsel *evsel,
|
|||||||
return printed;
|
return printed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *syscall__augmented_args(struct syscall *sc, struct perf_sample *sample, int *augmented_args_size)
|
static void *syscall__augmented_args(struct syscall *sc, struct perf_sample *sample, int *augmented_args_size, bool raw_augmented)
|
||||||
{
|
{
|
||||||
void *augmented_args = NULL;
|
void *augmented_args = NULL;
|
||||||
|
/*
|
||||||
|
* For now with BPF raw_augmented we hook into raw_syscalls:sys_enter
|
||||||
|
* and there we get all 6 syscall args plus the tracepoint common
|
||||||
|
* fields (sizeof(long)) and the syscall_nr (another long). So we check
|
||||||
|
* if that is the case and if so don't look after the sc->args_size,
|
||||||
|
* but always after the full raw_syscalls:sys_enter payload, which is
|
||||||
|
* fixed.
|
||||||
|
*
|
||||||
|
* We'll revisit this later to pass s->args_size to the BPF augmenter
|
||||||
|
* (now tools/perf/examples/bpf/augmented_raw_syscalls.c, so that it
|
||||||
|
* copies only what we need for each syscall, like what happens when we
|
||||||
|
* use syscalls:sys_enter_NAME, so that we reduce the kernel/userspace
|
||||||
|
* traffic to just what is needed for each syscall.
|
||||||
|
*/
|
||||||
|
int args_size = raw_augmented ? (8 * (int)sizeof(long)) : sc->args_size;
|
||||||
|
|
||||||
*augmented_args_size = sample->raw_size - sc->args_size;
|
*augmented_args_size = sample->raw_size - args_size;
|
||||||
if (*augmented_args_size > 0)
|
if (*augmented_args_size > 0)
|
||||||
augmented_args = sample->raw_data + sc->args_size;
|
augmented_args = sample->raw_data + args_size;
|
||||||
|
|
||||||
return augmented_args;
|
return augmented_args;
|
||||||
}
|
}
|
||||||
@ -1780,7 +1796,7 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel,
|
|||||||
* here and avoid using augmented syscalls when the evsel is the raw_syscalls one.
|
* here and avoid using augmented syscalls when the evsel is the raw_syscalls one.
|
||||||
*/
|
*/
|
||||||
if (evsel != trace->syscalls.events.sys_enter)
|
if (evsel != trace->syscalls.events.sys_enter)
|
||||||
augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size);
|
augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size, trace->raw_augmented_syscalls);
|
||||||
ttrace->entry_time = sample->time;
|
ttrace->entry_time = sample->time;
|
||||||
msg = ttrace->entry_str;
|
msg = ttrace->entry_str;
|
||||||
printed += scnprintf(msg + printed, trace__entry_str_size - printed, "%s(", sc->name);
|
printed += scnprintf(msg + printed, trace__entry_str_size - printed, "%s(", sc->name);
|
||||||
@ -1833,7 +1849,7 @@ static int trace__fprintf_sys_enter(struct trace *trace, struct perf_evsel *evse
|
|||||||
goto out_put;
|
goto out_put;
|
||||||
|
|
||||||
args = perf_evsel__sc_tp_ptr(evsel, args, sample);
|
args = perf_evsel__sc_tp_ptr(evsel, args, sample);
|
||||||
augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size);
|
augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size, trace->raw_augmented_syscalls);
|
||||||
syscall__scnprintf_args(sc, msg, sizeof(msg), args, augmented_args, augmented_args_size, trace, thread);
|
syscall__scnprintf_args(sc, msg, sizeof(msg), args, augmented_args, augmented_args_size, trace, thread);
|
||||||
fprintf(trace->output, "%s", msg);
|
fprintf(trace->output, "%s", msg);
|
||||||
err = 0;
|
err = 0;
|
||||||
@ -3501,7 +3517,15 @@ int cmd_trace(int argc, const char **argv)
|
|||||||
evsel->handler = trace__sys_enter;
|
evsel->handler = trace__sys_enter;
|
||||||
|
|
||||||
evlist__for_each_entry(trace.evlist, evsel) {
|
evlist__for_each_entry(trace.evlist, evsel) {
|
||||||
|
bool raw_syscalls_sys_exit = strcmp(perf_evsel__name(evsel), "raw_syscalls:sys_exit") == 0;
|
||||||
|
|
||||||
|
if (raw_syscalls_sys_exit) {
|
||||||
|
trace.raw_augmented_syscalls = true;
|
||||||
|
goto init_augmented_syscall_tp;
|
||||||
|
}
|
||||||
|
|
||||||
if (strstarts(perf_evsel__name(evsel), "syscalls:sys_exit_")) {
|
if (strstarts(perf_evsel__name(evsel), "syscalls:sys_exit_")) {
|
||||||
|
init_augmented_syscall_tp:
|
||||||
perf_evsel__init_augmented_syscall_tp(evsel);
|
perf_evsel__init_augmented_syscall_tp(evsel);
|
||||||
perf_evsel__init_augmented_syscall_tp_ret(evsel);
|
perf_evsel__init_augmented_syscall_tp_ret(evsel);
|
||||||
evsel->handler = trace__sys_exit;
|
evsel->handler = trace__sys_exit;
|
||||||
|
131
tools/perf/examples/bpf/augmented_raw_syscalls.c
Normal file
131
tools/perf/examples/bpf/augmented_raw_syscalls.c
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Augment the raw_syscalls tracepoints with the contents of the pointer arguments.
|
||||||
|
*
|
||||||
|
* Test it with:
|
||||||
|
*
|
||||||
|
* perf trace -e tools/perf/examples/bpf/augmented_raw_syscalls.c cat /etc/passwd > /dev/null
|
||||||
|
*
|
||||||
|
* This exactly matches what is marshalled into the raw_syscall:sys_enter
|
||||||
|
* payload expected by the 'perf trace' beautifiers.
|
||||||
|
*
|
||||||
|
* For now it just uses the existing tracepoint augmentation code in 'perf
|
||||||
|
* trace', in the next csets we'll hook up these with the sys_enter/sys_exit
|
||||||
|
* code that will combine entry/exit in a strace like way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <linux/socket.h>
|
||||||
|
|
||||||
|
/* bpf-output associated map */
|
||||||
|
struct bpf_map SEC("maps") __augmented_syscalls__ = {
|
||||||
|
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
|
||||||
|
.key_size = sizeof(int),
|
||||||
|
.value_size = sizeof(u32),
|
||||||
|
.max_entries = __NR_CPUS__,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct syscall_enter_args {
|
||||||
|
unsigned long long common_tp_fields;
|
||||||
|
long syscall_nr;
|
||||||
|
unsigned long args[6];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct syscall_exit_args {
|
||||||
|
unsigned long long common_tp_fields;
|
||||||
|
long syscall_nr;
|
||||||
|
long ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct augmented_filename {
|
||||||
|
unsigned int size;
|
||||||
|
int reserved;
|
||||||
|
char value[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SYS_OPEN 2
|
||||||
|
#define SYS_OPENAT 257
|
||||||
|
|
||||||
|
SEC("raw_syscalls:sys_enter")
|
||||||
|
int sys_enter(struct syscall_enter_args *args)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
struct syscall_enter_args args;
|
||||||
|
struct augmented_filename filename;
|
||||||
|
} augmented_args;
|
||||||
|
unsigned int len = sizeof(augmented_args);
|
||||||
|
const void *filename_arg = NULL;
|
||||||
|
|
||||||
|
probe_read(&augmented_args.args, sizeof(augmented_args.args), args);
|
||||||
|
/*
|
||||||
|
* Yonghong and Edward Cree sayz:
|
||||||
|
*
|
||||||
|
* https://www.spinics.net/lists/netdev/msg531645.html
|
||||||
|
*
|
||||||
|
* >> R0=inv(id=0) R1=inv2 R6=ctx(id=0,off=0,imm=0) R7=inv64 R10=fp0,call_-1
|
||||||
|
* >> 10: (bf) r1 = r6
|
||||||
|
* >> 11: (07) r1 += 16
|
||||||
|
* >> 12: (05) goto pc+2
|
||||||
|
* >> 15: (79) r3 = *(u64 *)(r1 +0)
|
||||||
|
* >> dereference of modified ctx ptr R1 off=16 disallowed
|
||||||
|
* > Aha, we at least got a different error message this time.
|
||||||
|
* > And indeed llvm has done that optimisation, rather than the more obvious
|
||||||
|
* > 11: r3 = *(u64 *)(r1 +16)
|
||||||
|
* > because it wants to have lots of reads share a single insn. You may be able
|
||||||
|
* > to defeat that optimisation by adding compiler barriers, idk. Maybe someone
|
||||||
|
* > with llvm knowledge can figure out how to stop it (ideally, llvm would know
|
||||||
|
* > when it's generating for bpf backend and not do that). -O0? ¯\_(ツ)_/¯
|
||||||
|
*
|
||||||
|
* The optimization mostly likes below:
|
||||||
|
*
|
||||||
|
* br1:
|
||||||
|
* ...
|
||||||
|
* r1 += 16
|
||||||
|
* goto merge
|
||||||
|
* br2:
|
||||||
|
* ...
|
||||||
|
* r1 += 20
|
||||||
|
* goto merge
|
||||||
|
* merge:
|
||||||
|
* *(u64 *)(r1 + 0)
|
||||||
|
*
|
||||||
|
* The compiler tries to merge common loads. There is no easy way to
|
||||||
|
* stop this compiler optimization without turning off a lot of other
|
||||||
|
* optimizations. The easiest way is to add barriers:
|
||||||
|
*
|
||||||
|
* __asm__ __volatile__("": : :"memory")
|
||||||
|
*
|
||||||
|
* after the ctx memory access to prevent their down stream merging.
|
||||||
|
*/
|
||||||
|
switch (augmented_args.args.syscall_nr) {
|
||||||
|
case SYS_OPEN: filename_arg = (const void *)args->args[0];
|
||||||
|
__asm__ __volatile__("": : :"memory");
|
||||||
|
break;
|
||||||
|
case SYS_OPENAT: filename_arg = (const void *)args->args[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename_arg != NULL) {
|
||||||
|
augmented_args.filename.reserved = 0;
|
||||||
|
augmented_args.filename.size = probe_read_str(&augmented_args.filename.value,
|
||||||
|
sizeof(augmented_args.filename.value),
|
||||||
|
filename_arg);
|
||||||
|
if (augmented_args.filename.size < sizeof(augmented_args.filename.value)) {
|
||||||
|
len -= sizeof(augmented_args.filename.value) - augmented_args.filename.size;
|
||||||
|
len &= sizeof(augmented_args.filename.value) - 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
len = sizeof(augmented_args.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
perf_event_output(args, &__augmented_syscalls__, BPF_F_CURRENT_CPU, &augmented_args, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("raw_syscalls:sys_exit")
|
||||||
|
int sys_exit(struct syscall_exit_args *args)
|
||||||
|
{
|
||||||
|
return 1; /* 0 as soon as we start copying data returned by the kernel, e.g. 'read' */
|
||||||
|
}
|
||||||
|
|
||||||
|
license(GPL);
|
@ -125,7 +125,7 @@ perf_get_timestamp(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
debug_cache_init(void)
|
create_jit_cache_dir(void)
|
||||||
{
|
{
|
||||||
char str[32];
|
char str[32];
|
||||||
char *base, *p;
|
char *base, *p;
|
||||||
@ -144,8 +144,13 @@ debug_cache_init(void)
|
|||||||
|
|
||||||
strftime(str, sizeof(str), JIT_LANG"-jit-%Y%m%d", &tm);
|
strftime(str, sizeof(str), JIT_LANG"-jit-%Y%m%d", &tm);
|
||||||
|
|
||||||
snprintf(jit_path, PATH_MAX - 1, "%s/.debug/", base);
|
ret = snprintf(jit_path, PATH_MAX, "%s/.debug/", base);
|
||||||
|
if (ret >= PATH_MAX) {
|
||||||
|
warnx("jvmti: cannot generate jit cache dir because %s/.debug/"
|
||||||
|
" is too long, please check the cwd, JITDUMPDIR, and"
|
||||||
|
" HOME variables", base);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
ret = mkdir(jit_path, 0755);
|
ret = mkdir(jit_path, 0755);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
if (errno != EEXIST) {
|
if (errno != EEXIST) {
|
||||||
@ -154,20 +159,32 @@ debug_cache_init(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit", base);
|
ret = snprintf(jit_path, PATH_MAX, "%s/.debug/jit", base);
|
||||||
|
if (ret >= PATH_MAX) {
|
||||||
|
warnx("jvmti: cannot generate jit cache dir because"
|
||||||
|
" %s/.debug/jit is too long, please check the cwd,"
|
||||||
|
" JITDUMPDIR, and HOME variables", base);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
ret = mkdir(jit_path, 0755);
|
ret = mkdir(jit_path, 0755);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
if (errno != EEXIST) {
|
if (errno != EEXIST) {
|
||||||
warn("cannot create jit cache dir %s", jit_path);
|
warn("jvmti: cannot create jit cache dir %s", jit_path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit/%s.XXXXXXXX", base, str);
|
ret = snprintf(jit_path, PATH_MAX, "%s/.debug/jit/%s.XXXXXXXX", base, str);
|
||||||
|
if (ret >= PATH_MAX) {
|
||||||
|
warnx("jvmti: cannot generate jit cache dir because"
|
||||||
|
" %s/.debug/jit/%s.XXXXXXXX is too long, please check"
|
||||||
|
" the cwd, JITDUMPDIR, and HOME variables",
|
||||||
|
base, str);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
p = mkdtemp(jit_path);
|
p = mkdtemp(jit_path);
|
||||||
if (p != jit_path) {
|
if (p != jit_path) {
|
||||||
warn("cannot create jit cache dir %s", jit_path);
|
warn("jvmti: cannot create jit cache dir %s", jit_path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +245,7 @@ void *jvmti_open(void)
|
|||||||
{
|
{
|
||||||
char dump_path[PATH_MAX];
|
char dump_path[PATH_MAX];
|
||||||
struct jitheader header;
|
struct jitheader header;
|
||||||
int fd;
|
int fd, ret;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
init_arch_timestamp();
|
init_arch_timestamp();
|
||||||
@ -245,12 +262,22 @@ void *jvmti_open(void)
|
|||||||
|
|
||||||
memset(&header, 0, sizeof(header));
|
memset(&header, 0, sizeof(header));
|
||||||
|
|
||||||
debug_cache_init();
|
/*
|
||||||
|
* jitdump file dir
|
||||||
|
*/
|
||||||
|
if (create_jit_cache_dir() < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* jitdump file name
|
* jitdump file name
|
||||||
*/
|
*/
|
||||||
scnprintf(dump_path, PATH_MAX, "%s/jit-%i.dump", jit_path, getpid());
|
ret = snprintf(dump_path, PATH_MAX, "%s/jit-%i.dump", jit_path, getpid());
|
||||||
|
if (ret >= PATH_MAX) {
|
||||||
|
warnx("jvmti: cannot generate jitdump file full path because"
|
||||||
|
" %s/jit-%i.dump is too long, please check the cwd,"
|
||||||
|
" JITDUMPDIR, and HOME variables", jit_path, getpid());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
fd = open(dump_path, O_CREAT|O_TRUNC|O_RDWR, 0666);
|
fd = open(dump_path, O_CREAT|O_TRUNC|O_RDWR, 0666);
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
|
@ -119,6 +119,14 @@ def dsoname(name):
|
|||||||
return "[kernel]"
|
return "[kernel]"
|
||||||
return name
|
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
|
# Percent to one decimal place
|
||||||
|
|
||||||
def PercentToOneDP(n, d):
|
def PercentToOneDP(n, d):
|
||||||
@ -1464,6 +1472,317 @@ class BranchWindow(QMdiSubWindow):
|
|||||||
else:
|
else:
|
||||||
self.find_bar.NotFound()
|
self.find_bar.NotFound()
|
||||||
|
|
||||||
|
# Dialog data item converted and validated using a SQL table
|
||||||
|
|
||||||
|
class SQLTableDialogDataItem():
|
||||||
|
|
||||||
|
def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
|
||||||
|
self.glb = glb
|
||||||
|
self.label = label
|
||||||
|
self.placeholder_text = placeholder_text
|
||||||
|
self.table_name = table_name
|
||||||
|
self.match_column = match_column
|
||||||
|
self.column_name1 = column_name1
|
||||||
|
self.column_name2 = column_name2
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.value = ""
|
||||||
|
|
||||||
|
self.widget = QLineEdit()
|
||||||
|
self.widget.editingFinished.connect(self.Validate)
|
||||||
|
self.widget.textChanged.connect(self.Invalidate)
|
||||||
|
self.red = False
|
||||||
|
self.error = ""
|
||||||
|
self.validated = True
|
||||||
|
|
||||||
|
self.last_id = 0
|
||||||
|
self.first_time = 0
|
||||||
|
self.last_time = 2 ** 64
|
||||||
|
if self.table_name == "<timeranges>":
|
||||||
|
query = QSqlQuery(self.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.last_time = int(query.value(1))
|
||||||
|
QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
|
||||||
|
if query.next():
|
||||||
|
self.first_time = int(query.value(0))
|
||||||
|
if placeholder_text:
|
||||||
|
placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
|
||||||
|
|
||||||
|
if placeholder_text:
|
||||||
|
self.widget.setPlaceholderText(placeholder_text)
|
||||||
|
|
||||||
|
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 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):
|
||||||
|
print "val ", 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):
|
||||||
|
print "vrange ", 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])
|
||||||
|
print "vrange2 ", vrange
|
||||||
|
if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
|
||||||
|
return False
|
||||||
|
print "ok1"
|
||||||
|
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
|
||||||
|
print "ok2"
|
||||||
|
vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
|
||||||
|
vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
|
||||||
|
print "vrange3 ", vrange
|
||||||
|
return True
|
||||||
|
|
||||||
|
def AddTimeRange(self, value, ranges):
|
||||||
|
print "value ", value
|
||||||
|
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 InvalidValue(self, value):
|
||||||
|
self.value = ""
|
||||||
|
palette = QPalette()
|
||||||
|
palette.setColor(QPalette.Text,Qt.red)
|
||||||
|
self.widget.setPalette(palette)
|
||||||
|
self.red = True
|
||||||
|
self.error = self.label + " invalid value '" + value + "'"
|
||||||
|
self.parent.ShowMessage(self.error)
|
||||||
|
|
||||||
|
def IsNumber(self, value):
|
||||||
|
try:
|
||||||
|
x = int(value)
|
||||||
|
except:
|
||||||
|
x = 0
|
||||||
|
return str(x) == value
|
||||||
|
|
||||||
|
def Invalidate(self):
|
||||||
|
self.validated = False
|
||||||
|
|
||||||
|
def Validate(self):
|
||||||
|
input_string = self.widget.text()
|
||||||
|
self.validated = True
|
||||||
|
if self.red:
|
||||||
|
palette = QPalette()
|
||||||
|
self.widget.setPalette(palette)
|
||||||
|
self.red = False
|
||||||
|
if not len(input_string.strip()):
|
||||||
|
self.error = ""
|
||||||
|
self.value = ""
|
||||||
|
return
|
||||||
|
if self.table_name == "<timeranges>":
|
||||||
|
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_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
|
||||||
|
self.value = " OR ".join(ranges)
|
||||||
|
elif self.table_name == "<ranges>":
|
||||||
|
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_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
|
||||||
|
if len(singles):
|
||||||
|
ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")")
|
||||||
|
self.value = " OR ".join(ranges)
|
||||||
|
elif self.table_name:
|
||||||
|
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) + ") )"
|
||||||
|
else:
|
||||||
|
self.value = input_string.strip()
|
||||||
|
self.error = ""
|
||||||
|
self.parent.ClearMessage()
|
||||||
|
|
||||||
|
def IsValid(self):
|
||||||
|
if not self.validated:
|
||||||
|
self.Validate()
|
||||||
|
if len(self.error):
|
||||||
|
self.parent.ShowMessage(self.error)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Selected branch report creation dialog
|
||||||
|
|
||||||
|
class SelectedBranchDialog(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, glb, parent=None):
|
||||||
|
super(SelectedBranchDialog, self).__init__(parent)
|
||||||
|
|
||||||
|
self.glb = glb
|
||||||
|
|
||||||
|
self.name = ""
|
||||||
|
self.where_clause = ""
|
||||||
|
|
||||||
|
self.setWindowTitle("Selected Branches")
|
||||||
|
self.setMinimumWidth(600)
|
||||||
|
|
||||||
|
items = (
|
||||||
|
("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""),
|
||||||
|
("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""),
|
||||||
|
("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""),
|
||||||
|
("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""),
|
||||||
|
("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""),
|
||||||
|
("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""),
|
||||||
|
("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"),
|
||||||
|
("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"),
|
||||||
|
("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""),
|
||||||
|
)
|
||||||
|
self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items]
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.name = self.data_items[0].value
|
||||||
|
if not self.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 len(d.value):
|
||||||
|
if len(self.where_clause):
|
||||||
|
self.where_clause += " AND "
|
||||||
|
self.where_clause += d.value
|
||||||
|
if len(self.where_clause):
|
||||||
|
self.where_clause = " AND ( " + self.where_clause + " ) "
|
||||||
|
else:
|
||||||
|
self.ShowMessage("No selection")
|
||||||
|
return
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def ShowMessage(self, msg):
|
||||||
|
self.status.setText("<font color=#FF0000>" + msg)
|
||||||
|
|
||||||
|
def ClearMessage(self):
|
||||||
|
self.status.setText("")
|
||||||
|
|
||||||
# Event list
|
# Event list
|
||||||
|
|
||||||
def GetEventList(db):
|
def GetEventList(db):
|
||||||
@ -1656,7 +1975,7 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
|
|||||||
def FindDone(self, row):
|
def FindDone(self, row):
|
||||||
self.find_bar.Idle()
|
self.find_bar.Idle()
|
||||||
if row >= 0:
|
if row >= 0:
|
||||||
self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
|
self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
|
||||||
else:
|
else:
|
||||||
self.find_bar.NotFound()
|
self.find_bar.NotFound()
|
||||||
|
|
||||||
@ -1765,6 +2084,149 @@ class WindowMenu():
|
|||||||
def setActiveSubWindow(self, nr):
|
def setActiveSubWindow(self, nr):
|
||||||
self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
|
self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
|
||||||
|
|
||||||
|
# Help text
|
||||||
|
|
||||||
|
glb_help_text = """
|
||||||
|
<h1>Contents</h1>
|
||||||
|
<style>
|
||||||
|
p.c1 {
|
||||||
|
text-indent: 40px;
|
||||||
|
}
|
||||||
|
p.c2 {
|
||||||
|
text-indent: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<p class=c1><a href=#reports>1. Reports</a></p>
|
||||||
|
<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
|
||||||
|
<p class=c2><a href=#allbranches>1.2 All branches</a></p>
|
||||||
|
<p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
|
||||||
|
<p class=c1><a href=#tables>2. Tables</a></p>
|
||||||
|
<h1 id=reports>1. Reports</h1>
|
||||||
|
<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
|
||||||
|
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:
|
||||||
|
<pre>
|
||||||
|
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
|
||||||
|
</pre>
|
||||||
|
<h3>Points to note:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>The top level is a command name (comm)</li>
|
||||||
|
<li>The next level is a thread (pid:tid)</li>
|
||||||
|
<li>Subsequent levels are functions</li>
|
||||||
|
<li>'Count' is the number of calls</li>
|
||||||
|
<li>'Time' is the elapsed time until the function returns</li>
|
||||||
|
<li>Percentages are relative to the level above</li>
|
||||||
|
<li>'Branch Count' is the total number of branches for that function and all functions that it calls
|
||||||
|
</ul>
|
||||||
|
<h3>Find</h3>
|
||||||
|
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.
|
||||||
|
<h2 id=allbranches>1.2 All branches</h2>
|
||||||
|
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.
|
||||||
|
<h3>Disassembly</h3>
|
||||||
|
Open a branch to display disassembly. This only works if:
|
||||||
|
<ol>
|
||||||
|
<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
|
||||||
|
<li>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.</li>
|
||||||
|
</ol>
|
||||||
|
<h4 id=xed>Intel XED Setup</h4>
|
||||||
|
To use Intel XED, libxed.so must be present. To build and install libxed.so:
|
||||||
|
<pre>
|
||||||
|
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
|
||||||
|
</pre>
|
||||||
|
<h3>Find</h3>
|
||||||
|
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.
|
||||||
|
<h2 id=selectedbranches>1.3 Selected branches</h2>
|
||||||
|
This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
|
||||||
|
by various selection criteria. A dialog box displays available criteria which are AND'ed together.
|
||||||
|
<h3>1.3.1 Time ranges</h3>
|
||||||
|
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:
|
||||||
|
<pre>
|
||||||
|
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
|
||||||
|
</pre>
|
||||||
|
N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
|
||||||
|
<h1 id=tables>2. Tables</h1>
|
||||||
|
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.
|
||||||
|
<p>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.
|
||||||
|
<h3>Find</h3>
|
||||||
|
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.
|
||||||
|
<p>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)
|
||||||
|
|
||||||
# Font resize
|
# Font resize
|
||||||
|
|
||||||
def ResizeFont(widget, diff):
|
def ResizeFont(widget, diff):
|
||||||
@ -1851,6 +2313,9 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self.window_menu = WindowMenu(self.mdi_area, 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))
|
||||||
|
|
||||||
def Find(self):
|
def Find(self):
|
||||||
win = self.mdi_area.activeSubWindow()
|
win = self.mdi_area.activeSubWindow()
|
||||||
if win:
|
if win:
|
||||||
@ -1888,6 +2353,8 @@ class MainWindow(QMainWindow):
|
|||||||
if event == "branches":
|
if event == "branches":
|
||||||
label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
|
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 x=dbid: self.NewBranchView(x), self))
|
reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda 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 x=dbid: self.NewSelectedBranchView(x), self))
|
||||||
|
|
||||||
def TableMenu(self, tables, menu):
|
def TableMenu(self, tables, menu):
|
||||||
table_menu = menu.addMenu("&Tables")
|
table_menu = menu.addMenu("&Tables")
|
||||||
@ -1900,9 +2367,18 @@ class MainWindow(QMainWindow):
|
|||||||
def NewBranchView(self, event_id):
|
def NewBranchView(self, event_id):
|
||||||
BranchWindow(self.glb, event_id, "", "", self)
|
BranchWindow(self.glb, event_id, "", "", self)
|
||||||
|
|
||||||
|
def NewSelectedBranchView(self, event_id):
|
||||||
|
dialog = SelectedBranchDialog(self.glb, self)
|
||||||
|
ret = dialog.exec_()
|
||||||
|
if ret:
|
||||||
|
BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self)
|
||||||
|
|
||||||
def NewTableView(self, table_name):
|
def NewTableView(self, table_name):
|
||||||
TableWindow(self.glb, table_name, self)
|
TableWindow(self.glb, table_name, self)
|
||||||
|
|
||||||
|
def Help(self):
|
||||||
|
HelpWindow(self.glb, self)
|
||||||
|
|
||||||
# XED Disassembler
|
# XED Disassembler
|
||||||
|
|
||||||
class xed_state_t(Structure):
|
class xed_state_t(Structure):
|
||||||
@ -1929,7 +2405,12 @@ class XEDInstruction():
|
|||||||
class LibXED():
|
class LibXED():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.libxed = CDLL("libxed.so")
|
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 = self.libxed.xed_tables_init
|
||||||
self.xed_tables_init.restype = None
|
self.xed_tables_init.restype = None
|
||||||
@ -2097,10 +2578,16 @@ class DBRef():
|
|||||||
|
|
||||||
def Main():
|
def Main():
|
||||||
if (len(sys.argv) < 2):
|
if (len(sys.argv) < 2):
|
||||||
print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
|
print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
|
||||||
raise Exception("Too few arguments")
|
raise Exception("Too few arguments")
|
||||||
|
|
||||||
dbname = sys.argv[1]
|
dbname = sys.argv[1]
|
||||||
|
if dbname == "--help-only":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
mainwindow = HelpOnlyWindow()
|
||||||
|
mainwindow.show()
|
||||||
|
err = app.exec_()
|
||||||
|
sys.exit(err)
|
||||||
|
|
||||||
is_sqlite3 = False
|
is_sqlite3 = False
|
||||||
try:
|
try:
|
||||||
|
@ -37,4 +37,3 @@ sample_freq=0
|
|||||||
sample_period=0
|
sample_period=0
|
||||||
freq=0
|
freq=0
|
||||||
write_backward=0
|
write_backward=0
|
||||||
sample_id_all=0
|
|
||||||
|
@ -1810,3 +1810,30 @@ void perf_evlist__force_leader(struct perf_evlist *evlist)
|
|||||||
leader->forced_leader = true;
|
leader->forced_leader = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct perf_evsel *perf_evlist__reset_weak_group(struct perf_evlist *evsel_list,
|
||||||
|
struct perf_evsel *evsel)
|
||||||
|
{
|
||||||
|
struct perf_evsel *c2, *leader;
|
||||||
|
bool is_open = true;
|
||||||
|
|
||||||
|
leader = evsel->leader;
|
||||||
|
pr_debug("Weak group for %s/%d failed\n",
|
||||||
|
leader->name, leader->nr_members);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for_each_group_member doesn't work here because it doesn't
|
||||||
|
* include the first entry.
|
||||||
|
*/
|
||||||
|
evlist__for_each_entry(evsel_list, c2) {
|
||||||
|
if (c2 == evsel)
|
||||||
|
is_open = false;
|
||||||
|
if (c2->leader == leader) {
|
||||||
|
if (is_open)
|
||||||
|
perf_evsel__close(c2);
|
||||||
|
c2->leader = c2;
|
||||||
|
c2->nr_members = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return leader;
|
||||||
|
}
|
||||||
|
@ -312,4 +312,7 @@ bool perf_evlist__exclude_kernel(struct perf_evlist *evlist);
|
|||||||
|
|
||||||
void perf_evlist__force_leader(struct perf_evlist *evlist);
|
void perf_evlist__force_leader(struct perf_evlist *evlist);
|
||||||
|
|
||||||
|
struct perf_evsel *perf_evlist__reset_weak_group(struct perf_evlist *evlist,
|
||||||
|
struct perf_evsel *evsel);
|
||||||
|
|
||||||
#endif /* __PERF_EVLIST_H */
|
#endif /* __PERF_EVLIST_H */
|
||||||
|
@ -956,7 +956,6 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts,
|
|||||||
attr->sample_freq = 0;
|
attr->sample_freq = 0;
|
||||||
attr->sample_period = 0;
|
attr->sample_period = 0;
|
||||||
attr->write_backward = 0;
|
attr->write_backward = 0;
|
||||||
attr->sample_id_all = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->no_samples)
|
if (opts->no_samples)
|
||||||
|
@ -1474,6 +1474,8 @@ static void intel_pt_calc_mtc_timestamp(struct intel_pt_decoder *decoder)
|
|||||||
decoder->have_calc_cyc_to_tsc = false;
|
decoder->have_calc_cyc_to_tsc = false;
|
||||||
intel_pt_calc_cyc_to_tsc(decoder, true);
|
intel_pt_calc_cyc_to_tsc(decoder, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intel_pt_log_to("Setting timestamp", decoder->timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void intel_pt_calc_cbr(struct intel_pt_decoder *decoder)
|
static void intel_pt_calc_cbr(struct intel_pt_decoder *decoder)
|
||||||
@ -1514,6 +1516,8 @@ static void intel_pt_calc_cyc_timestamp(struct intel_pt_decoder *decoder)
|
|||||||
decoder->timestamp = timestamp;
|
decoder->timestamp = timestamp;
|
||||||
|
|
||||||
decoder->timestamp_insn_cnt = 0;
|
decoder->timestamp_insn_cnt = 0;
|
||||||
|
|
||||||
|
intel_pt_log_to("Setting timestamp", decoder->timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Walk PSB+ packets when already in sync. */
|
/* Walk PSB+ packets when already in sync. */
|
||||||
|
@ -31,6 +31,11 @@ static FILE *f;
|
|||||||
static char log_name[MAX_LOG_NAME];
|
static char log_name[MAX_LOG_NAME];
|
||||||
bool intel_pt_enable_logging;
|
bool intel_pt_enable_logging;
|
||||||
|
|
||||||
|
void *intel_pt_log_fp(void)
|
||||||
|
{
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
void intel_pt_log_enable(void)
|
void intel_pt_log_enable(void)
|
||||||
{
|
{
|
||||||
intel_pt_enable_logging = true;
|
intel_pt_enable_logging = true;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
struct intel_pt_pkt;
|
struct intel_pt_pkt;
|
||||||
|
|
||||||
|
void *intel_pt_log_fp(void);
|
||||||
void intel_pt_log_enable(void);
|
void intel_pt_log_enable(void);
|
||||||
void intel_pt_log_disable(void);
|
void intel_pt_log_disable(void);
|
||||||
void intel_pt_log_set_name(const char *name);
|
void intel_pt_log_set_name(const char *name);
|
||||||
|
@ -206,6 +206,16 @@ static void intel_pt_dump_event(struct intel_pt *pt, unsigned char *buf,
|
|||||||
intel_pt_dump(pt, buf, len);
|
intel_pt_dump(pt, buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void intel_pt_log_event(union perf_event *event)
|
||||||
|
{
|
||||||
|
FILE *f = intel_pt_log_fp();
|
||||||
|
|
||||||
|
if (!intel_pt_enable_logging || !f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
perf_event__fprintf(event, f);
|
||||||
|
}
|
||||||
|
|
||||||
static int intel_pt_do_fix_overlap(struct intel_pt *pt, struct auxtrace_buffer *a,
|
static int intel_pt_do_fix_overlap(struct intel_pt *pt, struct auxtrace_buffer *a,
|
||||||
struct auxtrace_buffer *b)
|
struct auxtrace_buffer *b)
|
||||||
{
|
{
|
||||||
@ -2010,9 +2020,9 @@ static int intel_pt_process_event(struct perf_session *session,
|
|||||||
event->header.type == PERF_RECORD_SWITCH_CPU_WIDE)
|
event->header.type == PERF_RECORD_SWITCH_CPU_WIDE)
|
||||||
err = intel_pt_context_switch(pt, event, sample);
|
err = intel_pt_context_switch(pt, event, sample);
|
||||||
|
|
||||||
intel_pt_log("event %s (%u): cpu %d time %"PRIu64" tsc %#"PRIx64"\n",
|
intel_pt_log("event %u: cpu %d time %"PRIu64" tsc %#"PRIx64" ",
|
||||||
perf_event__name(event->header.type), event->header.type,
|
event->header.type, sample->cpu, sample->time, timestamp);
|
||||||
sample->cpu, sample->time, timestamp);
|
intel_pt_log_event(event);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -773,7 +773,7 @@ static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu)
|
|||||||
|
|
||||||
if (!is_arm_pmu_core(name)) {
|
if (!is_arm_pmu_core(name)) {
|
||||||
pname = pe->pmu ? pe->pmu : "cpu";
|
pname = pe->pmu ? pe->pmu : "cpu";
|
||||||
if (strncmp(pname, name, strlen(pname)))
|
if (strcmp(pname, name))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user