blob: 2c311b3d6be37f094fa573426af52a0af5fd7d0b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
*
*/
#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 "list.h"
#include "trace-local.h"
static unsigned int page_size;
static const char *default_input_file = DEFAULT_INPUT_FILE;
static const char *default_top_instance_name = "top";
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;
unsigned long long missed_events;
struct tep_record *record;
int cpu;
int fd;
int index;
void *commit;
void *page;
char *file;
};
struct handle_list {
struct list_head list;
const char *name;
int index;
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;
/**
* get_handle - Obtain a handle that must be closed once finished.
*/
static struct tracecmd_input *get_handle(struct handle_list *item)
{
struct tracecmd_input *top_handle, *handle;
top_handle = tracecmd_open(input_file, 0);
if (!top_handle)
die("Error reading %s", input_file);
if (item->was_top_instance) {
return top_handle;
} else {
handle = tracecmd_buffer_instance_handle(top_handle, item->index);
if (!handle)
warning("Could not retrieve handle %s", item->name);
tracecmd_close(top_handle);
return handle;
}
}
static void add_handle(const char *name, int index, bool was_top_instance)
{
struct handle_list *item;
item = calloc(1, sizeof(*item));
if (!item)
die("Failed to allocate handle item");
item->name = strdup(name);
if (!item->name)
die("Failed to duplicate %s", name);
item->index = index;
item->was_top_instance = was_top_instance;
item->handle = get_handle(item);
list_add_tail(&item->list, &handle_list);
}
static void free_handles(struct list_head *list)
{
struct handle_list *item;
while (!list_empty(list)) {
item = container_of(list->next, struct handle_list, list);
list_del(&item->list);
free((char *)item->name);
tracecmd_close(item->handle);
free(item);
}
}
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;
}
#define MISSING_EVENTS (1UL << 31)
#define MISSING_STORED (1UL << 30)
#define COMMIT_MASK ((1 << 27) - 1)
static void write_page(struct tep_handle *pevent,
struct cpu_data *cpu_data, int long_size)
{
unsigned long long *ptr = NULL;
unsigned int flags = 0;
if (cpu_data->missed_events) {
flags |= MISSING_EVENTS;
if (cpu_data->missed_events > 0) {
flags |= MISSING_STORED;
ptr = cpu_data->page + cpu_data->index;
}
}
if (long_size == 8) {
unsigned long long index = cpu_data->index - 16 + flags;;
*(unsigned long long *)cpu_data->commit =
tep_read_number(pevent, &index, 8);
} else {
unsigned int index = cpu_data->index - 12 + flags;;
*(unsigned int *)cpu_data->commit =
tep_read_number(pevent, &index, 4);
}
if (ptr)
*ptr = tep_read_number(pevent, &cpu_data->missed_events, 8);
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, bool *end_reached)
{
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) ||
record->missed_events) {
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");
}
cpu_data[cpu].missed_events = record->missed_events;
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 && (record->ts > end))
*end_reached = true;
else
*end_reached = false;
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 char *get_temp_file(const char *output_file, const char *name, int cpu)
{
const char *dot;
char *file = NULL;
char *output;
char *base;
char *dir;
int ret;
if (name)
dot = ".";
else
dot = name = "";
output = strdup(output_file);
if (!output)
die("Failed to duplicate %s", output_file);
/* Extract basename() first, as dirname() truncates output */
base = basename(output);
dir = dirname(output);
ret = asprintf(&file, "%s/.tmp.%s.%s%s%d", dir, base, name, dot, cpu);
if (ret < 0)
die("Failed to allocate file for %s %s %s %d", dir, base, name, cpu);
free(output);
return file;
}
static void delete_temp_file(const char *name)
{
unlink(name);
}
static void put_temp_file(char *file)
{
free(file);
}
static void touch_file(const char *file)
{
int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
die("could not create file %s\n", file);
close(fd);
}
static unsigned long long 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,
bool *end_reached)
{
unsigned long long current = 0;
struct handle_list *handle_entry;
struct tracecmd_output *ohandle;
struct cpu_data *cpu_data;
struct tep_record *record;
bool all_end_reached = true;
char **cpu_list;
char *file;
int cpus;
int cpu;
int ret;
int fd;
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) {
struct tracecmd_input *curr_handle;
bool curr_end_reached = false;
curr_handle = handle_entry->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);
touch_file(file);
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(curr_handle, cpu, start);
}
if (only_cpu >= 0) {
parse_cpu(curr_handle, cpu_data, start, end, count,
1, only_cpu, type, &curr_end_reached);
} else if (percpu) {
for (cpu = 0; cpu < cpus; cpu++)
parse_cpu(curr_handle, cpu_data, start,
end, count, percpu, cpu, type, &curr_end_reached);
} else {
parse_cpu(curr_handle, cpu_data, start,
end, count, percpu, -1, type, &curr_end_reached);
}
/* End is reached when all instances finished. */
all_end_reached &= curr_end_reached;
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;
if (handle_entry->was_top_instance)
ret = tracecmd_append_cpu_data(ohandle, cpus, cpu_list);
else
ret = tracecmd_append_buffer_cpu_data(ohandle, handle_entry->name, cpus,
cpu_list);
if (ret < 0)
die("Failed to append tracing data\n");
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(curr_handle, cpu_data[cpu].offset, NULL);
if (record && (!current || record->ts > current))
current = record->ts + 1;
tracecmd_free_record(record);
}
}
for (cpu = 0; cpu < cpus; cpu++) {
close(cpu_data[cpu].fd);
delete_temp_file(cpu_data[cpu].file);
put_temp_file(cpu_data[cpu].file);
}
free(cpu_data);
free(cpu_list);
}
tracecmd_output_close(ohandle);
*end_reached = all_end_reached;
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;
bool end_reached = false;
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 instances;
int count;
int repeat = 0;
int percpu = 0;
int cpu = -1;
int ac;
int c;
list_head_init(&handle_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) {
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 && strcmp(output, input_file) == 0) {
output = realloc(output, strlen(output) + 3);
strcat(output, ".1");
}
output_file = malloc(strlen(output) + 50);
if (!output_file)
die("Failed to allocate for %s", output);
c = 1;
add_handle(default_top_instance_name, -1, true);
instances = tracecmd_buffer_instances(handle);
if (instances) {
const char *name;
int i;
for (i = 0; i < instances; i++) {
name = tracecmd_buffer_instance_name(handle, i);
if (!name)
die("error in reading buffer instance");
add_handle(name, i, false);
}
}
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, &end_reached);
if (!repeat)
break;
start_ns = 0;
} while (!end_reached && (current && (!end_ns || current < end_ns)));
free(output);
free(output_file);
tracecmd_close(handle);
free_handles(&handle_list);
return;
}