/* Simple session layer for multiple perf events. */ /* * Copyright (c) 2015, Intel Corporation * Author: Andi Kleen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <string.h> #include <unistd.h> #include <linux/perf_event.h> #include <stdlib.h> #include <stdio.h> #include <stdint.h> #include <errno.h> #include <sys/fcntl.h> #include <stdbool.h> #include "jevents.h" #include "jsession.h" /** * alloc_eventlist - Alloc a list of events. */ struct eventlist *alloc_eventlist(void) { struct eventlist *el = calloc(sizeof(struct eventlist), 1); if (!el) return NULL; el->num_cpus = sysconf(_SC_NPROCESSORS_CONF); return el; } static struct event *new_event(struct eventlist *el, char *s) { struct event *e = calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1); e->next = NULL; if (!el->eventlist) el->eventlist = e; if (el->eventlist_last) el->eventlist_last->next = e; el->eventlist_last = e; e->event = strdup(s); return e; } /** * parse_events - parse a perf style string with events * @el: List of events allocated earlier * @events: Comma separated lists of events. {} style groups are legal. * * JSON events are supported, if the event lists are downloaded first. */ int parse_events(struct eventlist *el, char *events) { char *s, *tmp; events = strdup(events); if (! events) return -1; for (s = strtok_r(events, ",", &tmp); s; s = strtok_r(NULL, ",", &tmp)) { bool group_leader = false, end_group = false; int len; if (s[0] == '{') { s++; group_leader = true; } else if (len = strlen(s), len > 0 && s[len - 1] == '}') { s[len - 1] = 0; end_group = true; } struct event *e = new_event(el, s); e->group_leader = group_leader; e->end_group = end_group; if (resolve_event(s, &e->attr) < 0) { fprintf(stderr, "Cannot resolve %s\n", e->event); return -1; } } free(events); return 0; } static bool cpu_online(int i) { bool ret = false; char fn[100]; sprintf(fn, "/sys/devices/system/cpu/cpu%d/online", i); int fd = open(fn, O_RDONLY); if (fd >= 0) { char buf[128]; int n = read(fd, buf, 128); if (n > 0 && !strncmp(buf, "1", 1)) ret = true; close(fd); } return ret; } /** * setup_event - Create perf descriptor for a single event. * @e: Event to measure. * @cpu: CPU to measure. * @leader: Leader event to define a group. * @measure_all: If true measure all processes (may need root) * @measure_pid: If not -1 measure specific process. * * This is a low level function. Normally setup_events() should be used. * Return -1 on failure. */ int setup_event(struct event *e, int cpu, struct event *leader, bool measure_all, int measure_pid) { e->attr.inherit = 1; if (!measure_all) { e->attr.disabled = 1; e->attr.enable_on_exec = 1; } e->attr.read_format |= PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; e->efd[cpu].fd = perf_event_open(&e->attr, measure_all ? -1 : measure_pid, cpu, leader ? leader->efd[cpu].fd : -1, 0); if (e->efd[cpu].fd < 0) { /* Handle offline CPU */ if (errno == EINVAL && !cpu_online(cpu)) return 0; fprintf(stderr, "Cannot open perf event for %s/%d: %s\n", e->event, cpu, strerror(errno)); return -1; } return 0; } /** * setup_events - Set up perf events for a event list. * @el: List of events, allocated and parsed earlier. * @measure_all: If true measure all of system (may need root) * @measure_pid: If not -1 measure pid. * * Return -1 on failure, otherwise 0. */ int setup_events(struct eventlist *el, bool measure_all, int measure_pid) { struct event *e, *leader = NULL; int i; for (e = el->eventlist; e; e = e->next) { for (i = 0; i < el->num_cpus; i++) { if (setup_event(e, i, leader, measure_all, measure_pid) < 0) return -1; } if (e->group_leader) leader = e; if (e->end_group) leader = NULL; } return 0; } /** * read_event - Read the value of a single event for one CPU. * @e: event to read * @cpu: cpu number to read * Returns -1 on failure, otherwise 0. * The value read can be retrieved later with event_scaled_value. */ int read_event(struct event *e, int cpu) { int n = read(e->efd[cpu].fd, &e->efd[cpu].val, 3 * 8); if (n < 0) { fprintf(stderr, "Error reading from %s/%d: %s\n", e->event, cpu, strerror(errno)); return -1; } return 0; } /** * read_event - Read value of all events on all CPUs. * @el: eventlist. Must be allocated, parsed, set up earlier. * Returns -1 on failure, otherwise 0. */ int read_all_events(struct eventlist *el) { struct event *e; int i; for (e = el->eventlist; e; e = e->next) { for (i = 0; i < el->num_cpus; i++) { if (e->efd[i].fd < 0) continue; if (read_event(e, i) < 0) return -1; } } return 0; } /** * event_scaled_value - Retrieve a read value for a cpu * @e: Event * @cpu: CPU number * Return scaled value read earlier. */ uint64_t event_scaled_value(struct event *e, int cpu) { uint64_t *val = e->efd[cpu].val; if (val[1] != val[2] && val[2]) return val[0] * (double)val[1] / (double)val[2]; return val[0]; }