| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| */ |
| #define _LARGEFILE64_SOURCE |
| #include <dirent.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <libgen.h> |
| #include <getopt.h> |
| #include <stdarg.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <sys/mman.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <errno.h> |
| |
| #include "trace-local.h" |
| |
| static unsigned int page_size; |
| static const char *default_input_file = DEFAULT_INPUT_FILE; |
| static const char *input_file; |
| |
| enum split_types { |
| SPLIT_NONE, |
| /* The order of these must be reverse of the case statement in the options */ |
| SPLIT_SECONDS, |
| SPLIT_MSECS, |
| SPLIT_USECS, |
| SPLIT_EVENTS, |
| SPLIT_PAGES, |
| SPLIT_NR_TYPES, |
| }; |
| |
| struct cpu_data { |
| unsigned long long ts; |
| unsigned long long offset; |
| struct tep_record *record; |
| int cpu; |
| int fd; |
| int index; |
| void *commit; |
| void *page; |
| char *file; |
| }; |
| |
| static int create_type_len(struct tep_handle *pevent, int time, int len) |
| { |
| static int bigendian = -1; |
| char *ptr; |
| int test; |
| |
| if (bigendian < 0) { |
| test = 0x4321; |
| ptr = (char *)&test; |
| if (*ptr == 0x21) |
| bigendian = 0; |
| else |
| bigendian = 1; |
| } |
| |
| if (tep_is_file_bigendian(pevent)) |
| time |= (len << 27); |
| else |
| time = (time << 5) | len; |
| |
| return tep_read_number(pevent, &time, 4); |
| } |
| |
| static int write_record(struct tracecmd_input *handle, |
| struct tep_record *record, |
| struct cpu_data *cpu_data, |
| enum split_types type) |
| { |
| unsigned long long diff; |
| struct tep_handle *pevent; |
| void *page; |
| int len = 0; |
| char *ptr; |
| int index = 0; |
| int time; |
| |
| page = cpu_data->page; |
| |
| pevent = tracecmd_get_tep(handle); |
| |
| ptr = page + cpu_data->index; |
| |
| diff = record->ts - cpu_data->ts; |
| if (diff > (1 << 27)) { |
| /* Add a time stamp */ |
| len = RINGBUF_TYPE_TIME_EXTEND; |
| time = (unsigned int)(diff & ((1ULL << 27) - 1)); |
| time = create_type_len(pevent, time, len); |
| *(unsigned *)ptr = time; |
| ptr += 4; |
| time = (unsigned int)(diff >> 27); |
| *(unsigned *)ptr = tep_read_number(pevent, &time, 4); |
| cpu_data->ts = record->ts; |
| cpu_data->index += 8; |
| return 0; |
| } |
| |
| if (record->size && (record->size <= 28 * 4)) |
| len = record->size / 4; |
| |
| time = (unsigned)diff; |
| time = create_type_len(pevent, time, len); |
| |
| memcpy(ptr, &time, 4); |
| ptr += 4; |
| index = 4; |
| |
| if (!len) { |
| len = record->size + 4; |
| if ((len + 4) > record->record_size) |
| die("Bad calculation of record len (expect:%d actual:%d)", |
| record->record_size, len + 4); |
| *(unsigned *)ptr = tep_read_number(pevent, &len, 4); |
| ptr += 4; |
| index += 4; |
| } |
| |
| len = (record->size + 3) & ~3; |
| index += len; |
| |
| memcpy(ptr, record->data, len); |
| |
| cpu_data->index += index; |
| cpu_data->ts = record->ts; |
| |
| return 1; |
| } |
| |
| static void write_page(struct tep_handle *pevent, |
| struct cpu_data *cpu_data, int long_size) |
| { |
| if (long_size == 8) { |
| unsigned long long index = cpu_data->index - 16; |
| *(unsigned long long *)cpu_data->commit = |
| tep_read_number(pevent, &index, 8); |
| } else { |
| unsigned int index = cpu_data->index - 12; |
| *(unsigned int *)cpu_data->commit = |
| tep_read_number(pevent, &index, 4); |
| } |
| write(cpu_data->fd, cpu_data->page, page_size); |
| } |
| |
| static struct tep_record *read_record(struct tracecmd_input *handle, |
| int percpu, int *cpu) |
| { |
| if (percpu) |
| return tracecmd_read_data(handle, *cpu); |
| |
| return tracecmd_read_next_data(handle, cpu); |
| } |
| |
| static void set_cpu_time(struct tracecmd_input *handle, |
| int percpu, unsigned long long start, int cpu, int cpus) |
| { |
| if (percpu) { |
| tracecmd_set_cpu_to_timestamp(handle, cpu, start); |
| return; |
| } |
| |
| for (cpu = 0; cpu < cpus; cpu++) |
| tracecmd_set_cpu_to_timestamp(handle, cpu, start); |
| return; |
| } |
| |
| static int parse_cpu(struct tracecmd_input *handle, |
| struct cpu_data *cpu_data, |
| unsigned long long start, |
| unsigned long long end, |
| int count_limit, int percpu, int cpu, |
| enum split_types type) |
| { |
| struct tep_record *record; |
| struct tep_handle *pevent; |
| void *ptr; |
| int page_size; |
| int long_size = 0; |
| int cpus; |
| int count = 0; |
| int pages = 0; |
| |
| cpus = tracecmd_cpus(handle); |
| |
| long_size = tracecmd_long_size(handle); |
| page_size = tracecmd_page_size(handle); |
| pevent = tracecmd_get_tep(handle); |
| |
| /* Force new creation of first page */ |
| if (percpu) { |
| cpu_data[cpu].index = page_size + 1; |
| cpu_data[cpu].page = NULL; |
| } else { |
| for (cpu = 0; cpu < cpus; cpu++) { |
| cpu_data[cpu].index = page_size + 1; |
| cpu_data[cpu].page = NULL; |
| } |
| } |
| |
| /* |
| * Get the cpu pointers up to the start of the |
| * start time stamp. |
| */ |
| |
| record = read_record(handle, percpu, &cpu); |
| |
| if (start) { |
| set_cpu_time(handle, percpu, start, cpu, cpus); |
| while (record && record->ts < start) { |
| tracecmd_free_record(record); |
| record = read_record(handle, percpu, &cpu); |
| } |
| } else if (record) |
| start = record->ts; |
| |
| while (record && (!end || record->ts <= end)) { |
| if (cpu_data[cpu].index + record->record_size > page_size) { |
| |
| if (type == SPLIT_PAGES && ++pages > count_limit) |
| break; |
| |
| if (cpu_data[cpu].page) |
| write_page(pevent, &cpu_data[cpu], long_size); |
| else { |
| cpu_data[cpu].page = malloc(page_size); |
| if (!cpu_data[cpu].page) |
| die("Failed to allocate page"); |
| } |
| |
| memset(cpu_data[cpu].page, 0, page_size); |
| ptr = cpu_data[cpu].page; |
| |
| *(unsigned long long*)ptr = |
| tep_read_number(pevent, &(record->ts), 8); |
| cpu_data[cpu].ts = record->ts; |
| ptr += 8; |
| cpu_data[cpu].commit = ptr; |
| ptr += long_size; |
| cpu_data[cpu].index = 8 + long_size; |
| } |
| |
| cpu_data[cpu].offset = record->offset; |
| |
| if (write_record(handle, record, &cpu_data[cpu], type)) { |
| tracecmd_free_record(record); |
| record = read_record(handle, percpu, &cpu); |
| |
| /* if we hit the end of the cpu, clear the offset */ |
| if (!record) { |
| if (percpu) |
| cpu_data[cpu].offset = 0; |
| else |
| for (cpu = 0; cpu < cpus; cpu++) |
| cpu_data[cpu].offset = 0; |
| } |
| |
| switch (type) { |
| case SPLIT_NONE: |
| break; |
| case SPLIT_SECONDS: |
| if (record && |
| record->ts > |
| (start + (unsigned long long)count_limit * 1000000000ULL)) { |
| tracecmd_free_record(record); |
| record = NULL; |
| } |
| break; |
| case SPLIT_MSECS: |
| if (record && |
| record->ts > |
| (start + (unsigned long long)count_limit * 1000000ULL)) { |
| tracecmd_free_record(record); |
| record = NULL; |
| } |
| break; |
| case SPLIT_USECS: |
| if (record && |
| record->ts > |
| (start + (unsigned long long)count_limit * 1000ULL)) { |
| tracecmd_free_record(record); |
| record = NULL; |
| } |
| break; |
| case SPLIT_EVENTS: |
| if (++count >= count_limit) { |
| tracecmd_free_record(record); |
| record = NULL; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| if (record) |
| tracecmd_free_record(record); |
| |
| if (percpu) { |
| if (cpu_data[cpu].page) { |
| write_page(pevent, &cpu_data[cpu], long_size); |
| free(cpu_data[cpu].page); |
| cpu_data[cpu].page = NULL; |
| } |
| } else { |
| for (cpu = 0; cpu < cpus; cpu++) { |
| if (cpu_data[cpu].page) { |
| write_page(pevent, &cpu_data[cpu], long_size); |
| free(cpu_data[cpu].page); |
| cpu_data[cpu].page = NULL; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static double parse_file(struct tracecmd_input *handle, |
| const char *output_file, |
| unsigned long long start, |
| unsigned long long end, int percpu, int only_cpu, |
| int count, enum split_types type) |
| { |
| unsigned long long current; |
| struct tracecmd_output *ohandle; |
| struct cpu_data *cpu_data; |
| struct tep_record *record; |
| char **cpu_list; |
| char *output; |
| char *base; |
| char *file; |
| char *dir; |
| int cpus; |
| int cpu; |
| int fd; |
| |
| output = strdup(output_file); |
| dir = dirname(output); |
| base = basename(output); |
| |
| ohandle = tracecmd_copy(handle, output_file, TRACECMD_FILE_CMD_LINES, 0, NULL); |
| |
| cpus = tracecmd_cpus(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++) { |
| int ret; |
| |
| ret = asprintf(&file, "%s/.tmp.%s.%d", dir, base, cpu); |
| if (ret < 0) |
| die("Failed to allocate file for %s %s %d", dir, base, cpu); |
| fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0644); |
| cpu_data[cpu].cpu = cpu; |
| cpu_data[cpu].fd = fd; |
| cpu_data[cpu].file = file; |
| cpu_data[cpu].offset = 0; |
| if (start) |
| tracecmd_set_cpu_to_timestamp(handle, cpu, start); |
| } |
| |
| if (only_cpu >= 0) { |
| parse_cpu(handle, cpu_data, start, end, count, |
| 1, only_cpu, type); |
| } else if (percpu) { |
| for (cpu = 0; cpu < cpus; cpu++) |
| parse_cpu(handle, cpu_data, start, |
| end, count, percpu, cpu, type); |
| } else |
| parse_cpu(handle, cpu_data, start, |
| end, count, percpu, -1, type); |
| |
| cpu_list = malloc(sizeof(*cpu_list) * cpus); |
| if (!cpu_list) |
| die("Failed to allocate cpu_list for %d cpus", cpus); |
| for (cpu = 0; cpu < cpus; cpu ++) |
| cpu_list[cpu] = cpu_data[cpu].file; |
| |
| tracecmd_set_out_clock(ohandle, tracecmd_get_trace_clock(handle)); |
| if (tracecmd_append_cpu_data(ohandle, cpus, cpu_list) < 0) |
| die("Failed to append tracing data\n"); |
| |
| current = end; |
| for (cpu = 0; cpu < cpus; cpu++) { |
| /* Set the tracecmd cursor to the next set of records */ |
| if (cpu_data[cpu].offset) { |
| record = tracecmd_read_at(handle, cpu_data[cpu].offset, NULL); |
| if (record && (!current || record->ts > current)) |
| current = record->ts + 1; |
| tracecmd_free_record(record); |
| } |
| unlink(cpu_data[cpu].file); |
| free(cpu_data[cpu].file); |
| } |
| free(cpu_data); |
| free(cpu_list); |
| free(output); |
| tracecmd_output_close(ohandle); |
| |
| return current; |
| } |
| |
| void trace_split (int argc, char **argv) |
| { |
| struct tracecmd_input *handle; |
| unsigned long long start_ns = 0, end_ns = 0; |
| unsigned long long current; |
| double start, end; |
| char *endptr; |
| char *output = NULL; |
| char *output_file; |
| enum split_types split_type = SPLIT_NONE; |
| enum split_types type = SPLIT_NONE; |
| int count; |
| int repeat = 0; |
| int percpu = 0; |
| int cpu = -1; |
| int ac; |
| int c; |
| |
| if (strcmp(argv[1], "split") != 0) |
| usage(argv); |
| |
| while ((c = getopt(argc-1, argv+1, "+ho:i:s:m:u:e:p:rcC:")) >= 0) { |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'p': |
| type++; |
| case 'e': |
| type++; |
| case 'u': |
| type++; |
| case 'm': |
| type++; |
| case 's': |
| type++; |
| if (split_type != SPLIT_NONE) |
| die("Only one type of split is allowed"); |
| count = atoi(optarg); |
| if (count <= 0) |
| die("Units must be greater than 0"); |
| split_type = type; |
| |
| /* Spliting by pages only makes sense per cpu */ |
| if (type == SPLIT_PAGES) |
| percpu = 1; |
| break; |
| case 'r': |
| repeat = 1; |
| break; |
| case 'c': |
| percpu = 1; |
| break; |
| case 'C': |
| cpu = atoi(optarg); |
| break; |
| case 'o': |
| if (output) |
| die("only one output file allowed"); |
| output = strdup(optarg); |
| break; |
| case 'i': |
| input_file = optarg; |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| ac = (argc - optind); |
| |
| if (ac >= 2) { |
| optind++; |
| start = strtod(argv[optind], &endptr); |
| if (ac > 3) |
| usage(argv); |
| |
| /* Make sure a true start value was entered */ |
| if (*endptr != 0) |
| die("Start value not floating point: %s", argv[optind]); |
| |
| start_ns = (unsigned long long)(start * 1000000000.0); |
| optind++; |
| if (ac == 3) { |
| end = strtod(argv[optind], &endptr); |
| |
| /* Make sure a true end value was entered */ |
| if (*endptr != 0) |
| die("End value not floating point: %s", |
| argv[optind]); |
| |
| end_ns = (unsigned long long)(end * 1000000000.0); |
| if (end_ns < start_ns) |
| die("Error: end is less than start"); |
| } |
| } |
| |
| if (!input_file) |
| input_file = default_input_file; |
| |
| handle = tracecmd_open(input_file, 0); |
| if (!handle) |
| die("error reading %s", input_file); |
| |
| if (tracecmd_get_file_state(handle) == TRACECMD_FILE_CPU_LATENCY) |
| die("trace-cmd split does not work with latency traces\n"); |
| |
| page_size = tracecmd_page_size(handle); |
| |
| if (!output) |
| output = strdup(input_file); |
| |
| if (!repeat) { |
| output = realloc(output, strlen(output) + 3); |
| strcat(output, ".1"); |
| } |
| |
| current = start_ns; |
| output_file = malloc(strlen(output) + 50); |
| if (!output_file) |
| die("Failed to allocate for %s", output); |
| c = 1; |
| |
| do { |
| if (repeat) |
| sprintf(output_file, "%s.%04d", output, c++); |
| else |
| strcpy(output_file, output); |
| |
| current = parse_file(handle, output_file, start_ns, end_ns, |
| percpu, cpu, count, type); |
| if (!repeat) |
| break; |
| start_ns = 0; |
| } while (current && (!end_ns || current < end_ns)); |
| |
| free(output); |
| free(output_file); |
| |
| tracecmd_close(handle); |
| |
| return; |
| } |