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:
Linus Torvalds 2018-11-11 16:39:12 -06:00
commit 655c6b9777
19 changed files with 820 additions and 121 deletions

View File

@ -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 */

View File

@ -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:

View File

@ -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

View File

@ -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));

View File

@ -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;
} }

View File

@ -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;

View File

@ -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;

View 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);

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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;
}

View File

@ -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 */

View File

@ -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)

View File

@ -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. */

View File

@ -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;

View File

@ -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);

View File

@ -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;
} }

View File

@ -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;
} }