mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-30 08:02:30 +00:00
bcf3145fbe
When perf tries to start a workload, it relies on a pipe which the workload was blocked for reading. After closing the pipe on the parent, the workload (child) can start the actual work via exec(). However, if another process was forked after creating a workload, this mechanism cannot work since the other process (child) also inherits the pipe, so that closing the pipe in parent cannot unblock the workload. Fix it by using explicit write call can then closing it. For similar reason, the pipe fd on parent should be marked as CLOEXEC so that it can be closed after another child exec'ed. Signed-off-by: Namhyung Kim <namhyung@kernel.org> Cc: David Ahern <dsahern@gmail.com> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Ingo Molnar <mingo@kernel.org> Cc: Jiri Olsa <jolsa@redhat.com> Cc: Paul Mackerras <paulus@samba.org> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Stephane Eranian <eranian@google.com> Cc: Steven Rostedt <rostedt@goodmis.org> Link: http://lkml.kernel.org/r/1372230862-15861-13-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
875 lines
20 KiB
C
875 lines
20 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 "util.h"
|
|
#include <lk/debugfs.h>
|
|
#include <poll.h>
|
|
#include "cpumap.h"
|
|
#include "thread_map.h"
|
|
#include "target.h"
|
|
#include "evlist.h"
|
|
#include "evsel.h"
|
|
#include <unistd.h>
|
|
|
|
#include "parse-events.h"
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/hash.h>
|
|
|
|
#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y))
|
|
#define SID(e, x, y) xyarray__entry(e->sample_id, x, y)
|
|
|
|
void perf_evlist__init(struct perf_evlist *evlist, struct cpu_map *cpus,
|
|
struct thread_map *threads)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < PERF_EVLIST__HLIST_SIZE; ++i)
|
|
INIT_HLIST_HEAD(&evlist->heads[i]);
|
|
INIT_LIST_HEAD(&evlist->entries);
|
|
perf_evlist__set_maps(evlist, cpus, threads);
|
|
evlist->workload.pid = -1;
|
|
}
|
|
|
|
struct perf_evlist *perf_evlist__new(void)
|
|
{
|
|
struct perf_evlist *evlist = zalloc(sizeof(*evlist));
|
|
|
|
if (evlist != NULL)
|
|
perf_evlist__init(evlist, NULL, NULL);
|
|
|
|
return evlist;
|
|
}
|
|
|
|
void perf_evlist__config(struct perf_evlist *evlist,
|
|
struct perf_record_opts *opts)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
/*
|
|
* Set the evsel leader links before we configure attributes,
|
|
* since some might depend on this info.
|
|
*/
|
|
if (opts->group)
|
|
perf_evlist__set_leader(evlist);
|
|
|
|
if (evlist->cpus->map[0] < 0)
|
|
opts->no_inherit = true;
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
perf_evsel__config(evsel, opts);
|
|
|
|
if (evlist->nr_entries > 1)
|
|
perf_evsel__set_sample_id(evsel);
|
|
}
|
|
}
|
|
|
|
static void perf_evlist__purge(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *pos, *n;
|
|
|
|
list_for_each_entry_safe(pos, n, &evlist->entries, node) {
|
|
list_del_init(&pos->node);
|
|
perf_evsel__delete(pos);
|
|
}
|
|
|
|
evlist->nr_entries = 0;
|
|
}
|
|
|
|
void perf_evlist__exit(struct perf_evlist *evlist)
|
|
{
|
|
free(evlist->mmap);
|
|
free(evlist->pollfd);
|
|
evlist->mmap = NULL;
|
|
evlist->pollfd = NULL;
|
|
}
|
|
|
|
void perf_evlist__delete(struct perf_evlist *evlist)
|
|
{
|
|
perf_evlist__purge(evlist);
|
|
perf_evlist__exit(evlist);
|
|
free(evlist);
|
|
}
|
|
|
|
void perf_evlist__add(struct perf_evlist *evlist, struct perf_evsel *entry)
|
|
{
|
|
list_add_tail(&entry->node, &evlist->entries);
|
|
++evlist->nr_entries;
|
|
}
|
|
|
|
void perf_evlist__splice_list_tail(struct perf_evlist *evlist,
|
|
struct list_head *list,
|
|
int nr_entries)
|
|
{
|
|
list_splice_tail(list, &evlist->entries);
|
|
evlist->nr_entries += nr_entries;
|
|
}
|
|
|
|
void __perf_evlist__set_leader(struct list_head *list)
|
|
{
|
|
struct perf_evsel *evsel, *leader;
|
|
|
|
leader = list_entry(list->next, struct perf_evsel, node);
|
|
evsel = list_entry(list->prev, struct perf_evsel, node);
|
|
|
|
leader->nr_members = evsel->idx - leader->idx + 1;
|
|
|
|
list_for_each_entry(evsel, list, node) {
|
|
evsel->leader = leader;
|
|
}
|
|
}
|
|
|
|
void perf_evlist__set_leader(struct perf_evlist *evlist)
|
|
{
|
|
if (evlist->nr_entries) {
|
|
evlist->nr_groups = evlist->nr_entries > 1 ? 1 : 0;
|
|
__perf_evlist__set_leader(&evlist->entries);
|
|
}
|
|
}
|
|
|
|
int perf_evlist__add_default(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_event_attr attr = {
|
|
.type = PERF_TYPE_HARDWARE,
|
|
.config = PERF_COUNT_HW_CPU_CYCLES,
|
|
};
|
|
struct perf_evsel *evsel;
|
|
|
|
event_attr_init(&attr);
|
|
|
|
evsel = perf_evsel__new(&attr, 0);
|
|
if (evsel == NULL)
|
|
goto error;
|
|
|
|
/* use strdup() because free(evsel) assumes name is allocated */
|
|
evsel->name = strdup("cycles");
|
|
if (!evsel->name)
|
|
goto error_free;
|
|
|
|
perf_evlist__add(evlist, evsel);
|
|
return 0;
|
|
error_free:
|
|
perf_evsel__delete(evsel);
|
|
error:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int perf_evlist__add_attrs(struct perf_evlist *evlist,
|
|
struct perf_event_attr *attrs, size_t nr_attrs)
|
|
{
|
|
struct perf_evsel *evsel, *n;
|
|
LIST_HEAD(head);
|
|
size_t i;
|
|
|
|
for (i = 0; i < nr_attrs; i++) {
|
|
evsel = perf_evsel__new(attrs + i, evlist->nr_entries + i);
|
|
if (evsel == NULL)
|
|
goto out_delete_partial_list;
|
|
list_add_tail(&evsel->node, &head);
|
|
}
|
|
|
|
perf_evlist__splice_list_tail(evlist, &head, nr_attrs);
|
|
|
|
return 0;
|
|
|
|
out_delete_partial_list:
|
|
list_for_each_entry_safe(evsel, n, &head, node)
|
|
perf_evsel__delete(evsel);
|
|
return -1;
|
|
}
|
|
|
|
int __perf_evlist__add_default_attrs(struct perf_evlist *evlist,
|
|
struct perf_event_attr *attrs, size_t nr_attrs)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nr_attrs; i++)
|
|
event_attr_init(attrs + i);
|
|
|
|
return perf_evlist__add_attrs(evlist, attrs, nr_attrs);
|
|
}
|
|
|
|
struct perf_evsel *
|
|
perf_evlist__find_tracepoint_by_id(struct perf_evlist *evlist, int id)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
if (evsel->attr.type == PERF_TYPE_TRACEPOINT &&
|
|
(int)evsel->attr.config == id)
|
|
return evsel;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int perf_evlist__add_newtp(struct perf_evlist *evlist,
|
|
const char *sys, const char *name, void *handler)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
|
|
evsel = perf_evsel__newtp(sys, name, evlist->nr_entries);
|
|
if (evsel == NULL)
|
|
return -1;
|
|
|
|
evsel->handler.func = handler;
|
|
perf_evlist__add(evlist, evsel);
|
|
return 0;
|
|
}
|
|
|
|
void perf_evlist__disable(struct perf_evlist *evlist)
|
|
{
|
|
int cpu, thread;
|
|
struct perf_evsel *pos;
|
|
int nr_cpus = cpu_map__nr(evlist->cpus);
|
|
int nr_threads = thread_map__nr(evlist->threads);
|
|
|
|
for (cpu = 0; cpu < nr_cpus; cpu++) {
|
|
list_for_each_entry(pos, &evlist->entries, node) {
|
|
if (!perf_evsel__is_group_leader(pos))
|
|
continue;
|
|
for (thread = 0; thread < nr_threads; thread++)
|
|
ioctl(FD(pos, cpu, thread),
|
|
PERF_EVENT_IOC_DISABLE, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void perf_evlist__enable(struct perf_evlist *evlist)
|
|
{
|
|
int cpu, thread;
|
|
struct perf_evsel *pos;
|
|
int nr_cpus = cpu_map__nr(evlist->cpus);
|
|
int nr_threads = thread_map__nr(evlist->threads);
|
|
|
|
for (cpu = 0; cpu < nr_cpus; cpu++) {
|
|
list_for_each_entry(pos, &evlist->entries, node) {
|
|
if (!perf_evsel__is_group_leader(pos))
|
|
continue;
|
|
for (thread = 0; thread < nr_threads; thread++)
|
|
ioctl(FD(pos, cpu, thread),
|
|
PERF_EVENT_IOC_ENABLE, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int perf_evlist__alloc_pollfd(struct perf_evlist *evlist)
|
|
{
|
|
int nr_cpus = cpu_map__nr(evlist->cpus);
|
|
int nr_threads = thread_map__nr(evlist->threads);
|
|
int nfds = nr_cpus * nr_threads * evlist->nr_entries;
|
|
evlist->pollfd = malloc(sizeof(struct pollfd) * nfds);
|
|
return evlist->pollfd != NULL ? 0 : -ENOMEM;
|
|
}
|
|
|
|
void perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd)
|
|
{
|
|
fcntl(fd, F_SETFL, O_NONBLOCK);
|
|
evlist->pollfd[evlist->nr_fds].fd = fd;
|
|
evlist->pollfd[evlist->nr_fds].events = POLLIN;
|
|
evlist->nr_fds++;
|
|
}
|
|
|
|
static void perf_evlist__id_hash(struct perf_evlist *evlist,
|
|
struct perf_evsel *evsel,
|
|
int cpu, int thread, u64 id)
|
|
{
|
|
int hash;
|
|
struct perf_sample_id *sid = SID(evsel, cpu, thread);
|
|
|
|
sid->id = id;
|
|
sid->evsel = evsel;
|
|
hash = hash_64(sid->id, PERF_EVLIST__HLIST_BITS);
|
|
hlist_add_head(&sid->node, &evlist->heads[hash]);
|
|
}
|
|
|
|
void perf_evlist__id_add(struct perf_evlist *evlist, struct perf_evsel *evsel,
|
|
int cpu, int thread, u64 id)
|
|
{
|
|
perf_evlist__id_hash(evlist, evsel, cpu, thread, id);
|
|
evsel->id[evsel->ids++] = id;
|
|
}
|
|
|
|
static int perf_evlist__id_add_fd(struct perf_evlist *evlist,
|
|
struct perf_evsel *evsel,
|
|
int cpu, int thread, int fd)
|
|
{
|
|
u64 read_data[4] = { 0, };
|
|
int id_idx = 1; /* The first entry is the counter value */
|
|
|
|
if (!(evsel->attr.read_format & PERF_FORMAT_ID) ||
|
|
read(fd, &read_data, sizeof(read_data)) == -1)
|
|
return -1;
|
|
|
|
if (evsel->attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
|
|
++id_idx;
|
|
if (evsel->attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
|
|
++id_idx;
|
|
|
|
perf_evlist__id_add(evlist, evsel, cpu, thread, read_data[id_idx]);
|
|
return 0;
|
|
}
|
|
|
|
struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id)
|
|
{
|
|
struct hlist_head *head;
|
|
struct perf_sample_id *sid;
|
|
int hash;
|
|
|
|
if (evlist->nr_entries == 1)
|
|
return perf_evlist__first(evlist);
|
|
|
|
hash = hash_64(id, PERF_EVLIST__HLIST_BITS);
|
|
head = &evlist->heads[hash];
|
|
|
|
hlist_for_each_entry(sid, head, node)
|
|
if (sid->id == id)
|
|
return sid->evsel;
|
|
|
|
if (!perf_evlist__sample_id_all(evlist))
|
|
return perf_evlist__first(evlist);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
union perf_event *perf_evlist__mmap_read(struct perf_evlist *evlist, int idx)
|
|
{
|
|
struct perf_mmap *md = &evlist->mmap[idx];
|
|
unsigned int head = perf_mmap__read_head(md);
|
|
unsigned int old = md->prev;
|
|
unsigned char *data = md->base + page_size;
|
|
union perf_event *event = NULL;
|
|
|
|
if (evlist->overwrite) {
|
|
/*
|
|
* If we're further behind than half the buffer, there's a chance
|
|
* the writer will bite our tail and mess up the samples under us.
|
|
*
|
|
* If we somehow ended up ahead of the head, we got messed up.
|
|
*
|
|
* In either case, truncate and restart at head.
|
|
*/
|
|
int diff = head - old;
|
|
if (diff > md->mask / 2 || diff < 0) {
|
|
fprintf(stderr, "WARNING: failed to keep up with mmap data.\n");
|
|
|
|
/*
|
|
* head points to a known good entry, start there.
|
|
*/
|
|
old = head;
|
|
}
|
|
}
|
|
|
|
if (old != head) {
|
|
size_t size;
|
|
|
|
event = (union perf_event *)&data[old & md->mask];
|
|
size = event->header.size;
|
|
|
|
/*
|
|
* Event straddles the mmap boundary -- header should always
|
|
* be inside due to u64 alignment of output.
|
|
*/
|
|
if ((old & md->mask) + size != ((old + size) & md->mask)) {
|
|
unsigned int offset = old;
|
|
unsigned int len = min(sizeof(*event), size), cpy;
|
|
void *dst = &md->event_copy;
|
|
|
|
do {
|
|
cpy = min(md->mask + 1 - (offset & md->mask), len);
|
|
memcpy(dst, &data[offset & md->mask], cpy);
|
|
offset += cpy;
|
|
dst += cpy;
|
|
len -= cpy;
|
|
} while (len);
|
|
|
|
event = &md->event_copy;
|
|
}
|
|
|
|
old += size;
|
|
}
|
|
|
|
md->prev = old;
|
|
|
|
if (!evlist->overwrite)
|
|
perf_mmap__write_tail(md, old);
|
|
|
|
return event;
|
|
}
|
|
|
|
void perf_evlist__munmap(struct perf_evlist *evlist)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < evlist->nr_mmaps; i++) {
|
|
if (evlist->mmap[i].base != NULL) {
|
|
munmap(evlist->mmap[i].base, evlist->mmap_len);
|
|
evlist->mmap[i].base = NULL;
|
|
}
|
|
}
|
|
|
|
free(evlist->mmap);
|
|
evlist->mmap = NULL;
|
|
}
|
|
|
|
static int perf_evlist__alloc_mmap(struct perf_evlist *evlist)
|
|
{
|
|
evlist->nr_mmaps = cpu_map__nr(evlist->cpus);
|
|
if (cpu_map__all(evlist->cpus))
|
|
evlist->nr_mmaps = thread_map__nr(evlist->threads);
|
|
evlist->mmap = zalloc(evlist->nr_mmaps * sizeof(struct perf_mmap));
|
|
return evlist->mmap != NULL ? 0 : -ENOMEM;
|
|
}
|
|
|
|
static int __perf_evlist__mmap(struct perf_evlist *evlist,
|
|
int idx, int prot, int mask, int fd)
|
|
{
|
|
evlist->mmap[idx].prev = 0;
|
|
evlist->mmap[idx].mask = mask;
|
|
evlist->mmap[idx].base = mmap(NULL, evlist->mmap_len, prot,
|
|
MAP_SHARED, fd, 0);
|
|
if (evlist->mmap[idx].base == MAP_FAILED) {
|
|
evlist->mmap[idx].base = NULL;
|
|
return -1;
|
|
}
|
|
|
|
perf_evlist__add_pollfd(evlist, fd);
|
|
return 0;
|
|
}
|
|
|
|
static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, int prot, int mask)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
int cpu, thread;
|
|
int nr_cpus = cpu_map__nr(evlist->cpus);
|
|
int nr_threads = thread_map__nr(evlist->threads);
|
|
|
|
for (cpu = 0; cpu < nr_cpus; cpu++) {
|
|
int output = -1;
|
|
|
|
for (thread = 0; thread < nr_threads; thread++) {
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
int fd = FD(evsel, cpu, thread);
|
|
|
|
if (output == -1) {
|
|
output = fd;
|
|
if (__perf_evlist__mmap(evlist, cpu,
|
|
prot, mask, output) < 0)
|
|
goto out_unmap;
|
|
} else {
|
|
if (ioctl(fd, PERF_EVENT_IOC_SET_OUTPUT, output) != 0)
|
|
goto out_unmap;
|
|
}
|
|
|
|
if ((evsel->attr.read_format & PERF_FORMAT_ID) &&
|
|
perf_evlist__id_add_fd(evlist, evsel, cpu, thread, fd) < 0)
|
|
goto out_unmap;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_unmap:
|
|
for (cpu = 0; cpu < nr_cpus; cpu++) {
|
|
if (evlist->mmap[cpu].base != NULL) {
|
|
munmap(evlist->mmap[cpu].base, evlist->mmap_len);
|
|
evlist->mmap[cpu].base = NULL;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, int prot, int mask)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
int thread;
|
|
int nr_threads = thread_map__nr(evlist->threads);
|
|
|
|
for (thread = 0; thread < nr_threads; thread++) {
|
|
int output = -1;
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
int fd = FD(evsel, 0, thread);
|
|
|
|
if (output == -1) {
|
|
output = fd;
|
|
if (__perf_evlist__mmap(evlist, thread,
|
|
prot, mask, output) < 0)
|
|
goto out_unmap;
|
|
} else {
|
|
if (ioctl(fd, PERF_EVENT_IOC_SET_OUTPUT, output) != 0)
|
|
goto out_unmap;
|
|
}
|
|
|
|
if ((evsel->attr.read_format & PERF_FORMAT_ID) &&
|
|
perf_evlist__id_add_fd(evlist, evsel, 0, thread, fd) < 0)
|
|
goto out_unmap;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_unmap:
|
|
for (thread = 0; thread < nr_threads; thread++) {
|
|
if (evlist->mmap[thread].base != NULL) {
|
|
munmap(evlist->mmap[thread].base, evlist->mmap_len);
|
|
evlist->mmap[thread].base = NULL;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** perf_evlist__mmap - Create per cpu maps to receive events
|
|
*
|
|
* @evlist - list of events
|
|
* @pages - map length in pages
|
|
* @overwrite - overwrite older events?
|
|
*
|
|
* If overwrite is false the user needs to signal event consuption using:
|
|
*
|
|
* struct perf_mmap *m = &evlist->mmap[cpu];
|
|
* unsigned int head = perf_mmap__read_head(m);
|
|
*
|
|
* perf_mmap__write_tail(m, head)
|
|
*
|
|
* Using perf_evlist__read_on_cpu does this automatically.
|
|
*/
|
|
int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages,
|
|
bool overwrite)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
const struct cpu_map *cpus = evlist->cpus;
|
|
const struct thread_map *threads = evlist->threads;
|
|
int prot = PROT_READ | (overwrite ? 0 : PROT_WRITE), mask;
|
|
|
|
/* 512 kiB: default amount of unprivileged mlocked memory */
|
|
if (pages == UINT_MAX)
|
|
pages = (512 * 1024) / page_size;
|
|
else if (!is_power_of_2(pages))
|
|
return -EINVAL;
|
|
|
|
mask = pages * page_size - 1;
|
|
|
|
if (evlist->mmap == NULL && perf_evlist__alloc_mmap(evlist) < 0)
|
|
return -ENOMEM;
|
|
|
|
if (evlist->pollfd == NULL && perf_evlist__alloc_pollfd(evlist) < 0)
|
|
return -ENOMEM;
|
|
|
|
evlist->overwrite = overwrite;
|
|
evlist->mmap_len = (pages + 1) * page_size;
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
if ((evsel->attr.read_format & PERF_FORMAT_ID) &&
|
|
evsel->sample_id == NULL &&
|
|
perf_evsel__alloc_id(evsel, cpu_map__nr(cpus), threads->nr) < 0)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (cpu_map__all(cpus))
|
|
return perf_evlist__mmap_per_thread(evlist, prot, mask);
|
|
|
|
return perf_evlist__mmap_per_cpu(evlist, prot, mask);
|
|
}
|
|
|
|
int perf_evlist__create_maps(struct perf_evlist *evlist,
|
|
struct perf_target *target)
|
|
{
|
|
evlist->threads = thread_map__new_str(target->pid, target->tid,
|
|
target->uid);
|
|
|
|
if (evlist->threads == NULL)
|
|
return -1;
|
|
|
|
if (perf_target__has_task(target))
|
|
evlist->cpus = cpu_map__dummy_new();
|
|
else if (!perf_target__has_cpu(target) && !target->uses_mmap)
|
|
evlist->cpus = cpu_map__dummy_new();
|
|
else
|
|
evlist->cpus = cpu_map__new(target->cpu_list);
|
|
|
|
if (evlist->cpus == NULL)
|
|
goto out_delete_threads;
|
|
|
|
return 0;
|
|
|
|
out_delete_threads:
|
|
thread_map__delete(evlist->threads);
|
|
return -1;
|
|
}
|
|
|
|
void perf_evlist__delete_maps(struct perf_evlist *evlist)
|
|
{
|
|
cpu_map__delete(evlist->cpus);
|
|
thread_map__delete(evlist->threads);
|
|
evlist->cpus = NULL;
|
|
evlist->threads = NULL;
|
|
}
|
|
|
|
int perf_evlist__apply_filters(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
int err = 0;
|
|
const int ncpus = cpu_map__nr(evlist->cpus),
|
|
nthreads = thread_map__nr(evlist->threads);
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
if (evsel->filter == NULL)
|
|
continue;
|
|
|
|
err = perf_evsel__set_filter(evsel, ncpus, nthreads, evsel->filter);
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int perf_evlist__set_filter(struct perf_evlist *evlist, const char *filter)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
int err = 0;
|
|
const int ncpus = cpu_map__nr(evlist->cpus),
|
|
nthreads = thread_map__nr(evlist->threads);
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
err = perf_evsel__set_filter(evsel, ncpus, nthreads, filter);
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
bool perf_evlist__valid_sample_type(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *first = perf_evlist__first(evlist), *pos = first;
|
|
|
|
list_for_each_entry_continue(pos, &evlist->entries, node) {
|
|
if (first->attr.sample_type != pos->attr.sample_type)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u64 perf_evlist__sample_type(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *first = perf_evlist__first(evlist);
|
|
return first->attr.sample_type;
|
|
}
|
|
|
|
u16 perf_evlist__id_hdr_size(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *first = perf_evlist__first(evlist);
|
|
struct perf_sample *data;
|
|
u64 sample_type;
|
|
u16 size = 0;
|
|
|
|
if (!first->attr.sample_id_all)
|
|
goto out;
|
|
|
|
sample_type = first->attr.sample_type;
|
|
|
|
if (sample_type & PERF_SAMPLE_TID)
|
|
size += sizeof(data->tid) * 2;
|
|
|
|
if (sample_type & PERF_SAMPLE_TIME)
|
|
size += sizeof(data->time);
|
|
|
|
if (sample_type & PERF_SAMPLE_ID)
|
|
size += sizeof(data->id);
|
|
|
|
if (sample_type & PERF_SAMPLE_STREAM_ID)
|
|
size += sizeof(data->stream_id);
|
|
|
|
if (sample_type & PERF_SAMPLE_CPU)
|
|
size += sizeof(data->cpu) * 2;
|
|
out:
|
|
return size;
|
|
}
|
|
|
|
bool perf_evlist__valid_sample_id_all(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *first = perf_evlist__first(evlist), *pos = first;
|
|
|
|
list_for_each_entry_continue(pos, &evlist->entries, node) {
|
|
if (first->attr.sample_id_all != pos->attr.sample_id_all)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool perf_evlist__sample_id_all(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *first = perf_evlist__first(evlist);
|
|
return first->attr.sample_id_all;
|
|
}
|
|
|
|
void perf_evlist__set_selected(struct perf_evlist *evlist,
|
|
struct perf_evsel *evsel)
|
|
{
|
|
evlist->selected = evsel;
|
|
}
|
|
|
|
void perf_evlist__close(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
int ncpus = cpu_map__nr(evlist->cpus);
|
|
int nthreads = thread_map__nr(evlist->threads);
|
|
|
|
list_for_each_entry_reverse(evsel, &evlist->entries, node)
|
|
perf_evsel__close(evsel, ncpus, nthreads);
|
|
}
|
|
|
|
int perf_evlist__open(struct perf_evlist *evlist)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
int err;
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
err = perf_evsel__open(evsel, evlist->cpus, evlist->threads);
|
|
if (err < 0)
|
|
goto out_err;
|
|
}
|
|
|
|
return 0;
|
|
out_err:
|
|
perf_evlist__close(evlist);
|
|
errno = -err;
|
|
return err;
|
|
}
|
|
|
|
int perf_evlist__prepare_workload(struct perf_evlist *evlist,
|
|
struct perf_target *target,
|
|
const char *argv[], bool pipe_output,
|
|
bool want_signal)
|
|
{
|
|
int child_ready_pipe[2], go_pipe[2];
|
|
char bf;
|
|
|
|
if (pipe(child_ready_pipe) < 0) {
|
|
perror("failed to create 'ready' pipe");
|
|
return -1;
|
|
}
|
|
|
|
if (pipe(go_pipe) < 0) {
|
|
perror("failed to create 'go' pipe");
|
|
goto out_close_ready_pipe;
|
|
}
|
|
|
|
evlist->workload.pid = fork();
|
|
if (evlist->workload.pid < 0) {
|
|
perror("failed to fork");
|
|
goto out_close_pipes;
|
|
}
|
|
|
|
if (!evlist->workload.pid) {
|
|
if (pipe_output)
|
|
dup2(2, 1);
|
|
|
|
signal(SIGTERM, SIG_DFL);
|
|
|
|
close(child_ready_pipe[0]);
|
|
close(go_pipe[1]);
|
|
fcntl(go_pipe[0], F_SETFD, FD_CLOEXEC);
|
|
|
|
/*
|
|
* Do a dummy execvp to get the PLT entry resolved,
|
|
* so we avoid the resolver overhead on the real
|
|
* execvp call.
|
|
*/
|
|
execvp("", (char **)argv);
|
|
|
|
/*
|
|
* Tell the parent we're ready to go
|
|
*/
|
|
close(child_ready_pipe[1]);
|
|
|
|
/*
|
|
* Wait until the parent tells us to go.
|
|
*/
|
|
if (read(go_pipe[0], &bf, 1) == -1)
|
|
perror("unable to read pipe");
|
|
|
|
execvp(argv[0], (char **)argv);
|
|
|
|
perror(argv[0]);
|
|
if (want_signal)
|
|
kill(getppid(), SIGUSR1);
|
|
exit(-1);
|
|
}
|
|
|
|
if (perf_target__none(target))
|
|
evlist->threads->map[0] = evlist->workload.pid;
|
|
|
|
close(child_ready_pipe[1]);
|
|
close(go_pipe[0]);
|
|
/*
|
|
* wait for child to settle
|
|
*/
|
|
if (read(child_ready_pipe[0], &bf, 1) == -1) {
|
|
perror("unable to read pipe");
|
|
goto out_close_pipes;
|
|
}
|
|
|
|
fcntl(go_pipe[1], F_SETFD, FD_CLOEXEC);
|
|
evlist->workload.cork_fd = go_pipe[1];
|
|
close(child_ready_pipe[0]);
|
|
return 0;
|
|
|
|
out_close_pipes:
|
|
close(go_pipe[0]);
|
|
close(go_pipe[1]);
|
|
out_close_ready_pipe:
|
|
close(child_ready_pipe[0]);
|
|
close(child_ready_pipe[1]);
|
|
return -1;
|
|
}
|
|
|
|
int perf_evlist__start_workload(struct perf_evlist *evlist)
|
|
{
|
|
if (evlist->workload.cork_fd > 0) {
|
|
char bf;
|
|
int ret;
|
|
/*
|
|
* Remove the cork, let it rip!
|
|
*/
|
|
ret = write(evlist->workload.cork_fd, &bf, 1);
|
|
if (ret < 0)
|
|
perror("enable to write to pipe");
|
|
|
|
close(evlist->workload.cork_fd);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int perf_evlist__parse_sample(struct perf_evlist *evlist, union perf_event *event,
|
|
struct perf_sample *sample)
|
|
{
|
|
struct perf_evsel *evsel = perf_evlist__first(evlist);
|
|
return perf_evsel__parse_sample(evsel, event, sample);
|
|
}
|
|
|
|
size_t perf_evlist__fprintf(struct perf_evlist *evlist, FILE *fp)
|
|
{
|
|
struct perf_evsel *evsel;
|
|
size_t printed = 0;
|
|
|
|
list_for_each_entry(evsel, &evlist->entries, node) {
|
|
printed += fprintf(fp, "%s%s", evsel->idx ? ", " : "",
|
|
perf_evsel__name(evsel));
|
|
}
|
|
|
|
return printed + fprintf(fp, "\n");;
|
|
}
|