// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
 *
 */
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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 <signal.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>

#include "trace-local.h"
#include "trace-hash.h"
#include "trace-hash-local.h"
#include "kbuffer.h"
#include "list.h"

/*
 * tep_func_repeat_format is defined as a weak variable in the
 * libtraceevent library function plugin, to allow applications
 * to override the format of the timestamp it prints for the
 * last function that repeated.
 */
const char *tep_func_repeat_format;

static struct filter_str {
	struct filter_str	*next;
	char			*filter;
	int			neg;
} *filter_strings;
static struct filter_str **filter_next = &filter_strings;

struct event_str {
	struct event_str	*next;
	const char		*event;
};

struct input_files;

struct handle_list {
	struct list_head	list;
	struct tracecmd_input	*handle;
	struct input_files	*input_file;
	const char		*file;
	int			cpus;
};
static struct list_head handle_list;

struct input_files {
	struct list_head	list;
	const char		*file;
	struct filter_str	*filter_str;
	struct filter_str	**filter_str_next;
	long long		tsoffset;
	unsigned long long	ts2secs;
};
static struct list_head input_files;
static struct input_files *last_input_file;

struct pid_list {
	struct pid_list		*next;
	char			*pid;
	int			free;
} *pid_list;

struct pid_list *comm_list;

static unsigned int page_size;
static int input_fd;
static const char *default_input_file = DEFAULT_INPUT_FILE;
static const char *input_file;
static int multi_inputs;
static int max_file_size;

static int instances;

static int *filter_cpus;
static int nr_filter_cpus;
static int test_filters_mode;

static int show_wakeup;
static int wakeup_id;
static int wakeup_new_id;
static int sched_id;

static int profile;

static int buffer_breaks = 0;

static int no_irqs;
static int no_softirqs;

static int tsdiff;
static int tscheck;

static int latency_format;
static bool raw_format;
static const char *format_type = TEP_PRINT_INFO;

static struct tep_format_field *wakeup_task;
static struct tep_format_field *wakeup_success;
static struct tep_format_field *wakeup_new_task;
static struct tep_format_field *wakeup_new_success;
static struct tep_format_field *sched_task;
static struct tep_format_field *sched_prio;

static unsigned long long total_wakeup_lat;
static unsigned long wakeup_lat_count;

static unsigned long long total_wakeup_rt_lat;
static unsigned long wakeup_rt_lat_count;

struct wakeup_info {
	struct trace_hash_item	hash;
	unsigned long long	start;
	int			pid;
};

static struct hook_list *hooks;
static struct hook_list *last_hook;

#define WAKEUP_HASH_SIZE 1024
static struct trace_hash wakeup_hash;

static void print_event_name(struct trace_seq *s, struct tep_event *event)
{
	static const char *spaces = "                    "; /* 20 spaces */
	const char *name;
	int len;

	name = event ? event->name : "(NULL)";

	trace_seq_printf(s, " %s: ", name);

	/* Space out the event names evenly. */
	len = strlen(name);
	if (len < 20)
		trace_seq_printf(s, "%.*s", 20 - len, spaces);
}

enum time_fmt {
	TIME_FMT_LAT		= 1,
	TIME_FMT_NORMAL,
	TIME_FMT_TS,
};

static const char *time_format(struct tracecmd_input *handle, enum time_fmt tf)
{
	struct tep_handle *tep = tracecmd_get_tep(handle);

	switch (tf) {
	case TIME_FMT_LAT:
		if (latency_format)
			return "%8.8s-%-5d %3d";
		return "%16s-%-5d [%03d]";
	default:
		if (tracecmd_get_flags(handle) & TRACECMD_FL_IN_USECS) {
			if (tep_test_flag(tep, TEP_NSEC_OUTPUT))
				return tf == TIME_FMT_NORMAL ? " %9.1d:" : "%9.1d";
			else
				return tf == TIME_FMT_NORMAL ? " %6.1000d:" : "%6.1000d";
		} else
			return tf == TIME_FMT_NORMAL ? "%12d:" : "%12d";
	}
}

static void print_event(struct trace_seq *s, struct tracecmd_input *handle,
			struct tep_record *record)
{
	struct tep_handle *tep = tracecmd_get_tep(handle);
	struct tep_event *event;
	const char *lfmt = time_format(handle, TIME_FMT_LAT);
	const char *tfmt = time_format(handle, TIME_FMT_NORMAL);

	event = tep_find_event_by_record(tep, record);
	tep_print_event(tep, s, record, lfmt, TEP_PRINT_COMM,
			TEP_PRINT_PID, TEP_PRINT_CPU);
	tep_print_event(tep, s, record, tfmt, TEP_PRINT_TIME);
	print_event_name(s, event);
	tep_print_event(tep, s, record, "%s", format_type);
}

/* Debug variables for testing tracecmd_read_at */
#define TEST_READ_AT 0
#if TEST_READ_AT
#define DO_TEST
static off_t test_read_at_offset;
static int test_read_at_copy = 100;
static int test_read_at_index;
static void show_test(struct tracecmd_input *handle)
{
	struct tep_record *record;
	struct trace_seq s;

	if (!test_read_at_offset) {
		printf("\nNO RECORD COPIED\n");
		return;
	}

	record = tracecmd_read_at(handle, test_read_at_offset, NULL);
	printf("\nHERE'S THE COPY RECORD\n");
	trace_seq_init(&s);
	print_event(&s, handle, record);
	trace_seq_do_printf(&s);
	trace_seq_destroy(&s);
	printf("\n");

	tracecmd_free_record(record);
}

static void test_save(struct tep_record *record, int cpu)
{
	if (test_read_at_index++ == test_read_at_copy) {
		test_read_at_offset = record->offset;
		printf("\nUSING THIS RECORD\n");
	}
}
#endif /* TEST_READ_AT */

/* Debug variables for testing tracecmd_set_cpu_at_timestamp */
#define TEST_AT_TIMESTAMP 0
#if TEST_AT_TIMESTAMP
#define DO_TEST
static unsigned long long test_at_timestamp_ts;
static int test_at_timestamp_copy = 100;
static int test_at_timestamp_cpu = -1;
static int test_at_timestamp_index;
static void show_test(struct tracecmd_input *handle)
{
	struct tep_record *record;
	struct trace_seq s;
	int cpu = test_at_timestamp_cpu;

	if (!test_at_timestamp_ts) {
		printf("\nNO RECORD COPIED\n");
		return;
	}

	if (tracecmd_set_cpu_to_timestamp(handle, cpu, test_at_timestamp_ts))
		return;

	record = tracecmd_read_data(handle, cpu);
	printf("\nHERE'S THE COPY RECORD with page %p offset=%p\n",
	       (void *)(record->offset & ~(page_size - 1)),
	       (void *)record->offset);
	trace_seq_init(&s);
	print_event(&s, handle, record);
	trace_seq_do_printf(&s);
	trace_seq_destroy(&s);
	printf("\n");

	tracecmd_free_record(record);
}

static void test_save(struct tep_record *record, int cpu)
{
	if (test_at_timestamp_index++ == test_at_timestamp_copy) {
		test_at_timestamp_ts = record->ts;
		test_at_timestamp_cpu = cpu;
		printf("\nUSING THIS RECORD page=%p offset=%p\n",
		       (void *)(record->offset & ~(page_size - 1)),
		       (void *)record->offset);
	}
}
#endif /* TEST_AT_TIMESTAMP */

#define TEST_FIRST_LAST 0
#if TEST_FIRST_LAST
#define DO_TEST
static void show_test(struct tracecmd_input *handle)
{
	struct tep_record *record;
	struct trace_seq s;
	int cpu = 0;

	record = tracecmd_read_cpu_first(handle, cpu);
	if (!record) {
		printf("No first record?\n");
		return;
	}

	printf("\nHERE'S THE FIRST RECORD with offset %p\n",
	       (void *)record->offset);
	trace_seq_init(&s);
	print_event(&s, handle, record);
	trace_seq_do_printf(&s);
	trace_seq_destroy(&s);
	printf("\n");

	tracecmd_free_record(record);

	record = tracecmd_read_cpu_last(handle, cpu);
	if (!record) {
		printf("No last record?\n");
		return;
	}

	printf("\nHERE'S THE LAST RECORD with offset %p\n",
	       (void *)record->offset);
	trace_seq_init(&s);
	print_event(&s, handle, record);
	trace_seq_do_printf(&s);
	trace_seq_destroy(&s);
	printf("\n");

	tracecmd_free_record(record);
}
static void test_save(struct tep_record *record, int cpu)
{
}
#endif /* TEST_FIRST_LAST */

#ifndef DO_TEST
static void show_test(struct tracecmd_input *handle)
{
	/* quiet the compiler */
	if (0)
		print_event(NULL, NULL, NULL);
}
static void test_save(struct tep_record *record, int cpu)
{
}
#endif

static void free_filter_strings(struct filter_str *filter_str)
{
	struct filter_str *filter;

	while (filter_str) {
		filter = filter_str;
		filter_str = filter->next;
		free(filter->filter);
		free(filter);
	}
}

static struct input_files *add_input(const char *file)
{
	struct input_files *item;

	item = calloc(1, sizeof(*item));
	if (!item)
		die("Failed to allocate for %s", file);
	item->file = file;
	item->filter_str_next = &item->filter_str;
	list_add_tail(&item->list, &input_files);
	last_input_file = item;
	return item;
}

static void add_handle(struct tracecmd_input *handle, struct input_files *input_files)
{
	struct handle_list *item;
	const char *file = input_files ? input_files->file : input_file;

	item = calloc(1, sizeof(*item));
	if (!item)
		die("Failed ot allocate for %s", file);
	item->handle = handle;
	if (input_files) {
		item->file = file + strlen(file);
		/* we want just the base name */
		while (item->file >= file && *item->file != '/')
			item->file--;
		item->file++;
		if (strlen(item->file) > max_file_size)
			max_file_size = strlen(item->file);

		item->input_file = input_files;
	}
	list_add_tail(&item->list, &handle_list);
}

static void free_inputs(void)
{
	struct input_files *item;

	while (!list_empty(&input_files)) {
		item = container_of(input_files.next, struct input_files, list);
		list_del(&item->list);
		free_filter_strings(item->filter_str);
		free(item);
	}
}

static void free_handles(void)
{
	struct handle_list *item;

	while (!list_empty(&handle_list)) {
		item = container_of(handle_list.next, struct handle_list, list);
		list_del(&item->list);
		free(item);
	}
}

static void add_filter(struct input_files *input_file, const char *filter, int neg)
{
	struct filter_str *ftr;

	ftr = malloc(sizeof(*ftr));
	if (!ftr)
		die("Failed to allocate for filter %s", filter);
	ftr->filter = strdup(filter);
	if (!ftr->filter)
		die("malloc");
	ftr->next = NULL;
	ftr->neg = neg;

	/* must maintain order of command line */
	if (input_file) {
		*input_file->filter_str_next = ftr;
		input_file->filter_str_next = &ftr->next;
	} else {
		*filter_next = ftr;
		filter_next = &ftr->next;
	}
}

static void __add_filter(struct pid_list **head, const char *arg)
{
	struct pid_list *list;
	char *pids = strdup(arg);
	char *pid;
	char *sav;
	int free = 1;

	if (!pids)
		die("malloc");

	pid = strtok_r(pids, ",", &sav);
	while (pid) {
		list = malloc(sizeof(*list));
		if (!list)
			die("Failed to allocate for arg %s", arg);
		list->pid = pid;
		list->free = free;
		list->next = *head;
		*head = list;
		/* The first pid needs to be freed */
		free = 0;
		pid = strtok_r(NULL, ",", &sav);
	}
}

static void add_comm_filter(const char *arg)
{
	__add_filter(&comm_list, arg);
}

static void add_pid_filter(const char *arg)
{
	__add_filter(&pid_list, arg);
}

static char *append_pid_filter(char *curr_filter, char *pid)
{
	char *filter;
	int len, curr_len;

#define FILTER_FMT "(common_pid==" __STR ")||(pid==" __STR ")||(next_pid==" __STR ")"

#undef __STR
#define __STR ""

	/* strlen(".*:") > strlen("||") */
	len = strlen(".*:" FILTER_FMT) + strlen(pid) * 3 + 1;

#undef __STR
#define __STR "%s"

	if (!curr_filter) {
		filter = malloc(len);
		if (!filter)
			die("Failed to allocate pid filter");
		sprintf(filter, ".*:" FILTER_FMT, pid, pid, pid);
	} else {
		curr_len = strlen(curr_filter);
		len += curr_len;

		filter = realloc(curr_filter, len);
		if (!filter)
			die("realloc");
		sprintf(filter + curr_len, "||" FILTER_FMT, pid, pid, pid);
	}

	return filter;
}

static void convert_comm_filter(struct tracecmd_input *handle)
{
	struct tep_cmdline *cmdline;
	struct tep_handle *pevent;
	struct pid_list *list;

	char pidstr[100];

	if (!comm_list)
		return;

	pevent = tracecmd_get_tep(handle);

	/* Seach for comm names and get their pids */
	for (list = comm_list; list; list = list->next) {
		cmdline = tep_data_pid_from_comm(pevent, list->pid, NULL);
		if (!cmdline) {
			warning("comm: %s not in cmdline list", list->pid);
			continue;
		}
		do {
			sprintf(pidstr, "%d", tep_cmdline_pid(pevent, cmdline));
			add_pid_filter(pidstr);
			cmdline = tep_data_pid_from_comm(pevent, list->pid,
							    cmdline);
		} while (cmdline);
	}

	while (comm_list) {
		list = comm_list;
		comm_list = comm_list->next;
		if (list->free)
			free(list->pid);
		free(list);
	}
}

static void make_pid_filter(struct tracecmd_input *handle,
			    struct input_files *input_files)
{
	struct pid_list *list;
	char *str = NULL;

	convert_comm_filter(handle);

	if (!pid_list)
		return;

	/* First do all common pids */
	for (list = pid_list; list; list = list->next) {
		str = append_pid_filter(str, list->pid);
	}

	add_filter(input_files, str, 0);
	free(str);

	while (pid_list) {
		list = pid_list;
		pid_list = pid_list->next;
		if (list->free)
			free(list->pid);
		free(list);
	}
}

static int __process_filters(struct tracecmd_input *handle,
			     struct filter_str *filters)
{
	struct tracecmd_filter *trace_filter;

	for (; filters; filters = filters->next) {
		trace_filter = tracecmd_filter_add(handle,
						   filters->filter,
						   filters->neg);
		if (!trace_filter)
			die("Failed to create event filter: %s",
			    filters->filter);
	}

	return !!filters;
}

static void process_filters(struct handle_list *handles)
{
	struct input_files *input_file = handles->input_file ?: last_input_file;
	int added = 0;

	make_pid_filter(handles->handle, input_file);

	/*
	 * Order of filter processing matters. Apply the global filters
	 * before file-specific ones.
	 */
	added += __process_filters(handles->handle, filter_strings);
	if (input_file)
		added += __process_filters(handles->handle,
					   input_file->filter_str);

	if (added && test_filters_mode)
		exit(0);
}

static void init_wakeup(struct tracecmd_input *handle)
{
	struct tep_handle *pevent;
	struct tep_event *event;

	if (!show_wakeup)
		return;

	pevent = tracecmd_get_tep(handle);

	trace_hash_init(&wakeup_hash, WAKEUP_HASH_SIZE);

	event = tep_find_event_by_name(pevent, "sched", "sched_wakeup");
	if (!event)
		goto fail;
	wakeup_id = event->id;
	wakeup_task = tep_find_field(event, "pid");
	if (!wakeup_task)
		goto fail;
	wakeup_success = tep_find_field(event, "success");

	event = tep_find_event_by_name(pevent, "sched", "sched_switch");
	if (!event)
		goto fail;
	sched_id = event->id;
	sched_task = tep_find_field(event, "next_pid");
	if (!sched_task)
		goto fail;

	sched_prio = tep_find_field(event, "next_prio");
	if (!sched_prio)
		goto fail;


	wakeup_new_id = -1;

	event = tep_find_event_by_name(pevent, "sched", "sched_wakeup_new");
	if (!event)
		goto skip;
	wakeup_new_id = event->id;
	wakeup_new_task = tep_find_field(event, "pid");
	if (!wakeup_new_task)
		goto fail;
	wakeup_new_success = tep_find_field(event, "success");

 skip:
	return;

 fail:
	show_wakeup = 0;
}

static void add_wakeup(unsigned int val, unsigned long long start)
{
	unsigned int key = trace_hash(val);
	struct wakeup_info *info;
	struct trace_hash_item *item;

	item = trace_hash_find(&wakeup_hash, key, NULL, NULL);
	if (item) {
		info = container_of(item, struct wakeup_info, hash);
		/* Hmm, double wakeup? */
		info->start = start;
		return;
	}

	info = malloc(sizeof(*info));
	if (!info)
		die("Failed to allocate wakeup info");
	info->hash.key = key;
	info->start = start;
	trace_hash_add(&wakeup_hash, &info->hash);
}

static unsigned long long max_lat = 0;
static unsigned long long max_time;
static unsigned long long min_lat = -1;
static unsigned long long min_time;

static unsigned long long max_rt_lat = 0;
static unsigned long long max_rt_time;
static unsigned long long min_rt_lat = -1;
static unsigned long long min_rt_time;

static void add_sched(unsigned int val, unsigned long long end, int rt)
{
	struct trace_hash_item *item;
	unsigned int key = trace_hash(val);
	struct wakeup_info *info;
	unsigned long long cal;

	item = trace_hash_find(&wakeup_hash, key, NULL, NULL);
	if (!item)
		return;

	info = container_of(item, struct wakeup_info, hash);

	cal = end - info->start;

	if (cal > max_lat) {
		max_lat = cal;
		max_time = end;
	}
	if (cal < min_lat) {
		min_lat = cal;
		min_time = end;
	}

	if (rt) {
		if (cal > max_rt_lat) {
			max_rt_lat = cal;
			max_rt_time = end;
		}
		if (cal < min_rt_lat) {
			min_rt_lat = cal;
			min_rt_time = end;
		}
	}

	printf(" Latency: %llu.%03llu usecs", cal / 1000, cal % 1000);

	total_wakeup_lat += cal;
	wakeup_lat_count++;

	if (rt) {
		total_wakeup_rt_lat += cal;
		wakeup_rt_lat_count++;
	}

	trace_hash_del(item);
	free(info);
}

static void process_wakeup(struct tep_handle *pevent, struct tep_record *record)
{
	unsigned long long val;
	int id;

	if (!show_wakeup)
		return;

	id = tep_data_type(pevent, record);
	if (id == wakeup_id) {
		if (tep_read_number_field(wakeup_success, record->data, &val) == 0) {
			if (!val)
				return;
		}
		if (tep_read_number_field(wakeup_task, record->data, &val))
			return;
		add_wakeup(val, record->ts);
	} else if (id == wakeup_new_id) {
		if (tep_read_number_field(wakeup_new_success, record->data, &val) == 0) {
			if (!val)
				return;
		}
		if (tep_read_number_field(wakeup_new_task, record->data, &val))
			return;
		add_wakeup(val, record->ts);
	} else if (id == sched_id) {
		int rt = 1;
		if (tep_read_number_field(sched_prio, record->data, &val))
			return;
		if (val > 99)
			rt = 0;
		if (tep_read_number_field(sched_task, record->data, &val))
			return;
		add_sched(val, record->ts, rt);
	}
}

static void
show_wakeup_timings(unsigned long long total, unsigned long count,
		    unsigned long long lat_max, unsigned long long time_max,
		    unsigned long long lat_min, unsigned long long time_min)
{

	total /= count;

	printf("\nAverage wakeup latency: %llu.%03llu usecs\n",
	       total / 1000,
	       total % 1000);
	printf("Maximum Latency: %llu.%03llu usecs at ", lat_max / 1000, lat_max % 1000);
	printf("timestamp: %llu.%06llu\n",
	       time_max / 1000000000, ((time_max + 500) % 1000000000) / 1000);
	printf("Minimum Latency: %llu.%03llu usecs at ", lat_min / 1000, lat_min % 1000);
	printf("timestamp: %llu.%06llu\n\n", time_min / 1000000000,
	       ((time_min + 500) % 1000000000) / 1000);
}

static void finish_wakeup(void)
{
	struct wakeup_info *info;
	struct trace_hash_item **bucket;
	struct trace_hash_item *item;

	if (!show_wakeup || !wakeup_lat_count)
		return;

	show_wakeup_timings(total_wakeup_lat, wakeup_lat_count,
			    max_lat, max_time,
			    min_lat, min_time);


	if (wakeup_rt_lat_count) {
		printf("RT task timings:\n");
		show_wakeup_timings(total_wakeup_rt_lat, wakeup_rt_lat_count,
				    max_rt_lat, max_rt_time,
				    min_rt_lat, min_rt_time);
	}

	trace_hash_for_each_bucket(bucket, &wakeup_hash) {
		trace_hash_while_item(item, bucket) {
			trace_hash_del(item);
			info = container_of(item, struct wakeup_info, hash);
			free(info);
		}
	}

	trace_hash_free(&wakeup_hash);
}

void trace_show_data(struct tracecmd_input *handle, struct tep_record *record)
{
	tracecmd_show_data_func func = tracecmd_get_show_data_func(handle);
	const char *tfmt = time_format(handle, TIME_FMT_NORMAL);
	const char *cfmt = latency_format ? "%8.8s-%-5d %3d" : "%16s-%-5d [%03d]";
	struct tep_handle *pevent;
	struct tep_event *event;
	struct trace_seq s;
	int cpu = record->cpu;
	bool use_trace_clock;
	static unsigned long long last_ts;
	unsigned long long diff_ts;
	unsigned long page_size;
	char buf[50];

	page_size = tracecmd_page_size(handle);

	test_save(record, cpu);

	if (func) {
		func(handle, record);
		return;
	}

	pevent = tracecmd_get_tep(handle);
	event = tep_find_event_by_record(pevent, record);
	use_trace_clock = tracecmd_get_use_trace_clock(handle);

	trace_seq_init(&s);
	if (record->missed_events > 0)
		trace_seq_printf(&s, "CPU:%d [%lld EVENTS DROPPED]\n",
				 cpu, record->missed_events);
	else if (record->missed_events < 0)
		trace_seq_printf(&s, "CPU:%d [EVENTS DROPPED]\n", cpu);
	if (buffer_breaks || tracecmd_get_debug()) {
		if (tracecmd_record_at_buffer_start(handle, record)) {
			trace_seq_printf(&s, "CPU:%d [SUBBUFFER START]", cpu);
			if (tracecmd_get_debug())
				trace_seq_printf(&s, " [%lld:0x%llx]",
						 tracecmd_page_ts(handle, record),
						 record->offset & ~(page_size - 1));
			trace_seq_putc(&s, '\n');
		}
	}

	tep_print_event(pevent, &s, record, cfmt,
			TEP_PRINT_COMM,
			TEP_PRINT_PID,
			TEP_PRINT_CPU);

	if (raw_format)
		trace_seq_printf(&s, "-0x%x",
				 tep_data_flags(pevent, record));
	else
		tep_print_event(pevent, &s, record,
				latency_format ? "%s" : " %s",
				TEP_PRINT_LATENCY);

	tep_print_event(pevent, &s, record, tfmt, TEP_PRINT_TIME);

	if (tsdiff) {
		unsigned long long rec_ts = record->ts;

		buf[0] = 0;
		if (use_trace_clock && !tep_test_flag(pevent, TEP_NSEC_OUTPUT))
			rec_ts = (rec_ts + 500) / 1000;
		if (last_ts) {
			diff_ts = rec_ts - last_ts;
			snprintf(buf, 50, "(+%lld)", diff_ts);
			buf[49] = 0;
		}
		last_ts = rec_ts;
		trace_seq_printf(&s, " %-8s", buf);
	}

	print_event_name(&s, event);
	tep_print_event(pevent, &s, record, "%s", format_type);

	if (s.len && *(s.buffer + s.len - 1) == '\n')
		s.len--;
	if (tracecmd_get_debug()) {
		struct kbuffer *kbuf;
		struct kbuffer_raw_info info;
		void *page;
		void *offset;

		trace_seq_printf(&s, " [%d:0x%llx:%d]",
				 tracecmd_record_ts_delta(handle, record),
				 record->offset & (page_size - 1), record->size);
		kbuf = tracecmd_record_kbuf(handle, record);
		page = tracecmd_record_page(handle, record);
		offset = tracecmd_record_offset(handle, record);

		if (kbuf && page && offset) {
			struct kbuffer_raw_info *pi = &info;

			/* We need to get the record raw data to get next */
			pi->next = offset;
			pi = kbuffer_raw_get(kbuf, page, pi);
			while ((pi = kbuffer_raw_get(kbuf, page, pi))) {
				if (pi->type < KBUFFER_TYPE_PADDING)
					break;
				switch (pi->type) {
				case KBUFFER_TYPE_PADDING:
					trace_seq_printf(&s, "\n PADDING: ");
					break;
				case KBUFFER_TYPE_TIME_EXTEND:
					trace_seq_printf(&s, "\n TIME EXTEND: ");
					break;
				case KBUFFER_TYPE_TIME_STAMP:
					trace_seq_printf(&s, "\n TIME STAMP: ");
					break;
				}
				if (pi->type == KBUFFER_TYPE_TIME_STAMP)
					trace_seq_printf(&s, "timestamp:%lld length:%d",
							 pi->delta,
							 pi->length);
				else
					trace_seq_printf(&s, "delta:%lld length:%d",
							 pi->delta,
							 pi->length);
			}
		}
	}

	trace_seq_do_printf(&s);
	trace_seq_destroy(&s);

	process_wakeup(pevent, record);

	printf("\n");
}

static void read_latency(struct tracecmd_input *handle)
{
	char *buf = NULL;
	size_t size = 0;
	int r;

	do {
		r = tracecmd_latency_data_read(handle, &buf, &size);
		if (r > 0)
			printf("%.*s", r, buf);
	} while (r > 0);

	printf("\n");
	free(buf);
}

static int
test_filters(struct tep_handle *pevent, struct tep_record *record)
{
	int ret = FILTER_NONE;
	int flags;

	if (no_irqs || no_softirqs) {
		flags = tep_data_flags(pevent, record);
		if (no_irqs && (flags & TRACE_FLAG_HARDIRQ))
			return FILTER_MISS;
		if (no_softirqs && (flags & TRACE_FLAG_SOFTIRQ))
			return FILTER_MISS;
	}

	return ret;
}

struct stack_info_cpu {
	int			cpu;
	int			last_printed;
};

struct stack_info {
	struct stack_info	*next;
	struct handle_list	*handles;
	struct stack_info_cpu	*cpus;
	int			nr_cpus;
};

static void print_handle_file(struct handle_list *handles)
{
	/* Only print file names if more than one file is read */
	if (!multi_inputs && !instances)
		return;
	if (handles->file && *handles->file != '\0')
		printf("%*s: ", max_file_size, handles->file);
	else
		printf("%*s  ", max_file_size, "");
}

static bool skip_record(struct handle_list *handles, struct tep_record *record, int cpu)
{
	struct tep_handle *tep;
	bool found = false;
	int ret;

	tep = tracecmd_get_tep(handles->handle);

	if (filter_cpus) {
		int i;

		for (i = 0; filter_cpus[i] >= 0; i++) {
			if (filter_cpus[i] == cpu) {
				found = true;
				break;
			}
		}

		if (!found)
			return true;
		found = false;
	}

	ret = test_filters(tep, record);
	switch (ret) {
	case FILTER_NOEXIST:
		break;
	case FILTER_NONE:
	case FILTER_MATCH:
		/* Test the negative filters (-v) */
		ret = test_filters(tep, record);
		if (ret != FILTER_MATCH) {
			found = true;
			break;
		}
	}

	return !found;
}

struct kvm_cpu_map {
	struct tracecmd_input		*guest_handle;
	int				guest_vcpu;
	int				host_pid;
};

static struct kvm_cpu_map *vcpu_maps;
static int nr_vcpu_maps;

static int cmp_map(const void *A, const void *B)
{
	const struct kvm_cpu_map *a = A;
	const struct kvm_cpu_map *b = B;

	if (a->host_pid < b->host_pid)
		return -1;
	return a->host_pid > b->host_pid;
}

static void map_vcpus(struct tracecmd_input **handles, int nr_handles)
{
	struct tracecmd_input *host_handle = handles[0];
	unsigned long long traceid;
	struct kvm_cpu_map *map;
	const int *cpu_pids;
	const char *name;
	int vcpu_count;
	int ret;
	int i, k;

	for (i = 1; i < nr_handles; i++) {
		traceid = tracecmd_get_traceid(handles[i]);
		ret = tracecmd_get_guest_cpumap(host_handle, traceid,
						&name, &vcpu_count, &cpu_pids);
		if (ret)
			continue;
		map = realloc(vcpu_maps, sizeof(*map) * (nr_vcpu_maps + vcpu_count));
		if (!map)
			die("Could not allocate vcpu maps");

		vcpu_maps = map;
		map += nr_vcpu_maps;
		nr_vcpu_maps += vcpu_count;

		for (k = 0; k < vcpu_count; k++) {
			map[k].guest_handle = handles[i];
			map[k].guest_vcpu = k;
			map[k].host_pid = cpu_pids[k];
		}
	}
	if (!vcpu_maps)
		return;

	qsort(vcpu_maps, nr_vcpu_maps, sizeof(*map), cmp_map);
}


const char *tep_plugin_kvm_get_func(struct tep_event *event,
				    struct tep_record *record,
				    unsigned long long *val)
{
	struct tep_handle *tep;
	struct kvm_cpu_map *map;
	struct kvm_cpu_map key;
	unsigned long long rip = *val;
	const char *func;
	int pid;

	if (!vcpu_maps || !nr_vcpu_maps)
		return NULL;

	/*
	 * A kvm event is referencing an address of the guest.
	 * get the PID of this event, and then find which guest
	 * it belongs to. Then return the function name from that guest's
	 * handle.
	 */
	pid = tep_data_pid(event->tep, record);

	key.host_pid = pid;
	map = bsearch(&key, vcpu_maps, nr_vcpu_maps, sizeof(*vcpu_maps), cmp_map);

	if (!map)
		return NULL;

	tep = tracecmd_get_tep(map->guest_handle);
	func = tep_find_function(tep, rip);
	if (func)
		*val = tep_find_function_address(tep, rip);
	return func;
}

static int process_record(struct tracecmd_input *handle, struct tep_record *record,
			  int cpu, void *data)
{
	struct handle_list *handles = tracecmd_get_private(handle);
	unsigned long long *last_timestamp = data;

	if (skip_record(handles, record, cpu))
		return 0;

	if (tscheck && *last_timestamp > record->ts) {
		errno = 0;
		warning("WARNING: Record on cpu %d went backwards: %lld to %lld delta: -%lld\n",
			cpu, *last_timestamp, record->ts, *last_timestamp - record->ts);
	}
	*last_timestamp = record->ts;

	print_handle_file(handles);
	trace_show_data(handle, record);
	return 0;
}

enum output_type {
	OUTPUT_NORMAL,
	OUTPUT_STAT_ONLY,
	OUTPUT_UNAME_ONLY,
	OUTPUT_VERSION_ONLY,
};

static void read_data_info(struct list_head *handle_list, enum output_type otype,
			   int global, int align_ts)
{
	unsigned long long ts, first_ts;
	struct handle_list *handles;
	struct tracecmd_input **handle_array;
	unsigned long long last_timestamp = 0;
	int nr_handles = 0;
	int first = 1;
	int ret;

	list_for_each_entry(handles, handle_list, list) {
		int cpus;

		nr_handles++;

		if (!tracecmd_is_buffer_instance(handles->handle)) {
			ret = tracecmd_init_data(handles->handle);
			if (ret < 0)
				die("failed to init data");
		}
		cpus = tracecmd_cpus(handles->handle);
		handles->cpus = cpus;

		process_filters(handles);

		/* Don't process instances that we added here */
		if (tracecmd_is_buffer_instance(handles->handle))
			continue;

		if (align_ts) {
			ts = tracecmd_get_first_ts(handles->handle);
			if (first || first_ts > ts)
				first_ts = ts;
			first = 0;
		}
		print_handle_file(handles);
		printf("cpus=%d\n", cpus);

		/* Latency trace is just all ASCII */
		if (ret > 0) {
			if (multi_inputs)
				die("latency traces do not work with multiple inputs");
			read_latency(handles->handle);
			return;
		}

		switch (otype) {
		case OUTPUT_NORMAL:
			break;
		case OUTPUT_STAT_ONLY:
			printf("\nKernel buffer statistics:\n"
			       "  Note: \"entries\" are the entries left in the kernel ring buffer and are not\n"
			       "        recorded in the trace data. They should all be zero.\n\n");
			tracecmd_print_stats(handles->handle);
			continue;
		case OUTPUT_UNAME_ONLY:
			tracecmd_print_uname(handles->handle);
		case OUTPUT_VERSION_ONLY:
			tracecmd_print_version(handles->handle);
			continue;
		}

		init_wakeup(handles->handle);
		if (last_hook)
			last_hook->next = tracecmd_hooks(handles->handle);
		else
			hooks = tracecmd_hooks(handles->handle);
		if (profile)
			trace_init_profile(handles->handle, hooks, global);

		/* If this file has buffer instances, get the handles for them */
		instances = tracecmd_buffer_instances(handles->handle);
		if (instances) {
			struct tracecmd_input *new_handle;
			struct input_files *file_input;
			const char *save_name;
			const char *name;
			int i;

			file_input = handles->input_file;

			for (i = 0; i < instances; i++) {
				name = tracecmd_buffer_instance_name(handles->handle, i);
				if (!name)
					die("error in reading buffer instance");
				new_handle = tracecmd_buffer_instance_handle(handles->handle, i);
				if (!new_handle) {
					warning("could not retrieve handle %s", name);
					continue;
				}
				if (file_input) {
					save_name = file_input->file;
					file_input->file = name;
				} else {
					save_name = NULL;
					file_input = add_input(name);
				}
				add_handle(new_handle, file_input);
				if (save_name)
					file_input->file = save_name;
			}
		}
	}

	if (otype != OUTPUT_NORMAL)
		return;

	if (align_ts) {
		list_for_each_entry(handles, handle_list, list) {
			tracecmd_add_ts_offset(handles->handle, -first_ts);
		}
	}

	handle_array = calloc(nr_handles, sizeof(*handle_array));
	if (!handle_array)
		die("Could not allocate memory for handle list");

	nr_handles = 0;
	list_for_each_entry(handles, handle_list, list) {
		tracecmd_set_private(handles->handle, handles);
		handle_array[nr_handles++] = handles->handle;
	}

	map_vcpus(handle_array, nr_handles);

	tracecmd_iterate_events_multi(handle_array, nr_handles,
				      process_record, &last_timestamp);

	free(handle_array);

	if (profile)
		do_trace_profile();

	list_for_each_entry(handles, handle_list, list) {
		show_test(handles->handle);
	}
}

struct tracecmd_input *read_trace_header(const char *file, int flags)
{
	input_fd = open(file, O_RDONLY);
	if (input_fd < 0)
		die("opening '%s'\n", file);

	return tracecmd_alloc_fd(input_fd, flags);
}

static void sig_end(int sig)
{
	struct handle_list *handles;

	fprintf(stderr, "trace-cmd: Received SIGINT\n");

	list_for_each_entry(handles, &handle_list, list) {
		tracecmd_close(handles->handle);
	}

	exit(0);
}

static const char *skip_space_and_test_digit(const char *p, const char *cpu_str)
{
	while (isspace(*p))
		p++;
	if (!isdigit(*p))
		die("invalid character '%c' in cpu string '%s'",
		    *p, cpu_str);
	return p;
}

static void __add_cpu(int cpu)
{
	filter_cpus = tracecmd_add_id(filter_cpus, cpu, nr_filter_cpus++);
}

static void parse_cpulist(const char *cpu_str)
{
	unsigned a, b;
	const char *s = cpu_str;

	do {
		s = skip_space_and_test_digit(s, cpu_str);
		b = a = strtoul(s, (char **)&s, 10);
		if (*s == '-') {
			s = skip_space_and_test_digit(s + 1, cpu_str);
			b = strtoul(s, (char **)&s, 10);
		}
		if (!(a <= b))
			die("range of cpu numbers must be lower to greater");
		while (a <= b) {
			__add_cpu(a);
			a++;
		}
		if (*s == ',' || *s == ':')
			s++;
	} while (*s != '\0');
}

static void read_file_fd(int fd, char *dst, int len)
{
	size_t size = 0;
	int r;

	do {
		r = read(fd, dst+size, len);
		if (r > 0) {
			size += r;
			len -= r;
		}
	} while (r > 0);
}

static void add_functions(struct tep_handle *pevent, const char *file)
{
	struct stat st;
	char *buf;
	int ret;
	int fd;

	fd = open(file, O_RDONLY);
	if (fd < 0)
		die("Can't read file %s", file);

	ret = fstat(fd, &st);
	if (ret < 0)
		die("Can't stat file %s", file);

	buf = malloc(st.st_size + 1);
	if (!buf)
		die("Failed to allocate for function buffer");
	read_file_fd(fd, buf, st.st_size);
	buf[st.st_size] = '\0';
	close(fd);
	tep_parse_kallsyms(pevent, buf);
	free(buf);
}

static void process_plugin_option(char *option)
{
	char *name = option;
	char *val = NULL;
	char *p;

	if ((p = strstr(name, "="))) {
		*p = '\0';
		val = p+1;
	}
	tep_plugin_add_option(name, val);
}

static void set_event_flags(struct tep_handle *pevent, struct event_str *list,
			    unsigned int flag)
{
	struct tep_event **events;
	struct tep_event *event;
	struct event_str *str;
	regex_t regex;
	int ret;
	int i;

	if (!list)
		return;

	events = tep_list_events(pevent, 0);

	for (str = list; str; str = str->next) {
		char *match;

		match = malloc(strlen(str->event) + 3);
		if (!match)
			die("Failed to allocate for match string '%s'", str->event);
		sprintf(match, "^%s$", str->event);

		ret = regcomp(&regex, match, REG_ICASE|REG_NOSUB);
		if (ret < 0)
			die("Can't parse '%s'", str->event);
		free(match);
		for (i = 0; events[i]; i++) {
			event = events[i];
			if (!regexec(&regex, event->name, 0, NULL, 0) ||
			    !regexec(&regex, event->system, 0, NULL, 0))
				event->flags |= flag;
		}
	}
}

static void show_event_ts(struct tracecmd_input *handle,
			  struct tep_record *record)
{
	const char *tfmt = time_format(handle, TIME_FMT_TS);
	struct tep_handle *tep = tracecmd_get_tep(handle);
	struct trace_seq s;

	trace_seq_init(&s);
	tep_print_event(tep, &s, record, tfmt, TEP_PRINT_TIME);
	printf("%s", s.buffer);
	trace_seq_destroy(&s);
}

static void add_hook(const char *arg)
{
	struct hook_list *hook;

	hook = tracecmd_create_event_hook(arg);

	hook->next = hooks;
	hooks = hook;
	if (!last_hook)
		last_hook = hook;
}

static void add_first_input(const char *input_file, long long tsoffset)
{
	struct input_files *item;

	/* Copy filter strings to this input file */
	item = add_input(input_file);
	item->filter_str = filter_strings;
	if (filter_strings)
		item->filter_str_next = filter_next;
	else
		item->filter_str_next = &item->filter_str;
	/* Copy the tsoffset to this input file */
	item->tsoffset = tsoffset;
}

enum {
	OPT_verbose	= 234,
	OPT_align_ts	= 235,
	OPT_raw_ts	= 236,
	OPT_version	= 237,
	OPT_tscheck	= 238,
	OPT_tsdiff	= 239,
	OPT_ts2secs	= 240,
	OPT_tsoffset	= 241,
	OPT_bycomm	= 242,
	OPT_debug	= 243,
	OPT_uname	= 244,
	OPT_profile	= 245,
	OPT_event	= 246,
	OPT_comm	= 247,
	OPT_boundary	= 248,
	OPT_stat	= 249,
	OPT_pid		= 250,
	OPT_nodate	= 251,
	OPT_check_event_parsing	= 252,
	OPT_kallsyms	= 253,
	OPT_events	= 254,
	OPT_cpu		= 255,
	OPT_cpus	= 256,
	OPT_first	= 257,
	OPT_last	= 258,
};

void trace_report (int argc, char **argv)
{
	struct tracecmd_input *handle;
	struct tep_handle *pevent;
	struct event_str *raw_events = NULL;
	struct event_str *nohandler_events = NULL;
	struct event_str **raw_ptr = &raw_events;
	struct event_str **nohandler_ptr = &nohandler_events;
	const char *functions = NULL;
	const char *print_event = NULL;
	struct input_files *inputs;
	struct handle_list *handles;
	enum output_type otype;
	long long tsoffset = 0;
	unsigned long long ts2secs = 0;
	unsigned long long ts2sc;
	int open_flags = 0;
	int show_stat = 0;
	int show_funcs = 0;
	int show_endian = 0;
	int show_page_size = 0;
	int show_printk = 0;
	int show_uname = 0;
	int show_version = 0;
	int show_events = 0;
	int show_cpus = 0;
	int show_first = 0;
	int show_last = 0;
	int print_events = 0;
	int nanosec = 0;
	int no_date = 0;
	int raw_ts = 0;
	int align_ts = 0;
	int global = 0;
	int neg = 0;
	int ret = 0;
	int check_event_parsing = 0;
	int c;

	list_head_init(&handle_list);
	list_head_init(&input_files);

	if (argc < 2)
		usage(argv);

	if (strcmp(argv[1], "report") != 0)
		usage(argv);

	signal(SIGINT, sig_end);

	trace_set_loglevel(TEP_LOG_ERROR);

	for (;;) {
		int option_index = 0;
		static struct option long_options[] = {
			{"cpu", required_argument, NULL, OPT_cpu},
			{"cpus", no_argument, NULL, OPT_cpus},
			{"events", no_argument, NULL, OPT_events},
			{"event", required_argument, NULL, OPT_event},
			{"filter-test", no_argument, NULL, 'T'},
			{"first-event", no_argument, NULL, OPT_first},
			{"kallsyms", required_argument, NULL, OPT_kallsyms},
			{"pid", required_argument, NULL, OPT_pid},
			{"comm", required_argument, NULL, OPT_comm},
			{"check-events", no_argument, NULL,
				OPT_check_event_parsing},
			{"nodate", no_argument, NULL, OPT_nodate},
			{"stat", no_argument, NULL, OPT_stat},
			{"boundary", no_argument, NULL, OPT_boundary},
			{"debug", no_argument, NULL, OPT_debug},
			{"last-event", no_argument, NULL, OPT_last},
			{"profile", no_argument, NULL, OPT_profile},
			{"uname", no_argument, NULL, OPT_uname},
			{"version", no_argument, NULL, OPT_version},
			{"by-comm", no_argument, NULL, OPT_bycomm},
			{"ts-offset", required_argument, NULL, OPT_tsoffset},
			{"ts2secs", required_argument, NULL, OPT_ts2secs},
			{"ts-diff", no_argument, NULL, OPT_tsdiff},
			{"ts-check", no_argument, NULL, OPT_tscheck},
			{"raw-ts", no_argument, NULL, OPT_raw_ts},
			{"align-ts", no_argument, NULL, OPT_align_ts},
			{"verbose", optional_argument, NULL, OPT_verbose},
			{"help", no_argument, NULL, '?'},
			{NULL, 0, NULL, 0}
		};

		c = getopt_long (argc-1, argv+1, "+hSIi:H:feGpRr:tPNn:LlEwF:V::vTqO:",
			long_options, &option_index);
		if (c == -1)
			break;
		switch (c) {
		case 'h':
			usage(argv);
			break;
		case 'i':
			if (input_file) {
				multi_inputs++;
				add_input(optarg);
			} else {
				input_file = optarg;
				add_first_input(input_file, tsoffset);
			}
			break;
		case 'F':
			add_filter(last_input_file, optarg, neg);
			break;
		case 'H':
			add_hook(optarg);
			break;
		case 'T':
			test_filters_mode = 1;
			break;
		case 'f':
			show_funcs = 1;
			break;
		case 'I':
			no_irqs = 1;
			break;
		case 'S':
			no_softirqs = 1;
			break;
		case 'P':
			show_printk = 1;
			break;
		case 'L':
			open_flags |= TRACECMD_FL_LOAD_NO_SYSTEM_PLUGINS;
			break;
		case 'N':
			open_flags |= TRACECMD_FL_LOAD_NO_PLUGINS;
			break;
		case 'n':
			*nohandler_ptr = malloc(sizeof(struct event_str));
			if (!*nohandler_ptr)
				die("Failed to allocate for '-n %s'", optarg);
			(*nohandler_ptr)->event = optarg;
			(*nohandler_ptr)->next = NULL;
			nohandler_ptr = &(*nohandler_ptr)->next;
			break;
		case 'e':
			show_endian = 1;
			break;
		case 'p':
			show_page_size = 1;
			break;
		case 'E':
			show_events = 1;
			break;
		case 'G':
			global = 1;
			break;
		case 'R':
			raw_format = true;
			break;
		case 'r':
			*raw_ptr = malloc(sizeof(struct event_str));
			if (!*raw_ptr)
				die("Failed to allocate '-r %s'", optarg);
			(*raw_ptr)->event = optarg;
			(*raw_ptr)->next = NULL;
			raw_ptr = &(*raw_ptr)->next;
			break;
		case 't':
			nanosec = 1;
			break;
		case 'w':
			show_wakeup = 1;
			break;
		case 'l':
			latency_format = 1;
			break;
		case 'O':
			process_plugin_option(optarg);
			break;
		case 'v':
			if (neg)
				die("Only 1 -v can be used");
			neg = 1;
			break;
		case 'q':
			silence_warnings = 1;
			tracecmd_set_loglevel(TEP_LOG_NONE);
			break;
		case OPT_cpu:
			parse_cpulist(optarg);
			break;
		case OPT_cpus:
			show_cpus = 1;
			break;
		case OPT_events:
			print_events = 1;
			break;
		case OPT_event:
			print_event = optarg;
			break;
		case OPT_kallsyms:
			functions = optarg;
			break;
		case OPT_pid:
			add_pid_filter(optarg);
			break;
		case OPT_comm:
			add_comm_filter(optarg);
			break;
		case OPT_check_event_parsing:
			check_event_parsing = 1;
			break;
		case OPT_nodate:
			no_date = 1;
			break;
		case OPT_stat:
			show_stat = 1;
			break;
		case OPT_first:
			show_first = 1;
			show_cpus = 1;
			break;
		case OPT_last:
			show_last = 1;
			show_cpus = 1;
			break;
		case OPT_boundary:
			/* Debug to look at buffer breaks */
			buffer_breaks = 1;
			break;
		case OPT_debug:
			buffer_breaks = 1;
			tracecmd_set_debug(true);
			break;
		case OPT_profile:
			profile = 1;
			break;
		case OPT_uname:
			show_uname = 1;
			break;
		case OPT_version:
			show_version = 1;
			break;
		case OPT_bycomm:
			trace_profile_set_merge_like_comms();
			break;
		case OPT_ts2secs:
			ts2sc = atoll(optarg);
			if (multi_inputs)
				last_input_file->ts2secs = ts2sc;
			else
				ts2secs = ts2sc;
			break;
		case OPT_tsoffset:
			tsoffset = atoll(optarg);
			if (multi_inputs)
				last_input_file->tsoffset = tsoffset;
			if (!input_file)
				die("--ts-offset must come after -i");
			break;
		case OPT_tsdiff:
			tsdiff = 1;
			break;
		case OPT_tscheck:
			tscheck = 1;
			break;
		case OPT_raw_ts:
			raw_ts = 1;
			break;
		case OPT_align_ts:
			align_ts = 1;
			break;
		case 'V':
		case OPT_verbose:
			show_status = 1;
			if (trace_set_verbose(optarg) < 0)
				die("invalid verbose level %s", optarg);
			break;
		default:
			usage(argv);
		}
	}

	if ((argc - optind) >= 2) {
		if (input_file)
			usage(argv);
		input_file = argv[optind + 1];
		add_first_input(input_file, tsoffset);
		for (int i = optind + 2; i < argc; i++) {
			multi_inputs++;
			add_input(argv[i]);
		}
	}

	if (!multi_inputs) {
		if (!input_file) {
			input_file = default_input_file;
			add_first_input(input_file, tsoffset);
		}
	} else if (show_wakeup)
		die("Wakeup tracing can only be done on a single input file");

	list_for_each_entry(inputs, &input_files, list) {
		handle = read_trace_header(inputs->file, open_flags);
		if (!handle)
			die("error reading header for %s", inputs->file);

		/* If used with instances, top instance will have no tag */
		add_handle(handle, multi_inputs ? inputs : NULL);

		if (no_date)
			tracecmd_set_flag(handle, TRACECMD_FL_IGNORE_DATE);
		if (raw_ts)
			tracecmd_set_flag(handle, TRACECMD_FL_RAW_TS);
		page_size = tracecmd_page_size(handle);

		if (show_page_size) {
			printf("file page size is %d, and host page size is %d\n",
			       page_size,
			       getpagesize());
			return;
		}

		if (inputs->tsoffset)
			tracecmd_set_ts_offset(handle, inputs->tsoffset);

		if (inputs->ts2secs)
			tracecmd_set_ts2secs(handle, inputs->ts2secs);
		else if (ts2secs)
			tracecmd_set_ts2secs(handle, ts2secs);

		pevent = tracecmd_get_tep(handle);

		if (nanosec)
			tep_set_flag(pevent, TEP_NSEC_OUTPUT);

		if (raw_format)
			format_type = TEP_PRINT_INFO_RAW;

		if (test_filters_mode)
			tep_set_test_filters(pevent, 1);

		if (functions)
			add_functions(pevent, functions);

		if (show_endian) {
			printf("file is %s endian and host is %s endian\n",
				tep_is_file_bigendian(pevent) ? "big" : "little",
				tep_is_local_bigendian(pevent) ? "big" : "little");
			return;
		}

		if (print_events) {
			tracecmd_print_events(handle, NULL);
			return;
		}

		if (print_event) {
			tracecmd_print_events(handle, print_event);
			return;
		}

		ret = tracecmd_read_headers(handle, 0);
		if (check_event_parsing) {
			if (ret || tracecmd_get_parsing_failures(handle))
				exit(EINVAL);
			else
				exit(0);
		} else {
			if (ret)
				return;
		}

		if (show_funcs) {
			tep_print_funcs(pevent);
			return;
		}
		if (show_printk) {
			tep_print_printk(pevent);
			return;
		}

		if (show_events) {
			struct tep_event **events;
			struct tep_event *event;
			int i;

			events = tep_list_events(pevent, TEP_EVENT_SORT_SYSTEM);
			for (i = 0; events[i]; i++) {
				event = events[i];
				if (event->system)
					printf("%s:", event->system);
				printf("%s\n", event->name);
			}
			return;
		}

		if (show_cpus) {
			struct tep_record *record;
			int cpus;
			int ret;
			int i;

			if (!tracecmd_is_buffer_instance(handle)) {
				ret = tracecmd_init_data(handle);
				if (ret < 0)
					die("failed to init data");
			}
			cpus = tracecmd_cpus(handle);
			printf("List of CPUs in %s with data:\n", inputs->file);
			for (i = 0; i < cpus; i++) {
				if ((record = tracecmd_read_cpu_first(handle, i))) {
					printf("  %d", i);
					if (show_first) {
						printf("\tFirst event:");
						show_event_ts(handle, record);
					}
					if (show_last) {
						tracecmd_free_record(record);
						record = tracecmd_read_cpu_last(handle, i);
						if (record) {
							printf("\tLast event:");
							show_event_ts(handle, record);
						}
					}
					tracecmd_free_record(record);
					printf("\n");
				}
			}
			continue;
		}

		set_event_flags(pevent, nohandler_events, TEP_EVENT_FL_NOHANDLE);
		set_event_flags(pevent, raw_events, TEP_EVENT_FL_PRINTRAW);
	}

	if (show_cpus)
		return;

	otype = OUTPUT_NORMAL;

	if (tracecmd_get_flags(handle) & TRACECMD_FL_RAW_TS) {
		tep_func_repeat_format = "%d";
	} else if (tracecmd_get_flags(handle) & TRACECMD_FL_IN_USECS) {
		if (tep_test_flag(tracecmd_get_tep(handle), TEP_NSEC_OUTPUT))
			tep_func_repeat_format = "%9.1d";
		else
			tep_func_repeat_format = "%6.1000d";
	} else {
		tep_func_repeat_format = "%12d";
	}


	if (show_stat)
		otype = OUTPUT_STAT_ONLY;
	/* yeah yeah, uname overrides stat */
	if (show_uname)
		otype = OUTPUT_UNAME_ONLY;
	/* and version overrides uname! */
	if (show_version)
		otype = OUTPUT_VERSION_ONLY;
	read_data_info(&handle_list, otype, global, align_ts);

	list_for_each_entry(handles, &handle_list, list) {
		tracecmd_close(handles->handle);
	}
	free_handles();
	free_inputs();

	finish_wakeup();

	return;
}
