mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-13 05:54:23 +08:00
23773ca18b
As tracefs may be mounted instead of debugfs to get to the event directories, have perf know about tracefs, and use that file system over debugfs if it is present. Signed-off-by: Steven Rostedt <rostedt@goodmis.org> Acked-by: Jiri Olsa <jolsa@kernel.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Ingo Molnar <mingo@kernel.org> Cc: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> Cc: Namhyung Kim <namhyung@kernel.org> Link: http://lkml.kernel.org/r/20150202193553.340946602@goodmis.org [ Fixed up error messages about tracefs pointed out by Namhyung ] Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
611 lines
10 KiB
C
611 lines
10 KiB
C
#include "../perf.h"
|
|
#include "util.h"
|
|
#include "debug.h"
|
|
#include <api/fs/fs.h>
|
|
#include <sys/mman.h>
|
|
#ifdef HAVE_BACKTRACE_SUPPORT
|
|
#include <execinfo.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <byteswap.h>
|
|
#include <linux/kernel.h>
|
|
#include <unistd.h>
|
|
#include "callchain.h"
|
|
|
|
struct callchain_param callchain_param = {
|
|
.mode = CHAIN_GRAPH_REL,
|
|
.min_percent = 0.5,
|
|
.order = ORDER_CALLEE,
|
|
.key = CCKEY_FUNCTION
|
|
};
|
|
|
|
/*
|
|
* XXX We need to find a better place for these things...
|
|
*/
|
|
unsigned int page_size;
|
|
int cacheline_size;
|
|
|
|
bool test_attr__enabled;
|
|
|
|
bool perf_host = true;
|
|
bool perf_guest = false;
|
|
|
|
char tracing_events_path[PATH_MAX + 1] = "/sys/kernel/debug/tracing/events";
|
|
|
|
void event_attr_init(struct perf_event_attr *attr)
|
|
{
|
|
if (!perf_host)
|
|
attr->exclude_host = 1;
|
|
if (!perf_guest)
|
|
attr->exclude_guest = 1;
|
|
/* to capture ABI version */
|
|
attr->size = sizeof(*attr);
|
|
}
|
|
|
|
int mkdir_p(char *path, mode_t mode)
|
|
{
|
|
struct stat st;
|
|
int err;
|
|
char *d = path;
|
|
|
|
if (*d != '/')
|
|
return -1;
|
|
|
|
if (stat(path, &st) == 0)
|
|
return 0;
|
|
|
|
while (*++d == '/');
|
|
|
|
while ((d = strchr(d, '/'))) {
|
|
*d = '\0';
|
|
err = stat(path, &st) && mkdir(path, mode);
|
|
*d++ = '/';
|
|
if (err)
|
|
return -1;
|
|
while (*d == '/')
|
|
++d;
|
|
}
|
|
return (stat(path, &st) && mkdir(path, mode)) ? -1 : 0;
|
|
}
|
|
|
|
static int slow_copyfile(const char *from, const char *to, mode_t mode)
|
|
{
|
|
int err = -1;
|
|
char *line = NULL;
|
|
size_t n;
|
|
FILE *from_fp = fopen(from, "r"), *to_fp;
|
|
mode_t old_umask;
|
|
|
|
if (from_fp == NULL)
|
|
goto out;
|
|
|
|
old_umask = umask(mode ^ 0777);
|
|
to_fp = fopen(to, "w");
|
|
umask(old_umask);
|
|
if (to_fp == NULL)
|
|
goto out_fclose_from;
|
|
|
|
while (getline(&line, &n, from_fp) > 0)
|
|
if (fputs(line, to_fp) == EOF)
|
|
goto out_fclose_to;
|
|
err = 0;
|
|
out_fclose_to:
|
|
fclose(to_fp);
|
|
free(line);
|
|
out_fclose_from:
|
|
fclose(from_fp);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
int copyfile_mode(const char *from, const char *to, mode_t mode)
|
|
{
|
|
int fromfd, tofd;
|
|
struct stat st;
|
|
void *addr;
|
|
int err = -1;
|
|
|
|
if (stat(from, &st))
|
|
goto out;
|
|
|
|
if (st.st_size == 0) /* /proc? do it slowly... */
|
|
return slow_copyfile(from, to, mode);
|
|
|
|
fromfd = open(from, O_RDONLY);
|
|
if (fromfd < 0)
|
|
goto out;
|
|
|
|
tofd = creat(to, mode);
|
|
if (tofd < 0)
|
|
goto out_close_from;
|
|
|
|
addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fromfd, 0);
|
|
if (addr == MAP_FAILED)
|
|
goto out_close_to;
|
|
|
|
if (write(tofd, addr, st.st_size) == st.st_size)
|
|
err = 0;
|
|
|
|
munmap(addr, st.st_size);
|
|
out_close_to:
|
|
close(tofd);
|
|
if (err)
|
|
unlink(to);
|
|
out_close_from:
|
|
close(fromfd);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
int copyfile(const char *from, const char *to)
|
|
{
|
|
return copyfile_mode(from, to, 0755);
|
|
}
|
|
|
|
unsigned long convert_unit(unsigned long value, char *unit)
|
|
{
|
|
*unit = ' ';
|
|
|
|
if (value > 1000) {
|
|
value /= 1000;
|
|
*unit = 'K';
|
|
}
|
|
|
|
if (value > 1000) {
|
|
value /= 1000;
|
|
*unit = 'M';
|
|
}
|
|
|
|
if (value > 1000) {
|
|
value /= 1000;
|
|
*unit = 'G';
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static ssize_t ion(bool is_read, int fd, void *buf, size_t n)
|
|
{
|
|
void *buf_start = buf;
|
|
size_t left = n;
|
|
|
|
while (left) {
|
|
ssize_t ret = is_read ? read(fd, buf, left) :
|
|
write(fd, buf, left);
|
|
|
|
if (ret < 0 && errno == EINTR)
|
|
continue;
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
left -= ret;
|
|
buf += ret;
|
|
}
|
|
|
|
BUG_ON((size_t)(buf - buf_start) != n);
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Read exactly 'n' bytes or return an error.
|
|
*/
|
|
ssize_t readn(int fd, void *buf, size_t n)
|
|
{
|
|
return ion(true, fd, buf, n);
|
|
}
|
|
|
|
/*
|
|
* Write exactly 'n' bytes or return an error.
|
|
*/
|
|
ssize_t writen(int fd, void *buf, size_t n)
|
|
{
|
|
return ion(false, fd, buf, n);
|
|
}
|
|
|
|
size_t hex_width(u64 v)
|
|
{
|
|
size_t n = 1;
|
|
|
|
while ((v >>= 4))
|
|
++n;
|
|
|
|
return n;
|
|
}
|
|
|
|
static int hex(char ch)
|
|
{
|
|
if ((ch >= '0') && (ch <= '9'))
|
|
return ch - '0';
|
|
if ((ch >= 'a') && (ch <= 'f'))
|
|
return ch - 'a' + 10;
|
|
if ((ch >= 'A') && (ch <= 'F'))
|
|
return ch - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* While we find nice hex chars, build a long_val.
|
|
* Return number of chars processed.
|
|
*/
|
|
int hex2u64(const char *ptr, u64 *long_val)
|
|
{
|
|
const char *p = ptr;
|
|
*long_val = 0;
|
|
|
|
while (*p) {
|
|
const int hex_val = hex(*p);
|
|
|
|
if (hex_val < 0)
|
|
break;
|
|
|
|
*long_val = (*long_val << 4) | hex_val;
|
|
p++;
|
|
}
|
|
|
|
return p - ptr;
|
|
}
|
|
|
|
/* Obtain a backtrace and print it to stdout. */
|
|
#ifdef HAVE_BACKTRACE_SUPPORT
|
|
void dump_stack(void)
|
|
{
|
|
void *array[16];
|
|
size_t size = backtrace(array, ARRAY_SIZE(array));
|
|
char **strings = backtrace_symbols(array, size);
|
|
size_t i;
|
|
|
|
printf("Obtained %zd stack frames.\n", size);
|
|
|
|
for (i = 0; i < size; i++)
|
|
printf("%s\n", strings[i]);
|
|
|
|
free(strings);
|
|
}
|
|
#else
|
|
void dump_stack(void) {}
|
|
#endif
|
|
|
|
void get_term_dimensions(struct winsize *ws)
|
|
{
|
|
char *s = getenv("LINES");
|
|
|
|
if (s != NULL) {
|
|
ws->ws_row = atoi(s);
|
|
s = getenv("COLUMNS");
|
|
if (s != NULL) {
|
|
ws->ws_col = atoi(s);
|
|
if (ws->ws_row && ws->ws_col)
|
|
return;
|
|
}
|
|
}
|
|
#ifdef TIOCGWINSZ
|
|
if (ioctl(1, TIOCGWINSZ, ws) == 0 &&
|
|
ws->ws_row && ws->ws_col)
|
|
return;
|
|
#endif
|
|
ws->ws_row = 25;
|
|
ws->ws_col = 80;
|
|
}
|
|
|
|
void set_term_quiet_input(struct termios *old)
|
|
{
|
|
struct termios tc;
|
|
|
|
tcgetattr(0, old);
|
|
tc = *old;
|
|
tc.c_lflag &= ~(ICANON | ECHO);
|
|
tc.c_cc[VMIN] = 0;
|
|
tc.c_cc[VTIME] = 0;
|
|
tcsetattr(0, TCSANOW, &tc);
|
|
}
|
|
|
|
static void set_tracing_events_path(const char *tracing, const char *mountpoint)
|
|
{
|
|
snprintf(tracing_events_path, sizeof(tracing_events_path), "%s/%s%s",
|
|
mountpoint, tracing, "events");
|
|
}
|
|
|
|
static const char *__perf_tracefs_mount(const char *mountpoint)
|
|
{
|
|
const char *mnt;
|
|
|
|
mnt = tracefs_mount(mountpoint);
|
|
if (!mnt)
|
|
return NULL;
|
|
|
|
set_tracing_events_path("", mnt);
|
|
|
|
return mnt;
|
|
}
|
|
|
|
static const char *__perf_debugfs_mount(const char *mountpoint)
|
|
{
|
|
const char *mnt;
|
|
|
|
mnt = debugfs_mount(mountpoint);
|
|
if (!mnt)
|
|
return NULL;
|
|
|
|
set_tracing_events_path("tracing/", mnt);
|
|
|
|
return mnt;
|
|
}
|
|
|
|
const char *perf_debugfs_mount(const char *mountpoint)
|
|
{
|
|
const char *mnt;
|
|
|
|
mnt = __perf_tracefs_mount(mountpoint);
|
|
if (mnt)
|
|
return mnt;
|
|
|
|
mnt = __perf_debugfs_mount(mountpoint);
|
|
|
|
return mnt;
|
|
}
|
|
|
|
void perf_debugfs_set_path(const char *mntpt)
|
|
{
|
|
snprintf(debugfs_mountpoint, strlen(debugfs_mountpoint), "%s", mntpt);
|
|
set_tracing_events_path("tracing/", mntpt);
|
|
}
|
|
|
|
static const char *find_tracefs(void)
|
|
{
|
|
const char *path = __perf_tracefs_mount(NULL);
|
|
|
|
return path;
|
|
}
|
|
|
|
static const char *find_debugfs(void)
|
|
{
|
|
const char *path = __perf_debugfs_mount(NULL);
|
|
|
|
if (!path)
|
|
fprintf(stderr, "Your kernel does not support the debugfs filesystem");
|
|
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* Finds the path to the debugfs/tracing
|
|
* Allocates the string and stores it.
|
|
*/
|
|
const char *find_tracing_dir(void)
|
|
{
|
|
const char *tracing_dir = "";
|
|
static char *tracing;
|
|
static int tracing_found;
|
|
const char *debugfs;
|
|
|
|
if (tracing_found)
|
|
return tracing;
|
|
|
|
debugfs = find_tracefs();
|
|
if (!debugfs) {
|
|
tracing_dir = "/tracing";
|
|
debugfs = find_debugfs();
|
|
if (!debugfs)
|
|
return NULL;
|
|
}
|
|
|
|
if (asprintf(&tracing, "%s%s", debugfs, tracing_dir) < 0)
|
|
return NULL;
|
|
|
|
tracing_found = 1;
|
|
return tracing;
|
|
}
|
|
|
|
char *get_tracing_file(const char *name)
|
|
{
|
|
const char *tracing;
|
|
char *file;
|
|
|
|
tracing = find_tracing_dir();
|
|
if (!tracing)
|
|
return NULL;
|
|
|
|
if (asprintf(&file, "%s/%s", tracing, name) < 0)
|
|
return NULL;
|
|
|
|
return file;
|
|
}
|
|
|
|
void put_tracing_file(char *file)
|
|
{
|
|
free(file);
|
|
}
|
|
|
|
int parse_nsec_time(const char *str, u64 *ptime)
|
|
{
|
|
u64 time_sec, time_nsec;
|
|
char *end;
|
|
|
|
time_sec = strtoul(str, &end, 10);
|
|
if (*end != '.' && *end != '\0')
|
|
return -1;
|
|
|
|
if (*end == '.') {
|
|
int i;
|
|
char nsec_buf[10];
|
|
|
|
if (strlen(++end) > 9)
|
|
return -1;
|
|
|
|
strncpy(nsec_buf, end, 9);
|
|
nsec_buf[9] = '\0';
|
|
|
|
/* make it nsec precision */
|
|
for (i = strlen(nsec_buf); i < 9; i++)
|
|
nsec_buf[i] = '0';
|
|
|
|
time_nsec = strtoul(nsec_buf, &end, 10);
|
|
if (*end != '\0')
|
|
return -1;
|
|
} else
|
|
time_nsec = 0;
|
|
|
|
*ptime = time_sec * NSEC_PER_SEC + time_nsec;
|
|
return 0;
|
|
}
|
|
|
|
unsigned long parse_tag_value(const char *str, struct parse_tag *tags)
|
|
{
|
|
struct parse_tag *i = tags;
|
|
|
|
while (i->tag) {
|
|
char *s;
|
|
|
|
s = strchr(str, i->tag);
|
|
if (s) {
|
|
unsigned long int value;
|
|
char *endptr;
|
|
|
|
value = strtoul(str, &endptr, 10);
|
|
if (s != endptr)
|
|
break;
|
|
|
|
if (value > ULONG_MAX / i->mult)
|
|
break;
|
|
value *= i->mult;
|
|
return value;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return (unsigned long) -1;
|
|
}
|
|
|
|
int filename__read_str(const char *filename, char **buf, size_t *sizep)
|
|
{
|
|
size_t size = 0, alloc_size = 0;
|
|
void *bf = NULL, *nbf;
|
|
int fd, n, err = 0;
|
|
char sbuf[STRERR_BUFSIZE];
|
|
|
|
fd = open(filename, O_RDONLY);
|
|
if (fd < 0)
|
|
return -errno;
|
|
|
|
do {
|
|
if (size == alloc_size) {
|
|
alloc_size += BUFSIZ;
|
|
nbf = realloc(bf, alloc_size);
|
|
if (!nbf) {
|
|
err = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
bf = nbf;
|
|
}
|
|
|
|
n = read(fd, bf + size, alloc_size - size);
|
|
if (n < 0) {
|
|
if (size) {
|
|
pr_warning("read failed %d: %s\n", errno,
|
|
strerror_r(errno, sbuf, sizeof(sbuf)));
|
|
err = 0;
|
|
} else
|
|
err = -errno;
|
|
|
|
break;
|
|
}
|
|
|
|
size += n;
|
|
} while (n > 0);
|
|
|
|
if (!err) {
|
|
*sizep = size;
|
|
*buf = bf;
|
|
} else
|
|
free(bf);
|
|
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
const char *get_filename_for_perf_kvm(void)
|
|
{
|
|
const char *filename;
|
|
|
|
if (perf_host && !perf_guest)
|
|
filename = strdup("perf.data.host");
|
|
else if (!perf_host && perf_guest)
|
|
filename = strdup("perf.data.guest");
|
|
else
|
|
filename = strdup("perf.data.kvm");
|
|
|
|
return filename;
|
|
}
|
|
|
|
int perf_event_paranoid(void)
|
|
{
|
|
int value;
|
|
|
|
if (sysctl__read_int("kernel/perf_event_paranoid", &value))
|
|
return INT_MAX;
|
|
|
|
return value;
|
|
}
|
|
|
|
void mem_bswap_32(void *src, int byte_size)
|
|
{
|
|
u32 *m = src;
|
|
while (byte_size > 0) {
|
|
*m = bswap_32(*m);
|
|
byte_size -= sizeof(u32);
|
|
++m;
|
|
}
|
|
}
|
|
|
|
void mem_bswap_64(void *src, int byte_size)
|
|
{
|
|
u64 *m = src;
|
|
|
|
while (byte_size > 0) {
|
|
*m = bswap_64(*m);
|
|
byte_size -= sizeof(u64);
|
|
++m;
|
|
}
|
|
}
|
|
|
|
bool find_process(const char *name)
|
|
{
|
|
size_t len = strlen(name);
|
|
DIR *dir;
|
|
struct dirent *d;
|
|
int ret = -1;
|
|
|
|
dir = opendir(procfs__mountpoint());
|
|
if (!dir)
|
|
return -1;
|
|
|
|
/* Walk through the directory. */
|
|
while (ret && (d = readdir(dir)) != NULL) {
|
|
char path[PATH_MAX];
|
|
char *data;
|
|
size_t size;
|
|
|
|
if ((d->d_type != DT_DIR) ||
|
|
!strcmp(".", d->d_name) ||
|
|
!strcmp("..", d->d_name))
|
|
continue;
|
|
|
|
scnprintf(path, sizeof(path), "%s/%s/comm",
|
|
procfs__mountpoint(), d->d_name);
|
|
|
|
if (filename__read_str(path, &data, &size))
|
|
continue;
|
|
|
|
ret = strncmp(name, data, len);
|
|
free(data);
|
|
}
|
|
|
|
closedir(dir);
|
|
return ret ? false : true;
|
|
}
|