| // 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; |
| 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) |
| { |
| struct kbuffer *kbuf; |
| |
| if (!cpu->tcpu) |
| return -1; |
| |
| kbuf = tracefs_cpu_buffered_read_buf(cpu->tcpu, true); |
| /* |
| * tracefs_cpu_buffered_read_buf() only reads in full subbuffer size, |
| * but this wants partial buffers as well. If the function returns |
| * empty (-1 for EAGAIN), try tracefs_cpu_flush_buf() next, as that can |
| * read partially filled buffers too, but isn't as efficient. |
| */ |
| if (!kbuf) |
| kbuf = tracefs_cpu_flush_buf(cpu->tcpu); |
| if (!kbuf) |
| return -1; |
| |
| cpu->kbuf = kbuf; |
| |
| 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, |
| bool snapshot) |
| { |
| 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; |
| if (snapshot) |
| tcpu = tracefs_cpu_snapshot_open(instance, cpu, true); |
| else |
| tcpu = tracefs_cpu_open_mapped(instance, cpu, true); |
| if (!tcpu) |
| goto error; |
| |
| tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp)); |
| if (!tmp) { |
| tracefs_cpu_close(tcpu); |
| goto error; |
| } |
| |
| *all_cpus = tmp; |
| |
| memset(tmp + i, 0, sizeof(*tmp)); |
| |
| tmp[i].tcpu = tcpu; |
| tmp[i].cpu = cpu; |
| i++; |
| } |
| *count = i; |
| return 0; |
| error: |
| tmp = *all_cpus; |
| for (i--; i >= 0; i--) { |
| tracefs_cpu_close(tmp[i].tcpu); |
| } |
| 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; |
| } |
| |
| /** |
| * tracefs_follow_event_clear - Remove callbacks for specific events for iterators |
| * @instance: The instance to follow |
| * @system: The system of the event to remove (NULL for all) |
| * @event_name: The name of the event to remove (NULL for all) |
| * |
| * This removes all callbacks from an instance that matches a specific |
| * event. If @event_name is NULL, then it removes all followers that match |
| * @system. If @system is NULL, then it removes all followers that match |
| * @event_name. If both @system and @event_name are NULL then it removes all |
| * followers for all events. |
| * |
| * Returns 0 on success and -1 on error (which includes no followers found) |
| */ |
| int tracefs_follow_event_clear(struct tracefs_instance *instance, |
| const char *system, const char *event_name) |
| { |
| struct follow_event **followers; |
| struct follow_event *follower; |
| int *nr_followers; |
| int nr; |
| int i, n; |
| |
| if (instance) { |
| followers = &instance->followers; |
| nr_followers = &instance->nr_followers; |
| } else { |
| followers = &root_followers; |
| nr_followers = &nr_root_followers; |
| } |
| |
| if (!*nr_followers) |
| return -1; |
| |
| /* If both system and event_name are NULL just remove all */ |
| if (!system && !event_name) { |
| free(*followers); |
| *followers = NULL; |
| *nr_followers = 0; |
| return 0; |
| } |
| |
| nr = *nr_followers; |
| follower = *followers; |
| |
| for (i = 0, n = 0; i < nr; i++) { |
| if (event_name && strcmp(event_name, follower[n].event->name) != 0) { |
| n++; |
| continue; |
| } |
| if (system && strcmp(system, follower[n].event->system) != 0) { |
| n++; |
| continue; |
| } |
| /* If there are no more after this, continue to increment i */ |
| if (i == nr - 1) |
| continue; |
| /* Remove this follower */ |
| memmove(&follower[n], &follower[n + 1], |
| sizeof(*follower) * (nr - (n + 1))); |
| } |
| |
| /* Did we find anything? */ |
| if (n == i) |
| return -1; |
| |
| /* NULL out the rest */ |
| memset(&follower[n], 0, (sizeof(*follower)) * (nr - n)); |
| *nr_followers = n; |
| |
| return 0; |
| } |
| |
| /** |
| * tracefs_follow_missed_events_clear - Remove callbacks for missed events |
| * @instance: The instance to remove missed callback followers |
| * |
| * This removes all callbacks from an instance that are for missed events. |
| * |
| * Returns 0 on success and -1 on error (which includes no followers found) |
| */ |
| int tracefs_follow_missed_events_clear(struct tracefs_instance *instance) |
| { |
| struct follow_event **followers; |
| int *nr_followers; |
| |
| if (instance) { |
| followers = &instance->missed_followers; |
| nr_followers = &instance->nr_missed_followers; |
| } else { |
| followers = &root_missed_followers; |
| nr_followers = &nr_root_missed_followers; |
| } |
| |
| if (!*nr_followers) |
| return -1; |
| |
| free(*followers); |
| *followers = NULL; |
| *nr_followers = 0; |
| return 0; |
| } |
| |
| static bool top_iterate_keep_going; |
| |
| static int iterate_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 snapshot) |
| { |
| 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, snapshot); |
| 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++) { |
| tracefs_cpu_close(all_cpus[i].tcpu); |
| } |
| free(all_cpus); |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * 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) |
| { |
| return iterate_events(tep, instance, cpus, cpu_size, callback, |
| callback_context, false); |
| } |
| |
| /* |
| * tracefs_iterate_snapshot_events - Iterate through events in snapshot_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_snapshot_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) |
| { |
| return iterate_events(tep, instance, cpus, cpu_size, callback, |
| callback_context, true); |
| } |
| |
| /** |
| * 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); |
| free(enable); |
| free(sys); |
| if (ret >= 0) { |
| if (add_list_string(&systems, name) < 0) |
| goto out_free; |
| } |
| } |
| |
| 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; |
| } |
| free(event); |
| |
| if (add_list_string(&events, name) < 0) |
| goto out_free; |
| } |
| |
| closedir(dir); |
| |
| out_free: |
| free(system_dir); |
| |
| return events; |
| } |
| |
| static char **list_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; |
| } |
| |
| /** |
| * 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) |
| { |
| return list_tracers(tracing_dir); |
| } |
| |
| /** |
| * tracefs_instance_tracers - returns an array of available tracers for an instance |
| * @instance: ftrace instance, can be NULL for the top instance |
| * |
| * 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_instance_tracers(struct tracefs_instance *instance) |
| { |
| const char *tracing_dir = NULL; |
| |
| if (instance) |
| tracing_dir = instance->trace_dir; |
| |
| return list_tracers(tracing_dir); |
| } |
| |
| 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); |
| } |
| |
| /** |
| * tracefs_load_headers - load just the headers into a tep handle |
| * @tracing_dir: The directory to load from (NULL to figure it out) |
| * @tep: The tep handle to load the headers into. |
| * |
| * Updates the @tep handle with the event and sub-buffer header |
| * information. |
| * |
| * Returns 0 on success and -1 on error. |
| */ |
| int tracefs_load_headers(const char *tracing_dir, struct tep_handle *tep) |
| { |
| int ret; |
| |
| if (!tracing_dir) |
| tracing_dir = tracefs_tracing_dir(); |
| |
| ret = read_header(tep, tracing_dir); |
| |
| return ret < 0 ? -1 : 0; |
| } |
| |
| 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; |
| } |
| } |