blob: c2adf415342e3454bc3033deef270c7e50c2f3e4 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
*
* Updates:
* Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <kbuffer.h>
#include "tracefs.h"
#include "tracefs-local.h"
static struct follow_event *root_followers;
static int nr_root_followers;
static struct follow_event *root_missed_followers;
static int nr_root_missed_followers;
struct cpu_iterate {
struct tracefs_cpu *tcpu;
struct tep_record record;
struct tep_event *event;
struct kbuffer *kbuf;
void *page;
int psize;
int cpu;
};
static int read_kbuf_record(struct cpu_iterate *cpu)
{
unsigned long long ts;
void *ptr;
if (!cpu || !cpu->kbuf)
return -1;
ptr = kbuffer_read_event(cpu->kbuf, &ts);
if (!ptr)
return -1;
memset(&cpu->record, 0, sizeof(cpu->record));
cpu->record.ts = ts;
cpu->record.size = kbuffer_event_size(cpu->kbuf);
cpu->record.record_size = kbuffer_curr_size(cpu->kbuf);
cpu->record.missed_events = kbuffer_missed_events(cpu->kbuf);
cpu->record.cpu = cpu->cpu;
cpu->record.data = ptr;
cpu->record.ref_count = 1;
kbuffer_next_event(cpu->kbuf, NULL);
return 0;
}
int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu)
{
enum kbuffer_long_size long_size;
enum kbuffer_endian endian;
int r;
if (!cpu->tcpu)
return -1;
r = tracefs_cpu_buffered_read(cpu->tcpu, cpu->page, true);
/*
* tracefs_cpu_buffered_read() only reads in full subbuffer size,
* but this wants partial buffers as well. If the function returns
* empty (-1 for EAGAIN), try tracefs_cpu_read() next, as that can
* read partially filled buffers too, but isn't as efficient.
*/
if (r <= 0)
r = tracefs_cpu_read(cpu->tcpu, cpu->page, true);
if (r <= 0)
return -1;
if (!cpu->kbuf) {
if (tep_is_file_bigendian(tep))
endian = KBUFFER_ENDIAN_BIG;
else
endian = KBUFFER_ENDIAN_LITTLE;
if (tep_get_header_page_size(tep) == 8)
long_size = KBUFFER_LSIZE_8;
else
long_size = KBUFFER_LSIZE_4;
cpu->kbuf = kbuffer_alloc(long_size, endian);
if (!cpu->kbuf)
return -1;
}
kbuffer_load_subbuffer(cpu->kbuf, cpu->page);
if (kbuffer_subbuffer_size(cpu->kbuf) > r) {
tracefs_warning("%s: page_size > %d", __func__, r);
return -1;
}
return 0;
}
int read_next_record(struct tep_handle *tep, struct cpu_iterate *cpu)
{
int id;
do {
while (!read_kbuf_record(cpu)) {
id = tep_data_type(tep, &(cpu->record));
cpu->event = tep_find_event(tep, id);
if (cpu->event)
return 0;
}
} while (!read_next_page(tep, cpu));
return -1;
}
/**
* tracefs_follow_missed_events - Add callback for missed events for iterators
* @instance: The instance to follow
* @callback: The function to call when missed events is detected
* @callback_data: The data to pass to @callback
*
* This attaches a callback to an @instance or the root instance if @instance
* is NULL, where if tracefs_iterate_raw_events() is called, that if missed
* events are detected, it will call @callback, with the following parameters:
* @event: The event pointer of the record with the missing events
* @record; The event instance of @event.
* @cpu: The cpu that the event happened on.
* @callback_data: The same as @callback_data passed to the function.
*
* If the count of missing events is available, @record->missed_events
* will have a positive number holding the number of missed events since
* the last event on the same CPU, or just -1 if that number is unknown
* but missed events did happen.
*
* Returns 0 on success and -1 on error.
*/
int tracefs_follow_missed_events(struct tracefs_instance *instance,
int (*callback)(struct tep_event *,
struct tep_record *,
int, void *),
void *callback_data)
{
struct follow_event **followers;
struct follow_event *follower;
struct follow_event follow;
int *nr_followers;
follow.event = NULL;
follow.callback = callback;
follow.callback_data = callback_data;
if (instance) {
followers = &instance->missed_followers;
nr_followers = &instance->nr_missed_followers;
} else {
followers = &root_missed_followers;
nr_followers = &nr_root_missed_followers;
}
follower = realloc(*followers, sizeof(*follower) *
((*nr_followers) + 1));
if (!follower)
return -1;
*followers = follower;
follower[(*nr_followers)++] = follow;
return 0;
}
static int call_missed_events(struct tracefs_instance *instance,
struct tep_event *event, struct tep_record *record, int cpu)
{
struct follow_event *followers;
int nr_followers;
int ret = 0;
int i;
if (instance) {
followers = instance->missed_followers;
nr_followers = instance->nr_missed_followers;
} else {
followers = root_missed_followers;
nr_followers = nr_root_missed_followers;
}
if (!followers)
return 0;
for (i = 0; i < nr_followers; i++) {
ret |= followers[i].callback(event, record,
cpu, followers[i].callback_data);
}
return ret;
}
static int call_followers(struct tracefs_instance *instance,
struct tep_event *event, struct tep_record *record, int cpu)
{
struct follow_event *followers;
int nr_followers;
int ret = 0;
int i;
if (record->missed_events)
ret = call_missed_events(instance, event, record, cpu);
if (ret)
return ret;
if (instance) {
followers = instance->followers;
nr_followers = instance->nr_followers;
} else {
followers = root_followers;
nr_followers = nr_root_followers;
}
if (!followers)
return 0;
for (i = 0; i < nr_followers; i++) {
if (followers[i].event == event)
ret |= followers[i].callback(event, record,
cpu, followers[i].callback_data);
}
return ret;
}
static int read_cpu_pages(struct tep_handle *tep, struct tracefs_instance *instance,
struct cpu_iterate *cpus, int count,
int (*callback)(struct tep_event *,
struct tep_record *,
int, void *),
void *callback_context,
bool *keep_going)
{
bool has_data = false;
int ret;
int i, j;
for (i = 0; i < count; i++) {
ret = read_next_record(tep, cpus + i);
if (!ret)
has_data = true;
}
while (has_data && *(volatile bool *)keep_going) {
j = count;
for (i = 0; i < count; i++) {
if (!cpus[i].event)
continue;
if (j == count || cpus[j].record.ts > cpus[i].record.ts)
j = i;
}
if (j < count) {
if (call_followers(instance, cpus[j].event, &cpus[j].record, cpus[j].cpu))
break;
if (callback &&
callback(cpus[j].event, &cpus[j].record, cpus[j].cpu, callback_context))
break;
cpus[j].event = NULL;
read_next_record(tep, cpus + j);
} else {
has_data = false;
}
}
return 0;
}
static int open_cpu_files(struct tracefs_instance *instance, cpu_set_t *cpus,
int cpu_size, struct cpu_iterate **all_cpus, int *count)
{
struct tracefs_cpu *tcpu;
struct cpu_iterate *tmp;
int nr_cpus;
int cpu;
int i = 0;
*all_cpus = NULL;
nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
for (cpu = 0; cpu < nr_cpus; cpu++) {
if (cpus && !CPU_ISSET_S(cpu, cpu_size, cpus))
continue;
tcpu = tracefs_cpu_open(instance, cpu, true);
tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp));
if (!tmp) {
i--;
goto error;
}
*all_cpus = tmp;
memset(tmp + i, 0, sizeof(*tmp));
if (!tcpu)
goto error;
tmp[i].tcpu = tcpu;
tmp[i].cpu = cpu;
tmp[i].psize = tracefs_cpu_read_size(tcpu);
tmp[i].page = malloc(tmp[i].psize);
if (!tmp[i++].page)
goto error;
}
*count = i;
return 0;
error:
tmp = *all_cpus;
for (; i >= 0; i--) {
tracefs_cpu_close(tmp[i].tcpu);
free(tmp[i].page);
}
free(tmp);
*all_cpus = NULL;
return -1;
}
/**
* tracefs_follow_event - Add callback for specific events for iterators
* @tep: a handle to the trace event parser context
* @instance: The instance to follow
* @system: The system of the event to track
* @event_name: The name of the event to track
* @callback: The function to call when the event is hit in an iterator
* @callback_data: The data to pass to @callback
*
* This attaches a callback to an @instance or the root instance if @instance
* is NULL, where if tracefs_iterate_raw_events() is called, that if the specified
* event is hit, it will call @callback, with the following parameters:
* @event: The event pointer that was found by @system and @event_name.
* @record; The event instance of @event.
* @cpu: The cpu that the event happened on.
* @callback_data: The same as @callback_data passed to the function.
*
* Returns 0 on success and -1 on error.
*/
int tracefs_follow_event(struct tep_handle *tep, struct tracefs_instance *instance,
const char *system, const char *event_name,
int (*callback)(struct tep_event *,
struct tep_record *,
int, void *),
void *callback_data)
{
struct follow_event **followers;
struct follow_event *follower;
struct follow_event follow;
int *nr_followers;
if (!tep) {
errno = EINVAL;
return -1;
}
follow.event = tep_find_event_by_name(tep, system, event_name);
if (!follow.event) {
errno = ENOENT;
return -1;
}
follow.callback = callback;
follow.callback_data = callback_data;
if (instance) {
followers = &instance->followers;
nr_followers = &instance->nr_followers;
} else {
followers = &root_followers;
nr_followers = &nr_root_followers;
}
follower = realloc(*followers, sizeof(*follower) *
((*nr_followers) + 1));
if (!follower)
return -1;
*followers = follower;
follower[(*nr_followers)++] = follow;
return 0;
}
static bool top_iterate_keep_going;
/*
* tracefs_iterate_raw_events - Iterate through events in trace_pipe_raw,
* per CPU trace buffers
* @tep: a handle to the trace event parser context
* @instance: ftrace instance, can be NULL for the top instance
* @cpus: Iterate only through the buffers of CPUs, set in the mask.
* If NULL, iterate through all CPUs.
* @cpu_size: size of @cpus set
* @callback: A user function, called for each record from the file
* @callback_context: A custom context, passed to the user callback function
*
* If the @callback returns non-zero, the iteration stops - in that case all
* records from the current page will be lost from future reads
* The events are iterated in sorted order, oldest first.
*
* Returns -1 in case of an error, or 0 otherwise
*/
int tracefs_iterate_raw_events(struct tep_handle *tep,
struct tracefs_instance *instance,
cpu_set_t *cpus, int cpu_size,
int (*callback)(struct tep_event *,
struct tep_record *,
int, void *),
void *callback_context)
{
bool *keep_going = instance ? &instance->iterate_keep_going :
&top_iterate_keep_going;
struct follow_event *followers;
struct cpu_iterate *all_cpus;
int count = 0;
int ret;
int i;
(*(volatile bool *)keep_going) = true;
if (!tep)
return -1;
if (instance)
followers = instance->followers;
else
followers = root_followers;
if (!callback && !followers)
return -1;
ret = open_cpu_files(instance, cpus, cpu_size, &all_cpus, &count);
if (ret < 0)
goto out;
ret = read_cpu_pages(tep, instance, all_cpus, count,
callback, callback_context,
keep_going);
out:
if (all_cpus) {
for (i = 0; i < count; i++) {
kbuffer_free(all_cpus[i].kbuf);
tracefs_cpu_close(all_cpus[i].tcpu);
free(all_cpus[i].page);
}
free(all_cpus);
}
return ret;
}
/**
* tracefs_iterate_stop - stop the iteration over the raw events.
* @instance: ftrace instance, can be NULL for top tracing instance.
*/
void tracefs_iterate_stop(struct tracefs_instance *instance)
{
if (instance)
instance->iterate_keep_going = false;
else
top_iterate_keep_going = false;
}
static int add_list_string(char ***list, const char *name)
{
char **tmp;
tmp = tracefs_list_add(*list, name);
if (!tmp) {
tracefs_list_free(*list);
*list = NULL;
return -1;
}
*list = tmp;
return 0;
}
__hidden char *trace_append_file(const char *dir, const char *name)
{
char *file;
int ret;
ret = asprintf(&file, "%s/%s", dir, name);
return ret < 0 ? NULL : file;
}
static int event_file(char **path, const char *system,
const char *event, const char *file)
{
if (!system || !event || !file)
return -1;
return asprintf(path, "events/%s/%s/%s",
system, event, file);
}
/**
* tracefs_event_get_file - return a file in an event directory
* @instance: The instance the event is in (NULL for top level)
* @system: The system name that the event file is in
* @event: The event name of the event
* @file: The name of the file in the event directory.
*
* Returns a path to a file in the event director.
* or NULL on error. The path returned must be freed with
* tracefs_put_tracing_file().
*/
char *tracefs_event_get_file(struct tracefs_instance *instance,
const char *system, const char *event,
const char *file)
{
char *instance_path;
char *path;
int ret;
ret = event_file(&path, system, event, file);
if (ret < 0)
return NULL;
instance_path = tracefs_instance_get_file(instance, path);
free(path);
return instance_path;
}
/**
* tracefs_event_file_read - read the content from an event file
* @instance: The instance the event is in (NULL for top level)
* @system: The system name that the event file is in
* @event: The event name of the event
* @file: The name of the file in the event directory.
* @psize: the size of the content read.
*
* Reads the content of the event file that is passed via the
* arguments and returns the content.
*
* Return a string containing the content of the file or NULL
* on error. The string returned must be freed with free().
*/
char *tracefs_event_file_read(struct tracefs_instance *instance,
const char *system, const char *event,
const char *file, int *psize)
{
char *content;
char *path;
int ret;
ret = event_file(&path, system, event, file);
if (ret < 0)
return NULL;
content = tracefs_instance_file_read(instance, path, psize);
free(path);
return content;
}
/**
* tracefs_event_file_write - write to an event file
* @instance: The instance the event is in (NULL for top level)
* @system: The system name that the event file is in
* @event: The event name of the event
* @file: The name of the file in the event directory.
* @str: The string to write into the file
*
* Writes the content of @str to a file in the instance directory.
* The content of the file will be overwritten by @str.
*
* Return 0 on success, and -1 on error.
*/
int tracefs_event_file_write(struct tracefs_instance *instance,
const char *system, const char *event,
const char *file, const char *str)
{
char *path;
int ret;
ret = event_file(&path, system, event, file);
if (ret < 0)
return -1;
ret = tracefs_instance_file_write(instance, path, str);
free(path);
return ret;
}
/**
* tracefs_event_file_append - write to an event file
* @instance: The instance the event is in (NULL for top level)
* @system: The system name that the event file is in
* @event: The event name of the event
* @file: The name of the file in the event directory.
* @str: The string to write into the file
*
* Writes the content of @str to a file in the instance directory.
* The content of @str will be appended to the content of the file.
* The current content should not be lost.
*
* Return 0 on success, and -1 on error.
*/
int tracefs_event_file_append(struct tracefs_instance *instance,
const char *system, const char *event,
const char *file, const char *str)
{
char *path;
int ret;
ret = event_file(&path, system, event, file);
if (ret < 0)
return -1;
ret = tracefs_instance_file_append(instance, path, str);
free(path);
return ret;
}
/**
* tracefs_event_file_clear - clear an event file
* @instance: The instance the event is in (NULL for top level)
* @system: The system name that the event file is in
* @event: The event name of the event
* @file: The name of the file in the event directory.
*
* Clears the content of the event file. That is, it is opened
* with O_TRUNC and then closed.
*
* Return 0 on success, and -1 on error.
*/
int tracefs_event_file_clear(struct tracefs_instance *instance,
const char *system, const char *event,
const char *file)
{
char *path;
int ret;
ret = event_file(&path, system, event, file);
if (ret < 0)
return -1;
ret = tracefs_instance_file_clear(instance, path);
free(path);
return ret;
}
/**
* tracefs_event_file_exits - test if a file exists
* @instance: The instance the event is in (NULL for top level)
* @system: The system name that the event file is in
* @event: The event name of the event
* @file: The name of the file in the event directory.
*
* Return true if the file exists, false if it odes not or
* an error occurred.
*/
bool tracefs_event_file_exists(struct tracefs_instance *instance,
const char *system, const char *event,
const char *file)
{
char *path;
bool ret;
if (event_file(&path, system, event, file) < 0)
return false;
ret = tracefs_file_exists(instance, path);
free(path);
return ret;
}
/**
* tracefs_event_systems - return list of systems for tracing
* @tracing_dir: directory holding the "events" directory
* if NULL, top tracing directory is used
*
* Returns an allocated list of system names. Both the names and
* the list must be freed with tracefs_list_free()
* The list returned ends with a "NULL" pointer
*/
char **tracefs_event_systems(const char *tracing_dir)
{
struct dirent *dent;
char **systems = NULL;
char *events_dir;
struct stat st;
DIR *dir;
int ret;
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
if (!tracing_dir)
return NULL;
events_dir = trace_append_file(tracing_dir, "events");
if (!events_dir)
return NULL;
/*
* Search all the directories in the events directory,
* and collect the ones that have the "enable" file.
*/
ret = stat(events_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
dir = opendir(events_dir);
if (!dir)
goto out_free;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *enable;
char *sys;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
sys = trace_append_file(events_dir, name);
ret = stat(sys, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(sys);
continue;
}
enable = trace_append_file(sys, "enable");
ret = stat(enable, &st);
if (ret >= 0) {
if (add_list_string(&systems, name) < 0)
goto out_free;
}
free(enable);
free(sys);
}
closedir(dir);
out_free:
free(events_dir);
return systems;
}
/**
* tracefs_system_events - return list of events for system
* @tracing_dir: directory holding the "events" directory
* @system: the system to return the events for
*
* Returns an allocated list of event names. Both the names and
* the list must be freed with tracefs_list_free()
* The list returned ends with a "NULL" pointer
*/
char **tracefs_system_events(const char *tracing_dir, const char *system)
{
struct dirent *dent;
char **events = NULL;
char *system_dir = NULL;
struct stat st;
DIR *dir;
int ret;
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
if (!tracing_dir || !system)
return NULL;
asprintf(&system_dir, "%s/events/%s", tracing_dir, system);
if (!system_dir)
return NULL;
ret = stat(system_dir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode))
goto out_free;
dir = opendir(system_dir);
if (!dir)
goto out_free;
while ((dent = readdir(dir))) {
const char *name = dent->d_name;
char *event;
if (strcmp(name, ".") == 0 ||
strcmp(name, "..") == 0)
continue;
event = trace_append_file(system_dir, name);
ret = stat(event, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
free(event);
continue;
}
if (add_list_string(&events, name) < 0)
goto out_free;
free(event);
}
closedir(dir);
out_free:
free(system_dir);
return events;
}
/**
* tracefs_tracers - returns an array of available tracers
* @tracing_dir: The directory that contains the tracing directory
*
* Returns an allocate list of plugins. The array ends with NULL
* Both the plugin names and array must be freed with tracefs_list_free()
*/
char **tracefs_tracers(const char *tracing_dir)
{
char *available_tracers;
struct stat st;
char **plugins = NULL;
char *buf;
char *str, *saveptr;
char *plugin;
int slen;
int len;
int ret;
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
if (!tracing_dir)
return NULL;
available_tracers = trace_append_file(tracing_dir, "available_tracers");
if (!available_tracers)
return NULL;
ret = stat(available_tracers, &st);
if (ret < 0)
goto out_free;
len = str_read_file(available_tracers, &buf, true);
if (len <= 0)
goto out_free;
for (str = buf; ; str = NULL) {
plugin = strtok_r(str, " ", &saveptr);
if (!plugin)
break;
slen = strlen(plugin);
if (!slen)
continue;
/* chop off any newlines */
if (plugin[slen - 1] == '\n')
plugin[slen - 1] = '\0';
/* Skip the non tracers */
if (strcmp(plugin, "nop") == 0 ||
strcmp(plugin, "none") == 0)
continue;
if (add_list_string(&plugins, plugin) < 0)
break;
}
free(buf);
out_free:
free(available_tracers);
return plugins;
}
static int load_events(struct tep_handle *tep,
const char *tracing_dir, const char *system, bool check)
{
int ret = 0, failure = 0;
char **events = NULL;
struct stat st;
int len = 0;
int i;
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
events = tracefs_system_events(tracing_dir, system);
if (!events)
return -ENOENT;
for (i = 0; events[i]; i++) {
char *format;
char *buf;
ret = asprintf(&format, "%s/events/%s/%s/format",
tracing_dir, system, events[i]);
if (ret < 0) {
failure = -ENOMEM;
break;
}
ret = stat(format, &st);
if (ret < 0)
goto next_event;
/* check if event is already added, to avoid duplicates */
if (check && tep_find_event_by_name(tep, system, events[i]))
goto next_event;
len = str_read_file(format, &buf, true);
if (len <= 0)
goto next_event;
ret = tep_parse_event(tep, buf, len, system);
free(buf);
next_event:
free(format);
if (ret)
failure = ret;
}
tracefs_list_free(events);
return failure;
}
__hidden int trace_rescan_events(struct tep_handle *tep,
const char *tracing_dir, const char *system)
{
/* ToDo: add here logic for deleting removed events from tep handle */
return load_events(tep, tracing_dir, system, true);
}
__hidden int trace_load_events(struct tep_handle *tep,
const char *tracing_dir, const char *system)
{
return load_events(tep, tracing_dir, system, false);
}
__hidden struct tep_event *get_tep_event(struct tep_handle *tep,
const char *system, const char *name)
{
struct tep_event *event;
/* Check if event exists in the system */
if (!tracefs_event_file_exists(NULL, system, name, "format"))
return NULL;
/* If the event is already loaded in the tep, return it */
event = tep_find_event_by_name(tep, system, name);
if (event)
return event;
/* Try to load any new events from the given system */
if (trace_rescan_events(tep, NULL, system))
return NULL;
return tep_find_event_by_name(tep, system, name);
}
static int read_header(struct tep_handle *tep, const char *tracing_dir)
{
struct stat st;
char *header;
char *buf;
int len;
int ret = -1;
header = trace_append_file(tracing_dir, "events/header_page");
ret = stat(header, &st);
if (ret < 0)
goto out;
len = str_read_file(header, &buf, true);
if (len <= 0)
goto out;
tep_parse_header_page(tep, buf, len, sizeof(long));
free(buf);
ret = 0;
out:
free(header);
return ret;
}
static bool contains(const char *name, const char * const *names)
{
if (!names)
return false;
for (; *names; names++)
if (strcmp(name, *names) == 0)
return true;
return false;
}
static void load_kallsyms(struct tep_handle *tep)
{
char *buf;
if (str_read_file("/proc/kallsyms", &buf, false) <= 0)
return;
tep_parse_kallsyms(tep, buf);
free(buf);
}
static int load_saved_cmdlines(const char *tracing_dir,
struct tep_handle *tep, bool warn)
{
char *path;
char *buf;
int ret;
path = trace_append_file(tracing_dir, "saved_cmdlines");
if (!path)
return -1;
ret = str_read_file(path, &buf, false);
free(path);
if (ret <= 0)
return -1;
ret = tep_parse_saved_cmdlines(tep, buf);
free(buf);
return ret;
}
static void load_printk_formats(const char *tracing_dir,
struct tep_handle *tep)
{
char *path;
char *buf;
int ret;
path = trace_append_file(tracing_dir, "printk_formats");
if (!path)
return;
ret = str_read_file(path, &buf, false);
free(path);
if (ret <= 0)
return;
tep_parse_printk_formats(tep, buf);
free(buf);
}
/*
* Do a best effort attempt to load kallsyms, saved_cmdlines and
* printk_formats. If they can not be loaded, then this will not
* do the mappings. But this does not fail the loading of events.
*/
static void load_mappings(const char *tracing_dir,
struct tep_handle *tep)
{
load_kallsyms(tep);
/* If there's no tracing_dir no reason to go further */
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
if (!tracing_dir)
return;
load_saved_cmdlines(tracing_dir, tep, false);
load_printk_formats(tracing_dir, tep);
}
int tracefs_load_cmdlines(const char *tracing_dir, struct tep_handle *tep)
{
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
if (!tracing_dir)
return -1;
return load_saved_cmdlines(tracing_dir, tep, true);
}
static int fill_local_events_system(const char *tracing_dir,
struct tep_handle *tep,
const char * const *sys_names,
int *parsing_failures)
{
char **systems = NULL;
int ret;
int i;
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
if (!tracing_dir)
return -1;
systems = tracefs_event_systems(tracing_dir);
if (!systems)
return -1;
ret = read_header(tep, tracing_dir);
if (ret < 0) {
ret = -1;
goto out;
}
if (parsing_failures)
*parsing_failures = 0;
for (i = 0; systems[i]; i++) {
if (sys_names && !contains(systems[i], sys_names))
continue;
ret = trace_load_events(tep, tracing_dir, systems[i]);
if (ret && parsing_failures)
(*parsing_failures)++;
}
/* Include ftrace, as it is excluded for not having "enable" file */
if (!sys_names || contains("ftrace", sys_names))
trace_load_events(tep, tracing_dir, "ftrace");
load_mappings(tracing_dir, tep);
/* always succeed because parsing failures are not critical */
ret = 0;
out:
tracefs_list_free(systems);
return ret;
}
static void set_tep_cpus(const char *tracing_dir, struct tep_handle *tep)
{
struct stat st;
char path[PATH_MAX];
int cpus = sysconf(_SC_NPROCESSORS_CONF);
int max_cpu = 0;
int ret;
int i;
if (!tracing_dir)
tracing_dir = tracefs_tracing_dir();
/*
* Paranoid: in case sysconf() above does not work.
* And we also only care about the number of tracing
* buffers that exist. If cpus is 32, but the top half
* is offline, there may only be 16 tracing buffers.
* That's what we want to know.
*/
for (i = 0; !cpus || i < cpus; i++) {
snprintf(path, PATH_MAX, "%s/per_cpu/cpu%d", tracing_dir, i);
ret = stat(path, &st);
if (!ret && S_ISDIR(st.st_mode))
max_cpu = i + 1;
else if (i >= cpus)
break;
}
if (!max_cpu)
max_cpu = cpus;
tep_set_cpus(tep, max_cpu);
}
/**
* tracefs_local_events_system - create a tep from the events of the specified subsystem.
*
* @tracing_dir: The directory that contains the events.
* @sys_name: Array of system names, to load the events from.
* The last element from the array must be NULL
*
* Returns a tep structure that contains the tep local to
* the system.
*/
struct tep_handle *tracefs_local_events_system(const char *tracing_dir,
const char * const *sys_names)
{
struct tep_handle *tep = NULL;
tep = tep_alloc();
if (!tep)
return NULL;
if (fill_local_events_system(tracing_dir, tep, sys_names, NULL)) {
tep_free(tep);
tep = NULL;
}
set_tep_cpus(tracing_dir, tep);
/* Set the long size for this tep handle */
tep_set_long_size(tep, tep_get_header_page_size(tep));
return tep;
}
/**
* tracefs_local_events - create a tep from the events on system
* @tracing_dir: The directory that contains the events.
*
* Returns a tep structure that contains the teps local to
* the system.
*/
struct tep_handle *tracefs_local_events(const char *tracing_dir)
{
return tracefs_local_events_system(tracing_dir, NULL);
}
/**
* tracefs_fill_local_events - Fill a tep with the events on system
* @tracing_dir: The directory that contains the events.
* @tep: Allocated tep handler which will be filled
* @parsing_failures: return number of failures while parsing the event files
*
* Returns whether the operation succeeded
*/
int tracefs_fill_local_events(const char *tracing_dir,
struct tep_handle *tep, int *parsing_failures)
{
return fill_local_events_system(tracing_dir, tep,
NULL, parsing_failures);
}
static bool match(const char *str, regex_t *re)
{
return regexec(re, str, 0, NULL, 0) == 0;
}
enum event_state {
STATE_INIT,
STATE_ENABLED,
STATE_DISABLED,
STATE_MIXED,
STATE_ERROR,
};
static int read_event_state(struct tracefs_instance *instance, const char *file,
enum event_state *state)
{
char *val;
int ret = 0;
if (*state == STATE_ERROR)
return -1;
val = tracefs_instance_file_read(instance, file, NULL);
if (!val)
return -1;
switch (val[0]) {
case '0':
switch (*state) {
case STATE_INIT:
*state = STATE_DISABLED;
break;
case STATE_ENABLED:
*state = STATE_MIXED;
break;
default:
break;
}
break;
case '1':
switch (*state) {
case STATE_INIT:
*state = STATE_ENABLED;
break;
case STATE_DISABLED:
*state = STATE_MIXED;
break;
default:
break;
}
break;
case 'X':
*state = STATE_MIXED;
break;
default:
*state = TRACEFS_ERROR;
ret = -1;
break;
}
free(val);
return ret;
}
static int enable_disable_event(struct tracefs_instance *instance,
const char *system, const char *event,
bool enable, enum event_state *state)
{
const char *str = enable ? "1" : "0";
char *system_event;
int ret;
ret = asprintf(&system_event, "events/%s/%s/enable", system, event);
if (ret < 0)
return ret;
if (state)
ret = read_event_state(instance, system_event, state);
else
ret = tracefs_instance_file_write(instance, system_event, str);
free(system_event);
return ret;
}
static int enable_disable_system(struct tracefs_instance *instance,
const char *system, bool enable,
enum event_state *state)
{
const char *str = enable ? "1" : "0";
char *system_path;
int ret;
ret = asprintf(&system_path, "events/%s/enable", system);
if (ret < 0)
return ret;
if (state)
ret = read_event_state(instance, system_path, state);
else
ret = tracefs_instance_file_write(instance, system_path, str);
free(system_path);
return ret;
}
static int enable_disable_all(struct tracefs_instance *instance,
bool enable)
{
const char *str = enable ? "1" : "0";
int ret;
ret = tracefs_instance_file_write(instance, "events/enable", str);
return ret < 0 ? ret : 0;
}
static int make_regex(regex_t *re, const char *match)
{
int len = strlen(match);
char str[len + 3];
char *p = &str[0];
if (!len || match[0] != '^')
*(p++) = '^';
strcpy(p, match);
p += len;
if (!len || match[len-1] != '$')
*(p++) = '$';
*p = '\0';
return regcomp(re, str, REG_ICASE|REG_NOSUB);
}
static int event_enable_disable(struct tracefs_instance *instance,
const char *system, const char *event,
bool enable, enum event_state *state)
{
regex_t system_re, event_re;
char **systems;
char **events = NULL;
int ret = -1;
int s, e;
/* Handle all events first */
if (!system && !event)
return enable_disable_all(instance, enable);
systems = tracefs_event_systems(NULL);
if (!systems)
goto out_free;
if (system) {
ret = make_regex(&system_re, system);
if (ret < 0)
goto out_free;
}
if (event) {
ret = make_regex(&event_re, event);
if (ret < 0) {
if (system)
regfree(&system_re);
goto out_free;
}
}
ret = -1;
for (s = 0; systems[s]; s++) {
if (system && !match(systems[s], &system_re))
continue;
/* Check for the short cut first */
if (!event) {
ret = enable_disable_system(instance, systems[s], enable, state);
if (ret < 0)
break;
ret = 0;
continue;
}
events = tracefs_system_events(NULL, systems[s]);
if (!events)
continue; /* Error? */
for (e = 0; events[e]; e++) {
if (!match(events[e], &event_re))
continue;
ret = enable_disable_event(instance, systems[s],
events[e], enable, state);
if (ret < 0)
break;
ret = 0;
}
tracefs_list_free(events);
events = NULL;
}
if (system)
regfree(&system_re);
if (event)
regfree(&event_re);
out_free:
tracefs_list_free(systems);
tracefs_list_free(events);
return ret;
}
/**
* tracefs_event_enable - enable specified events
* @instance: ftrace instance, can be NULL for the top instance
* @system: A regex of a system (NULL to match all systems)
* @event: A regex of the event in the system (NULL to match all events)
*
* This will enable events that match the @system and @event.
* If both @system and @event are NULL, then it will enable all events.
* If @system is NULL, it will look at all systems for matching events
* to @event.
* If @event is NULL, then it will enable all events in the systems
* that match @system.
*
* Returns 0 on success, and -1 if it encountered an error,
* or if no events matched. If no events matched, then -1 is set
* but errno will not be.
*/
int tracefs_event_enable(struct tracefs_instance *instance,
const char *system, const char *event)
{
return event_enable_disable(instance, system, event, true, NULL);
}
int tracefs_event_disable(struct tracefs_instance *instance,
const char *system, const char *event)
{
return event_enable_disable(instance, system, event, false, NULL);
}
/**
* tracefs_event_is_enabled - return if the event is enabled or not
* @instance: ftrace instance, can be NULL for the top instance
* @system: The name of the system to check
* @event: The name of the event to check
*
* Checks is an event or multiple events are enabled.
*
* If @system is NULL, then it will check all the systems where @event is
* a match.
*
* If @event is NULL, then it will check all events where @system is a match.
*
* If both @system and @event are NULL, then it will check all events
*
* Returns TRACEFS_ALL_ENABLED if all matching are enabled.
* Returns TRACEFS_SOME_ENABLED if some are enabled and some are not
* Returns TRACEFS_ALL_DISABLED if none of the events are enabled.
* Returns TRACEFS_ERROR if there is an error reading the events.
*/
enum tracefs_enable_state
tracefs_event_is_enabled(struct tracefs_instance *instance,
const char *system, const char *event)
{
enum event_state state = STATE_INIT;
int ret;
ret = event_enable_disable(instance, system, event, false, &state);
if (ret < 0)
return TRACEFS_ERROR;
switch (state) {
case STATE_ENABLED:
return TRACEFS_ALL_ENABLED;
case STATE_DISABLED:
return TRACEFS_ALL_DISABLED;
case STATE_MIXED:
return TRACEFS_SOME_ENABLED;
default:
return TRACEFS_ERROR;
}
}