mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-03 04:26:54 +07:00
4af4c9550c
perf_evsel__alloc_fd allocates an array of file descriptors with the memory initialized to 0. The array has dimensions for cpus and threads. Later, __perf_evsel__open calls sys_perf_event_open for each cpu and thread dimensions. If the open fails for any of the cpus or threads then the fd's for this event are closed and the fd entry in the array is set to -1. Now, if the first attempt fails for the event (e.g., the event is not supported) the remaining dimensions (cpu > 0 and thread > 0) are not touched and left at the initialized value of 0. builtin-stat catches ENOENT and ENOSYS failures and allows the command to continue. The end result is that stat attempts to read from an fd of 0 which of course is stdin and so the command hangs until you type ctrl-D. Resolve by initializing the array to -1 since an fd < 0 is already handled. Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Paul Mackerras <paulus@samba.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Link: http://lkml.kernel.org/r/1306511914-8016-1-git-send-email-dsahern@gmail.com Signed-off-by: David Ahern <dsahern@gmail.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
425 lines
8.6 KiB
C
425 lines
8.6 KiB
C
/*
|
|
* Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
|
|
*
|
|
* Parts came from builtin-{top,stat,record}.c, see those files for further
|
|
* copyright notes.
|
|
*
|
|
* Released under the GPL v2. (and only v2, not any later version)
|
|
*/
|
|
|
|
#include "evsel.h"
|
|
#include "evlist.h"
|
|
#include "util.h"
|
|
#include "cpumap.h"
|
|
#include "thread_map.h"
|
|
|
|
#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y))
|
|
|
|
void perf_evsel__init(struct perf_evsel *evsel,
|
|
struct perf_event_attr *attr, int idx)
|
|
{
|
|
evsel->idx = idx;
|
|
evsel->attr = *attr;
|
|
INIT_LIST_HEAD(&evsel->node);
|
|
}
|
|
|
|
struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr, int idx)
|
|
{
|
|
struct perf_evsel *evsel = zalloc(sizeof(*evsel));
|
|
|
|
if (evsel != NULL)
|
|
perf_evsel__init(evsel, attr, idx);
|
|
|
|
return evsel;
|
|
}
|
|
|
|
int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads)
|
|
{
|
|
int cpu, thread;
|
|
evsel->fd = xyarray__new(ncpus, nthreads, sizeof(int));
|
|
|
|
if (evsel->fd) {
|
|
for (cpu = 0; cpu < ncpus; cpu++) {
|
|
for (thread = 0; thread < nthreads; thread++) {
|
|
FD(evsel, cpu, thread) = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return evsel->fd != NULL ? 0 : -ENOMEM;
|
|
}
|
|
|
|
int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads)
|
|
{
|
|
evsel->sample_id = xyarray__new(ncpus, nthreads, sizeof(struct perf_sample_id));
|
|
if (evsel->sample_id == NULL)
|
|
return -ENOMEM;
|
|
|
|
evsel->id = zalloc(ncpus * nthreads * sizeof(u64));
|
|
if (evsel->id == NULL) {
|
|
xyarray__delete(evsel->sample_id);
|
|
evsel->sample_id = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int perf_evsel__alloc_counts(struct perf_evsel *evsel, int ncpus)
|
|
{
|
|
evsel->counts = zalloc((sizeof(*evsel->counts) +
|
|
(ncpus * sizeof(struct perf_counts_values))));
|
|
return evsel->counts != NULL ? 0 : -ENOMEM;
|
|
}
|
|
|
|
void perf_evsel__free_fd(struct perf_evsel *evsel)
|
|
{
|
|
xyarray__delete(evsel->fd);
|
|
evsel->fd = NULL;
|
|
}
|
|
|
|
void perf_evsel__free_id(struct perf_evsel *evsel)
|
|
{
|
|
xyarray__delete(evsel->sample_id);
|
|
evsel->sample_id = NULL;
|
|
free(evsel->id);
|
|
evsel->id = NULL;
|
|
}
|
|
|
|
void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads)
|
|
{
|
|
int cpu, thread;
|
|
|
|
for (cpu = 0; cpu < ncpus; cpu++)
|
|
for (thread = 0; thread < nthreads; ++thread) {
|
|
close(FD(evsel, cpu, thread));
|
|
FD(evsel, cpu, thread) = -1;
|
|
}
|
|
}
|
|
|
|
void perf_evsel__exit(struct perf_evsel *evsel)
|
|
{
|
|
assert(list_empty(&evsel->node));
|
|
xyarray__delete(evsel->fd);
|
|
xyarray__delete(evsel->sample_id);
|
|
free(evsel->id);
|
|
}
|
|
|
|
void perf_evsel__delete(struct perf_evsel *evsel)
|
|
{
|
|
perf_evsel__exit(evsel);
|
|
close_cgroup(evsel->cgrp);
|
|
free(evsel->name);
|
|
free(evsel);
|
|
}
|
|
|
|
int __perf_evsel__read_on_cpu(struct perf_evsel *evsel,
|
|
int cpu, int thread, bool scale)
|
|
{
|
|
struct perf_counts_values count;
|
|
size_t nv = scale ? 3 : 1;
|
|
|
|
if (FD(evsel, cpu, thread) < 0)
|
|
return -EINVAL;
|
|
|
|
if (evsel->counts == NULL && perf_evsel__alloc_counts(evsel, cpu + 1) < 0)
|
|
return -ENOMEM;
|
|
|
|
if (readn(FD(evsel, cpu, thread), &count, nv * sizeof(u64)) < 0)
|
|
return -errno;
|
|
|
|
if (scale) {
|
|
if (count.run == 0)
|
|
count.val = 0;
|
|
else if (count.run < count.ena)
|
|
count.val = (u64)((double)count.val * count.ena / count.run + 0.5);
|
|
} else
|
|
count.ena = count.run = 0;
|
|
|
|
evsel->counts->cpu[cpu] = count;
|
|
return 0;
|
|
}
|
|
|
|
int __perf_evsel__read(struct perf_evsel *evsel,
|
|
int ncpus, int nthreads, bool scale)
|
|
{
|
|
size_t nv = scale ? 3 : 1;
|
|
int cpu, thread;
|
|
struct perf_counts_values *aggr = &evsel->counts->aggr, count;
|
|
|
|
aggr->val = aggr->ena = aggr->run = 0;
|
|
|
|
for (cpu = 0; cpu < ncpus; cpu++) {
|
|
for (thread = 0; thread < nthreads; thread++) {
|
|
if (FD(evsel, cpu, thread) < 0)
|
|
continue;
|
|
|
|
if (readn(FD(evsel, cpu, thread),
|
|
&count, nv * sizeof(u64)) < 0)
|
|
return -errno;
|
|
|
|
aggr->val += count.val;
|
|
if (scale) {
|
|
aggr->ena += count.ena;
|
|
aggr->run += count.run;
|
|
}
|
|
}
|
|
}
|
|
|
|
evsel->counts->scaled = 0;
|
|
if (scale) {
|
|
if (aggr->run == 0) {
|
|
evsel->counts->scaled = -1;
|
|
aggr->val = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (aggr->run < aggr->ena) {
|
|
evsel->counts->scaled = 1;
|
|
aggr->val = (u64)((double)aggr->val * aggr->ena / aggr->run + 0.5);
|
|
}
|
|
} else
|
|
aggr->ena = aggr->run = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus,
|
|
struct thread_map *threads, bool group)
|
|
{
|
|
int cpu, thread;
|
|
unsigned long flags = 0;
|
|
int pid = -1;
|
|
|
|
if (evsel->fd == NULL &&
|
|
perf_evsel__alloc_fd(evsel, cpus->nr, threads->nr) < 0)
|
|
return -1;
|
|
|
|
if (evsel->cgrp) {
|
|
flags = PERF_FLAG_PID_CGROUP;
|
|
pid = evsel->cgrp->fd;
|
|
}
|
|
|
|
for (cpu = 0; cpu < cpus->nr; cpu++) {
|
|
int group_fd = -1;
|
|
|
|
for (thread = 0; thread < threads->nr; thread++) {
|
|
|
|
if (!evsel->cgrp)
|
|
pid = threads->map[thread];
|
|
|
|
FD(evsel, cpu, thread) = sys_perf_event_open(&evsel->attr,
|
|
pid,
|
|
cpus->map[cpu],
|
|
group_fd, flags);
|
|
if (FD(evsel, cpu, thread) < 0)
|
|
goto out_close;
|
|
|
|
if (group && group_fd == -1)
|
|
group_fd = FD(evsel, cpu, thread);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_close:
|
|
do {
|
|
while (--thread >= 0) {
|
|
close(FD(evsel, cpu, thread));
|
|
FD(evsel, cpu, thread) = -1;
|
|
}
|
|
thread = threads->nr;
|
|
} while (--cpu >= 0);
|
|
return -1;
|
|
}
|
|
|
|
static struct {
|
|
struct cpu_map map;
|
|
int cpus[1];
|
|
} empty_cpu_map = {
|
|
.map.nr = 1,
|
|
.cpus = { -1, },
|
|
};
|
|
|
|
static struct {
|
|
struct thread_map map;
|
|
int threads[1];
|
|
} empty_thread_map = {
|
|
.map.nr = 1,
|
|
.threads = { -1, },
|
|
};
|
|
|
|
int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus,
|
|
struct thread_map *threads, bool group)
|
|
{
|
|
if (cpus == NULL) {
|
|
/* Work around old compiler warnings about strict aliasing */
|
|
cpus = &empty_cpu_map.map;
|
|
}
|
|
|
|
if (threads == NULL)
|
|
threads = &empty_thread_map.map;
|
|
|
|
return __perf_evsel__open(evsel, cpus, threads, group);
|
|
}
|
|
|
|
int perf_evsel__open_per_cpu(struct perf_evsel *evsel,
|
|
struct cpu_map *cpus, bool group)
|
|
{
|
|
return __perf_evsel__open(evsel, cpus, &empty_thread_map.map, group);
|
|
}
|
|
|
|
int perf_evsel__open_per_thread(struct perf_evsel *evsel,
|
|
struct thread_map *threads, bool group)
|
|
{
|
|
return __perf_evsel__open(evsel, &empty_cpu_map.map, threads, group);
|
|
}
|
|
|
|
static int perf_event__parse_id_sample(const union perf_event *event, u64 type,
|
|
struct perf_sample *sample)
|
|
{
|
|
const u64 *array = event->sample.array;
|
|
|
|
array += ((event->header.size -
|
|
sizeof(event->header)) / sizeof(u64)) - 1;
|
|
|
|
if (type & PERF_SAMPLE_CPU) {
|
|
u32 *p = (u32 *)array;
|
|
sample->cpu = *p;
|
|
array--;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_STREAM_ID) {
|
|
sample->stream_id = *array;
|
|
array--;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_ID) {
|
|
sample->id = *array;
|
|
array--;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_TIME) {
|
|
sample->time = *array;
|
|
array--;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_TID) {
|
|
u32 *p = (u32 *)array;
|
|
sample->pid = p[0];
|
|
sample->tid = p[1];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool sample_overlap(const union perf_event *event,
|
|
const void *offset, u64 size)
|
|
{
|
|
const void *base = event;
|
|
|
|
if (offset + size > base + event->header.size)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int perf_event__parse_sample(const union perf_event *event, u64 type,
|
|
int sample_size, bool sample_id_all,
|
|
struct perf_sample *data)
|
|
{
|
|
const u64 *array;
|
|
|
|
data->cpu = data->pid = data->tid = -1;
|
|
data->stream_id = data->id = data->time = -1ULL;
|
|
|
|
if (event->header.type != PERF_RECORD_SAMPLE) {
|
|
if (!sample_id_all)
|
|
return 0;
|
|
return perf_event__parse_id_sample(event, type, data);
|
|
}
|
|
|
|
array = event->sample.array;
|
|
|
|
if (sample_size + sizeof(event->header) > event->header.size)
|
|
return -EFAULT;
|
|
|
|
if (type & PERF_SAMPLE_IP) {
|
|
data->ip = event->ip.ip;
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_TID) {
|
|
u32 *p = (u32 *)array;
|
|
data->pid = p[0];
|
|
data->tid = p[1];
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_TIME) {
|
|
data->time = *array;
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_ADDR) {
|
|
data->addr = *array;
|
|
array++;
|
|
}
|
|
|
|
data->id = -1ULL;
|
|
if (type & PERF_SAMPLE_ID) {
|
|
data->id = *array;
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_STREAM_ID) {
|
|
data->stream_id = *array;
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_CPU) {
|
|
u32 *p = (u32 *)array;
|
|
data->cpu = *p;
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_PERIOD) {
|
|
data->period = *array;
|
|
array++;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_READ) {
|
|
fprintf(stderr, "PERF_SAMPLE_READ is unsuported for now\n");
|
|
return -1;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_CALLCHAIN) {
|
|
if (sample_overlap(event, array, sizeof(data->callchain->nr)))
|
|
return -EFAULT;
|
|
|
|
data->callchain = (struct ip_callchain *)array;
|
|
|
|
if (sample_overlap(event, array, data->callchain->nr))
|
|
return -EFAULT;
|
|
|
|
array += 1 + data->callchain->nr;
|
|
}
|
|
|
|
if (type & PERF_SAMPLE_RAW) {
|
|
u32 *p = (u32 *)array;
|
|
|
|
if (sample_overlap(event, array, sizeof(u32)))
|
|
return -EFAULT;
|
|
|
|
data->raw_size = *p;
|
|
p++;
|
|
|
|
if (sample_overlap(event, p, data->raw_size))
|
|
return -EFAULT;
|
|
|
|
data->raw_data = p;
|
|
}
|
|
|
|
return 0;
|
|
}
|