perf/core fixes and improvements:

kernel + tools/perf:
 
   Alexey Budankov:
 
   - Introduce CAP_PERFMON to kernel and user space.
 
 callchains:
 
   Adrian Hunter:
 
   - Allow using Intel PT to synthesize callchains for regular events.
 
   Kan Liang:
 
   - Stitch LBR records from multiple samples to get deeper backtraces,
     there are caveats, see the csets for details.
 
 perf script:
 
   Andreas Gerstmayr:
 
   - Add flamegraph.py script
 
 BPF:
 
   Jiri Olsa:
 
   - Synthesize bpf_trampoline/dispatcher ksymbol events.
 
 perf stat:
 
   Arnaldo Carvalho de Melo:
 
   - Honour --timeout for forked workloads.
 
   Stephane Eranian:
 
   - Force error in fallback on :k events, to avoid counting nothing when
     the user asks for kernel events but is not allowed to.
 
 perf bench:
 
   Ian Rogers:
 
   - Add event synthesis benchmark.
 
 tools api fs:
 
   Stephane Eranian:
 
  - Make xxx__mountpoint() more scalable
 
 libtraceevent:
 
   He Zhe:
 
   - Handle return value of asprintf.
 
 Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQR2GiIUctdOfX2qHhGyPKLppCJ+JwUCXp2LlQAKCRCyPKLppCJ+
 J95oAP0ZihVUhESv/gdeX0IDE5g6Rd2V6LNcRj+jb7gX9NlQkwD/UfS454WV1ftQ
 qTwrkKPzY/5Tm2cLuVE7r7fJ6naDHgU=
 =FHm4
 -----END PGP SIGNATURE-----

Merge tag 'perf-core-for-mingo-5.8-20200420' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/core

Pull perf/core fixes and improvements from Arnaldo Carvalho de Melo:

kernel + tools/perf:

  Alexey Budankov:

  - Introduce CAP_PERFMON to kernel and user space.

callchains:

  Adrian Hunter:

  - Allow using Intel PT to synthesize callchains for regular events.

  Kan Liang:

  - Stitch LBR records from multiple samples to get deeper backtraces,
    there are caveats, see the csets for details.

perf script:

  Andreas Gerstmayr:

  - Add flamegraph.py script

BPF:

  Jiri Olsa:

  - Synthesize bpf_trampoline/dispatcher ksymbol events.

perf stat:

  Arnaldo Carvalho de Melo:

  - Honour --timeout for forked workloads.

  Stephane Eranian:

  - Force error in fallback on :k events, to avoid counting nothing when
    the user asks for kernel events but is not allowed to.

perf bench:

  Ian Rogers:

  - Add event synthesis benchmark.

tools api fs:

  Stephane Eranian:

 - Make xxx__mountpoint() more scalable

libtraceevent:

  He Zhe:

  - Handle return value of asprintf.

Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
Ingo Molnar 2020-04-22 14:08:28 +02:00
commit 87cfeb1920
85 changed files with 1855 additions and 517 deletions

View File

@ -1,6 +1,6 @@
.. _perf_security:
Perf Events and tool security
Perf events and tool security
=============================
Overview
@ -42,11 +42,11 @@ categories:
Data that belong to the fourth category can potentially contain
sensitive process data. If PMUs in some monitoring modes capture values
of execution context registers or data from process memory then access
to such monitoring capabilities requires to be ordered and secured
properly. So, perf_events/Perf performance monitoring is the subject for
security access control management [5]_ .
to such monitoring modes requires to be ordered and secured properly.
So, perf_events performance monitoring and observability operations are
the subject for security access control management [5]_ .
perf_events/Perf access control
perf_events access control
-------------------------------
To perform security checks, the Linux implementation splits processes
@ -66,11 +66,25 @@ into distinct units, known as capabilities [6]_ , which can be
independently enabled and disabled on per-thread basis for processes and
files of unprivileged users.
Unprivileged processes with enabled CAP_SYS_ADMIN capability are treated
Unprivileged processes with enabled CAP_PERFMON capability are treated
as privileged processes with respect to perf_events performance
monitoring and bypass *scope* permissions checks in the kernel.
monitoring and observability operations, thus, bypass *scope* permissions
checks in the kernel. CAP_PERFMON implements the principle of least
privilege [13]_ (POSIX 1003.1e: 2.2.2.39) for performance monitoring and
observability operations in the kernel and provides a secure approach to
perfomance monitoring and observability in the system.
Unprivileged processes using perf_events system call API is also subject
For backward compatibility reasons the access to perf_events monitoring and
observability operations is also open for CAP_SYS_ADMIN privileged
processes but CAP_SYS_ADMIN usage for secure monitoring and observability
use cases is discouraged with respect to the CAP_PERFMON capability.
If system audit records [14]_ for a process using perf_events system call
API contain denial records of acquiring both CAP_PERFMON and CAP_SYS_ADMIN
capabilities then providing the process with CAP_PERFMON capability singly
is recommended as the preferred secure approach to resolve double access
denial logging related to usage of performance monitoring and observability.
Unprivileged processes using perf_events system call are also subject
for PTRACE_MODE_READ_REALCREDS ptrace access mode check [7]_ , whose
outcome determines whether monitoring is permitted. So unprivileged
processes provided with CAP_SYS_PTRACE capability are effectively
@ -82,14 +96,14 @@ performance analysis of monitored processes or a system. For example,
CAP_SYSLOG capability permits reading kernel space memory addresses from
/proc/kallsyms file.
perf_events/Perf privileged users
Privileged Perf users groups
---------------------------------
Mechanisms of capabilities, privileged capability-dumb files [6]_ and
file system ACLs [10]_ can be used to create a dedicated group of
perf_events/Perf privileged users who are permitted to execute
performance monitoring without scope limits. The following steps can be
taken to create such a group of privileged Perf users.
file system ACLs [10]_ can be used to create dedicated groups of
privileged Perf users who are permitted to execute performance monitoring
and observability without scope limits. The following steps can be
taken to create such groups of privileged Perf users.
1. Create perf_users group of privileged Perf users, assign perf_users
group to Perf tool executable and limit access to the executable for
@ -108,30 +122,51 @@ taken to create such a group of privileged Perf users.
-rwxr-x--- 2 root perf_users 11M Oct 19 15:12 perf
2. Assign the required capabilities to the Perf tool executable file and
enable members of perf_users group with performance monitoring
enable members of perf_users group with monitoring and observability
privileges [6]_ :
::
# setcap "cap_sys_admin,cap_sys_ptrace,cap_syslog=ep" perf
# setcap -v "cap_sys_admin,cap_sys_ptrace,cap_syslog=ep" perf
# setcap "cap_perfmon,cap_sys_ptrace,cap_syslog=ep" perf
# setcap -v "cap_perfmon,cap_sys_ptrace,cap_syslog=ep" perf
perf: OK
# getcap perf
perf = cap_sys_ptrace,cap_sys_admin,cap_syslog+ep
perf = cap_sys_ptrace,cap_syslog,cap_perfmon+ep
If the libcap installed doesn't yet support "cap_perfmon", use "38" instead,
i.e.:
::
# setcap "38,cap_ipc_lock,cap_sys_ptrace,cap_syslog=ep" perf
Note that you may need to have 'cap_ipc_lock' in the mix for tools such as
'perf top', alternatively use 'perf top -m N', to reduce the memory that
it uses for the perf ring buffer, see the memory allocation section below.
Using a libcap without support for CAP_PERFMON will make cap_get_flag(caps, 38,
CAP_EFFECTIVE, &val) fail, which will lead the default event to be 'cycles:u',
so as a workaround explicitly ask for the 'cycles' event, i.e.:
::
# perf top -e cycles
To get kernel and user samples with a perf binary with just CAP_PERFMON.
As a result, members of perf_users group are capable of conducting
performance monitoring by using functionality of the configured Perf
tool executable that, when executes, passes perf_events subsystem scope
checks.
performance monitoring and observability by using functionality of the
configured Perf tool executable that, when executes, passes perf_events
subsystem scope checks.
This specific access control management is only available to superuser
or root running processes with CAP_SETPCAP, CAP_SETFCAP [6]_
capabilities.
perf_events/Perf unprivileged users
Unprivileged users
-----------------------------------
perf_events/Perf *scope* and *access* control for unprivileged processes
perf_events *scope* and *access* control for unprivileged processes
is governed by perf_event_paranoid [2]_ setting:
-1:
@ -166,7 +201,7 @@ is governed by perf_event_paranoid [2]_ setting:
perf_event_mlock_kb locking limit is imposed but ignored for
unprivileged processes with CAP_IPC_LOCK capability.
perf_events/Perf resource control
Resource control
---------------------------------
Open file descriptors
@ -227,4 +262,5 @@ Bibliography
.. [10] `<http://man7.org/linux/man-pages/man5/acl.5.html>`_
.. [11] `<http://man7.org/linux/man-pages/man2/getrlimit.2.html>`_
.. [12] `<http://man7.org/linux/man-pages/man5/limits.conf.5.html>`_
.. [13] `<https://sites.google.com/site/fullycapable>`_
.. [14] `<http://man7.org/linux/man-pages/man8/auditd.8.html>`_

View File

@ -721,7 +721,13 @@ perf_event_paranoid
===================
Controls use of the performance events system by unprivileged
users (without CAP_SYS_ADMIN). The default value is 2.
users (without CAP_PERFMON). The default value is 2.
For backward compatibility reasons access to system performance
monitoring and observability remains open for CAP_SYS_ADMIN
privileged processes but CAP_SYS_ADMIN usage for secure system
performance monitoring and observability operations is discouraged
with respect to CAP_PERFMON use cases.
=== ==================================================================
-1 Allow use of (almost) all events by all users.
@ -730,13 +736,13 @@ users (without CAP_SYS_ADMIN). The default value is 2.
``CAP_IPC_LOCK``.
>=0 Disallow ftrace function tracepoint by users without
``CAP_SYS_ADMIN``.
``CAP_PERFMON``.
Disallow raw tracepoint access by users without ``CAP_SYS_ADMIN``.
Disallow raw tracepoint access by users without ``CAP_PERFMON``.
>=1 Disallow CPU event access by users without ``CAP_SYS_ADMIN``.
>=1 Disallow CPU event access by users without ``CAP_PERFMON``.
>=2 Disallow kernel profiling by users without ``CAP_SYS_ADMIN``.
>=2 Disallow kernel profiling by users without ``CAP_PERFMON``.
=== ==================================================================

View File

@ -300,7 +300,7 @@ static ssize_t perf_write(struct file *file, const char __user *buf,
else
return -EFAULT;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EACCES;
if (count != sizeof(uint32_t))

View File

@ -976,7 +976,7 @@ static int thread_imc_event_init(struct perf_event *event)
if (event->attr.type != event->pmu->type)
return -ENOENT;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EACCES;
/* Sampling not supported */
@ -1412,7 +1412,7 @@ static int trace_imc_event_init(struct perf_event *event)
if (event->attr.type != event->pmu->type)
return -ENOENT;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EACCES;
/* Return if this is a couting event */

View File

@ -3390,10 +3390,10 @@ i915_perf_open_ioctl_locked(struct i915_perf *perf,
/* Similar to perf's kernel.perf_paranoid_cpu sysctl option
* we check a dev.i915.perf_stream_paranoid sysctl option
* to determine if it's ok to access system wide OA counters
* without CAP_SYS_ADMIN privileges.
* without CAP_PERFMON or CAP_SYS_ADMIN privileges.
*/
if (privileged_op &&
i915_perf_stream_paranoid && !capable(CAP_SYS_ADMIN)) {
i915_perf_stream_paranoid && !perfmon_capable()) {
DRM_DEBUG("Insufficient privileges to open i915 perf stream\n");
ret = -EACCES;
goto err_ctx;
@ -3586,9 +3586,8 @@ static int read_properties_unlocked(struct i915_perf *perf,
} else
oa_freq_hz = 0;
if (oa_freq_hz > i915_oa_max_sample_rate &&
!capable(CAP_SYS_ADMIN)) {
DRM_DEBUG("OA exponent would exceed the max sampling frequency (sysctl dev.i915.oa_max_sample_rate) %uHz without root privileges\n",
if (oa_freq_hz > i915_oa_max_sample_rate && !perfmon_capable()) {
DRM_DEBUG("OA exponent would exceed the max sampling frequency (sysctl dev.i915.oa_max_sample_rate) %uHz without CAP_PERFMON or CAP_SYS_ADMIN privileges\n",
i915_oa_max_sample_rate);
return -EACCES;
}
@ -4009,7 +4008,7 @@ int i915_perf_add_config_ioctl(struct drm_device *dev, void *data,
return -EINVAL;
}
if (i915_perf_stream_paranoid && !capable(CAP_SYS_ADMIN)) {
if (i915_perf_stream_paranoid && !perfmon_capable()) {
DRM_DEBUG("Insufficient privileges to add i915 OA config\n");
return -EACCES;
}
@ -4156,7 +4155,7 @@ int i915_perf_remove_config_ioctl(struct drm_device *dev, void *data,
return -ENOTSUPP;
}
if (i915_perf_stream_paranoid && !capable(CAP_SYS_ADMIN)) {
if (i915_perf_stream_paranoid && !perfmon_capable()) {
DRM_DEBUG("Insufficient privileges to remove i915 OA config\n");
return -EACCES;
}

View File

@ -113,7 +113,7 @@ static int event_buffer_open(struct inode *inode, struct file *file)
{
int err = -EPERM;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EPERM;
if (test_and_set_bit_lock(0, &buffer_opened))

View File

@ -274,7 +274,7 @@ static u64 arm_spe_event_to_pmscr(struct perf_event *event)
if (!attr->exclude_kernel)
reg |= BIT(SYS_PMSCR_EL1_E1SPE_SHIFT);
if (IS_ENABLED(CONFIG_PID_IN_CONTEXTIDR) && capable(CAP_SYS_ADMIN))
if (IS_ENABLED(CONFIG_PID_IN_CONTEXTIDR) && perfmon_capable())
reg |= BIT(SYS_PMSCR_EL1_CX_SHIFT);
return reg;
@ -700,7 +700,7 @@ static int arm_spe_pmu_event_init(struct perf_event *event)
return -EOPNOTSUPP;
reg = arm_spe_event_to_pmscr(event);
if (!capable(CAP_SYS_ADMIN) &&
if (!perfmon_capable() &&
(reg & (BIT(SYS_PMSCR_EL1_PA_SHIFT) |
BIT(SYS_PMSCR_EL1_CX_SHIFT) |
BIT(SYS_PMSCR_EL1_PCT_SHIFT))))

View File

@ -251,6 +251,10 @@ extern bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct
extern bool capable_wrt_inode_uidgid(const struct inode *inode, int cap);
extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap);
extern bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns);
static inline bool perfmon_capable(void)
{
return capable(CAP_PERFMON) || capable(CAP_SYS_ADMIN);
}
/* audit system wants to get cap info from files as well */
extern int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps);

View File

@ -1305,7 +1305,7 @@ static inline int perf_is_paranoid(void)
static inline int perf_allow_kernel(struct perf_event_attr *attr)
{
if (sysctl_perf_event_paranoid > 1 && !capable(CAP_SYS_ADMIN))
if (sysctl_perf_event_paranoid > 1 && !perfmon_capable())
return -EACCES;
return security_perf_event_open(attr, PERF_SECURITY_KERNEL);
@ -1313,7 +1313,7 @@ static inline int perf_allow_kernel(struct perf_event_attr *attr)
static inline int perf_allow_cpu(struct perf_event_attr *attr)
{
if (sysctl_perf_event_paranoid > 0 && !capable(CAP_SYS_ADMIN))
if (sysctl_perf_event_paranoid > 0 && !perfmon_capable())
return -EACCES;
return security_perf_event_open(attr, PERF_SECURITY_CPU);
@ -1321,7 +1321,7 @@ static inline int perf_allow_cpu(struct perf_event_attr *attr)
static inline int perf_allow_tracepoint(struct perf_event_attr *attr)
{
if (sysctl_perf_event_paranoid > -1 && !capable(CAP_SYS_ADMIN))
if (sysctl_perf_event_paranoid > -1 && !perfmon_capable())
return -EPERM;
return security_perf_event_open(attr, PERF_SECURITY_TRACEPOINT);

View File

@ -367,8 +367,14 @@ struct vfs_ns_cap_data {
#define CAP_AUDIT_READ 37
/*
* Allow system performance and observability privileged operations
* using perf_events, i915_perf and other kernel subsystems
*/
#define CAP_LAST_CAP CAP_AUDIT_READ
#define CAP_PERFMON 38
#define CAP_LAST_CAP CAP_PERFMON
#define cap_valid(x) ((x) >= 0 && (x) <= CAP_LAST_CAP)

View File

@ -9397,7 +9397,7 @@ static int perf_kprobe_event_init(struct perf_event *event)
if (event->attr.type != perf_kprobe.type)
return -ENOENT;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EACCES;
/*
@ -9457,7 +9457,7 @@ static int perf_uprobe_event_init(struct perf_event *event)
if (event->attr.type != perf_uprobe.type)
return -ENOENT;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EACCES;
/*
@ -11504,7 +11504,7 @@ SYSCALL_DEFINE5(perf_event_open,
}
if (attr.namespaces) {
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EACCES;
}

View File

@ -1468,7 +1468,7 @@ int perf_event_query_prog_array(struct perf_event *event, void __user *info)
u32 *ids, prog_cnt, ids_len;
int ret;
if (!capable(CAP_SYS_ADMIN))
if (!perfmon_capable())
return -EPERM;
if (event->attr.type != PERF_TYPE_TRACEPOINT)
return -EINVAL;

View File

@ -27,9 +27,9 @@
"audit_control", "setfcap"
#define COMMON_CAP2_PERMS "mac_override", "mac_admin", "syslog", \
"wake_alarm", "block_suspend", "audit_read"
"wake_alarm", "block_suspend", "audit_read", "perfmon"
#if CAP_LAST_CAP > CAP_AUDIT_READ
#if CAP_LAST_CAP > CAP_PERFMON
#error New capability defined, please update COMMON_CAP2_PERMS.
#endif

View File

@ -90,6 +90,7 @@ struct fs {
const char * const *mounts;
char path[PATH_MAX];
bool found;
bool checked;
long magic;
};
@ -111,31 +112,37 @@ static struct fs fs__entries[] = {
.name = "sysfs",
.mounts = sysfs__fs_known_mountpoints,
.magic = SYSFS_MAGIC,
.checked = false,
},
[FS__PROCFS] = {
.name = "proc",
.mounts = procfs__known_mountpoints,
.magic = PROC_SUPER_MAGIC,
.checked = false,
},
[FS__DEBUGFS] = {
.name = "debugfs",
.mounts = debugfs__known_mountpoints,
.magic = DEBUGFS_MAGIC,
.checked = false,
},
[FS__TRACEFS] = {
.name = "tracefs",
.mounts = tracefs__known_mountpoints,
.magic = TRACEFS_MAGIC,
.checked = false,
},
[FS__HUGETLBFS] = {
.name = "hugetlbfs",
.mounts = hugetlbfs__known_mountpoints,
.magic = HUGETLBFS_MAGIC,
.checked = false,
},
[FS__BPF_FS] = {
.name = "bpf",
.mounts = bpf_fs__known_mountpoints,
.magic = BPF_FS_MAGIC,
.checked = false,
},
};
@ -158,6 +165,7 @@ static bool fs__read_mounts(struct fs *fs)
}
fclose(fp);
fs->checked = true;
return fs->found = found;
}
@ -220,6 +228,7 @@ static bool fs__env_override(struct fs *fs)
return false;
fs->found = true;
fs->checked = true;
strncpy(fs->path, override_path, sizeof(fs->path) - 1);
fs->path[sizeof(fs->path) - 1] = '\0';
return true;
@ -246,6 +255,14 @@ static const char *fs__mountpoint(int idx)
if (fs->found)
return (const char *)fs->path;
/* the mount point was already checked for the mount point
* but and did not exist, so return NULL to avoid scanning again.
* This makes the found and not found paths cost equivalent
* in case of multiple calls.
*/
if (fs->checked)
return NULL;
return fs__get_mountpoint(fs);
}

View File

@ -18,6 +18,18 @@
const char *name##__mount(void); \
bool name##__configured(void); \
/*
* The xxxx__mountpoint() entry points find the first match mount point for each
* filesystems listed below, where xxxx is the filesystem type.
*
* The interface is as follows:
*
* - If a mount point is found on first call, it is cached and used for all
* subsequent calls.
*
* - If a mount point is not found, NULL is returned on first call and all
* subsequent calls.
*/
FS(sysfs)
FS(procfs)
FS(debugfs)

View File

@ -1958,7 +1958,8 @@ static char *op_to_str(struct tep_event_filter *filter, struct tep_filter_arg *a
default:
break;
}
asprintf(&str, val ? "TRUE" : "FALSE");
if (asprintf(&str, val ? "TRUE" : "FALSE") < 0)
str = NULL;
break;
}
}
@ -1976,7 +1977,8 @@ static char *op_to_str(struct tep_event_filter *filter, struct tep_filter_arg *a
break;
}
asprintf(&str, "(%s) %s (%s)", left, op, right);
if (asprintf(&str, "(%s) %s (%s)", left, op, right) < 0)
str = NULL;
break;
case TEP_FILTER_OP_NOT:
@ -1992,10 +1994,12 @@ static char *op_to_str(struct tep_event_filter *filter, struct tep_filter_arg *a
right_val = 0;
if (right_val >= 0) {
/* just return the opposite */
asprintf(&str, right_val ? "FALSE" : "TRUE");
if (asprintf(&str, right_val ? "FALSE" : "TRUE") < 0)
str = NULL;
break;
}
asprintf(&str, "%s(%s)", op, right);
if (asprintf(&str, "%s(%s)", op, right) < 0)
str = NULL;
break;
default:
@ -2011,7 +2015,8 @@ static char *val_to_str(struct tep_event_filter *filter, struct tep_filter_arg *
{
char *str = NULL;
asprintf(&str, "%lld", arg->value.val);
if (asprintf(&str, "%lld", arg->value.val) < 0)
str = NULL;
return str;
}
@ -2069,7 +2074,8 @@ static char *exp_to_str(struct tep_event_filter *filter, struct tep_filter_arg *
break;
}
asprintf(&str, "%s %s %s", lstr, op, rstr);
if (asprintf(&str, "%s %s %s", lstr, op, rstr) < 0)
str = NULL;
out:
free(lstr);
free(rstr);
@ -2113,7 +2119,8 @@ static char *num_to_str(struct tep_event_filter *filter, struct tep_filter_arg *
if (!op)
op = "<=";
asprintf(&str, "%s %s %s", lstr, op, rstr);
if (asprintf(&str, "%s %s %s", lstr, op, rstr) < 0)
str = NULL;
break;
default:
@ -2148,8 +2155,9 @@ static char *str_to_str(struct tep_event_filter *filter, struct tep_filter_arg *
if (!op)
op = "!~";
asprintf(&str, "%s %s \"%s\"",
arg->str.field->name, op, arg->str.val);
if (asprintf(&str, "%s %s \"%s\"",
arg->str.field->name, op, arg->str.val) < 0)
str = NULL;
break;
default:
@ -2165,7 +2173,8 @@ static char *arg_to_str(struct tep_event_filter *filter, struct tep_filter_arg *
switch (arg->type) {
case TEP_FILTER_ARG_BOOLEAN:
asprintf(&str, arg->boolean.value ? "TRUE" : "FALSE");
if (asprintf(&str, arg->boolean.value ? "TRUE" : "FALSE") < 0)
str = NULL;
return str;
case TEP_FILTER_ARG_OP:

View File

@ -48,7 +48,7 @@ man5dir=$(mandir)/man5
man7dir=$(mandir)/man7
ASCIIDOC=asciidoc
ASCIIDOC_EXTRA = --unsafe -f asciidoc.conf
ASCIIDOC_EXTRA += --unsafe -f asciidoc.conf
ASCIIDOC_HTML = xhtml11
MANPAGE_XSL = manpage-normal.xsl
XMLTO_EXTRA =
@ -59,7 +59,7 @@ HTML_REF = origin/html
ifdef USE_ASCIIDOCTOR
ASCIIDOC = asciidoctor
ASCIIDOC_EXTRA = -a compat-mode
ASCIIDOC_EXTRA += -a compat-mode
ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions
ASCIIDOC_EXTRA += -a mansource="perf" -a manmanual="perf Manual"
ASCIIDOC_HTML = xhtml5

View File

@ -10,6 +10,7 @@
e synthesize error events
d create a debug log
g synthesize a call chain (use with i or x)
G synthesize a call chain on existing event records
l synthesize last branch entries (use with i or x)
s skip initial number of events

View File

@ -61,6 +61,9 @@ SUBSYSTEM
'epoll'::
Eventpoll (epoll) stressing benchmarks.
'internals'::
Benchmark internal perf functionality.
'all'::
All benchmark subsystems.
@ -214,6 +217,11 @@ Suite for evaluating concurrent epoll_wait calls.
*ctl*::
Suite for evaluating multiple epoll_ctl calls.
SUITES FOR 'internals'
~~~~~~~~~~~~~~~~~~~~~~
*synthesize*::
Suite for evaluating perf's event synthesis performance.
SEE ALSO
--------
linkperf:perf[1]

View File

@ -111,6 +111,17 @@ REPORT OPTIONS
--display::
Switch to HITM type (rmt, lcl) to display and sort on. Total HITMs as default.
--stitch-lbr::
Show callgraph with stitched LBRs, which may have more complete
callgraph. The perf.data file must have been obtained using
perf c2c record --call-graph lbr.
Disabled by default. In common cases with call stack overflows,
it can recreate better call stacks than the default lbr call stack
output. But this approach is not full proof. There can be cases
where it creates incorrect call stacks from incorrect matches.
The known limitations include exception handing such as
setjmp/longjmp will have calls/returns not match.
C2C RECORD
----------
The perf c2c record command setup options related to HITM cacheline analysis

View File

@ -115,6 +115,11 @@ raw encoding of 0x1A8 can be used:
perf stat -e r1a8 -a sleep 1
perf record -e r1a8 ...
It's also possible to use pmu syntax:
perf record -e r1a8 -a sleep 1
perf record -e cpu/r1a8/ ...
You should refer to the processor specific documentation for getting these
details. Some of them are referenced in the SEE ALSO section below.
@ -258,6 +263,9 @@ Normally all events in an event group sample, but with :S only
the first event (the leader) samples, and it only reads the values of the
other events in the group.
However, in the case AUX area events (e.g. Intel PT or CoreSight), the AUX
area event must be the leader, so then the second event samples, not the first.
OPTIONS
-------

View File

@ -488,6 +488,17 @@ include::itrace.txt[]
This option extends the perf report to show reference callgraphs,
which collected by reference event, in no callgraph event.
--stitch-lbr::
Show callgraph with stitched LBRs, which may have more complete
callgraph. The perf.data file must have been obtained using
perf record --call-graph lbr.
Disabled by default. In common cases with call stack overflows,
it can recreate better call stacks than the default lbr call stack
output. But this approach is not full proof. There can be cases
where it creates incorrect call stacks from incorrect matches.
The known limitations include exception handing such as
setjmp/longjmp will have calls/returns not match.
--socket-filter::
Only report the samples on the processor socket that match with this filter

View File

@ -440,6 +440,17 @@ include::itrace.txt[]
--show-on-off-events::
Show the --switch-on/off events too.
--stitch-lbr::
Show callgraph with stitched LBRs, which may have more complete
callgraph. The perf.data file must have been obtained using
perf record --call-graph lbr.
Disabled by default. In common cases with call stack overflows,
it can recreate better call stacks than the default lbr call stack
output. But this approach is not full proof. There can be cases
where it creates incorrect call stacks from incorrect matches.
The known limitations include exception handing such as
setjmp/longjmp will have calls/returns not match.
SEE ALSO
--------
linkperf:perf-record[1], linkperf:perf-script-perl[1],

View File

@ -319,6 +319,15 @@ Default is to monitor all CPUS.
go straight to the histogram browser, just like 'perf top' with no events
explicitely specified does.
--stitch-lbr::
Show callgraph with stitched LBRs, which may have more complete
callgraph. The option must be used with --call-graph lbr recording.
Disabled by default. In common cases with call stack overflows,
it can recreate better call stacks than the default lbr call stack
output. But this approach is not full proof. There can be cases
where it creates incorrect call stacks from incorrect matches.
The known limitations include exception handing such as
setjmp/longjmp will have calls/returns not match.
INTERACTIVE PROMPTING KEYS
--------------------------

View File

@ -373,6 +373,22 @@ struct {
Indicates that trace contains records of PERF_RECORD_COMPRESSED type
that have perf_events records in compressed form.
HEADER_CPU_PMU_CAPS = 28,
A list of cpu PMU capabilities. The format of data is as below.
struct {
u32 nr_cpu_pmu_caps;
{
char name[];
char value[];
} [nr_cpu_pmu_caps]
};
Example:
cpu pmu capabilities: branches=32, max_precise=3, pmu_name=icelake
other bits are reserved and should ignored for now
HEADER_FEAT_BITS = 256,

View File

@ -6,9 +6,9 @@ perf-y += futex-wake.o
perf-y += futex-wake-parallel.o
perf-y += futex-requeue.o
perf-y += futex-lock-pi.o
perf-y += epoll-wait.o
perf-y += epoll-ctl.o
perf-y += synthesize.o
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-lib.o
perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o

View File

@ -41,9 +41,9 @@ int bench_futex_wake_parallel(int argc, const char **argv);
int bench_futex_requeue(int argc, const char **argv);
/* pi futexes */
int bench_futex_lock_pi(int argc, const char **argv);
int bench_epoll_wait(int argc, const char **argv);
int bench_epoll_ctl(int argc, const char **argv);
int bench_synthesize(int argc, const char **argv);
#define BENCH_FORMAT_DEFAULT_STR "default"
#define BENCH_FORMAT_DEFAULT 0

View File

@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Benchmark synthesis of perf events such as at the start of a 'perf
* record'. Synthesis is done on the current process and the 'dummy' event
* handlers are invoked that support dump_trace but otherwise do nothing.
*
* Copyright 2019 Google LLC.
*/
#include <stdio.h>
#include "bench.h"
#include "../util/debug.h"
#include "../util/session.h"
#include "../util/synthetic-events.h"
#include "../util/target.h"
#include "../util/thread_map.h"
#include "../util/tool.h"
#include <linux/err.h>
#include <linux/time64.h>
#include <subcmd/parse-options.h>
static unsigned int iterations = 10000;
static const struct option options[] = {
OPT_UINTEGER('i', "iterations", &iterations,
"Number of iterations used to compute average"),
OPT_END()
};
static const char *const usage[] = {
"perf bench internals synthesize <options>",
NULL
};
static int do_synthesize(struct perf_session *session,
struct perf_thread_map *threads,
struct target *target, bool data_mmap)
{
const unsigned int nr_threads_synthesize = 1;
struct timeval start, end, diff;
u64 runtime_us;
unsigned int i;
double average;
int err;
gettimeofday(&start, NULL);
for (i = 0; i < iterations; i++) {
err = machine__synthesize_threads(&session->machines.host,
target, threads, data_mmap,
nr_threads_synthesize);
if (err)
return err;
}
gettimeofday(&end, NULL);
timersub(&end, &start, &diff);
runtime_us = diff.tv_sec * USEC_PER_SEC + diff.tv_usec;
average = (double)runtime_us/(double)iterations;
printf("Average %ssynthesis took: %f usec\n",
data_mmap ? "data " : "", average);
return 0;
}
int bench_synthesize(int argc, const char **argv)
{
struct perf_tool tool;
struct perf_session *session;
struct target target = {
.pid = "self",
};
struct perf_thread_map *threads;
int err;
argc = parse_options(argc, argv, options, usage, 0);
session = perf_session__new(NULL, false, NULL);
if (IS_ERR(session)) {
pr_err("Session creation failed.\n");
return PTR_ERR(session);
}
threads = thread_map__new_by_pid(getpid());
if (!threads) {
pr_err("Thread map creation failed.\n");
err = -ENOMEM;
goto err_out;
}
perf_tool__fill_defaults(&tool);
err = do_synthesize(session, threads, &target, false);
if (err)
goto err_out;
err = do_synthesize(session, threads, &target, true);
err_out:
if (threads)
perf_thread_map__put(threads);
perf_session__delete(session);
return err;
}

View File

@ -76,6 +76,11 @@ static struct bench epoll_benchmarks[] = {
};
#endif // HAVE_EVENTFD
static struct bench internals_benchmarks[] = {
{ "synthesize", "Benchmark perf event synthesis", bench_synthesize },
{ NULL, NULL, NULL }
};
struct collection {
const char *name;
const char *summary;
@ -92,6 +97,7 @@ static struct collection collections[] = {
#ifdef HAVE_EVENTFD
{"epoll", "Epoll stressing benchmarks", epoll_benchmarks },
#endif
{ "internals", "Perf-internals benchmarks", internals_benchmarks },
{ "all", "All benchmarks", NULL },
{ NULL, NULL, NULL }
};

View File

@ -95,6 +95,7 @@ struct perf_c2c {
bool use_stdio;
bool stats_only;
bool symbol_full;
bool stitch_lbr;
/* HITM shared clines stats */
struct c2c_stats hitm_stats;
@ -273,6 +274,9 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused,
return -1;
}
if (c2c.stitch_lbr)
al.thread->lbr_stitch_enable = true;
ret = sample__resolve_callchain(sample, &callchain_cursor, NULL,
evsel, &al, sysctl_perf_event_max_stack);
if (ret)
@ -2601,6 +2605,12 @@ static int setup_callchain(struct evlist *evlist)
}
}
if (c2c.stitch_lbr && (mode != CALLCHAIN_LBR)) {
ui__warning("Can't find LBR callchain. Switch off --stitch-lbr.\n"
"Please apply --call-graph lbr when recording.\n");
c2c.stitch_lbr = false;
}
callchain_param.record_mode = mode;
callchain_param.min_percent = 0;
return 0;
@ -2752,6 +2762,8 @@ static int perf_c2c__report(int argc, const char **argv)
OPT_STRING('c', "coalesce", &coalesce, "coalesce fields",
"coalesce fields: pid,tid,iaddr,dso"),
OPT_BOOLEAN('f', "force", &symbol_conf.force, "don't complain, do it"),
OPT_BOOLEAN(0, "stitch-lbr", &c2c.stitch_lbr,
"Enable LBR callgraph stitching approach"),
OPT_PARENT(c2c_options),
OPT_END()
};

View File

@ -284,10 +284,11 @@ static int __cmd_ftrace(struct perf_ftrace *ftrace, int argc, const char **argv)
.events = POLLIN,
};
if (!perf_cap__capable(CAP_SYS_ADMIN)) {
if (!(perf_cap__capable(CAP_PERFMON) ||
perf_cap__capable(CAP_SYS_ADMIN))) {
pr_err("ftrace only works for %s!\n",
#ifdef HAVE_LIBCAP_SUPPORT
"users with the SYS_ADMIN capability"
"users with the CAP_PERFMON or CAP_SYS_ADMIN capability"
#else
"root"
#endif

View File

@ -84,6 +84,7 @@ struct report {
bool header_only;
bool nonany_branch_mode;
bool group_set;
bool stitch_lbr;
int max_stack;
struct perf_read_values show_threads_values;
struct annotation_options annotation_opts;
@ -267,6 +268,9 @@ static int process_sample_event(struct perf_tool *tool,
return -1;
}
if (rep->stitch_lbr)
al.thread->lbr_stitch_enable = true;
if (symbol_conf.hide_unresolved && al.sym == NULL)
goto out_put;
@ -339,6 +343,7 @@ static int report__setup_sample_type(struct report *rep)
bool is_pipe = perf_data__is_pipe(session->data);
if (session->itrace_synth_opts->callchain ||
session->itrace_synth_opts->add_callchain ||
(!is_pipe &&
perf_header__has_feat(&session->header, HEADER_AUXTRACE) &&
!session->itrace_synth_opts->set))
@ -407,6 +412,12 @@ static int report__setup_sample_type(struct report *rep)
callchain_param.record_mode = CALLCHAIN_FP;
}
if (rep->stitch_lbr && (callchain_param.record_mode != CALLCHAIN_LBR)) {
ui__warning("Can't find LBR callchain. Switch off --stitch-lbr.\n"
"Please apply --call-graph lbr when recording.\n");
rep->stitch_lbr = false;
}
/* ??? handle more cases than just ANY? */
if (!(perf_evlist__combined_branch_type(session->evlist) &
PERF_SAMPLE_BRANCH_ANY))
@ -1257,6 +1268,8 @@ int cmd_report(int argc, const char **argv)
"Show full source file name path for source lines"),
OPT_BOOLEAN(0, "show-ref-call-graph", &symbol_conf.show_ref_callgraph,
"Show callgraph from reference event"),
OPT_BOOLEAN(0, "stitch-lbr", &report.stitch_lbr,
"Enable LBR callgraph stitching approach"),
OPT_INTEGER(0, "socket-filter", &report.socket_filter,
"only show processor socket that match with this filter"),
OPT_BOOLEAN(0, "raw-trace", &symbol_conf.raw_trace,
@ -1332,7 +1345,7 @@ int cmd_report(int argc, const char **argv)
if (symbol_conf.cumulate_callchain && !callchain_param.order_set)
callchain_param.order = ORDER_CALLER;
if (itrace_synth_opts.callchain &&
if ((itrace_synth_opts.callchain || itrace_synth_opts.add_callchain) &&
(int)itrace_synth_opts.callchain_sz > report.max_stack)
report.max_stack = itrace_synth_opts.callchain_sz;

View File

@ -1697,6 +1697,7 @@ struct perf_script {
bool show_cgroup_events;
bool allocated;
bool per_event_dump;
bool stitch_lbr;
struct evswitch evswitch;
struct perf_cpu_map *cpus;
struct perf_thread_map *threads;
@ -1923,6 +1924,9 @@ static void process_event(struct perf_script *script,
if (PRINT_FIELD(IP)) {
struct callchain_cursor *cursor = NULL;
if (script->stitch_lbr)
al->thread->lbr_stitch_enable = true;
if (symbol_conf.use_callchain && sample->callchain &&
thread__resolve_callchain(al->thread, &callchain_cursor, evsel,
sample, NULL, NULL, scripting_max_stack) == 0)
@ -2040,7 +2044,7 @@ static int cleanup_scripting(void)
static bool filter_cpu(struct perf_sample *sample)
{
if (cpu_list)
if (cpu_list && sample->cpu != (u32)-1)
return !test_bit(sample->cpu, cpu_bitmap);
return false;
}
@ -2138,41 +2142,59 @@ static int process_attr(struct perf_tool *tool, union perf_event *event,
return err;
}
static int print_event_with_time(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
struct machine *machine,
pid_t pid, pid_t tid, u64 timestamp)
{
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
struct thread *thread = NULL;
if (evsel && !evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = timestamp;
sample->pid = pid;
sample->tid = tid;
}
if (filter_cpu(sample))
return 0;
if (tid != -1)
thread = machine__findnew_thread(machine, pid, tid);
if (thread && evsel) {
perf_sample__fprintf_start(sample, thread, evsel,
event->header.type, stdout);
}
perf_event__fprintf(event, stdout);
thread__put(thread);
return 0;
}
static int print_event(struct perf_tool *tool, union perf_event *event,
struct perf_sample *sample, struct machine *machine,
pid_t pid, pid_t tid)
{
return print_event_with_time(tool, event, sample, machine, pid, tid, 0);
}
static int process_comm_event(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
int ret = -1;
thread = machine__findnew_thread(machine, event->comm.pid, event->comm.tid);
if (thread == NULL) {
pr_debug("problem processing COMM event, skipping it.\n");
return -1;
}
if (perf_event__process_comm(tool, event, sample, machine) < 0)
goto out;
return -1;
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = 0;
sample->tid = event->comm.tid;
sample->pid = event->comm.pid;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_COMM, stdout);
perf_event__fprintf(event, stdout);
}
ret = 0;
out:
thread__put(thread);
return ret;
return print_event(tool, event, sample, machine, event->comm.pid,
event->comm.tid);
}
static int process_namespaces_event(struct perf_tool *tool,
@ -2180,37 +2202,11 @@ static int process_namespaces_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
int ret = -1;
thread = machine__findnew_thread(machine, event->namespaces.pid,
event->namespaces.tid);
if (thread == NULL) {
pr_debug("problem processing NAMESPACES event, skipping it.\n");
return -1;
}
if (perf_event__process_namespaces(tool, event, sample, machine) < 0)
goto out;
return -1;
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = 0;
sample->tid = event->namespaces.tid;
sample->pid = event->namespaces.pid;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_NAMESPACES, stdout);
perf_event__fprintf(event, stdout);
}
ret = 0;
out:
thread__put(thread);
return ret;
return print_event(tool, event, sample, machine, event->namespaces.pid,
event->namespaces.tid);
}
static int process_cgroup_event(struct perf_tool *tool,
@ -2218,34 +2214,11 @@ static int process_cgroup_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
int ret = -1;
thread = machine__findnew_thread(machine, sample->pid, sample->tid);
if (thread == NULL) {
pr_debug("problem processing CGROUP event, skipping it.\n");
return -1;
}
if (perf_event__process_cgroup(tool, event, sample, machine) < 0)
goto out;
return -1;
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = 0;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_CGROUP, stdout);
perf_event__fprintf(event, stdout);
}
ret = 0;
out:
thread__put(thread);
return ret;
return print_event(tool, event, sample, machine, sample->pid,
sample->tid);
}
static int process_fork_event(struct perf_tool *tool,
@ -2253,69 +2226,24 @@ static int process_fork_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
if (perf_event__process_fork(tool, event, sample, machine) < 0)
return -1;
thread = machine__findnew_thread(machine, event->fork.pid, event->fork.tid);
if (thread == NULL) {
pr_debug("problem processing FORK event, skipping it.\n");
return -1;
}
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = event->fork.time;
sample->tid = event->fork.tid;
sample->pid = event->fork.pid;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_FORK, stdout);
perf_event__fprintf(event, stdout);
}
thread__put(thread);
return 0;
return print_event_with_time(tool, event, sample, machine,
event->fork.pid, event->fork.tid,
event->fork.time);
}
static int process_exit_event(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
struct machine *machine)
{
int err = 0;
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
thread = machine__findnew_thread(machine, event->fork.pid, event->fork.tid);
if (thread == NULL) {
pr_debug("problem processing EXIT event, skipping it.\n");
/* Print before 'exit' deletes anything */
if (print_event_with_time(tool, event, sample, machine, event->fork.pid,
event->fork.tid, event->fork.time))
return -1;
}
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = 0;
sample->tid = event->fork.tid;
sample->pid = event->fork.pid;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_EXIT, stdout);
perf_event__fprintf(event, stdout);
}
if (perf_event__process_exit(tool, event, sample, machine) < 0)
err = -1;
thread__put(thread);
return err;
return perf_event__process_exit(tool, event, sample, machine);
}
static int process_mmap_event(struct perf_tool *tool,
@ -2323,33 +2251,11 @@ static int process_mmap_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
if (perf_event__process_mmap(tool, event, sample, machine) < 0)
return -1;
thread = machine__findnew_thread(machine, event->mmap.pid, event->mmap.tid);
if (thread == NULL) {
pr_debug("problem processing MMAP event, skipping it.\n");
return -1;
}
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = 0;
sample->tid = event->mmap.tid;
sample->pid = event->mmap.pid;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_MMAP, stdout);
perf_event__fprintf(event, stdout);
}
thread__put(thread);
return 0;
return print_event(tool, event, sample, machine, event->mmap.pid,
event->mmap.tid);
}
static int process_mmap2_event(struct perf_tool *tool,
@ -2357,33 +2263,11 @@ static int process_mmap2_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
if (perf_event__process_mmap2(tool, event, sample, machine) < 0)
return -1;
thread = machine__findnew_thread(machine, event->mmap2.pid, event->mmap2.tid);
if (thread == NULL) {
pr_debug("problem processing MMAP2 event, skipping it.\n");
return -1;
}
if (!evsel->core.attr.sample_id_all) {
sample->cpu = 0;
sample->time = 0;
sample->tid = event->mmap2.tid;
sample->pid = event->mmap2.pid;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_MMAP2, stdout);
perf_event__fprintf(event, stdout);
}
thread__put(thread);
return 0;
return print_event(tool, event, sample, machine, event->mmap2.pid,
event->mmap2.tid);
}
static int process_switch_event(struct perf_tool *tool,
@ -2391,10 +2275,7 @@ static int process_switch_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
if (perf_event__process_switch(tool, event, sample, machine) < 0)
return -1;
@ -2405,20 +2286,8 @@ static int process_switch_event(struct perf_tool *tool,
if (!script->show_switch_events)
return 0;
thread = machine__findnew_thread(machine, sample->pid,
sample->tid);
if (thread == NULL) {
pr_debug("problem processing SWITCH event, skipping it.\n");
return -1;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_SWITCH, stdout);
perf_event__fprintf(event, stdout);
}
thread__put(thread);
return 0;
return print_event(tool, event, sample, machine, sample->pid,
sample->tid);
}
static int
@ -2427,23 +2296,8 @@ process_lost_event(struct perf_tool *tool,
struct perf_sample *sample,
struct machine *machine)
{
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
struct thread *thread;
thread = machine__findnew_thread(machine, sample->pid,
sample->tid);
if (thread == NULL)
return -1;
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
PERF_RECORD_LOST, stdout);
perf_event__fprintf(event, stdout);
}
thread__put(thread);
return 0;
return print_event(tool, event, sample, machine, sample->pid,
sample->tid);
}
static int
@ -2462,33 +2316,11 @@ process_bpf_events(struct perf_tool *tool __maybe_unused,
struct perf_sample *sample,
struct machine *machine)
{
struct thread *thread;
struct perf_script *script = container_of(tool, struct perf_script, tool);
struct perf_session *session = script->session;
struct evsel *evsel = perf_evlist__id2evsel(session->evlist, sample->id);
if (machine__process_ksymbol(machine, event, sample) < 0)
return -1;
if (!evsel->core.attr.sample_id_all) {
perf_event__fprintf(event, stdout);
return 0;
}
thread = machine__findnew_thread(machine, sample->pid, sample->tid);
if (thread == NULL) {
pr_debug("problem processing MMAP event, skipping it.\n");
return -1;
}
if (!filter_cpu(sample)) {
perf_sample__fprintf_start(sample, thread, evsel,
event->header.type, stdout);
perf_event__fprintf(event, stdout);
}
thread__put(thread);
return 0;
return print_event(tool, event, sample, machine, sample->pid,
sample->tid);
}
static void sig_handler(int sig __maybe_unused)
@ -3342,6 +3174,12 @@ static void script__setup_sample_type(struct perf_script *script)
else
callchain_param.record_mode = CALLCHAIN_FP;
}
if (script->stitch_lbr && (callchain_param.record_mode != CALLCHAIN_LBR)) {
pr_warning("Can't find LBR callchain. Switch off --stitch-lbr.\n"
"Please apply --call-graph lbr when recording.\n");
script->stitch_lbr = false;
}
}
static int process_stat_round_event(struct perf_session *session,
@ -3653,6 +3491,8 @@ int cmd_script(int argc, const char **argv)
"file", "file saving guest os /proc/kallsyms"),
OPT_STRING(0, "guestmodules", &symbol_conf.default_guest_modules,
"file", "file saving guest os /proc/modules"),
OPT_BOOLEAN('\0', "stitch-lbr", &script.stitch_lbr,
"Enable LBR callgraph stitching approach"),
OPTS_EVSWITCH(&script.evswitch),
OPT_END()
};
@ -3709,7 +3549,7 @@ int cmd_script(int argc, const char **argv)
return -1;
}
if (itrace_synth_opts.callchain &&
if ((itrace_synth_opts.callchain || itrace_synth_opts.add_callchain) &&
itrace_synth_opts.callchain_sz > scripting_max_stack)
scripting_max_stack = itrace_synth_opts.callchain_sz;

View File

@ -686,8 +686,11 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx)
break;
}
}
if (child_pid != -1)
if (child_pid != -1) {
if (timeout)
kill(child_pid, SIGTERM);
wait4(child_pid, &status, 0, &stat_config.ru_data);
}
if (workload_exec_errno) {
const char *emsg = str_error_r(workload_exec_errno, msg, sizeof(msg));

View File

@ -33,6 +33,7 @@
#include "util/map.h"
#include "util/mmap.h"
#include "util/session.h"
#include "util/thread.h"
#include "util/symbol.h"
#include "util/synthetic-events.h"
#include "util/top.h"
@ -775,6 +776,9 @@ static void perf_event__process_sample(struct perf_tool *tool,
if (machine__resolve(machine, &al, sample) < 0)
return;
if (top->stitch_lbr)
al.thread->lbr_stitch_enable = true;
if (!machine->kptr_restrict_warned &&
symbol_conf.kptr_restrict &&
al.cpumode == PERF_RECORD_MISC_KERNEL) {
@ -1571,6 +1575,8 @@ int cmd_top(int argc, const char **argv)
"Sort the output by the event at the index n in group. "
"If n is invalid, sort by the first event. "
"WARNING: should be used on grouped events."),
OPT_BOOLEAN(0, "stitch-lbr", &top.stitch_lbr,
"Enable LBR callgraph stitching approach"),
OPTS_EVSWITCH(&top.evswitch),
OPT_END()
};
@ -1640,6 +1646,11 @@ int cmd_top(int argc, const char **argv)
}
}
if (top.stitch_lbr && !(callchain_param.record_mode == CALLCHAIN_LBR)) {
pr_err("Error: --stitch-lbr must be used with --call-graph lbr\n");
goto out_delete_evlist;
}
if (opts->branch_stack && callchain_param.enabled)
symbol_conf.show_branchflag_count = true;

View File

@ -258,7 +258,8 @@ gets schedule to. Per task counters can be created by any user, for
their own tasks.
A 'pid == -1' and 'cpu == x' counter is a per CPU counter that counts
all events on CPU-x. Per CPU counters need CAP_SYS_ADMIN privilege.
all events on CPU-x. Per CPU counters need CAP_PERFMON or CAP_SYS_ADMIN
privilege.
The 'flags' parameter is currently unused and must be zero.

View File

@ -0,0 +1,2 @@
#!/usr/bin/sh
perf record -g "$@"

View File

@ -0,0 +1,3 @@
#!/usr/bin/sh
# description: create flame graphs
perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py -- "$@"

View File

@ -0,0 +1,124 @@
# flamegraph.py - create flame graphs from perf samples
# SPDX-License-Identifier: GPL-2.0
#
# Usage:
#
# perf record -a -g -F 99 sleep 60
# perf script report flamegraph
#
# Combined:
#
# perf script flamegraph -a -F 99 sleep 60
#
# Written by Andreas Gerstmayr <agerstmayr@redhat.com>
# Flame Graphs invented by Brendan Gregg <bgregg@netflix.com>
# Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com>
from __future__ import print_function
import sys
import os
import argparse
import json
class Node:
def __init__(self, name, libtype=""):
self.name = name
self.libtype = libtype
self.value = 0
self.children = []
def toJSON(self):
return {
"n": self.name,
"l": self.libtype,
"v": self.value,
"c": self.children
}
class FlameGraphCLI:
def __init__(self, args):
self.args = args
self.stack = Node("root")
if self.args.format == "html" and \
not os.path.isfile(self.args.template):
print("Flame Graph template {} does not exist. Please install "
"the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) "
"package, specify an existing flame graph template "
"(--template PATH) or another output format "
"(--format FORMAT).".format(self.args.template),
file=sys.stderr)
sys.exit(1)
def find_or_create_node(self, node, name, dso):
libtype = "kernel" if dso == "[kernel.kallsyms]" else ""
if name is None:
name = "[unknown]"
for child in node.children:
if child.name == name and child.libtype == libtype:
return child
child = Node(name, libtype)
node.children.append(child)
return child
def process_event(self, event):
node = self.find_or_create_node(self.stack, event["comm"], None)
if "callchain" in event:
for entry in reversed(event['callchain']):
node = self.find_or_create_node(
node, entry.get("sym", {}).get("name"), event.get("dso"))
else:
node = self.find_or_create_node(
node, entry.get("symbol"), event.get("dso"))
node.value += 1
def trace_end(self):
json_str = json.dumps(self.stack, default=lambda x: x.toJSON())
if self.args.format == "html":
try:
with open(self.args.template) as f:
output_str = f.read().replace("/** @flamegraph_json **/",
json_str)
except IOError as e:
print("Error reading template file: {}".format(e), file=sys.stderr)
sys.exit(1)
output_fn = self.args.output or "flamegraph.html"
else:
output_str = json_str
output_fn = self.args.output or "stacks.json"
if output_fn == "-":
sys.stdout.write(output_str)
else:
print("dumping data to {}".format(output_fn))
try:
with open(output_fn, "w") as out:
out.write(output_str)
except IOError as e:
print("Error writing output file: {}".format(e), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Create flame graphs.")
parser.add_argument("-f", "--format",
default="html", choices=["json", "html"],
help="output file format")
parser.add_argument("-o", "--output",
help="output file name")
parser.add_argument("--template",
default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
help="path to flamegraph HTML template")
parser.add_argument("-i", "--input",
help=argparse.SUPPRESS)
args = parser.parse_args()
cli = FlameGraphCLI(args)
process_event = cli.process_event
trace_end = cli.trace_end

View File

@ -6,7 +6,7 @@
#include <string.h>
#include <linux/zalloc.h>
static int test(struct parse_ctx *ctx, const char *e, double val2)
static int test(struct expr_parse_ctx *ctx, const char *e, double val2)
{
double val;
@ -22,7 +22,7 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
const char **other;
double val;
int i, ret;
struct parse_ctx ctx;
struct expr_parse_ctx ctx;
int num_other;
expr__ctx_init(&ctx);

View File

@ -1356,6 +1356,16 @@ static int test__checkevent_complex_name(struct evlist *evlist)
return 0;
}
static int test__checkevent_raw_pmu(struct evlist *evlist)
{
struct evsel *evsel = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong config", 0x1a == evsel->core.attr.config);
return 0;
}
static int test__sym_event_slash(struct evlist *evlist)
{
struct evsel *evsel = evlist__first(evlist);
@ -1750,7 +1760,12 @@ static struct evlist_test test__events_pmu[] = {
.name = "cpu/name='COMPLEX_CYCLES_NAME:orig=cycles,desc=chip-clock-ticks',period=0x1,event=0x2/ukp",
.check = test__checkevent_complex_name,
.id = 3,
}
},
{
.name = "software/r1a/",
.check = test__checkevent_raw_pmu,
.id = 4,
},
};
struct terms_test {

View File

@ -1821,6 +1821,24 @@ static int symbol__disassemble_bpf(struct symbol *sym __maybe_unused,
}
#endif // defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT)
static int
symbol__disassemble_bpf_image(struct symbol *sym,
struct annotate_args *args)
{
struct annotation *notes = symbol__annotation(sym);
struct disasm_line *dl;
args->offset = -1;
args->line = strdup("to be implemented");
args->line_nr = 0;
dl = disasm_line__new(args);
if (dl)
annotation_line__add(&dl->al, &notes->src->source);
free(args->line);
return 0;
}
/*
* Possibly create a new version of line with tabs expanded. Returns the
* existing or new line, storage is updated if a new line is allocated. If
@ -1920,6 +1938,8 @@ static int symbol__disassemble(struct symbol *sym, struct annotate_args *args)
if (dso->binary_type == DSO_BINARY_TYPE__BPF_PROG_INFO) {
return symbol__disassemble_bpf(sym, args);
} else if (dso->binary_type == DSO_BINARY_TYPE__BPF_IMAGE) {
return symbol__disassemble_bpf_image(sym, args);
} else if (dso__is_kcore(dso)) {
kce.kcore_filename = symfs_filename;
kce.addr = map__rip_2objdump(map, sym->start);

View File

@ -176,6 +176,14 @@ static void arm_spe_free(struct perf_session *session)
free(spe);
}
static bool arm_spe_evsel_is_auxtrace(struct perf_session *session,
struct evsel *evsel)
{
struct arm_spe *spe = container_of(session->auxtrace, struct arm_spe, auxtrace);
return evsel->core.attr.type == spe->pmu_type;
}
static const char * const arm_spe_info_fmts[] = {
[ARM_SPE_PMU_TYPE] = " PMU Type %"PRId64"\n",
};
@ -218,6 +226,7 @@ int arm_spe_process_auxtrace_info(union perf_event *event,
spe->auxtrace.flush_events = arm_spe_flush;
spe->auxtrace.free_events = arm_spe_free_events;
spe->auxtrace.free = arm_spe_free;
spe->auxtrace.evsel_is_auxtrace = arm_spe_evsel_is_auxtrace;
session->auxtrace = &spe->auxtrace;
arm_spe_print_info(&auxtrace_info->priv[0]);

View File

@ -58,25 +58,6 @@
#include "symbol/kallsyms.h"
#include <internal/lib.h>
static struct perf_pmu *perf_evsel__find_pmu(struct evsel *evsel)
{
struct perf_pmu *pmu = NULL;
while ((pmu = perf_pmu__scan(pmu)) != NULL) {
if (pmu->type == evsel->core.attr.type)
break;
}
return pmu;
}
static bool perf_evsel__is_aux_event(struct evsel *evsel)
{
struct perf_pmu *pmu = perf_evsel__find_pmu(evsel);
return pmu && pmu->auxtrace;
}
/*
* Make a group from 'leader' to 'last', requiring that the events were not
* already grouped to a different leader.
@ -1234,29 +1215,79 @@ int perf_event__synthesize_auxtrace_info(struct auxtrace_record *itr,
return err;
}
static void unleader_evsel(struct evlist *evlist, struct evsel *leader)
{
struct evsel *new_leader = NULL;
struct evsel *evsel;
/* Find new leader for the group */
evlist__for_each_entry(evlist, evsel) {
if (evsel->leader != leader || evsel == leader)
continue;
if (!new_leader)
new_leader = evsel;
evsel->leader = new_leader;
}
/* Update group information */
if (new_leader) {
zfree(&new_leader->group_name);
new_leader->group_name = leader->group_name;
leader->group_name = NULL;
new_leader->core.nr_members = leader->core.nr_members - 1;
leader->core.nr_members = 1;
}
}
static void unleader_auxtrace(struct perf_session *session)
{
struct evsel *evsel;
evlist__for_each_entry(session->evlist, evsel) {
if (auxtrace__evsel_is_auxtrace(session, evsel) &&
perf_evsel__is_group_leader(evsel)) {
unleader_evsel(session->evlist, evsel);
}
}
}
int perf_event__process_auxtrace_info(struct perf_session *session,
union perf_event *event)
{
enum auxtrace_type type = event->auxtrace_info.type;
int err;
if (dump_trace)
fprintf(stdout, " type: %u\n", type);
switch (type) {
case PERF_AUXTRACE_INTEL_PT:
return intel_pt_process_auxtrace_info(event, session);
err = intel_pt_process_auxtrace_info(event, session);
break;
case PERF_AUXTRACE_INTEL_BTS:
return intel_bts_process_auxtrace_info(event, session);
err = intel_bts_process_auxtrace_info(event, session);
break;
case PERF_AUXTRACE_ARM_SPE:
return arm_spe_process_auxtrace_info(event, session);
err = arm_spe_process_auxtrace_info(event, session);
break;
case PERF_AUXTRACE_CS_ETM:
return cs_etm__process_auxtrace_info(event, session);
err = cs_etm__process_auxtrace_info(event, session);
break;
case PERF_AUXTRACE_S390_CPUMSF:
return s390_cpumsf_process_auxtrace_info(event, session);
err = s390_cpumsf_process_auxtrace_info(event, session);
break;
case PERF_AUXTRACE_UNKNOWN:
default:
return -EINVAL;
}
if (err)
return err;
unleader_auxtrace(session);
return 0;
}
s64 perf_event__process_auxtrace(struct perf_session *session,
@ -1412,8 +1443,12 @@ int itrace_parse_synth_opts(const struct option *opt, const char *str,
synth_opts->branches = true;
synth_opts->returns = true;
break;
case 'G':
case 'g':
synth_opts->callchain = true;
if (p[-1] == 'G')
synth_opts->add_callchain = true;
else
synth_opts->callchain = true;
synth_opts->callchain_sz =
PERF_ITRACE_DEFAULT_CALLCHAIN_SZ;
while (*p == ' ' || *p == ',')
@ -2577,3 +2612,12 @@ void auxtrace__free(struct perf_session *session)
return session->auxtrace->free(session);
}
bool auxtrace__evsel_is_auxtrace(struct perf_session *session,
struct evsel *evsel)
{
if (!session->auxtrace || !session->auxtrace->evsel_is_auxtrace)
return false;
return session->auxtrace->evsel_is_auxtrace(session, evsel);
}

View File

@ -21,6 +21,7 @@
union perf_event;
struct perf_session;
struct evlist;
struct evsel;
struct perf_tool;
struct mmap;
struct perf_sample;
@ -73,6 +74,7 @@ enum itrace_period_type {
* @calls: limit branch samples to calls (can be combined with @returns)
* @returns: limit branch samples to returns (can be combined with @calls)
* @callchain: add callchain to 'instructions' events
* @add_callchain: add callchain to existing event records
* @thread_stack: feed branches to the thread_stack
* @last_branch: add branch context to 'instruction' events
* @callchain_sz: maximum callchain size
@ -100,6 +102,7 @@ struct itrace_synth_opts {
bool calls;
bool returns;
bool callchain;
bool add_callchain;
bool thread_stack;
bool last_branch;
unsigned int callchain_sz;
@ -166,6 +169,8 @@ struct auxtrace {
struct perf_tool *tool);
void (*free_events)(struct perf_session *session);
void (*free)(struct perf_session *session);
bool (*evsel_is_auxtrace)(struct perf_session *session,
struct evsel *evsel);
};
/**
@ -584,6 +589,8 @@ void auxtrace__dump_auxtrace_sample(struct perf_session *session,
int auxtrace__flush_events(struct perf_session *session, struct perf_tool *tool);
void auxtrace__free_events(struct perf_session *session);
void auxtrace__free(struct perf_session *session);
bool auxtrace__evsel_is_auxtrace(struct perf_session *session,
struct evsel *evsel);
#define ITRACE_HELP \
" i: synthesize instructions events\n" \
@ -749,6 +756,13 @@ void auxtrace_index__free(struct list_head *head __maybe_unused)
{
}
static inline
bool auxtrace__evsel_is_auxtrace(struct perf_session *session __maybe_unused,
struct evsel *evsel __maybe_unused)
{
return false;
}
static inline
int auxtrace_parse_filters(struct evlist *evlist __maybe_unused)
{

View File

@ -6,6 +6,9 @@
#include <bpf/libbpf.h>
#include <linux/btf.h>
#include <linux/err.h>
#include <linux/string.h>
#include <internal/lib.h>
#include <symbol/kallsyms.h>
#include "bpf-event.h"
#include "debug.h"
#include "dso.h"
@ -290,11 +293,82 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
return err ? -1 : 0;
}
struct kallsyms_parse {
union perf_event *event;
perf_event__handler_t process;
struct machine *machine;
struct perf_tool *tool;
};
static int
process_bpf_image(char *name, u64 addr, struct kallsyms_parse *data)
{
struct machine *machine = data->machine;
union perf_event *event = data->event;
struct perf_record_ksymbol *ksymbol;
int len;
ksymbol = &event->ksymbol;
*ksymbol = (struct perf_record_ksymbol) {
.header = {
.type = PERF_RECORD_KSYMBOL,
.size = offsetof(struct perf_record_ksymbol, name),
},
.addr = addr,
.len = page_size,
.ksym_type = PERF_RECORD_KSYMBOL_TYPE_BPF,
.flags = 0,
};
len = scnprintf(ksymbol->name, KSYM_NAME_LEN, "%s", name);
ksymbol->header.size += PERF_ALIGN(len + 1, sizeof(u64));
memset((void *) event + event->header.size, 0, machine->id_hdr_size);
event->header.size += machine->id_hdr_size;
return perf_tool__process_synth_event(data->tool, event, machine,
data->process);
}
static int
kallsyms_process_symbol(void *data, const char *_name,
char type __maybe_unused, u64 start)
{
char disp[KSYM_NAME_LEN];
char *module, *name;
unsigned long id;
int err = 0;
module = strchr(_name, '\t');
if (!module)
return 0;
/* We are going after [bpf] module ... */
if (strcmp(module + 1, "[bpf]"))
return 0;
name = memdup(_name, (module - _name) + 1);
if (!name)
return -ENOMEM;
name[module - _name] = 0;
/* .. and only for trampolines and dispatchers */
if ((sscanf(name, "bpf_trampoline_%lu", &id) == 1) ||
(sscanf(name, "bpf_dispatcher_%s", disp) == 1))
err = process_bpf_image(name, start, data);
free(name);
return err;
}
int perf_event__synthesize_bpf_events(struct perf_session *session,
perf_event__handler_t process,
struct machine *machine,
struct record_opts *opts)
{
const char *kallsyms_filename = "/proc/kallsyms";
struct kallsyms_parse arg;
union perf_event *event;
__u32 id = 0;
int err;
@ -303,6 +377,8 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
event = malloc(sizeof(event->bpf) + KSYM_NAME_LEN + machine->id_hdr_size);
if (!event)
return -1;
/* Synthesize all the bpf programs in system. */
while (true) {
err = bpf_prog_get_next_id(id, &id);
if (err) {
@ -335,6 +411,23 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
break;
}
}
/* Synthesize all the bpf images - trampolines/dispatchers. */
if (symbol_conf.kallsyms_name != NULL)
kallsyms_filename = symbol_conf.kallsyms_name;
arg = (struct kallsyms_parse) {
.event = event,
.process = process,
.machine = machine,
.tool = session->tool,
};
if (kallsyms__parse(kallsyms_filename, &arg, kallsyms_process_symbol)) {
pr_err("%s: failed to synthesize bpf images: %s\n",
__func__, strerror(errno));
}
free(event);
return err;
}

View File

@ -15,13 +15,18 @@
#include "event.h"
struct branch_flags {
u64 mispred:1;
u64 predicted:1;
u64 in_tx:1;
u64 abort:1;
u64 cycles:16;
u64 type:4;
u64 reserved:40;
union {
u64 value;
struct {
u64 mispred:1;
u64 predicted:1;
u64 in_tx:1;
u64 abort:1;
u64 cycles:16;
u64 type:4;
u64 reserved:40;
};
};
};
struct branch_info {

View File

@ -143,6 +143,9 @@ struct callchain_cursor_node {
u64 ip;
struct map_symbol ms;
const char *srcline;
/* Indicate valid cursor node for LBR stitch */
bool valid;
bool branch;
struct branch_flags branch_flags;
u64 branch_from;
@ -151,6 +154,11 @@ struct callchain_cursor_node {
struct callchain_cursor_node *next;
};
struct stitch_list {
struct list_head node;
struct callchain_cursor_node cursor;
};
struct callchain_cursor {
u64 nr;
struct callchain_cursor_node *first;

View File

@ -29,4 +29,8 @@ static inline bool perf_cap__capable(int cap __maybe_unused)
#define CAP_SYSLOG 34
#endif
#ifndef CAP_PERFMON
#define CAP_PERFMON 38
#endif
#endif /* __PERF_CAP_H */

View File

@ -631,6 +631,16 @@ static void cs_etm__free(struct perf_session *session)
zfree(&aux);
}
static bool cs_etm__evsel_is_auxtrace(struct perf_session *session,
struct evsel *evsel)
{
struct cs_etm_auxtrace *aux = container_of(session->auxtrace,
struct cs_etm_auxtrace,
auxtrace);
return evsel->core.attr.type == aux->pmu_type;
}
static u8 cs_etm__cpu_mode(struct cs_etm_queue *etmq, u64 address)
{
struct machine *machine;
@ -2618,6 +2628,7 @@ int cs_etm__process_auxtrace_info(union perf_event *event,
etm->auxtrace.flush_events = cs_etm__flush_events;
etm->auxtrace.free_events = cs_etm__free_events;
etm->auxtrace.free = cs_etm__free;
etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace;
session->auxtrace = &etm->auxtrace;
etm->unknown_thread = thread__new(999999999, 999999999);

View File

@ -191,6 +191,7 @@ int dso__read_binary_type_filename(const struct dso *dso,
case DSO_BINARY_TYPE__GUEST_KALLSYMS:
case DSO_BINARY_TYPE__JAVA_JIT:
case DSO_BINARY_TYPE__BPF_PROG_INFO:
case DSO_BINARY_TYPE__BPF_IMAGE:
case DSO_BINARY_TYPE__NOT_FOUND:
ret = -1;
break;

View File

@ -40,6 +40,7 @@ enum dso_binary_type {
DSO_BINARY_TYPE__GUEST_KCORE,
DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO,
DSO_BINARY_TYPE__BPF_PROG_INFO,
DSO_BINARY_TYPE__BPF_IMAGE,
DSO_BINARY_TYPE__NOT_FOUND,
};

View File

@ -48,6 +48,7 @@ struct perf_env {
char *cpuid;
unsigned long long total_mem;
unsigned int msr_pmu_type;
unsigned int max_branches;
int nr_cmdline;
int nr_sibling_cores;
@ -57,12 +58,14 @@ struct perf_env {
int nr_memory_nodes;
int nr_pmu_mappings;
int nr_groups;
int nr_cpu_pmu_caps;
char *cmdline;
const char **cmdline_argv;
char *sibling_cores;
char *sibling_dies;
char *sibling_threads;
char *pmu_mappings;
char *cpu_pmu_caps;
struct cpu_topology_map *cpu;
struct cpu_cache_level *caches;
int caches_cnt;

View File

@ -1131,8 +1131,10 @@ bool perf_evlist__valid_read_format(struct evlist *evlist)
u64 sample_type = first->core.attr.sample_type;
evlist__for_each_entry(evlist, pos) {
if (read_format != pos->core.attr.read_format)
return false;
if (read_format != pos->core.attr.read_format) {
pr_debug("Read format differs %#" PRIx64 " vs %#" PRIx64 "\n",
read_format, (u64)pos->core.attr.read_format);
}
}
/* PERF_SAMPLE_READ imples PERF_FORMAT_ID. */

View File

@ -1002,25 +1002,6 @@ void perf_evsel__config(struct evsel *evsel, struct record_opts *opts,
}
}
/*
* Disable sampling for all group members other
* than leader in case leader 'leads' the sampling.
*/
if ((leader != evsel) && leader->sample_read) {
attr->freq = 0;
attr->sample_freq = 0;
attr->sample_period = 0;
attr->write_backward = 0;
/*
* We don't get sample for slave events, we make them
* when delivering group leader sample. Set the slave
* event to follow the master sample_type to ease up
* report.
*/
attr->sample_type = leader->core.attr.sample_type;
}
if (opts->no_samples)
attr->sample_freq = 0;
@ -2136,7 +2117,7 @@ int perf_evsel__parse_sample(struct evsel *evsel, union perf_event *event,
}
}
if (evsel__has_callchain(evsel)) {
if (type & PERF_SAMPLE_CALLCHAIN) {
const u64 max_callchain_nr = UINT64_MAX / sizeof(u64);
OVERFLOW_CHECK_u64(array);
@ -2446,6 +2427,10 @@ bool perf_evsel__fallback(struct evsel *evsel, int err,
char *new_name;
const char *sep = ":";
/* If event has exclude user then don't exclude kernel. */
if (evsel->core.attr.exclude_user)
return false;
/* Is there already the separator in the name. */
if (strchr(name, '/') ||
strchr(name, ':'))
@ -2523,14 +2508,14 @@ int perf_evsel__open_strerror(struct evsel *evsel, struct target *target,
"You may not have permission to collect %sstats.\n\n"
"Consider tweaking /proc/sys/kernel/perf_event_paranoid,\n"
"which controls use of the performance events system by\n"
"unprivileged users (without CAP_SYS_ADMIN).\n\n"
"unprivileged users (without CAP_PERFMON or CAP_SYS_ADMIN).\n\n"
"The current value is %d:\n\n"
" -1: Allow use of (almost) all events by all users\n"
" Ignore mlock limit after perf_event_mlock_kb without CAP_IPC_LOCK\n"
">= 0: Disallow ftrace function tracepoint by users without CAP_SYS_ADMIN\n"
" Disallow raw tracepoint access by users without CAP_SYS_ADMIN\n"
">= 1: Disallow CPU event access by users without CAP_SYS_ADMIN\n"
">= 2: Disallow kernel profiling by users without CAP_SYS_ADMIN\n\n"
">= 0: Disallow ftrace function tracepoint by users without CAP_PERFMON or CAP_SYS_ADMIN\n"
" Disallow raw tracepoint access by users without CAP_SYS_PERFMON or CAP_SYS_ADMIN\n"
">= 1: Disallow CPU event access by users without CAP_PERFMON or CAP_SYS_ADMIN\n"
">= 2: Disallow kernel profiling by users without CAP_PERFMON or CAP_SYS_ADMIN\n\n"
"To make this setting permanent, edit /etc/sysctl.conf too, e.g.:\n\n"
" kernel.perf_event_paranoid = -1\n" ,
target->system_wide ? "system-wide " : "",

View File

@ -104,6 +104,14 @@ struct evsel {
perf_evsel__sb_cb_t *cb;
void *data;
} side_band;
/*
* For reporting purposes, an evsel sample can have a callchain
* synthesized from AUX area data. Keep track of synthesized sample
* types here. Note, the recorded sample_type cannot be changed because
* it is needed to continue to parse events.
* See also evsel__has_callchain().
*/
__u64 synth_sample_type;
};
struct perf_missing_features {
@ -150,6 +158,9 @@ int perf_evsel__object_config(size_t object_size,
int (*init)(struct evsel *evsel),
void (*fini)(struct evsel *evsel));
struct perf_pmu *perf_evsel__find_pmu(struct evsel *evsel);
bool perf_evsel__is_aux_event(struct evsel *evsel);
struct evsel *perf_evsel__new_idx(struct perf_event_attr *attr, int idx);
static inline struct evsel *evsel__new(struct perf_event_attr *attr)
@ -398,7 +409,12 @@ static inline bool perf_evsel__has_branch_hw_idx(const struct evsel *evsel)
static inline bool evsel__has_callchain(const struct evsel *evsel)
{
return (evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN) != 0;
/*
* For reporting purposes, an evsel sample can have a recorded callchain
* or a callchain synthesized from AUX area data.
*/
return evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN ||
evsel->synth_sample_type & PERF_SAMPLE_CALLCHAIN;
}
struct perf_env *perf_evsel__env(struct evsel *evsel);

View File

@ -3,7 +3,6 @@
#include <assert.h>
#include "expr.h"
#include "expr-bison.h"
#define YY_EXTRA_TYPE int
#include "expr-flex.h"
#ifdef PARSER_DEBUG
@ -11,7 +10,7 @@ extern int expr_debug;
#endif
/* Caller must make sure id is allocated */
void expr__add_id(struct parse_ctx *ctx, const char *name, double val)
void expr__add_id(struct expr_parse_ctx *ctx, const char *name, double val)
{
int idx;
@ -21,20 +20,23 @@ void expr__add_id(struct parse_ctx *ctx, const char *name, double val)
ctx->ids[idx].val = val;
}
void expr__ctx_init(struct parse_ctx *ctx)
void expr__ctx_init(struct expr_parse_ctx *ctx)
{
ctx->num_ids = 0;
}
static int
__expr__parse(double *val, struct parse_ctx *ctx, const char *expr,
__expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
int start)
{
struct expr_scanner_ctx scanner_ctx = {
.start_token = start,
};
YY_BUFFER_STATE buffer;
void *scanner;
int ret;
ret = expr_lex_init_extra(start, &scanner);
ret = expr_lex_init_extra(&scanner_ctx, &scanner);
if (ret)
return ret;
@ -52,7 +54,7 @@ __expr__parse(double *val, struct parse_ctx *ctx, const char *expr,
return ret;
}
int expr__parse(double *final_val, struct parse_ctx *ctx, const char *expr)
int expr__parse(double *final_val, struct expr_parse_ctx *ctx, const char *expr)
{
return __expr__parse(final_val, ctx, expr, EXPR_PARSE) ? -1 : 0;
}
@ -75,7 +77,7 @@ int expr__find_other(const char *expr, const char *one, const char ***other,
int *num_other)
{
int err, i = 0, j = 0;
struct parse_ctx ctx;
struct expr_parse_ctx ctx;
expr__ctx_init(&ctx);
err = __expr__parse(NULL, &ctx, expr, EXPR_OTHER);

View File

@ -5,19 +5,23 @@
#define EXPR_MAX_OTHER 20
#define MAX_PARSE_ID EXPR_MAX_OTHER
struct parse_id {
struct expr_parse_id {
const char *name;
double val;
};
struct parse_ctx {
struct expr_parse_ctx {
int num_ids;
struct parse_id ids[MAX_PARSE_ID];
struct expr_parse_id ids[MAX_PARSE_ID];
};
void expr__ctx_init(struct parse_ctx *ctx);
void expr__add_id(struct parse_ctx *ctx, const char *id, double val);
int expr__parse(double *final_val, struct parse_ctx *ctx, const char *expr);
struct expr_scanner_ctx {
int start_token;
};
void expr__ctx_init(struct expr_parse_ctx *ctx);
void expr__add_id(struct expr_parse_ctx *ctx, const char *id, double val);
int expr__parse(double *final_val, struct expr_parse_ctx *ctx, const char *expr);
int expr__find_other(const char *expr, const char *one, const char ***other,
int *num_other);

View File

@ -76,13 +76,13 @@ sym [0-9a-zA-Z_\.:@]+
symbol {spec}*{sym}*{spec}*{sym}*
%%
struct expr_scanner_ctx *sctx = expr_get_extra(yyscanner);
{
int start_token;
int start_token = sctx->start_token;
start_token = expr_get_extra(yyscanner);
if (start_token) {
expr_set_extra(NULL, yyscanner);
if (sctx->start_token) {
sctx->start_token = 0;
return start_token;
}
}

View File

@ -15,7 +15,7 @@
%define api.pure full
%parse-param { double *final_val }
%parse-param { struct parse_ctx *ctx }
%parse-param { struct expr_parse_ctx *ctx }
%parse-param {void *scanner}
%lex-param {void* scanner}
@ -39,14 +39,14 @@
%{
static void expr_error(double *final_val __maybe_unused,
struct parse_ctx *ctx __maybe_unused,
struct expr_parse_ctx *ctx __maybe_unused,
void *scanner,
const char *s)
{
pr_debug("%s\n", s);
}
static int lookup_id(struct parse_ctx *ctx, char *id, double *val)
static int lookup_id(struct expr_parse_ctx *ctx, char *id, double *val)
{
int i;

View File

@ -1395,6 +1395,38 @@ static int write_compressed(struct feat_fd *ff __maybe_unused,
return do_write(ff, &(ff->ph->env.comp_mmap_len), sizeof(ff->ph->env.comp_mmap_len));
}
static int write_cpu_pmu_caps(struct feat_fd *ff,
struct evlist *evlist __maybe_unused)
{
struct perf_pmu *cpu_pmu = perf_pmu__find("cpu");
struct perf_pmu_caps *caps = NULL;
int nr_caps;
int ret;
if (!cpu_pmu)
return -ENOENT;
nr_caps = perf_pmu__caps_parse(cpu_pmu);
if (nr_caps < 0)
return nr_caps;
ret = do_write(ff, &nr_caps, sizeof(nr_caps));
if (ret < 0)
return ret;
list_for_each_entry(caps, &cpu_pmu->caps, list) {
ret = do_write_string(ff, caps->name);
if (ret < 0)
return ret;
ret = do_write_string(ff, caps->value);
if (ret < 0)
return ret;
}
return ret;
}
static void print_hostname(struct feat_fd *ff, FILE *fp)
{
fprintf(fp, "# hostname : %s\n", ff->ph->env.hostname);
@ -1809,6 +1841,27 @@ static void print_compressed(struct feat_fd *ff, FILE *fp)
ff->ph->env.comp_level, ff->ph->env.comp_ratio);
}
static void print_cpu_pmu_caps(struct feat_fd *ff, FILE *fp)
{
const char *delimiter = "# cpu pmu capabilities: ";
u32 nr_caps = ff->ph->env.nr_cpu_pmu_caps;
char *str;
if (!nr_caps) {
fprintf(fp, "# cpu pmu capabilities: not available\n");
return;
}
str = ff->ph->env.cpu_pmu_caps;
while (nr_caps--) {
fprintf(fp, "%s%s", delimiter, str);
delimiter = ", ";
str += strlen(str) + 1;
}
fprintf(fp, "\n");
}
static void print_pmu_mappings(struct feat_fd *ff, FILE *fp)
{
const char *delimiter = "# pmu mappings: ";
@ -2846,6 +2899,60 @@ static int process_compressed(struct feat_fd *ff,
return 0;
}
static int process_cpu_pmu_caps(struct feat_fd *ff,
void *data __maybe_unused)
{
char *name, *value;
struct strbuf sb;
u32 nr_caps;
if (do_read_u32(ff, &nr_caps))
return -1;
if (!nr_caps) {
pr_debug("cpu pmu capabilities not available\n");
return 0;
}
ff->ph->env.nr_cpu_pmu_caps = nr_caps;
if (strbuf_init(&sb, 128) < 0)
return -1;
while (nr_caps--) {
name = do_read_string(ff);
if (!name)
goto error;
value = do_read_string(ff);
if (!value)
goto free_name;
if (strbuf_addf(&sb, "%s=%s", name, value) < 0)
goto free_value;
/* include a NULL character at the end */
if (strbuf_add(&sb, "", 1) < 0)
goto free_value;
if (!strcmp(name, "branches"))
ff->ph->env.max_branches = atoi(value);
free(value);
free(name);
}
ff->ph->env.cpu_pmu_caps = strbuf_detach(&sb, NULL);
return 0;
free_value:
free(value);
free_name:
free(name);
error:
strbuf_release(&sb);
return -1;
}
#define FEAT_OPR(n, func, __full_only) \
[HEADER_##n] = { \
.name = __stringify(n), \
@ -2903,6 +3010,7 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = {
FEAT_OPR(BPF_PROG_INFO, bpf_prog_info, false),
FEAT_OPR(BPF_BTF, bpf_btf, false),
FEAT_OPR(COMPRESSED, compressed, false),
FEAT_OPR(CPU_PMU_CAPS, cpu_pmu_caps, false),
};
struct header_print_data {

View File

@ -43,6 +43,7 @@ enum {
HEADER_BPF_PROG_INFO,
HEADER_BPF_BTF,
HEADER_COMPRESSED,
HEADER_CPU_PMU_CAPS,
HEADER_LAST_FEATURE,
HEADER_FEAT_BITS = 256,
};

View File

@ -1070,6 +1070,20 @@ iter_next_cumulative_entry(struct hist_entry_iter *iter,
return fill_callchain_info(al, node, iter->hide_unresolved);
}
static bool
hist_entry__fast__sym_diff(struct hist_entry *left,
struct hist_entry *right)
{
struct symbol *sym_l = left->ms.sym;
struct symbol *sym_r = right->ms.sym;
if (!sym_l && !sym_r)
return left->ip != right->ip;
return !!_sort__sym_cmp(sym_l, sym_r);
}
static int
iter_add_next_cumulative_entry(struct hist_entry_iter *iter,
struct addr_location *al)
@ -1096,6 +1110,7 @@ iter_add_next_cumulative_entry(struct hist_entry_iter *iter,
};
int i;
struct callchain_cursor cursor;
bool fast = hists__has(he_tmp.hists, sym);
callchain_cursor_snapshot(&cursor, &callchain_cursor);
@ -1106,6 +1121,14 @@ iter_add_next_cumulative_entry(struct hist_entry_iter *iter,
* It's possible that it has cycles or recursive calls.
*/
for (i = 0; i < iter->curr; i++) {
/*
* For most cases, there are no duplicate entries in callchain.
* The symbols are usually different. Do a quick check for
* symbols first.
*/
if (fast && hist_entry__fast__sym_diff(he_cache[i], &he_tmp))
continue;
if (hist_entry__cmp(he_cache[i], &he_tmp) == 0) {
/* to avoid calling callback function */
iter->he = NULL;

View File

@ -728,6 +728,15 @@ static void intel_bts_free(struct perf_session *session)
free(bts);
}
static bool intel_bts_evsel_is_auxtrace(struct perf_session *session,
struct evsel *evsel)
{
struct intel_bts *bts = container_of(session->auxtrace, struct intel_bts,
auxtrace);
return evsel->core.attr.type == bts->pmu_type;
}
struct intel_bts_synth {
struct perf_tool dummy_tool;
struct perf_session *session;
@ -883,6 +892,7 @@ int intel_bts_process_auxtrace_info(union perf_event *event,
bts->auxtrace.flush_events = intel_bts_flush;
bts->auxtrace.free_events = intel_bts_free_events;
bts->auxtrace.free = intel_bts_free;
bts->auxtrace.evsel_is_auxtrace = intel_bts_evsel_is_auxtrace;
session->auxtrace = &bts->auxtrace;
intel_bts_print_info(&auxtrace_info->priv[0], INTEL_BTS_PMU_TYPE,

View File

@ -124,6 +124,8 @@ struct intel_pt {
struct range *time_ranges;
unsigned int range_cnt;
struct ip_callchain *chain;
};
enum switch_state {
@ -868,6 +870,45 @@ static u64 intel_pt_ns_to_ticks(const struct intel_pt *pt, u64 ns)
pt->tc.time_mult;
}
static struct ip_callchain *intel_pt_alloc_chain(struct intel_pt *pt)
{
size_t sz = sizeof(struct ip_callchain);
/* Add 1 to callchain_sz for callchain context */
sz += (pt->synth_opts.callchain_sz + 1) * sizeof(u64);
return zalloc(sz);
}
static int intel_pt_callchain_init(struct intel_pt *pt)
{
struct evsel *evsel;
evlist__for_each_entry(pt->session->evlist, evsel) {
if (!(evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN))
evsel->synth_sample_type |= PERF_SAMPLE_CALLCHAIN;
}
pt->chain = intel_pt_alloc_chain(pt);
if (!pt->chain)
return -ENOMEM;
return 0;
}
static void intel_pt_add_callchain(struct intel_pt *pt,
struct perf_sample *sample)
{
struct thread *thread = machine__findnew_thread(pt->machine,
sample->pid,
sample->tid);
thread_stack__sample_late(thread, sample->cpu, pt->chain,
pt->synth_opts.callchain_sz + 1, sample->ip,
pt->kernel_start);
sample->callchain = pt->chain;
}
static struct intel_pt_queue *intel_pt_alloc_queue(struct intel_pt *pt,
unsigned int queue_nr)
{
@ -880,11 +921,7 @@ static struct intel_pt_queue *intel_pt_alloc_queue(struct intel_pt *pt,
return NULL;
if (pt->synth_opts.callchain) {
size_t sz = sizeof(struct ip_callchain);
/* Add 1 to callchain_sz for callchain context */
sz += (pt->synth_opts.callchain_sz + 1) * sizeof(u64);
ptq->chain = zalloc(sz);
ptq->chain = intel_pt_alloc_chain(pt);
if (!ptq->chain)
goto out_free;
}
@ -1680,15 +1717,14 @@ static u64 intel_pt_lbr_flags(u64 info)
union {
struct branch_flags flags;
u64 result;
} u = {
.flags = {
.mispred = !!(info & LBR_INFO_MISPRED),
.predicted = !(info & LBR_INFO_MISPRED),
.in_tx = !!(info & LBR_INFO_IN_TX),
.abort = !!(info & LBR_INFO_ABORT),
.cycles = info & LBR_INFO_CYCLES,
}
};
} u;
u.result = 0;
u.flags.mispred = !!(info & LBR_INFO_MISPRED);
u.flags.predicted = !(info & LBR_INFO_MISPRED);
u.flags.in_tx = !!(info & LBR_INFO_IN_TX);
u.flags.abort = !!(info & LBR_INFO_ABORT);
u.flags.cycles = info & LBR_INFO_CYCLES;
return u.result;
}
@ -1992,7 +2028,8 @@ static int intel_pt_sample(struct intel_pt_queue *ptq)
if (!(state->type & INTEL_PT_BRANCH))
return 0;
if (pt->synth_opts.callchain || pt->synth_opts.thread_stack)
if (pt->synth_opts.callchain || pt->synth_opts.add_callchain ||
pt->synth_opts.thread_stack)
thread_stack__event(ptq->thread, ptq->cpu, ptq->flags, state->from_ip,
state->to_ip, ptq->insn_len,
state->trace_nr);
@ -2639,6 +2676,11 @@ static int intel_pt_process_event(struct perf_session *session,
if (err)
return err;
if (event->header.type == PERF_RECORD_SAMPLE) {
if (pt->synth_opts.add_callchain && !sample->callchain)
intel_pt_add_callchain(pt, sample);
}
if (event->header.type == PERF_RECORD_AUX &&
(event->aux.flags & PERF_AUX_FLAG_TRUNCATED) &&
pt->synth_opts.errors) {
@ -2710,11 +2752,21 @@ static void intel_pt_free(struct perf_session *session)
session->auxtrace = NULL;
thread__put(pt->unknown_thread);
addr_filters__exit(&pt->filts);
zfree(&pt->chain);
zfree(&pt->filter);
zfree(&pt->time_ranges);
free(pt);
}
static bool intel_pt_evsel_is_auxtrace(struct perf_session *session,
struct evsel *evsel)
{
struct intel_pt *pt = container_of(session->auxtrace, struct intel_pt,
auxtrace);
return evsel->core.attr.type == pt->pmu_type;
}
static int intel_pt_process_auxtrace_event(struct perf_session *session,
union perf_event *event,
struct perf_tool *tool __maybe_unused)
@ -3310,6 +3362,7 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
pt->auxtrace.flush_events = intel_pt_flush;
pt->auxtrace.free_events = intel_pt_free_events;
pt->auxtrace.free = intel_pt_free;
pt->auxtrace.evsel_is_auxtrace = intel_pt_evsel_is_auxtrace;
session->auxtrace = &pt->auxtrace;
if (dump_trace)
@ -3338,6 +3391,7 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
!session->itrace_synth_opts->inject) {
pt->synth_opts.branches = false;
pt->synth_opts.callchain = true;
pt->synth_opts.add_callchain = true;
}
pt->synth_opts.thread_stack =
session->itrace_synth_opts->thread_stack;
@ -3370,14 +3424,22 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
pt->branches_filter |= PERF_IP_FLAG_RETURN |
PERF_IP_FLAG_TRACE_BEGIN;
if (pt->synth_opts.callchain && !symbol_conf.use_callchain) {
if ((pt->synth_opts.callchain || pt->synth_opts.add_callchain) &&
!symbol_conf.use_callchain) {
symbol_conf.use_callchain = true;
if (callchain_register_param(&callchain_param) < 0) {
symbol_conf.use_callchain = false;
pt->synth_opts.callchain = false;
pt->synth_opts.add_callchain = false;
}
}
if (pt->synth_opts.add_callchain) {
err = intel_pt_callchain_init(pt);
if (err)
goto err_delete_thread;
}
err = intel_pt_synth_events(pt, session);
if (err)
goto err_delete_thread;
@ -3400,6 +3462,7 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
return 0;
err_delete_thread:
zfree(&pt->chain);
thread__zput(pt->unknown_thread);
err_free_queues:
intel_pt_log_disable();

View File

@ -736,6 +736,12 @@ int machine__process_switch_event(struct machine *machine __maybe_unused,
return 0;
}
static int is_bpf_image(const char *name)
{
return strncmp(name, "bpf_trampoline_", sizeof("bpf_trampoline_") - 1) ||
strncmp(name, "bpf_dispatcher_", sizeof("bpf_dispatcher_") - 1);
}
static int machine__process_ksymbol_register(struct machine *machine,
union perf_event *event,
struct perf_sample *sample __maybe_unused)
@ -759,6 +765,12 @@ static int machine__process_ksymbol_register(struct machine *machine,
map->start = event->ksymbol.addr;
map->end = map->start + event->ksymbol.len;
maps__insert(&machine->kmaps, map);
dso__set_loaded(dso);
if (is_bpf_image(event->ksymbol.name)) {
dso->binary_type = DSO_BINARY_TYPE__BPF_IMAGE;
dso__set_long_name(dso, "", false);
}
}
sym = symbol__new(map->map_ip(map, map->start),
@ -2178,6 +2190,303 @@ static int remove_loops(struct branch_entry *l, int nr,
return nr;
}
static int lbr_callchain_add_kernel_ip(struct thread *thread,
struct callchain_cursor *cursor,
struct perf_sample *sample,
struct symbol **parent,
struct addr_location *root_al,
u64 branch_from,
bool callee, int end)
{
struct ip_callchain *chain = sample->callchain;
u8 cpumode = PERF_RECORD_MISC_USER;
int err, i;
if (callee) {
for (i = 0; i < end + 1; i++) {
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, chain->ips[i],
false, NULL, NULL, branch_from);
if (err)
return err;
}
return 0;
}
for (i = end; i >= 0; i--) {
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, chain->ips[i],
false, NULL, NULL, branch_from);
if (err)
return err;
}
return 0;
}
static void save_lbr_cursor_node(struct thread *thread,
struct callchain_cursor *cursor,
int idx)
{
struct lbr_stitch *lbr_stitch = thread->lbr_stitch;
if (!lbr_stitch)
return;
if (cursor->pos == cursor->nr) {
lbr_stitch->prev_lbr_cursor[idx].valid = false;
return;
}
if (!cursor->curr)
cursor->curr = cursor->first;
else
cursor->curr = cursor->curr->next;
memcpy(&lbr_stitch->prev_lbr_cursor[idx], cursor->curr,
sizeof(struct callchain_cursor_node));
lbr_stitch->prev_lbr_cursor[idx].valid = true;
cursor->pos++;
}
static int lbr_callchain_add_lbr_ip(struct thread *thread,
struct callchain_cursor *cursor,
struct perf_sample *sample,
struct symbol **parent,
struct addr_location *root_al,
u64 *branch_from,
bool callee)
{
struct branch_stack *lbr_stack = sample->branch_stack;
struct branch_entry *entries = perf_sample__branch_entries(sample);
u8 cpumode = PERF_RECORD_MISC_USER;
int lbr_nr = lbr_stack->nr;
struct branch_flags *flags;
int err, i;
u64 ip;
/*
* The curr and pos are not used in writing session. They are cleared
* in callchain_cursor_commit() when the writing session is closed.
* Using curr and pos to track the current cursor node.
*/
if (thread->lbr_stitch) {
cursor->curr = NULL;
cursor->pos = cursor->nr;
if (cursor->nr) {
cursor->curr = cursor->first;
for (i = 0; i < (int)(cursor->nr - 1); i++)
cursor->curr = cursor->curr->next;
}
}
if (callee) {
/* Add LBR ip from first entries.to */
ip = entries[0].to;
flags = &entries[0].flags;
*branch_from = entries[0].from;
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, ip,
true, flags, NULL,
*branch_from);
if (err)
return err;
/*
* The number of cursor node increases.
* Move the current cursor node.
* But does not need to save current cursor node for entry 0.
* It's impossible to stitch the whole LBRs of previous sample.
*/
if (thread->lbr_stitch && (cursor->pos != cursor->nr)) {
if (!cursor->curr)
cursor->curr = cursor->first;
else
cursor->curr = cursor->curr->next;
cursor->pos++;
}
/* Add LBR ip from entries.from one by one. */
for (i = 0; i < lbr_nr; i++) {
ip = entries[i].from;
flags = &entries[i].flags;
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, ip,
true, flags, NULL,
*branch_from);
if (err)
return err;
save_lbr_cursor_node(thread, cursor, i);
}
return 0;
}
/* Add LBR ip from entries.from one by one. */
for (i = lbr_nr - 1; i >= 0; i--) {
ip = entries[i].from;
flags = &entries[i].flags;
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, ip,
true, flags, NULL,
*branch_from);
if (err)
return err;
save_lbr_cursor_node(thread, cursor, i);
}
/* Add LBR ip from first entries.to */
ip = entries[0].to;
flags = &entries[0].flags;
*branch_from = entries[0].from;
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, ip,
true, flags, NULL,
*branch_from);
if (err)
return err;
return 0;
}
static int lbr_callchain_add_stitched_lbr_ip(struct thread *thread,
struct callchain_cursor *cursor)
{
struct lbr_stitch *lbr_stitch = thread->lbr_stitch;
struct callchain_cursor_node *cnode;
struct stitch_list *stitch_node;
int err;
list_for_each_entry(stitch_node, &lbr_stitch->lists, node) {
cnode = &stitch_node->cursor;
err = callchain_cursor_append(cursor, cnode->ip,
&cnode->ms,
cnode->branch,
&cnode->branch_flags,
cnode->nr_loop_iter,
cnode->iter_cycles,
cnode->branch_from,
cnode->srcline);
if (err)
return err;
}
return 0;
}
static struct stitch_list *get_stitch_node(struct thread *thread)
{
struct lbr_stitch *lbr_stitch = thread->lbr_stitch;
struct stitch_list *stitch_node;
if (!list_empty(&lbr_stitch->free_lists)) {
stitch_node = list_first_entry(&lbr_stitch->free_lists,
struct stitch_list, node);
list_del(&stitch_node->node);
return stitch_node;
}
return malloc(sizeof(struct stitch_list));
}
static bool has_stitched_lbr(struct thread *thread,
struct perf_sample *cur,
struct perf_sample *prev,
unsigned int max_lbr,
bool callee)
{
struct branch_stack *cur_stack = cur->branch_stack;
struct branch_entry *cur_entries = perf_sample__branch_entries(cur);
struct branch_stack *prev_stack = prev->branch_stack;
struct branch_entry *prev_entries = perf_sample__branch_entries(prev);
struct lbr_stitch *lbr_stitch = thread->lbr_stitch;
int i, j, nr_identical_branches = 0;
struct stitch_list *stitch_node;
u64 cur_base, distance;
if (!cur_stack || !prev_stack)
return false;
/* Find the physical index of the base-of-stack for current sample. */
cur_base = max_lbr - cur_stack->nr + cur_stack->hw_idx + 1;
distance = (prev_stack->hw_idx > cur_base) ? (prev_stack->hw_idx - cur_base) :
(max_lbr + prev_stack->hw_idx - cur_base);
/* Previous sample has shorter stack. Nothing can be stitched. */
if (distance + 1 > prev_stack->nr)
return false;
/*
* Check if there are identical LBRs between two samples.
* Identicall LBRs must have same from, to and flags values. Also,
* they have to be saved in the same LBR registers (same physical
* index).
*
* Starts from the base-of-stack of current sample.
*/
for (i = distance, j = cur_stack->nr - 1; (i >= 0) && (j >= 0); i--, j--) {
if ((prev_entries[i].from != cur_entries[j].from) ||
(prev_entries[i].to != cur_entries[j].to) ||
(prev_entries[i].flags.value != cur_entries[j].flags.value))
break;
nr_identical_branches++;
}
if (!nr_identical_branches)
return false;
/*
* Save the LBRs between the base-of-stack of previous sample
* and the base-of-stack of current sample into lbr_stitch->lists.
* These LBRs will be stitched later.
*/
for (i = prev_stack->nr - 1; i > (int)distance; i--) {
if (!lbr_stitch->prev_lbr_cursor[i].valid)
continue;
stitch_node = get_stitch_node(thread);
if (!stitch_node)
return false;
memcpy(&stitch_node->cursor, &lbr_stitch->prev_lbr_cursor[i],
sizeof(struct callchain_cursor_node));
if (callee)
list_add(&stitch_node->node, &lbr_stitch->lists);
else
list_add_tail(&stitch_node->node, &lbr_stitch->lists);
}
return true;
}
static bool alloc_lbr_stitch(struct thread *thread, unsigned int max_lbr)
{
if (thread->lbr_stitch)
return true;
thread->lbr_stitch = zalloc(sizeof(*thread->lbr_stitch));
if (!thread->lbr_stitch)
goto err;
thread->lbr_stitch->prev_lbr_cursor = calloc(max_lbr + 1, sizeof(struct callchain_cursor_node));
if (!thread->lbr_stitch->prev_lbr_cursor)
goto free_lbr_stitch;
INIT_LIST_HEAD(&thread->lbr_stitch->lists);
INIT_LIST_HEAD(&thread->lbr_stitch->free_lists);
return true;
free_lbr_stitch:
zfree(&thread->lbr_stitch);
err:
pr_warning("Failed to allocate space for stitched LBRs. Disable LBR stitch\n");
thread->lbr_stitch_enable = false;
return false;
}
/*
* Recolve LBR callstack chain sample
* Return:
@ -2190,12 +2499,16 @@ static int resolve_lbr_callchain_sample(struct thread *thread,
struct perf_sample *sample,
struct symbol **parent,
struct addr_location *root_al,
int max_stack)
int max_stack,
unsigned int max_lbr)
{
bool callee = (callchain_param.order == ORDER_CALLEE);
struct ip_callchain *chain = sample->callchain;
int chain_nr = min(max_stack, (int)chain->nr), i;
u8 cpumode = PERF_RECORD_MISC_USER;
u64 ip, branch_from = 0;
struct lbr_stitch *lbr_stitch;
bool stitched_lbr = false;
u64 branch_from = 0;
int err;
for (i = 0; i < chain_nr; i++) {
if (chain->ips[i] == PERF_CONTEXT_USER)
@ -2203,71 +2516,65 @@ static int resolve_lbr_callchain_sample(struct thread *thread,
}
/* LBR only affects the user callchain */
if (i != chain_nr) {
struct branch_stack *lbr_stack = sample->branch_stack;
struct branch_entry *entries = perf_sample__branch_entries(sample);
int lbr_nr = lbr_stack->nr, j, k;
bool branch;
struct branch_flags *flags;
/*
* LBR callstack can only get user call chain.
* The mix_chain_nr is kernel call chain
* number plus LBR user call chain number.
* i is kernel call chain number,
* 1 is PERF_CONTEXT_USER,
* lbr_nr + 1 is the user call chain number.
* For details, please refer to the comments
* in callchain__printf
*/
int mix_chain_nr = i + 1 + lbr_nr + 1;
if (i == chain_nr)
return 0;
for (j = 0; j < mix_chain_nr; j++) {
int err;
branch = false;
flags = NULL;
if (thread->lbr_stitch_enable && !sample->no_hw_idx &&
(max_lbr > 0) && alloc_lbr_stitch(thread, max_lbr)) {
lbr_stitch = thread->lbr_stitch;
if (callchain_param.order == ORDER_CALLEE) {
if (j < i + 1)
ip = chain->ips[j];
else if (j > i + 1) {
k = j - i - 2;
ip = entries[k].from;
branch = true;
flags = &entries[k].flags;
} else {
ip = entries[0].to;
branch = true;
flags = &entries[0].flags;
branch_from = entries[0].from;
}
} else {
if (j < lbr_nr) {
k = lbr_nr - j - 1;
ip = entries[k].from;
branch = true;
flags = &entries[k].flags;
}
else if (j > lbr_nr)
ip = chain->ips[i + 1 - (j - lbr_nr)];
else {
ip = entries[0].to;
branch = true;
flags = &entries[0].flags;
branch_from = entries[0].from;
}
}
stitched_lbr = has_stitched_lbr(thread, sample,
&lbr_stitch->prev_sample,
max_lbr, callee);
err = add_callchain_ip(thread, cursor, parent,
root_al, &cpumode, ip,
branch, flags, NULL,
branch_from);
if (err)
return (err < 0) ? err : 0;
if (!stitched_lbr && !list_empty(&lbr_stitch->lists)) {
list_replace_init(&lbr_stitch->lists,
&lbr_stitch->free_lists);
}
return 1;
memcpy(&lbr_stitch->prev_sample, sample, sizeof(*sample));
}
return 0;
if (callee) {
/* Add kernel ip */
err = lbr_callchain_add_kernel_ip(thread, cursor, sample,
parent, root_al, branch_from,
true, i);
if (err)
goto error;
err = lbr_callchain_add_lbr_ip(thread, cursor, sample, parent,
root_al, &branch_from, true);
if (err)
goto error;
if (stitched_lbr) {
err = lbr_callchain_add_stitched_lbr_ip(thread, cursor);
if (err)
goto error;
}
} else {
if (stitched_lbr) {
err = lbr_callchain_add_stitched_lbr_ip(thread, cursor);
if (err)
goto error;
}
err = lbr_callchain_add_lbr_ip(thread, cursor, sample, parent,
root_al, &branch_from, false);
if (err)
goto error;
/* Add kernel ip */
err = lbr_callchain_add_kernel_ip(thread, cursor, sample,
parent, root_al, branch_from,
false, i);
if (err)
goto error;
}
return 1;
error:
return (err < 0) ? err : 0;
}
static int find_prev_cpumode(struct ip_callchain *chain, struct thread *thread,
@ -2312,8 +2619,11 @@ static int thread__resolve_callchain_sample(struct thread *thread,
chain_nr = chain->nr;
if (perf_evsel__has_branch_callstack(evsel)) {
struct perf_env *env = perf_evsel__env(evsel);
err = resolve_lbr_callchain_sample(thread, cursor, sample, parent,
root_al, max_stack);
root_al, max_stack,
!env ? 0 : env->max_branches);
if (err)
return (err < 0) ? err : 0;
}

View File

@ -485,6 +485,39 @@ static bool metricgroup__has_constraint(struct pmu_event *pe)
return false;
}
static int __metricgroup__add_metric(struct strbuf *events,
struct list_head *group_list, struct pmu_event *pe)
{
const char **ids;
int idnum;
struct egroup *eg;
if (expr__find_other(pe->metric_expr, NULL, &ids, &idnum) < 0)
return -EINVAL;
if (events->len > 0)
strbuf_addf(events, ",");
if (metricgroup__has_constraint(pe))
metricgroup__add_metric_non_group(events, ids, idnum);
else
metricgroup__add_metric_weak_group(events, ids, idnum);
eg = malloc(sizeof(*eg));
if (!eg)
return -ENOMEM;
eg->ids = ids;
eg->idnum = idnum;
eg->metric_name = pe->metric_name;
eg->metric_expr = pe->metric_expr;
eg->metric_unit = pe->unit;
list_add_tail(&eg->nd, group_list);
return 0;
}
static int metricgroup__add_metric(const char *metric, struct strbuf *events,
struct list_head *group_list)
{
@ -504,35 +537,12 @@ static int metricgroup__add_metric(const char *metric, struct strbuf *events,
continue;
if (match_metric(pe->metric_group, metric) ||
match_metric(pe->metric_name, metric)) {
const char **ids;
int idnum;
struct egroup *eg;
pr_debug("metric expr %s for %s\n", pe->metric_expr, pe->metric_name);
if (expr__find_other(pe->metric_expr,
NULL, &ids, &idnum) < 0)
continue;
if (events->len > 0)
strbuf_addf(events, ",");
if (metricgroup__has_constraint(pe))
metricgroup__add_metric_non_group(events, ids, idnum);
else
metricgroup__add_metric_weak_group(events, ids, idnum);
eg = malloc(sizeof(struct egroup));
if (!eg) {
ret = -ENOMEM;
ret = __metricgroup__add_metric(events, group_list, pe);
if (ret == -ENOMEM)
break;
}
eg->ids = ids;
eg->idnum = idnum;
eg->metric_name = pe->metric_name;
eg->metric_expr = pe->metric_expr;
eg->metric_unit = pe->unit;
list_add_tail(&eg->nd, group_list);
ret = 0;
}
}
return ret;

View File

@ -286,6 +286,7 @@ no-overwrite { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_NOOVERWRITE); }
percore { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_PERCORE); }
aux-output { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_AUX_OUTPUT); }
aux-sample-size { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_AUX_SAMPLE_SIZE); }
r{num_raw_hex} { return raw(yyscanner); }
, { return ','; }
"/" { BEGIN(INITIAL); return '/'; }
{name_minus} { return str(yyscanner, PE_NAME); }

View File

@ -706,6 +706,15 @@ event_term
}
event_term:
PE_RAW
{
struct parse_events_term *term;
ABORT_ON(parse_events_term__num(&term, PARSE_EVENTS__TERM_TYPE_CONFIG,
NULL, $1, false, &@1, NULL));
$$ = term;
}
|
PE_NAME '=' PE_NAME
{
struct parse_events_term *term;

View File

@ -18,6 +18,7 @@
#include <regex.h>
#include <perf/cpumap.h>
#include "debug.h"
#include "evsel.h"
#include "pmu.h"
#include "parse-events.h"
#include "header.h"
@ -849,6 +850,7 @@ static struct perf_pmu *pmu_lookup(const char *name)
INIT_LIST_HEAD(&pmu->format);
INIT_LIST_HEAD(&pmu->aliases);
INIT_LIST_HEAD(&pmu->caps);
list_splice(&format, &pmu->format);
list_splice(&aliases, &pmu->aliases);
list_add_tail(&pmu->list, &pmus);
@ -884,6 +886,25 @@ struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu)
return NULL;
}
struct perf_pmu *perf_evsel__find_pmu(struct evsel *evsel)
{
struct perf_pmu *pmu = NULL;
while ((pmu = perf_pmu__scan(pmu)) != NULL) {
if (pmu->type == evsel->core.attr.type)
break;
}
return pmu;
}
bool perf_evsel__is_aux_event(struct evsel *evsel)
{
struct perf_pmu *pmu = perf_evsel__find_pmu(evsel);
return pmu && pmu->auxtrace;
}
struct perf_pmu *perf_pmu__find(const char *name)
{
struct perf_pmu *pmu;
@ -1574,3 +1595,84 @@ int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt,
va_end(args);
return ret;
}
static int perf_pmu__new_caps(struct list_head *list, char *name, char *value)
{
struct perf_pmu_caps *caps = zalloc(sizeof(*caps));
if (!caps)
return -ENOMEM;
caps->name = strdup(name);
if (!caps->name)
goto free_caps;
caps->value = strndup(value, strlen(value) - 1);
if (!caps->value)
goto free_name;
list_add_tail(&caps->list, list);
return 0;
free_name:
zfree(caps->name);
free_caps:
free(caps);
return -ENOMEM;
}
/*
* Reading/parsing the given pmu capabilities, which should be located at:
* /sys/bus/event_source/devices/<dev>/caps as sysfs group attributes.
* Return the number of capabilities
*/
int perf_pmu__caps_parse(struct perf_pmu *pmu)
{
struct stat st;
char caps_path[PATH_MAX];
const char *sysfs = sysfs__mountpoint();
DIR *caps_dir;
struct dirent *evt_ent;
int nr_caps = 0;
if (!sysfs)
return -1;
snprintf(caps_path, PATH_MAX,
"%s" EVENT_SOURCE_DEVICE_PATH "%s/caps", sysfs, pmu->name);
if (stat(caps_path, &st) < 0)
return 0; /* no error if caps does not exist */
caps_dir = opendir(caps_path);
if (!caps_dir)
return -EINVAL;
while ((evt_ent = readdir(caps_dir)) != NULL) {
char path[PATH_MAX + NAME_MAX + 1];
char *name = evt_ent->d_name;
char value[128];
FILE *file;
if (!strcmp(name, ".") || !strcmp(name, ".."))
continue;
snprintf(path, sizeof(path), "%s/%s", caps_path, name);
file = fopen(path, "r");
if (!file)
continue;
if (!fgets(value, sizeof(value), file) ||
(perf_pmu__new_caps(&pmu->caps, name, value) < 0)) {
fclose(file);
continue;
}
nr_caps++;
fclose(file);
}
closedir(caps_dir);
return nr_caps;
}

View File

@ -22,6 +22,12 @@ enum {
struct perf_event_attr;
struct perf_pmu_caps {
char *name;
char *value;
struct list_head list;
};
struct perf_pmu {
char *name;
__u32 type;
@ -33,6 +39,7 @@ struct perf_pmu {
struct perf_cpu_map *cpus;
struct list_head format; /* HEAD struct perf_pmu_format -> list */
struct list_head aliases; /* HEAD struct perf_pmu_alias -> list */
struct list_head caps; /* HEAD struct perf_pmu_caps -> list */
struct list_head list; /* ELEM */
};
@ -107,4 +114,6 @@ bool pmu_uncore_alias_match(const char *pmu_name, const char *name);
int perf_pmu__convert_scale(const char *scale, char **end, double *sval);
int perf_pmu__caps_parse(struct perf_pmu *pmu);
#endif /* __PMU_H */

View File

@ -167,6 +167,64 @@ bool perf_can_aux_sample(void)
return true;
}
/*
* perf_evsel__config_leader_sampling() uses special rules for leader sampling.
* However, if the leader is an AUX area event, then assume the event to sample
* is the next event.
*/
static struct evsel *perf_evsel__read_sampler(struct evsel *evsel,
struct evlist *evlist)
{
struct evsel *leader = evsel->leader;
if (perf_evsel__is_aux_event(leader)) {
evlist__for_each_entry(evlist, evsel) {
if (evsel->leader == leader && evsel != evsel->leader)
return evsel;
}
}
return leader;
}
static void perf_evsel__config_leader_sampling(struct evsel *evsel,
struct evlist *evlist)
{
struct perf_event_attr *attr = &evsel->core.attr;
struct evsel *leader = evsel->leader;
struct evsel *read_sampler;
if (!leader->sample_read)
return;
read_sampler = perf_evsel__read_sampler(evsel, evlist);
if (evsel == read_sampler)
return;
/*
* Disable sampling for all group members other than the leader in
* case the leader 'leads' the sampling, except when the leader is an
* AUX area event, in which case the 2nd event in the group is the one
* that 'leads' the sampling.
*/
attr->freq = 0;
attr->sample_freq = 0;
attr->sample_period = 0;
attr->write_backward = 0;
/*
* We don't get a sample for slave events, we make them when delivering
* the group leader sample. Set the slave event to follow the master
* sample_type to ease up reporting.
* An AUX area event also has sample_type requirements, so also include
* the sample type bits from the leader's sample_type to cover that
* case.
*/
attr->sample_type = read_sampler->core.attr.sample_type |
leader->core.attr.sample_type;
}
void perf_evlist__config(struct evlist *evlist, struct record_opts *opts,
struct callchain_param *callchain)
{
@ -193,6 +251,10 @@ void perf_evlist__config(struct evlist *evlist, struct record_opts *opts,
evsel->core.attr.comm_exec = 1;
}
/* Configure leader sampling here now that the sample type is known */
evlist__for_each_entry(evlist, evsel)
perf_evsel__config_leader_sampling(evsel, evlist);
if (opts->full_auxtrace) {
/*
* Need to be able to synthesize and parse selected events with

View File

@ -11,6 +11,7 @@
#define S390_CPUMCF_DIAG_DEF 0xfeef /* Counter diagnostic entry ID */
#define PERF_EVENT_CPUM_CF_DIAG 0xBC000 /* Event: Counter sets */
#define PERF_EVENT_CPUM_SF_DIAG 0xBD000 /* Event: Combined-sampling */
struct cf_ctrset_entry { /* CPU-M CF counter set entry (8 byte) */
unsigned int def:16; /* 0-15 Data Entry Format */

View File

@ -1047,6 +1047,14 @@ static void s390_cpumsf_free(struct perf_session *session)
free(sf);
}
static bool
s390_cpumsf_evsel_is_auxtrace(struct perf_session *session __maybe_unused,
struct evsel *evsel)
{
return evsel->core.attr.type == PERF_TYPE_RAW &&
evsel->core.attr.config == PERF_EVENT_CPUM_SF_DIAG;
}
static int s390_cpumsf_get_type(const char *cpuid)
{
int ret, family = 0;
@ -1071,7 +1079,7 @@ static bool check_auxtrace_itrace(struct itrace_synth_opts *itops)
itops->pwr_events || itops->errors ||
itops->dont_decode || itops->calls || itops->returns ||
itops->callchain || itops->thread_stack ||
itops->last_branch;
itops->last_branch || itops->add_callchain;
if (!ison)
return true;
pr_err("Unsupported --itrace options specified\n");
@ -1142,6 +1150,7 @@ int s390_cpumsf_process_auxtrace_info(union perf_event *event,
sf->auxtrace.flush_events = s390_cpumsf_flush;
sf->auxtrace.free_events = s390_cpumsf_free_events;
sf->auxtrace.free = s390_cpumsf_free;
sf->auxtrace.evsel_is_auxtrace = s390_cpumsf_evsel_is_auxtrace;
session->auxtrace = &sf->auxtrace;
if (dump_trace)

View File

@ -237,7 +237,7 @@ static int64_t _sort__addr_cmp(u64 left_ip, u64 right_ip)
return (int64_t)(right_ip - left_ip);
}
static int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r)
int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r)
{
if (!sym_l || !sym_r)
return cmp_null(sym_l, sym_r);

View File

@ -311,5 +311,7 @@ int64_t
sort__daddr_cmp(struct hist_entry *left, struct hist_entry *right);
int64_t
sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right);
int64_t
_sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r);
char *hist_entry__srcline(struct hist_entry *he);
#endif /* __PERF_SORT_H */

View File

@ -729,7 +729,7 @@ static void generic_metric(struct perf_stat_config *config,
struct runtime_stat *st)
{
print_metric_t print_metric = out->print_metric;
struct parse_ctx pctx;
struct expr_parse_ctx pctx;
double ratio, scale;
int i;
void *ctxp = out->ctx;

View File

@ -1544,6 +1544,7 @@ static bool dso__is_compatible_symtab_type(struct dso *dso, bool kmod,
return true;
case DSO_BINARY_TYPE__BPF_PROG_INFO:
case DSO_BINARY_TYPE__BPF_IMAGE:
case DSO_BINARY_TYPE__NOT_FOUND:
default:
return false;

View File

@ -71,7 +71,6 @@ int perf_tool__process_synth_event(struct perf_tool *tool,
static int perf_event__get_comm_ids(pid_t pid, char *comm, size_t len,
pid_t *tgid, pid_t *ppid)
{
char filename[PATH_MAX];
char bf[4096];
int fd;
size_t size = 0;
@ -81,11 +80,11 @@ static int perf_event__get_comm_ids(pid_t pid, char *comm, size_t len,
*tgid = -1;
*ppid = -1;
snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
snprintf(bf, sizeof(bf), "/proc/%d/status", pid);
fd = open(filename, O_RDONLY);
fd = open(bf, O_RDONLY);
if (fd < 0) {
pr_debug("couldn't open %s\n", filename);
pr_debug("couldn't open %s\n", bf);
return -1;
}
@ -281,9 +280,9 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool,
struct machine *machine,
bool mmap_data)
{
char filename[PATH_MAX];
FILE *fp;
unsigned long long t;
char bf[BUFSIZ];
bool truncation = false;
unsigned long long timeout = proc_map_timeout * 1000000ULL;
int rc = 0;
@ -293,15 +292,15 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool,
if (machine__is_default_guest(machine))
return 0;
snprintf(filename, sizeof(filename), "%s/proc/%d/task/%d/maps",
machine->root_dir, pid, pid);
snprintf(bf, sizeof(bf), "%s/proc/%d/task/%d/maps",
machine->root_dir, pid, pid);
fp = fopen(filename, "r");
fp = fopen(bf, "r");
if (fp == NULL) {
/*
* We raced with a task exiting - just return:
*/
pr_debug("couldn't open %s\n", filename);
pr_debug("couldn't open %s\n", bf);
return -1;
}
@ -309,7 +308,6 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool,
t = rdclock();
while (1) {
char bf[BUFSIZ];
char prot[5];
char execname[PATH_MAX];
char anonstr[] = "//anon";
@ -321,10 +319,10 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool,
break;
if ((rdclock() - t) > timeout) {
pr_warning("Reading %s time out. "
pr_warning("Reading %s/proc/%d/task/%d/maps time out. "
"You may want to increase "
"the time limit by --proc-map-timeout\n",
filename);
machine->root_dir, pid, pid);
truncation = true;
goto out;
}

View File

@ -497,6 +497,63 @@ void thread_stack__sample(struct thread *thread, int cpu,
chain->nr = i;
}
/*
* Hardware sample records, created some time after the event occurred, need to
* have subsequent addresses removed from the call chain.
*/
void thread_stack__sample_late(struct thread *thread, int cpu,
struct ip_callchain *chain, size_t sz,
u64 sample_ip, u64 kernel_start)
{
struct thread_stack *ts = thread__stack(thread, cpu);
u64 sample_context = callchain_context(sample_ip, kernel_start);
u64 last_context, context, ip;
size_t nr = 0, j;
if (sz < 2) {
chain->nr = 0;
return;
}
if (!ts)
goto out;
/*
* When tracing kernel space, kernel addresses occur at the top of the
* call chain after the event occurred but before tracing stopped.
* Skip them.
*/
for (j = 1; j <= ts->cnt; j++) {
ip = ts->stack[ts->cnt - j].ret_addr;
context = callchain_context(ip, kernel_start);
if (context == PERF_CONTEXT_USER ||
(context == sample_context && ip == sample_ip))
break;
}
last_context = sample_ip; /* Use sample_ip as an invalid context */
for (; nr < sz && j <= ts->cnt; nr++, j++) {
ip = ts->stack[ts->cnt - j].ret_addr;
context = callchain_context(ip, kernel_start);
if (context != last_context) {
if (nr >= sz - 1)
break;
chain->ips[nr++] = context;
last_context = context;
}
chain->ips[nr] = ip;
}
out:
if (nr) {
chain->nr = nr;
} else {
chain->ips[0] = sample_context;
chain->ips[1] = sample_ip;
chain->nr = 2;
}
}
struct call_return_processor *
call_return_processor__new(int (*process)(struct call_return *cr, u64 *parent_db_id, void *data),
void *data)

View File

@ -85,6 +85,9 @@ int thread_stack__event(struct thread *thread, int cpu, u32 flags, u64 from_ip,
void thread_stack__set_trace_nr(struct thread *thread, int cpu, u64 trace_nr);
void thread_stack__sample(struct thread *thread, int cpu, struct ip_callchain *chain,
size_t sz, u64 ip, u64 kernel_start);
void thread_stack__sample_late(struct thread *thread, int cpu,
struct ip_callchain *chain, size_t sz, u64 ip,
u64 kernel_start);
int thread_stack__flush(struct thread *thread);
void thread_stack__free(struct thread *thread);
size_t thread_stack__depth(struct thread *thread, int cpu);

View File

@ -47,6 +47,7 @@ struct thread *thread__new(pid_t pid, pid_t tid)
thread->tid = tid;
thread->ppid = -1;
thread->cpu = -1;
thread->lbr_stitch_enable = false;
INIT_LIST_HEAD(&thread->namespaces_list);
INIT_LIST_HEAD(&thread->comm_list);
init_rwsem(&thread->namespaces_lock);
@ -110,6 +111,7 @@ void thread__delete(struct thread *thread)
exit_rwsem(&thread->namespaces_lock);
exit_rwsem(&thread->comm_lock);
thread__free_stitch_list(thread);
free(thread);
}
@ -452,3 +454,25 @@ int thread__memcpy(struct thread *thread, struct machine *machine,
return dso__data_read_offset(al.map->dso, machine, offset, buf, len);
}
void thread__free_stitch_list(struct thread *thread)
{
struct lbr_stitch *lbr_stitch = thread->lbr_stitch;
struct stitch_list *pos, *tmp;
if (!lbr_stitch)
return;
list_for_each_entry_safe(pos, tmp, &lbr_stitch->lists, node) {
list_del_init(&pos->node);
free(pos);
}
list_for_each_entry_safe(pos, tmp, &lbr_stitch->free_lists, node) {
list_del_init(&pos->node);
free(pos);
}
zfree(&lbr_stitch->prev_lbr_cursor);
zfree(&thread->lbr_stitch);
}

View File

@ -13,6 +13,8 @@
#include <strlist.h>
#include <intlist.h>
#include "rwsem.h"
#include "event.h"
#include "callchain.h"
struct addr_location;
struct map;
@ -20,6 +22,13 @@ struct perf_record_namespaces;
struct thread_stack;
struct unwind_libunwind_ops;
struct lbr_stitch {
struct list_head lists;
struct list_head free_lists;
struct perf_sample prev_sample;
struct callchain_cursor_node *prev_lbr_cursor;
};
struct thread {
union {
struct rb_node rb_node;
@ -46,6 +55,10 @@ struct thread {
struct srccode_state srccode_state;
bool filter;
int filter_entry_depth;
/* LBR call stack stitch */
bool lbr_stitch_enable;
struct lbr_stitch *lbr_stitch;
};
struct machine;
@ -142,4 +155,6 @@ static inline bool thread__is_filtered(struct thread *thread)
return false;
}
void thread__free_stitch_list(struct thread *thread);
#endif /* __PERF_THREAD_H */

View File

@ -36,6 +36,7 @@ struct perf_top {
bool use_tui, use_stdio;
bool vmlinux_warned;
bool dump_symtab;
bool stitch_lbr;
struct hist_entry *sym_filter_entry;
struct evsel *sym_evsel;
struct perf_session *session;

View File

@ -290,6 +290,7 @@ int perf_event_paranoid(void)
bool perf_event_paranoid_check(int max_level)
{
return perf_cap__capable(CAP_SYS_ADMIN) ||
perf_cap__capable(CAP_PERFMON) ||
perf_event_paranoid() <= max_level;
}