From: Kyle Tomsic <ktomsic(a)lexmark.com>
Since 3.10, the Linux kernel has supported multiple ftrace buffers.
However, the trace extension predates this addition and could only
extract data from the global instance.
This changeset adds support for dumping these additional buffers by
walking the ftrace_trace_arrays list to find the additional buffers.
Several functions that relied on global variables have been refactored
to accept the data they operate on as parameters so they could be reused
for the instances as well.
---
extensions/trace.c | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 228 insertions(+), 9 deletions(-)
diff --git a/extensions/trace.c b/extensions/trace.c
index f97aa6a..1344e48 100644
--- a/extensions/trace.c
+++ b/extensions/trace.c
@@ -38,6 +38,10 @@ static int trace_buffer_available;
* max_buffer is supported
*/
static int max_buffer_available;
+/*
+ * multiple trace instances are supported
+ */
+static int multiple_instances_available;
#define koffset(struct, member) struct##_##member##_offset
@@ -78,6 +82,8 @@ static int koffset(ftrace_event_field, offset);
static int koffset(ftrace_event_field, size);
static int koffset(ftrace_event_field, is_signed);
+static int koffset(trace_array, name);
+
static int koffset(POINTER_SYM, POINTER) = 0;
struct ring_buffer_per_cpu {
@@ -104,6 +110,7 @@ static ulong global_trace;
static ulong max_tr_trace;
struct trace_instance {
+ char name[NAME_MAX + 1];
ulong trace_buffer;
ulong max_buffer;
ulong ring_buffer;
@@ -115,7 +122,10 @@ struct trace_instance {
struct ring_buffer_per_cpu *max_tr_buffers;
};
+static ulong ftrace_trace_arrays;
static struct trace_instance global_trace_instance;
+static struct trace_instance *trace_instances = NULL;
+static int instance_count;
static ulong ftrace_events;
static ulong current_trace;
@@ -234,6 +244,9 @@ static int init_offsets(void)
init_offset(ftrace_event_field, size);
init_offset(ftrace_event_field, is_signed);
+ if (MEMBER_EXISTS("trace_array", "name"))
+ init_offset(trace_array, name);
+
return 0;
#undef init_offset
}
@@ -487,6 +500,92 @@ out_fail:
return -1;
}
+static void ftrace_destroy_all_instance_buffers()
+{
+ int i;
+
+ for (i = 0; i < instance_count; i++)
+ {
+ struct trace_instance *ti = &trace_instances[i];
+
+ if (ti->max_tr_ring_buffer) {
+ ftrace_destroy_buffers(ti->max_tr_buffers);
+ free(ti->max_tr_buffers);
+ }
+
+ ftrace_destroy_buffers(ti->buffers);
+ free(ti->buffers);
+ }
+}
+
+static void ftrace_destroy_instances()
+{
+ ftrace_destroy_all_instance_buffers();
+ free(trace_instances);
+}
+
+static int ftrace_init_instances()
+{
+ int i;
+ struct trace_instance *ti;
+ struct list_data list_data;
+ struct list_data *ld = &list_data;
+
+ if (!multiple_instances_available)
+ return 0;
+
+ BZERO(ld, sizeof(struct list_data));
+ ld->start = ftrace_trace_arrays;
+ ld->end = global_trace;
+ ld->flags = LIST_ALLOCATE;
+ instance_count = do_list(ld);
+
+ /* The do_list count includes the list_head, which is not a
+ * proper instance */
+ instance_count--;
+ if (instance_count <= 0)
+ return 0;
+
+ trace_instances = calloc(sizeof(struct trace_instance), instance_count);
+
+ /* We start i at 1 to skip over the list_head and continue to the last
+ * instance, which lies at index instance_count */
+ for (i = 1; i <= instance_count; i++)
+ {
+ ulong instance_ptr;
+ ulong name_addr;
+ int ret;
+
+ ti = &trace_instances[i-1];
+ instance_ptr = ld->list_ptr[i];
+ read_value(name_addr, instance_ptr, trace_array, name);
+ if (!name_addr)
+ {
+ console("Instance name is NULL\n");
+ }
+ else if (!read_string(name_addr, ti->name, sizeof(ti->name)))
+ {
+ console("Failed to read instance name at address %p\n", (void*)name_addr);
+ goto out_fail;
+ }
+
+ ret = ftrace_init_trace(ti, instance_ptr);
+ if (ret < 0)
+ goto out_fail;
+ }
+ FREEBUF(ld->list_ptr);
+
+ return 0;
+
+out_fail:
+ /* We've already freed the current instance's trace buffer info, so
+ * we'll clear that out to avoid double freeing in
+ * ftrace_destroy_instances() */
+ BZERO(ti, sizeof(struct trace_instance));
+ ftrace_destroy_instances();
+
+ return -1;
+}
static int ftrace_init_current_tracer(void)
{
@@ -519,9 +618,11 @@ static int ftrace_init(void)
struct syment *sym_max_tr_trace;
struct syment *sym_ftrace_events;
struct syment *sym_current_trace;
+ struct syment *sym_ftrace_trace_arrays;
sym_global_trace = symbol_search("global_trace");
sym_ftrace_events = symbol_search("ftrace_events");
+ sym_ftrace_trace_arrays = symbol_search("ftrace_trace_arrays");
if (sym_global_trace == NULL || sym_ftrace_events == NULL)
return -1;
@@ -529,6 +630,13 @@ static int ftrace_init(void)
global_trace = sym_global_trace->value;
ftrace_events = sym_ftrace_events->value;
+ if (sym_ftrace_trace_arrays)
+ {
+ multiple_instances_available = 1;
+ ftrace_trace_arrays = sym_ftrace_trace_arrays->value;
+ }
+
+
if (MEMBER_EXISTS("trace_array", "current_trace")) {
encapsulated_current_trace = 1;
} else {
@@ -562,17 +670,21 @@ static int ftrace_init(void)
if (ftrace_init_trace(&global_trace_instance, global_trace) < 0)
goto out_0;
+ if (ftrace_init_instances() < 0)
+ goto out_1;
if (ftrace_init_event_types() < 0)
- goto out_1;
+ goto out_2;
if (ftrace_init_current_tracer() < 0)
- goto out_2;
+ goto out_3;
return 0;
-out_2:
+out_3:
ftrace_destroy_event_types();
+out_2:
+ ftrace_destroy_instances();
out_1:
if (global_trace_instance.max_tr_ring_buffer) {
ftrace_destroy_buffers(global_trace_instance.max_tr_buffers);
@@ -589,6 +701,8 @@ static void ftrace_destroy(void)
free(current_tracer_name);
ftrace_destroy_event_types();
+ ftrace_destroy_instances();
+
if (global_trace_instance.max_tr_ring_buffer) {
ftrace_destroy_buffers(global_trace_instance.max_tr_buffers);
free(global_trace_instance.max_tr_buffers);
@@ -1521,8 +1635,10 @@ static int populate_ftrace_dir_tree(struct trace_instance *ti,
static void ftrace_dump(int argc, char *argv[])
{
int c;
+ int i;
uint flags = 0;
char *dump_tracing_dir;
+ char instance_path[PATH_MAX];
while ((c = getopt(argc, argv, "smt")) != EOF) {
switch(c)
@@ -1567,7 +1683,34 @@ static void ftrace_dump(int argc, char *argv[])
return;
}
- populate_ftrace_dir_tree(&global_trace_instance, dump_tracing_dir, flags);
+ if (!populate_ftrace_dir_tree(&global_trace_instance, dump_tracing_dir, flags))
+ return;
+
+ if (!multiple_instances_available || instance_count == 0)
+ return;
+
+ /* Create an instances directory, and dump instance data in there */
+ snprintf(instance_path, sizeof(instance_path),
+ "%s/instances", dump_tracing_dir);
+ if (try_mkdir(instance_path, 0755) < 0)
+ return;
+
+ /* Don't care about the flags anymore */
+ flags = 0;
+
+ for (i = 0; i < instance_count; i++)
+ {
+ struct trace_instance *ti = &trace_instances[i];
+
+ snprintf(instance_path, sizeof(instance_path),
+ "%s/instances/%s", dump_tracing_dir,
+ ti->name);
+
+ if (populate_ftrace_dir_tree(ti, instance_path, flags) < 0)
+ break;
+ }
+
+ return;
}
static void ftrace_show(int argc, char *argv[])
@@ -2171,19 +2314,62 @@ static int save_ftrace_cmdlines(int fd)
return tmp_file_flush(fd);
}
-static int save_res_data(int fd, int nr_cpu_buffers)
+/* From trace-cmd.h */
+enum {
+ TRACECMD_OPTION_DONE, /* 0 */
+ TRACECMD_OPTION_DATE, /* 1 */
+ TRACECMD_OPTION_CPUSTAT, /* 2 */
+ TRACECMD_OPTION_BUFFER, /* 3 */
+ TRACECMD_OPTION_TRACECLOCK, /* 4 */
+ TRACECMD_OPTION_UNAME, /* 5 */
+ TRACECMD_OPTION_HOOK, /* 6 */
+};
+
+static int write_options(int fd, unsigned long long *buffer_offsets)
{
- unsigned short option = 0;
+ int i;
+ unsigned short option;
- if (write_and_check(fd, &nr_cpu_buffers, 4))
- return -1;
+ if (!multiple_instances_available)
+ return 0;
if (write_and_check(fd, "options ", 10))
return -1;
+ option = TRACECMD_OPTION_BUFFER;
+ for (i = 0; i < instance_count; i++)
+ {
+ char *name = trace_instances[i].name;
+ size_t name_size = strlen(name) + 1; /* Name length + '\0' */
+ unsigned long long option_size = 8 + name_size;
+ unsigned long long offset;
+
+ offset = buffer_offsets ? buffer_offsets[i] : 0;
+ if (write_and_check(fd, &option, 2))
+ return -1;
+ if (write_and_check(fd, &option_size, 4))
+ return -1;
+ if (write_and_check(fd, &offset, 8))
+ return -1;
+ if (write_and_check(fd, name, name_size))
+ return -1;
+ }
+
+ option = TRACECMD_OPTION_DONE;
if (write_and_check(fd, &option, 2))
return -1;
+ return 0;
+}
+
+static int save_res_data(int fd, int nr_cpu_buffers, unsigned long long *buffer_offsets)
+{
+ if (write_and_check(fd, &nr_cpu_buffers, 4))
+ return -1;
+
+ if (write_options(fd, buffer_offsets))
+ return -1;
+
if (write_and_check(fd, "flyrecord", 10))
return -1;
@@ -2261,6 +2447,10 @@ static int get_nr_cpu_buffers(struct trace_instance *ti)
static int __trace_cmd_data_output(int fd)
{
int nr_cpu_buffers;
+ unsigned long long global_res_data_offset;
+ unsigned long long *instance_offsets;
+
+ instance_offsets = calloc(sizeof(unsigned long long), instance_count);
nr_cpu_buffers = get_nr_cpu_buffers(&global_trace_instance);
@@ -2276,11 +2466,40 @@ static int __trace_cmd_data_output(int fd)
return -1;
if (save_ftrace_cmdlines(fd))
return -1;
- if (save_res_data(fd, nr_cpu_buffers))
+
+ /* We don't have the instance buffer offsets yet, so we'll write in 0s
+ * for now, and fix it up after we have that information available */
+ global_res_data_offset = lseek(fd, 0, SEEK_CUR);
+ if (save_res_data(fd, nr_cpu_buffers, NULL))
return -1;
if (save_record_data(fd, nr_cpu_buffers, &global_trace_instance))
return -1;
+ if (multiple_instances_available)
+ {
+ int i;
+
+ for (i = 0; i < instance_count; i++)
+ {
+ struct trace_instance *ti = &trace_instances[i];
+ nr_cpu_buffers = get_nr_cpu_buffers(ti);
+
+ /* Save off the instance offset for fixup later */
+ instance_offsets[i] = lseek(fd, 0, SEEK_CUR);
+
+ if (write_and_check(fd, "flyrecord", 10))
+ return -1;
+ if (save_record_data(fd, nr_cpu_buffers, ti))
+ return -1;
+ }
+ }
+
+ /* Fix up the global trace's options header with the instance offsets */
+ lseek(fd, global_res_data_offset, SEEK_SET);
+ nr_cpu_buffers = get_nr_cpu_buffers(&global_trace_instance);
+ if (save_res_data(fd, nr_cpu_buffers, instance_offsets))
+ return -1;
+
return 0;
}
--
2.9.2