blob: 35b40174408e38d396f169f8773363242eaf09ac [file] [log] [blame]
/*
* Copyright 2010, Intel Corporation
*
* This file is part of PowerTOP
*
* This program file is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program in a file named COPYING; if not, write to the
* Free Software Foundation, Inc,
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
* or just google for it.
*
* Authors:
* Arjan van de Ven <arjan@linux.intel.com>
*/
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "perf_event.h"
#include "perf.h"
#include "../lib.h"
#include "../display.h"
struct pevent *perf_event::pevent;
static int get_trace_type(const char *eventname)
{
string str;
int this_trace;
str = read_sysfs_string("/sys/kernel/debug/tracing/events/%s/id",
eventname);
if (str.length() < 1)
return -1;
this_trace = strtoull(str.c_str(), NULL, 10);
return this_trace;
}
static inline int sys_perf_event_open(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd,
unsigned long flags)
{
attr->size = sizeof(*attr);
return syscall(__NR_perf_event_open, attr, pid, cpu,
group_fd, flags);
}
void perf_event::create_perf_event(char *eventname, int _cpu)
{
struct perf_event_attr attr;
int ret;
struct {
__u64 count;
__u64 time_enabled;
__u64 time_running;
__u64 id;
} read_data;
if (perf_fd != -1)
clear();
memset(&attr, 0, sizeof(attr));
attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
PERF_FORMAT_TOTAL_TIME_RUNNING |
PERF_FORMAT_ID;
attr.sample_freq = 0;
attr.sample_period = 1;
attr.sample_type |= PERF_SAMPLE_RAW | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
attr.mmap = 1;
attr.comm = 1;
attr.inherit = 0;
attr.disabled = 1;
attr.type = PERF_TYPE_TRACEPOINT;
attr.config = trace_type;
if (attr.config <= 0)
return;
perf_fd = sys_perf_event_open(&attr, -1, _cpu, -1, 0);
if (perf_fd < 0) {
reset_display();
fprintf(stderr, _("PowerTOP %s needs the kernel to support the 'perf' subsystem\n"), POWERTOP_VERSION);
fprintf(stderr, _("as well as support for trace points in the kernel:\n"));
fprintf(stderr, "CONFIG_PERF_EVENTS=y\nCONFIG_PERF_COUNTERS=y\nCONFIG_TRACEPOINTS=y\nCONFIG_TRACING=y\n");
exit(EXIT_FAILURE);
}
if (read(perf_fd, &read_data, sizeof(read_data)) == -1) {
reset_display();
perror("Unable to read perf file descriptor\n");
exit(-1);
}
fcntl(perf_fd, F_SETFL, O_NONBLOCK);
perf_mmap = mmap(NULL, (bufsize+1)*getpagesize(),
PROT_READ | PROT_WRITE, MAP_SHARED, perf_fd, 0);
if (perf_mmap == MAP_FAILED) {
fprintf(stderr, "failed to mmap with %d (%s)\n", errno, strerror(errno));
return;
}
ret = ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);
if (ret < 0) {
fprintf(stderr, "failed to enable perf \n");
}
pc = (perf_event_mmap_page *)perf_mmap;
data_mmap = (unsigned char *)perf_mmap + getpagesize();
}
void perf_event::set_event_name(const char *event_name)
{
if (name)
free(name);
name = strdup(event_name);
if (!name) {
fprintf(stderr, "failed to allocate event name\n");
return;
}
char *c;
c = strchr(name, ':');
if (c)
*c = '/';
trace_type = get_trace_type(name);
}
perf_event::~perf_event(void)
{
if (name)
free(name);
if (perf_event::pevent->ref_count == 1) {
pevent_free(perf_event::pevent);
perf_event::pevent = NULL;
} else
pevent_unref(perf_event::pevent);
}
void perf_event::set_cpu(int _cpu)
{
cpu = _cpu;
}
static void allocate_pevent(void)
{
if (!perf_event::pevent)
perf_event::pevent = pevent_alloc();
else
pevent_ref(perf_event::pevent);
}
perf_event::perf_event(const char *event_name, int _cpu, int buffer_size)
{
allocate_pevent();
name = NULL;
perf_fd = -1;
bufsize = buffer_size;
cpu = _cpu;
perf_mmap = NULL;
trace_type = 0;
set_event_name(event_name);
}
perf_event::perf_event(void)
{
allocate_pevent();
name = NULL;
perf_fd = -1;
bufsize = 128;
perf_mmap = NULL;
cpu = 0;
trace_type = 0;
}
void perf_event::start(void)
{
create_perf_event(name, cpu);
}
void perf_event::stop(void)
{
int ret;
ret = ioctl(perf_fd, PERF_EVENT_IOC_DISABLE, 0);
if (ret)
cout << "stop failing\n";
}
void perf_event::process(void *cookie)
{
struct perf_event_header *header;
if (perf_fd < 0)
return;
while (pc->data_tail != pc->data_head ) {
while (pc->data_tail >= (unsigned int)bufsize * getpagesize())
pc->data_tail -= bufsize * getpagesize();
header = (struct perf_event_header *)( (unsigned char *)data_mmap + pc->data_tail);
if (header->size == 0)
break;
pc->data_tail += header->size;
while (pc->data_tail >= (unsigned int)bufsize * getpagesize())
pc->data_tail -= bufsize * getpagesize();
if (header->type == PERF_RECORD_SAMPLE)
handle_event(header, cookie);
}
pc->data_tail = pc->data_head;
}
void perf_event::clear(void)
{
if (perf_mmap) {
// memset(perf_mmap, 0, (bufsize)*getpagesize());
munmap(perf_mmap, (bufsize+1)*getpagesize());
perf_mmap = NULL;
}
if (perf_fd != -1)
close(perf_fd);
perf_fd = -1;
}