The 'trace-cmd split' command conserves all buffers/instances of the input .dat file. Add support to select the instances to keep: - '--top' to keep the top instance - '-b' top rename the top instance. Must follow '--top'. - '-B buffer' to keep a buffer/instance - t top promote a buffer to the top instance. Must follow '-B'. For example, with a trace recorded with: $ trace-cmd record -e sched_wakeup -B switch_instance \ -e sched_switch Creating a test.dat file containing the top instance and the switch_instance: $ trace-cmd split --top -B switch_instance -o test.dat Creating a test.dat file containing the switch_instance as the top instance, and the initial top instance as an instance named 'old_top': $ trace-cmd split --top -b old_top -B switch_instance -t \ -o test.dat Also update the trace-usage.c and the relevant documentation for the new options. Signed-off-by: Pierre Gondois <pierre.gondois@xxxxxxx> --- Notes: v4: - Update the options as discussed at: https://lore.kernel.org/all/20240212162800.3fe15000@xxxxxxxxxxxxxxxxxx/ v5: - Use list_for_each_entry_safe() instead of container_of() Documentation/trace-cmd/trace-cmd-split.1.txt | 25 +++ tracecmd/trace-split.c | 186 ++++++++++++++++-- tracecmd/trace-usage.c | 7 + 3 files changed, 204 insertions(+), 14 deletions(-) diff --git a/Documentation/trace-cmd/trace-cmd-split.1.txt b/Documentation/trace-cmd/trace-cmd-split.1.txt index 25385796..d2fea252 100644 --- a/Documentation/trace-cmd/trace-cmd-split.1.txt +++ b/Documentation/trace-cmd/trace-cmd-split.1.txt @@ -86,6 +86,31 @@ OPTIONS This will split out all the events for cpu 1 in the file. +*--top*:: + This allows to keep the top buffer. + The top buffer can be renamed using the '-b' option. + + trace-cmd split --top + + This will keep only the top buffer. + + trace-cmd split --top -b old_top + + This will keep only the top buffer and rename it 'old_top'. + +*-B* 'buffer':: + This allows to keep the selected buffer. + A buffer can be promoted to the top buffer using the '-t' option. + + trace-cmd split -B timer -B sched + + This will keep the 'timer' and 'sched' buffers. + + trace-cmd split -B timer -t -B sched + + This will keep the 'timer' and 'sched' buffers, with the events + from the 'timer' buffer promoted to the top instance. + SEE ALSO -------- trace-cmd(1), trace-cmd-record(1), trace-cmd-report(1), trace-cmd-start(1), diff --git a/tracecmd/trace-split.c b/tracecmd/trace-split.c index 2c311b3d..387001dd 100644 --- a/tracecmd/trace-split.c +++ b/tracecmd/trace-split.c @@ -55,13 +55,10 @@ struct handle_list { struct list_head list; const char *name; int index; - struct tracecmd_input *handle; + struct tracecmd_input *handle; /* Identify the top instance in the input trace. */ bool was_top_instance; - - /* Identify the top instance in each output trace. */ - bool is_top_instance; }; static struct list_head handle_list; @@ -109,10 +106,9 @@ static void add_handle(const char *name, int index, bool was_top_instance) static void free_handles(struct list_head *list) { - struct handle_list *item; + struct handle_list *item, *n; - while (!list_empty(list)) { - item = container_of(list->next, struct handle_list, list); + list_for_each_entry_safe(item, n, list, list) { list_del(&item->list); free((char *)item->name); tracecmd_close(item->handle); @@ -120,6 +116,50 @@ static void free_handles(struct list_head *list) } } +static struct list_head inst_list; + +struct inst_list { + struct list_head list; + const char *name; + struct handle_list *handle; + + /* Identify the top instance in the input trace. */ + bool was_top_instance; + + /* Identify the top instance in the output trace. */ + bool is_top_instance; +}; + +static void free_inst(struct list_head *list) +{ + struct inst_list *item, *n; + + list_for_each_entry_safe(item, n, list, list) { + list_del(&item->list); + free((char *)item->name); + free(item); + } +} + +static struct inst_list *add_inst(const char *name, bool was_top_instance, + bool is_top_instance) +{ + struct inst_list *item; + + item = calloc(1, sizeof(*item)); + if (!item) + die("Failed to allocate output_file item"); + + item->name = strdup(name); + if (!item->name) + die("Failed to duplicate %s", name); + + item->was_top_instance = was_top_instance; + item->is_top_instance = is_top_instance; + list_add_tail(&item->list, &inst_list); + return item; +} + static int create_type_len(struct tep_handle *pevent, int time, int len) { static int bigendian = -1; @@ -481,8 +521,8 @@ static unsigned long long parse_file(struct tracecmd_input *handle, bool *end_reached) { unsigned long long current = 0; - struct handle_list *handle_entry; struct tracecmd_output *ohandle; + struct inst_list *inst_entry; struct cpu_data *cpu_data; struct tep_record *record; bool all_end_reached = true; @@ -496,18 +536,18 @@ static unsigned long long parse_file(struct tracecmd_input *handle, ohandle = tracecmd_copy(handle, output_file, TRACECMD_FILE_CMD_LINES, 0, NULL); tracecmd_set_out_clock(ohandle, tracecmd_get_trace_clock(handle)); - list_for_each_entry(handle_entry, &handle_list, list) { + list_for_each_entry(inst_entry, &inst_list, list) { struct tracecmd_input *curr_handle; bool curr_end_reached = false; - curr_handle = handle_entry->handle; + curr_handle = inst_entry->handle->handle; cpus = tracecmd_cpus(curr_handle); cpu_data = malloc(sizeof(*cpu_data) * cpus); if (!cpu_data) die("Failed to allocate cpu_data for %d cpus", cpus); for (cpu = 0; cpu < cpus; cpu++) { - file = get_temp_file(output_file, handle_entry->name, cpu); + file = get_temp_file(output_file, inst_entry->name, cpu); touch_file(file); fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0644); @@ -540,10 +580,10 @@ static unsigned long long parse_file(struct tracecmd_input *handle, for (cpu = 0; cpu < cpus; cpu++) cpu_list[cpu] = cpu_data[cpu].file; - if (handle_entry->was_top_instance) + if (inst_entry->is_top_instance) ret = tracecmd_append_cpu_data(ohandle, cpus, cpu_list); else - ret = tracecmd_append_buffer_cpu_data(ohandle, handle_entry->name, cpus, + ret = tracecmd_append_buffer_cpu_data(ohandle, inst_entry->name, cpus, cpu_list); if (ret < 0) die("Failed to append tracing data\n"); @@ -573,11 +613,80 @@ static unsigned long long parse_file(struct tracecmd_input *handle, return current; } +/* Map the instance names to their handle. */ +static void map_inst_handle(void) +{ + struct handle_list *handle_entry; + struct inst_list *inst_entry; + + /* + * No specific instance was given for this output file. + * Add all the available instances. + */ + if (list_empty(&inst_list)) { + list_for_each_entry(handle_entry, &handle_list, list) { + add_inst(handle_entry->name, handle_entry->was_top_instance, + handle_entry->was_top_instance); + } + } + + list_for_each_entry(inst_entry, &inst_list, list) { + list_for_each_entry(handle_entry, &handle_list, list) { + if ((inst_entry->was_top_instance && + handle_entry->was_top_instance) || + (!inst_entry->was_top_instance && + !strcmp(handle_entry->name, inst_entry->name))) { + inst_entry->handle = handle_entry; + goto found; + } + } + + warning("Requested instance %s was not found in trace.", inst_entry->name); + break; +found: + continue; + } +} + +static bool is_top_instance_unique(void) +{ + struct inst_list *inst_entry; + bool has_top_buffer = false; + + /* Check there is at most one top buffer. */ + list_for_each_entry(inst_entry, &inst_list, list) { + if (inst_entry->is_top_instance) { + if (has_top_buffer) + return false; + has_top_buffer = true; + } + } + + return true; +} + +enum { + OPT_top = 237, +}; + +/* + * Used to identify the arg. previously parsed. + * E.g. '-b' can only follow '--top'. + */ +enum prev_arg_type { + PREV_IS_NONE, + PREV_IS_TOP, + PREV_IS_BUFFER, +}; + void trace_split (int argc, char **argv) { struct tracecmd_input *handle; unsigned long long start_ns = 0, end_ns = 0; unsigned long long current; + enum prev_arg_type prev_arg_type; + struct inst_list *prev_inst = NULL; + int prev_arg_idx; bool end_reached = false; double start, end; char *endptr; @@ -593,12 +702,22 @@ void trace_split (int argc, char **argv) int ac; int c; + static struct option long_options[] = { + {"top", optional_argument, NULL, OPT_top}, + {NULL, 0, NULL, 0}, + }; + int option_index = 0; + + prev_arg_type = PREV_IS_NONE; + list_head_init(&handle_list); + list_head_init(&inst_list); if (strcmp(argv[1], "split") != 0) usage(argv); - while ((c = getopt(argc-1, argv+1, "+ho:i:s:m:u:e:p:rcC:")) >= 0) { + while ((c = getopt_long(argc - 1, argv + 1, "+ho:i:s:m:u:e:p:rcC:B:b:t", + long_options, &option_index)) >= 0) { switch (c) { case 'h': usage(argv); @@ -641,11 +760,47 @@ void trace_split (int argc, char **argv) case 'i': input_file = optarg; break; + case OPT_top: + prev_arg_type = PREV_IS_TOP; + prev_arg_idx = optind; + prev_inst = add_inst(default_top_instance_name, true, true); + break; + case 'b': + /* 1 as --top takes no argument. */ + if (prev_arg_type != PREV_IS_TOP && + (prev_arg_idx != optind - 1)) + usage(argv); + prev_arg_type = PREV_IS_NONE; + + prev_inst->is_top_instance = false; + + free((char *)prev_inst->name); + prev_inst->name = strdup(optarg); + if (!prev_inst->name) + die("Failed to duplicate %s", optarg); + break; + case 'B': + prev_arg_type = PREV_IS_BUFFER; + prev_arg_idx = optind; + prev_inst = add_inst(optarg, false, false); + break; + case 't': + /* 2 as -B takes an argument. */ + if (prev_arg_type != PREV_IS_BUFFER && + (prev_arg_idx != optind - 2)) + usage(argv); + prev_arg_type = PREV_IS_NONE; + + prev_inst->is_top_instance = true; + break; default: usage(argv); } } + if (!is_top_instance_unique()) + die("Can only have one top instance."); + ac = (argc - optind); if (ac >= 2) { @@ -713,6 +868,8 @@ void trace_split (int argc, char **argv) } } + map_inst_handle(); + do { if (repeat) sprintf(output_file, "%s.%04d", output, c++); @@ -732,6 +889,7 @@ void trace_split (int argc, char **argv) tracecmd_close(handle); free_handles(&handle_list); + free_inst(&inst_list); return; } diff --git a/tracecmd/trace-usage.c b/tracecmd/trace-usage.c index 6944a2c7..8bbf2e3e 100644 --- a/tracecmd/trace-usage.c +++ b/tracecmd/trace-usage.c @@ -311,6 +311,13 @@ static struct usage_help usage_help[] = { " -u n split file up by n microseconds\n" " -e n split file up by n events\n" " -p n split file up by n pages\n" + " -C n select CPU n\n" + " -B buffer keep buffer in resulting .dat file\n" + " Use -t to promote the buffer to the top instance.\n" + " -t promote preceding buffer to the top instance.\n" + " Must follow -B.\n" + " --top keep top buffer in resulting .dat file.\n" + " -b new name of the top instance. Must follow --top.\n" " -r repeat from start to end\n" " -c per cpu, that is -p 2 will be 2 pages for each CPU\n" " if option is specified, it will split the file\n" -- 2.25.1