aboutsummaryrefslogblamecommitdiffstats
path: root/trace-cmd.c
blob: 7c13264adb794af5ae3e545cefb8cc0a188b0d27 (plain) (tree)
1
2
  
                                                                                   


























                                                                             

                       



                    
                  
                  
                 
 
                        
 










                                             

                                   
                     
                
 

                   

                   
                              
 

                       
                                             

                   
                             


                     



                         
                       
                           
 








                                       


                                
                     
                










                                          


















                                                               











                                                               
                                


                                         
                                  

                                               
                                    











                                         
                              
                                            


                                            










                                         
                                  

                                                  
                                     



                 












                                               



















                                    
                            


                   


                             

                                    
                  








                                  



                            
                         








                          









                                      
                              
 

                                                       


                                    





                                                              




                                                                    

                 

 

















                                                














                                                       


                                              
                





                                                  
                                            


                       








                                                     




                                                 
                                                      
                                 





                                    
                                             
                                      
                                 


                       


                                 





                                       
                         

 








                                      
                                     





                                          























                                                                 
























































                                                     



































                                                  
                                                            














                                             



                                        












                                                























                                                              

                                                            


                       
                          






















                                                          








                                                             
                                       

                                                       



                                 
 


                                                     
                                                


                                                     




                           






















                                                   

                                                              
 
                       

                   


                  
                





                                                            

                                

                                       
                                                

                       
 
                         

                                                                    
 
                                                                 
 
                                       

                                                   
                                       

                                                




                                               



                                                     
                                                





                                                     









                                                            
                                                                            








                                               
                                                                    






                                                               
                                                             



                                              
                                                             








                                               

 
                                    
 
                           
                   
                
 














                                                      

 
                                
 
                            


                                    
 
 


                                 



                             


                          
                                         
                              
 
                      

 



























                                                                    
                                           














                                                                             
                                                   








                                                     


















                                                                           
                                                                          



                     






                                                             

 




                                                                   
                                
                                                                          




                                                                   
                                                                 








































                                                      

                                                  


                     





                                                  










                                                       
                                                               


                                                     

                                                        





                                                            
                                                                    




                              

                                                         





                                









                                                       

                                   
                   
                








                               
                               
 


                                  


                                        







                                                                               
 

                                              



                                                                       
                                         



                















































                                                                    
                                                              























                                                         





















                                                                            































                                                                   

                               

              
                                 
 


                                




                                                        
                                         



                                             
                             
 

                                       
              
 




                                 




                                                                              
 
                                                                            
 

                                                         
 
                                                                                  
 


                                                     
         


                                               

 






                            
                    
















                                                           
                                                      
                                

                              








                                









































                                                                      

























                                                    



                                  
                           
                                 
                                      
                           


                        
                        
                       
                        
                            
                          
                 
                


              

                  





                                             


                                                    


                                                   
                                                                 

                                                                   
 
                                                                                               




                                            

                                                    



                                                                      
                                                       
                                                        

                                                     
                                      



















                                                                                    
                                 

                                                                                   

                                                






                                                                                         
                                 

                                                    

                                              








                                                                 



                                                                       
                                                                         

                                      

                                                    


                                            

                                                                       
                                                        
                                                                          
                                                                      


                                                                            




                                                   
                                 

                                                    

                                                          


                                                       






                                                                             


                                            


                                                           


                         




                                                   









                                                                 
                              
                                  

                        

                                                  
                                                                    









                                            


                                            










                                            



                                                   



                                               

                                               



                        


                                                  
                
                                                                  


                            



                                                                        


                                                                          

                                
 
                                           

                                                                    

                                     
 


                                            
                            


                                        
                                  
         
 










                                                               
                 


                                                                              

                                           

         
                                




                                        





                                                     

                                             
                                
                 



                                                                        
                                             




                                                                 
 
                                  
         
 

                       
                      

                             


                                                                                                 







                                                       




                 
/*
 * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License (not later!)
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */
#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netdb.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <sched.h>
#include <errno.h>
#include <glob.h>

#include "trace-local.h"

#define _STR(x) #x
#define STR(x) _STR(x)
#define MAX_PATH 256

#define TRACE_CTRL	"tracing_on"
#define TRACE		"trace"
#define AVAILABLE	"available_tracers"
#define CURRENT		"current_tracer"
#define ITER_CTRL	"trace_options"
#define MAX_LATENCY	"tracing_max_latency"

#define UDP_MAX_PACKET (65536 - 20)

int silence_warnings;
int show_status;

static int rt_prio;

static int use_tcp;

static unsigned int page_size;

static int buffer_size;

static const char *output_file = "trace.dat";

static int latency;
static int sleep_time = 1000;
static int cpu_count;
static int *pids;

static char *host;
static int *client_ports;
static int sfd;

static int filter_task;
static int filter_pid = -1;

struct func_list {
	struct func_list *next;
	const char *func;
};

static struct func_list *filter_funcs;
static struct func_list *notrace_funcs;
static struct func_list *graph_funcs;

struct event_list {
	struct event_list *next;
	const char *event;
	char *filter;
	int neg;
};

static struct event_list *event_selection;

struct events {
	struct events *sibling;
	struct events *children;
	struct events *next;
	char *name;
};

static struct tracecmd_recorder *recorder;

static char *get_temp_file(int cpu)
{
	char *file = NULL;
	int size;

	size = snprintf(file, 0, "%s.cpu%d", output_file, cpu);
	file = malloc_or_die(size + 1);
	sprintf(file, "%s.cpu%d", output_file, cpu);

	return file;
}

static void put_temp_file(char *file)
{
	free(file);
}

static void delete_temp_file(int cpu)
{
	char file[MAX_PATH];

	snprintf(file, MAX_PATH, "%s.cpu%d", output_file, cpu);
	unlink(file);
}

static void kill_threads(void)
{
	int i;

	if (!cpu_count || !pids)
		return;

	for (i = 0; i < cpu_count; i++) {
		if (pids[i] > 0) {
			kill(pids[i], SIGKILL);
			delete_temp_file(i);
			pids[i] = 0;
		}
	}
}

static void delete_thread_data(void)
{
	int i;

	if (!cpu_count)
		return;

	for (i = 0; i < cpu_count; i++) {
		if (pids[i]) {
			delete_temp_file(i);
			if (pids[i] < 0)
				pids[i] = 0;
		}
	}
}

static void stop_threads(void)
{
	int i;

	if (!cpu_count)
		return;

	for (i = 0; i < cpu_count; i++) {
		if (pids[i] > 0) {
			kill(pids[i], SIGINT);
			waitpid(pids[i], NULL, 0);
			pids[i] = -1;
		}
	}
}

static void flush_threads(void)
{
	int i;

	if (!cpu_count)
		return;

	for (i = 0; i < cpu_count; i++) {
		if (pids[i] > 0)
			kill(pids[i], SIGUSR1);
	}
}

void die(char *fmt, ...)
{
	va_list ap;
	int ret = errno;

	if (errno)
		perror("trace-cmd");
	else
		ret = -1;

	kill_threads();
	va_start(ap, fmt);
	fprintf(stderr, "  ");
	vfprintf(stderr, fmt, ap);
	va_end(ap);

	fprintf(stderr, "\n");
	exit(ret);
}

void warning(char *fmt, ...)
{
	va_list ap;

	if (silence_warnings)
		return;

	if (errno)
		perror("trace-cmd");
	errno = 0;

	va_start(ap, fmt);
	fprintf(stderr, "  ");
	vfprintf(stderr, fmt, ap);
	va_end(ap);

	fprintf(stderr, "\n");
}

void pr_stat(char *fmt, ...)
{
	va_list ap;

	if (!show_status)
		return;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);

	printf("\n");
}

void *malloc_or_die(unsigned int size)
{
	void *data;

	data = malloc(size);
	if (!data)
		die("malloc");
	return data;
}

static int set_ftrace(int set)
{
	struct stat buf;
	char *path = "/proc/sys/kernel/ftrace_enabled";
	int fd;
	char *val = set ? "1" : "0";

	/* if ftace_enable does not exist, simply ignore it */
	fd = stat(path, &buf);
	if (fd < 0)
		return -ENODEV;

	fd = open(path, O_WRONLY);
	if (fd < 0)
		die ("Can't %s ftrace", set ? "enable" : "disable");

	write(fd, val, 1);
	close(fd);

	return 0;
}

static char *get_tracing_file(const char *name);
static void put_tracing_file(char *file);

static void clear_trace(void)
{
	FILE *fp;
	char *path;

	/* reset the trace */
	path = get_tracing_file("trace");
	fp = fopen(path, "w");
	if (!fp)
		die("writing to '%s'", path);
	put_tracing_file(path);
	fwrite("0", 1, 1, fp);
	fclose(fp);
}

static void reset_max_latency(void)
{
	FILE *fp;
	char *path;

	/* reset the trace */
	path = get_tracing_file("tracing_max_latency");
	fp = fopen(path, "w");
	if (!fp)
		die("writing to '%s'", path);
	put_tracing_file(path);
	fwrite("0", 1, 1, fp);
	fclose(fp);
}

static void update_ftrace_pid(const char *pid)
{
	char *path;
	int ret;
	int fd;

	path = get_tracing_file("set_ftrace_pid");
	if (!path)
		return;

	fd = open(path, O_WRONLY | O_TRUNC);
	if (fd < 0)
		return;

	ret = write(fd, pid, strlen(pid));

	/*
	 * Older kernels required "-1" to disable pid
	 */
	if (ret < 0 && !strlen(pid))
		ret = write(fd, "-1", 2);

	if (ret < 0)
		die("error writing to %s", path);

	close(fd);
}

static void update_pid_event_filters(const char *pid);
static void enable_tracing(void);

static void update_task_filter(void)
{
	int pid = getpid();
	char spid[100];

	if (!filter_task && filter_pid < 0) {
		update_ftrace_pid("");
		enable_tracing();
		return;
	}

	if (filter_pid >= 0)
		pid = filter_pid;

	snprintf(spid, 100, "%d", pid);

	update_ftrace_pid(spid);

	update_pid_event_filters(spid);

	enable_tracing();
}

void run_cmd(int argc, char **argv)
{
	int status;
	int pid;

	if ((pid = fork()) < 0)
		die("failed to fork");
	if (!pid) {
		/* child */
		update_task_filter();
		if (execvp(argv[0], argv))
			exit(-1);
	}
	waitpid(pid, &status, 0);
}

static char *get_tracing_file(const char *name)
{
	static const char *tracing;
	char *file;

	if (!tracing) {
		tracing = tracecmd_find_tracing_dir();
		if (!tracing)
			die("Can't find tracing dir");
	}

	file = malloc_or_die(strlen(tracing) + strlen(name) + 2);
	if (!file)
		return NULL;

	sprintf(file, "%s/%s", tracing, name);
	return file;
}

static void put_tracing_file(char *file)
{
	free(file);
}

static void show_events(void)
{
	char buf[BUFSIZ];
	char *path;
	FILE *fp;
	size_t n;

	path = get_tracing_file("available_events");
	fp = fopen(path, "r");
	if (!fp)
		die("reading %s", path);
	put_tracing_file(path);

	do {
		n = fread(buf, 1, BUFSIZ, fp);
		if (n > 0)
			fwrite(buf, 1, n, stdout);
	} while (n > 0);
	fclose(fp);
}

static void show_plugins(void)
{
	char buf[BUFSIZ];
	char *path;
	FILE *fp;
	size_t n;

	path = get_tracing_file("available_tracers");
	fp = fopen(path, "r");
	if (!fp)
		die("reading %s", path);
	put_tracing_file(path);

	do {
		n = fread(buf, 1, BUFSIZ, fp);
		if (n > 0)
			fwrite(buf, 1, n, stdout);
	} while (n > 0);
	fclose(fp);
}

static void set_plugin(const char *name)
{
	FILE *fp;
	char *path;

	path = get_tracing_file("current_tracer");
	fp = fopen(path, "w");
	if (!fp)
		die("writing to '%s'", path);
	put_tracing_file(path);

	fwrite(name, 1, strlen(name), fp);
	fclose(fp);
}

static void show_options(void)
{
	char buf[BUFSIZ];
	char *path;
	FILE *fp;
	size_t n;

	path = get_tracing_file("trace_options");
	fp = fopen(path, "r");
	if (!fp)
		die("reading %s", path);
	put_tracing_file(path);

	do {
		n = fread(buf, 1, BUFSIZ, fp);
		if (n > 0)
			fwrite(buf, 1, n, stdout);
	} while (n > 0);
	fclose(fp);
}

static void set_option(const char *option)
{
	FILE *fp;
	char *path;

	path = get_tracing_file("trace_options");
	fp = fopen(path, "w");
	if (!fp)
		die("writing to '%s'", path);
	put_tracing_file(path);

	fwrite(option, 1, strlen(option), fp);
	fclose(fp);
}

static void old_update_events(const char *name, char update)
{
	char *path;
	FILE *fp;
	int ret;

	if (strcmp(name, "all") == 0)
		name = "*:*";

	/* need to use old way */
	path = get_tracing_file("set_event");
	fp = fopen(path, "w");
	if (!fp)
		die("opening '%s'", path);
	put_tracing_file(path);

	/* Disable the event with "!" */
	if (update == '0')
		fwrite("!", 1, 1, fp);

	ret = fwrite(name, 1, strlen(name), fp);
	if (ret < 0)
		die("bad event '%s'", name);

	ret = fwrite("\n", 1, 1, fp);
	if (ret < 0)
		die("bad event '%s'", name);

	fclose(fp);

	return;
}

static void write_filter(const char *file, const char *filter)
{
	char buf[BUFSIZ];
	int fd;
	int ret;

	fd = open(file, O_WRONLY);
	if (fd < 0)
		die("opening to '%s'", file);
	ret = write(fd, filter, strlen(filter));
	close(fd);
	if (ret < 0) {
		/* filter failed */
		fd = open(file, O_RDONLY);
		if (fd < 0)
			die("writing to '%s'", file);
		/* the filter has the error */
		while ((ret = read(fd, buf, BUFSIZ)) > 0)
			fprintf(stderr, "%.*s", ret, buf);
		die("Failed filter of %s\n", file);
		close(fd);
	}
}

static int update_glob(const char *name, const char *filter,
		       int filter_only, char update)
{
	glob_t globbuf;
	FILE *fp;
	char *filter_file;
	char *path;
	char *str;
	int len;
	int ret;
	int i;
	int count = 0;

	len = strlen(name) + strlen("events//enable") + 1;
	str = malloc_or_die(len);
	snprintf(str, len, "events/%s/enable", name);
	path = get_tracing_file(str);
	free(str);

	globbuf.gl_offs = 0;
	printf("path = %s\n", path);
	ret = glob(path, GLOB_ONLYDIR, NULL, &globbuf);
	put_tracing_file(path);
	if (ret < 0)
		return 0;

	for (i = 0; i < globbuf.gl_pathc; i++) {
		path = globbuf.gl_pathv[i];

		filter_file = strdup(path);
		if (!filter_file)
			die("Allocating memory");

		/* s/enable/filter/ */
		memcpy(filter_file + strlen(filter_file) - 6,
		       "filter", 6);
		if (filter)
			write_filter(filter_file, filter);
		else if (update == '1')
			write_filter(filter_file, "0");
		free(filter_file);
		count++;

		if (filter_only)
			continue;

		fp = fopen(path, "w");
		if (!fp)
			die("writing to '%s'", path);
		ret = fwrite(&update, 1, 1, fp);
		fclose(fp);
		if (ret < 0)
			die("writing to '%s'", path);
	}
	globfree(&globbuf);
	return count;
}

static void filter_all_systems(const char *filter)
{
	glob_t globbuf;
	char *path;
	int ret;
	int i;

	path = get_tracing_file("events/*/filter");

	globbuf.gl_offs = 0;
	ret = glob(path, 0, NULL, &globbuf);
	put_tracing_file(path);
	if (ret < 0)
		die("No filters found");

	for (i = 0; i < globbuf.gl_pathc; i++) {
		path = globbuf.gl_pathv[i];

		write_filter(path, filter);
	}
	globfree(&globbuf);
}

static void update_event(const char *name, const char *filter,
			 int filter_only, char update)
{
	struct stat st;
	FILE *fp;
	char *path;
	char *str;
	char *ptr;
	int len;
	int ret;
	int ret2;

	/* Check if the kernel has the events/enable file */
	path = get_tracing_file("events/enable");
	ret = stat(path, &st);
	if (ret < 0) {
		if (filter_only)
			return;
		put_tracing_file(path);
		/* old kernel */
		old_update_events(name, update);
		return;
	}

	if (!filter_only)
		fprintf(stderr, "%s %s\n",
			update == '1' ? "enable" : "disable", name);

	/* We allow the user to use "all" to enable all events */

	if (strcmp(name, "all") == 0) {
		if (filter)
			filter_all_systems(filter);
		else if (update == '1')
			filter_all_systems("0");

		if (filter_only) {
			put_tracing_file(path);
			return;
		}

		fp = fopen(path, "w");
		if (!fp)
			die("writing to '%s'", path);
		put_tracing_file(path);
		ret = fwrite(&update, 1, 1, fp);
		fclose(fp);
		if (ret < 0)
			die("writing to '%s'", path);
		return;
	}

	ptr = strchr(name, ':');

	if (ptr) {
		len = ptr - name;
		str = strdup(name);
		if (!str)
			die("could not allocate memory");
		str[len] = 0;
		ptr++;
		if (!strlen(ptr) || strcmp(ptr, "*") == 0) {
			ret = update_glob(str, filter, filter_only, update);
			free(str);
			put_tracing_file(path);
			if (!ret)
				goto fail;
			return;
		}

		str[len] = '/';

		ret = update_glob(str, filter, filter_only, update);
		free(str);
		if (!ret)
			die("No events enabled with %s", name);
		return;
	}

	/* No ':' so enable all matching systems and events */
	ret = update_glob(name, filter, filter_only, update);

	len = strlen(name) + strlen("*/") + 1;
	str = malloc_or_die(len);
	snprintf(str, len, "*/%s", name);
	ret2 = update_glob(str, filter, filter_only, update);
	free(str);

	if (!ret && !ret2)
		goto fail;

	return;
 fail:
	die("No events enabled with %s", name);

}

static void write_tracing_on(int on)
{
	static int fd = -1;
	char *path;
	int ret;

	if (fd < 0) {
		path = get_tracing_file("tracing_on");
		fd = open(path, O_WRONLY);
		if (fd < 0)
			die("opening '%s'", path);
		put_tracing_file(path);
	}

	if (on)
		ret = write(fd, "1", 1);
	else
		ret = write(fd, "0", 1);

	if (ret < 0)
		die("writing 'tracing_on'");
}

static void enable_tracing(void)
{
	write_tracing_on(1);

	if (latency)
		reset_max_latency();
}

static void disable_tracing(void)
{
	write_tracing_on(0);
}

static void disable_all(void)
{
	disable_tracing();

	set_plugin("nop");
	update_event("all", "0", 0, '0');
	update_ftrace_pid("");

	clear_trace();
}

static void update_filter(const char *event_name, const char *field,
			  const char *pid)
{
	char buf[BUFSIZ];
	char *filter_name;
	char *path;
	char *filter;
	int fd;
	int ret;

	filter_name = malloc_or_die(strlen(event_name) +
				    strlen("events//filter") + 1);
	sprintf(filter_name, "events/%s/filter", event_name);

	path = get_tracing_file(filter_name);
	free(filter_name);

	/* Ignore if file does not exist */
	fd = open(path, O_RDONLY);
	if (fd < 0)
		goto out;

	ret = read(fd, buf, BUFSIZ);
	if (ret < 0)
		die("Can't read %s", path);
	close(fd);

	/* append unless there is currently no filter */
	if (strncmp(buf, "none", 4) == 0) {
		filter = malloc_or_die(strlen(pid) + strlen(field) +
				       strlen("(==)") + 1);
		sprintf(filter, "(%s==%s)", field, pid);
	} else {
		filter = malloc_or_die(strlen(pid) + strlen(field) +
				       strlen(buf) + strlen("()||(==)") + 1);
		sprintf(filter, "(%s)||(%s==%s)", buf, field, pid);
	}

	fd = open(path, O_WRONLY);
	if (fd < 0)
		die("can't open %s", path);

	ret = write(fd, filter, strlen(filter));
	if (ret < 0)
		warning("Can't write to %s", path);
	close(fd);

	free(filter);

 out:
	put_tracing_file(path);
}

static void update_pid_event_filters(const char *pid)
{
	struct event_list *event;
	char *filter;

	filter = malloc_or_die(strlen(pid) + strlen("(common_pid==)") + 1);
	sprintf(filter, "(common_pid==%s)", pid);

	for (event = event_selection; event; event = event->next) {
		if (!event->neg) {
			if (event->filter) {
				event->filter =
					realloc(event->filter,
						strlen(event->filter) +
						strlen("&&") +
						strlen(filter) + 1);
					strcat(event->filter, "&&");
					strcat(event->filter, filter);
			} else
				event->filter = strdup(filter);
			update_event(event->event, event->filter, 1, '1');
		}
	}

	free(filter);

	/*
	 * Also make sure that the sched_switch to this pid
	 * and wakeups of this pid are also traced.
	 */
	update_filter("sched/sched_switch", "next_pid", pid);
	update_filter("sched/sched_wakeup", "pid", pid);
}

static void enable_events(void)
{
	struct event_list *event;

	for (event = event_selection; event; event = event->next) {
		if (!event->neg)
			update_event(event->event, event->filter, 0, '1');
	}

	/* Now disable any events */
	for (event = event_selection; event; event = event->next) {
		if (event->neg)
			update_event(event->event, NULL, 0, '0');
	}
}

static int count_cpus(void)
{
	FILE *fp;
	char buf[1024];
	int cpus = 0;
	char *pbuf;
	size_t *pn;
	size_t n;
	int r;

	n = 1024;
	pn = &n;
	pbuf = buf;

	fp = fopen("/proc/cpuinfo", "r");
	if (!fp)
		die("Can not read cpuinfo");

	while ((r = getline(&pbuf, pn, fp)) >= 0) {
		char *p;

		if (strncmp(buf, "processor", 9) != 0)
			continue;
		for (p = buf+9; isspace(*p); p++)
			;
		if (*p == ':')
			cpus++;
	}
	fclose(fp);

	return cpus;
}

static int finished;

static void finish(int sig)
{
	/* all done */
	if (recorder)
		tracecmd_stop_recording(recorder);
	finished = 1;
}

static void flush(int sig)
{
	if (recorder)
		tracecmd_stop_recording(recorder);
}

static void connect_port(int cpu)
{
	struct addrinfo hints;
	struct addrinfo *results, *rp;
	int s;
	char buf[BUFSIZ];

	snprintf(buf, BUFSIZ, "%d", client_ports[cpu]);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = use_tcp ? SOCK_STREAM : SOCK_DGRAM;

	s = getaddrinfo(host, buf, &hints, &results);
	if (s != 0)
		die("connecting to %s server %s:%s",
		    use_tcp ? "TCP" : "UDP", host, buf);

	for (rp = results; rp != NULL; rp = rp->ai_next) {
		sfd = socket(rp->ai_family, rp->ai_socktype,
			     rp->ai_protocol);
		if (sfd == -1)
			continue;
		if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
			break;
		close(sfd);
	}

	if (rp == NULL)
		die("Can not connect to %s server %s:%s",
		    use_tcp ? "TCP" : "UDP", host, buf);

	freeaddrinfo(results);

	client_ports[cpu] = sfd;
}

static void set_prio(int prio)
{
	struct sched_param sp;

	memset(&sp, 0, sizeof(sp));
	sp.sched_priority = prio;
	if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0)
		warning("failed to set priority");
}

static int create_recorder(int cpu)
{
	char *file;
	int pid;

	pid = fork();
	if (pid < 0)
		die("fork");

	if (pid)
		return pid;

	signal(SIGINT, finish);
	signal(SIGUSR1, flush);

	if (rt_prio)
		set_prio(rt_prio);

	/* do not kill tasks on error */
	cpu_count = 0;

	if (client_ports) {
		connect_port(cpu);
		recorder = tracecmd_create_recorder_fd(client_ports[cpu], cpu);
	} else {
		file = get_temp_file(cpu);
		recorder = tracecmd_create_recorder(file, cpu);
		put_temp_file(file);
	}

	if (!recorder)
		die ("can't create recorder");
	while (!finished) {
		if (tracecmd_start_recording(recorder, sleep_time) < 0)
			break;
	}
	tracecmd_free_recorder(recorder);

	exit(0);
}

static void setup_network(void)
{
	struct tracecmd_output *handle;
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int sfd, s;
	ssize_t n;
	char buf[BUFSIZ];
	char *server;
	char *port;
	char *p;
	int cpu;
	int i;

	if (!strchr(host, ':')) {
		server = strdup("localhost");
		if (!server)
			die("alloctating server");
		port = host;
		host = server;
	} else {
		host = strdup(host);
		if (!host)
			die("alloctating server");
		server = strtok_r(host, ":", &p);
		port = strtok_r(NULL, ":", &p);
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	s = getaddrinfo(server, port, &hints, &result);
	if (s != 0)
		die("getaddrinfo: %s", gai_strerror(s));

	for (rp = result; rp != NULL; rp = rp->ai_next) {
		sfd = socket(rp->ai_family, rp->ai_socktype,
			     rp->ai_protocol);
		if (sfd == -1)
			continue;

		if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
			break;
		close(sfd);
	}

	if (!rp)
		die("Can not connect to %s:%s", server, port);

	freeaddrinfo(result);

	n = read(sfd, buf, 8);

	/* Make sure the server is the tracecmd server */
	if (memcmp(buf, "tracecmd", 8) != 0)
		die("server not tracecmd server");

	/* write the number of CPUs we have (in ASCII) */

	sprintf(buf, "%d", cpu_count);

	/* include \0 */
	write(sfd, buf, strlen(buf)+1);

	/* write the pagesize (in ASCII) */

	page_size = getpagesize();
	sprintf(buf, "%d", page_size);

	/* include \0 */
	write(sfd, buf, strlen(buf)+1);

	/*
	 * If we are using IPV4 and our page size is greater than
	 * or equal to 64K, we need to punt and use TCP. :-(
	 */

	/* TODO, test for ipv4 */
	if (page_size >= UDP_MAX_PACKET) {
		warning("page size too big for UDP using TCP in live read");
		use_tcp = 1;
	}

	if (use_tcp) {
		/* Send one option */
		write(sfd, "1", 2);
		/* Size 4 */
		write(sfd, "4", 2);
		/* use TCP */
		write(sfd, "TCP", 4);
	} else
		/* No options */
		write(sfd, "0", 2);

	client_ports = malloc_or_die(sizeof(int) * cpu_count);

	/*
	 * Now we will receive back a comma deliminated list
	 * of client ports to connect to.
	 */
	for (cpu = 0; cpu < cpu_count; cpu++) {
		for (i = 0; i < BUFSIZ; i++) {
			n = read(sfd, buf+i, 1);
			if (n != 1)
				die("Error, reading server ports");
			if (!buf[i] || buf[i] == ',')
				break;
		}
		if (i == BUFSIZ)
			die("read bad port number");
		buf[i] = 0;
		client_ports[cpu] = atoi(buf);
	}

	/* Now create the handle through this socket */
	handle = tracecmd_create_init_fd(sfd, cpu_count);

	/* OK, we are all set, let'r rip! */
}

static void finish_network(void)
{
	close(sfd);
	free(host);
}

static void start_threads(void)
{
	int i;

	cpu_count = count_cpus();

	if (host)
		setup_network();

	/* make a thread for every CPU we have */
	pids = malloc_or_die(sizeof(*pids) * cpu_count);

	memset(pids, 0, sizeof(*pids) * cpu_count);

	for (i = 0; i < cpu_count; i++) {
		pids[i] = create_recorder(i);
	}
}

static void record_data(void)
{
	struct tracecmd_output *handle;
	char **temp_files;
	int i;

	if (host) {
		finish_network();
		return;
	}

	if (latency)
		handle = tracecmd_create_file_latency(output_file, cpu_count);
	else {
		if (!cpu_count)
			return;

		temp_files = malloc_or_die(sizeof(*temp_files) * cpu_count);

		for (i = 0; i < cpu_count; i++)
			temp_files[i] = get_temp_file(i);

		handle = tracecmd_create_file(output_file, cpu_count, temp_files);

		for (i = 0; i < cpu_count; i++)
			put_temp_file(temp_files[i]);
		free(temp_files);
	}
	if (!handle)
		die("could not write to file");
	tracecmd_output_close(handle);
}

static int trace_empty(void)
{
	char *path;
	FILE *fp;
	char *line = NULL;
	size_t size;
	ssize_t n;
	int ret = 1;
	
	/*
	 * Test if the trace file is empty.
	 *
	 * Yes, this is a heck of a hack. What is done here
	 * is to read the trace file and ignore the
	 * lines starting with '#', and if we get a line
	 * that is without a '#' the trace is not empty.
	 * Otherwise it is.
	 */
	path = get_tracing_file("trace");
	fp = fopen(path, "r");
	if (!fp)
		die("reading '%s'", path);

	do {
		n = getline(&line, &size, fp);
		if (n > 0 && line && line[0] != '#') {
			ret = 0;
			break;
		}
	} while (line && n > 0);

	put_tracing_file(path);

	fclose(fp);

	return ret;
}

static void write_func_file(const char *file, struct func_list **list)
{
	struct func_list *item;
	char *path;
	int fd;

	path = get_tracing_file(file);

	fd = open(path, O_WRONLY | O_TRUNC);
	if (fd < 0)
		goto free;

	while (*list) {
		item = *list;
		*list = item->next;
		write(fd, item->func, strlen(item->func));
		write(fd, " ", 1);
		free(item);
	}
	close(fd);

 free:
	put_tracing_file(path);
}

static void set_funcs(void)
{
	write_func_file("set_ftrace_filter", &filter_funcs);
	write_func_file("set_ftrace_notrace", &notrace_funcs);
	write_func_file("set_graph_function", &graph_funcs);
}

static void add_func(struct func_list **list, const char *func)
{
	struct func_list *item;

	item = malloc_or_die(sizeof(*item));
	item->func = func;
	item->next = *list;
	*list = item;
}

void set_buffer_size(void)
{
	char buf[BUFSIZ];
	char *path;
	int ret;
	int fd;

	if (!buffer_size)
		return;

	if (buffer_size < 0)
		die("buffer size must be positive");

	snprintf(buf, BUFSIZ, "%d", buffer_size);

	path = get_tracing_file("buffer_size_kb");
	fd = open(path, O_WRONLY);
	if (fd < 0)
		die("can't open %s", path);

	ret = write(fd, buf, strlen(buf));
	if (ret < 0)
		warning("Can't write to %s", path);
	close(fd);
}

int main (int argc, char **argv)
{
	const char *plugin = NULL;
	const char *output = NULL;
	const char *option;
	struct event_list *event;
	struct event_list *last_event;
	struct trace_seq s;
	int disable = 0;
	int plug = 0;
	int events = 0;
	int options = 0;
	int record = 0;
	int extract = 0;
	int run_command = 0;
	int neg_event = 0;
	int fset;
	int cpu;

	int c;

	errno = 0;

	if (argc < 2)
		usage(argv);

	if (strcmp(argv[1], "report") == 0) {
		trace_report(argc, argv);
		exit(0);
	} else if (strcmp(argv[1], "listen") == 0) {
		trace_listen(argc, argv);
		exit(0);
	} else if (strcmp(argv[1], "split") == 0) {
		trace_split(argc, argv);
		exit(0);
	} else if ((record = (strcmp(argv[1], "record") == 0)) ||
		   (strcmp(argv[1], "start") == 0) ||
		   ((extract = strcmp(argv[1], "extract") == 0))) {

		while ((c = getopt(argc-1, argv+1, "+he:f:Fp:do:O:s:r:vg:l:n:P:N:tb:")) >= 0) {
			switch (c) {
			case 'h':
				usage(argv);
				break;
			case 'e':
				if (extract)
					usage(argv);
				events = 1;
				event = malloc_or_die(sizeof(*event));
				event->event = optarg;
				event->next = event_selection;
				event->neg = neg_event;
				event_selection = event;
				event->filter = NULL;
				last_event = event;
				break;
			case 'f':
				if (!last_event)
					die("filter must come after event");
				if (last_event->filter) {
					last_event->filter =
						realloc(last_event->filter,
							strlen(last_event->filter) +
							strlen("&&()") +
							strlen(optarg) + 1);
					strcat(last_event->filter, "&&(");
					strcat(last_event->filter, optarg);
					strcat(last_event->filter, ")");
				} else {
					last_event->filter =
						malloc_or_die(strlen(optarg) +
							      strlen("()") + 1);
					sprintf(last_event->filter, "(%s)", optarg);
				}
				break;

			case 'F':
				if (filter_pid >= 0)
					die("-P and -F can not both be specified");
				filter_task = 1;
				break;
			case 'P':
				if (filter_task)
					die("-P and -F can not both be specified");
				if (filter_pid >= 0)
					die("only one -P pid can be filtered at a time");
				filter_pid = atoi(optarg);
				break;
			case 'v':
				if (extract)
					usage(argv);
				neg_event = 1;
				break;
			case 'l':
				add_func(&filter_funcs, optarg);
				break;
			case 'n':
				add_func(&notrace_funcs, optarg);
				break;
			case 'g':
				add_func(&graph_funcs, optarg);
				break;
			case 'p':
				if (plugin)
					die("only one plugin allowed");
				plugin = optarg;
				fprintf(stderr, "  plugin %s\n", plugin);
				break;
			case 'd':
				if (extract)
					usage(argv);
				disable = 1;
				break;
			case 'o':
				if (host)
					die("-o incompatible with -N");
				if (!record && !extract)
					die("start does not take output\n"
					    "Did you mean 'record'?");
				if (output)
					die("only one output file allowed");
				output = optarg;
				break;
			case 'O':
				option = optarg;
				set_option(option);
				break;
			case 's':
				if (extract)
					usage(argv);
				sleep_time = atoi(optarg);
				break;
			case 'r':
				rt_prio = atoi(optarg);
				break;
			case 'N':
				if (!record)
					die("-N only available with record");
				if (output)
					die("-N incompatible with -o");
				host = optarg;
				break;
			case 't':
				use_tcp = 1;
				break;
			case 'b':
				buffer_size = atoi(optarg);
				break;
			}
		}

	} else if (strcmp(argv[1], "stop") == 0) {
		disable_tracing();
		exit(0);

	} else if (strcmp(argv[1], "reset") == 0) {
		while ((c = getopt(argc-1, argv+1, "b:")) >= 0) {
			switch (c) {
			case 'b':
				buffer_size = atoi(optarg);
				/* Min buffer size is 1 */
				if (strcmp(optarg, "0") == 0)
					buffer_size = 1;
				break;
			}
		}
		disable_all();
		set_buffer_size();
		exit(0);

	} else if (strcmp(argv[1], "list") == 0) {

		while ((c = getopt(argc-1, argv+1, "+hepo")) >= 0) {
			switch (c) {
			case 'h':
				usage(argv);
				break;
			case 'e':
				events = 1;
				break;
			case 'p':
				plug = 1;
				break;
			case 'o':
				options = 1;
				break;
			default:
				usage(argv);
			}
		}

		if (events)
			show_events();

		if (plug)
			show_plugins();

		if (options)
			show_options();

		if (!events && !plug && !options) {
			printf("events:\n");
			show_events();
			printf("\nplugins:\n");
			show_plugins();
			printf("\noptions:\n");
			show_options();
		}

		exit(0);

	} else if (strcmp(argv[1], "-h") == 0 ||
		   strcmp(argv[1], "help") == 0) {
		usage(argv);
	} else {
		fprintf(stderr, "unknown command: %s\n", argv[1]);
		usage(argv);
	}

	if ((argc - optind) >= 2) {
		if (!record)
			die("Command start does not take any commands\n"
			    "Did you mean 'record'?");
		if (extract)
			die("Command extract does not take any commands\n"
			    "Did you mean 'record'?");
		run_command = 1;
	}

	if (!events && !plugin && !extract)
		die("no event or plugin was specified... aborting");

	if (output)
		output_file = output;

	if (!extract) {
		fset = set_ftrace(!disable);
		disable_all();
		set_funcs();

		if (events)
			enable_events();
		set_buffer_size();
	}

	if (plugin) {
		/*
		 * Latency tracers just save the trace and kill
		 * the threads.
		 */
		if (strcmp(plugin, "irqsoff") == 0 ||
		    strcmp(plugin, "preemptoff") == 0 ||
		    strcmp(plugin, "preemptirqsoff") == 0 ||
		    strcmp(plugin, "wakeup") == 0 ||
		    strcmp(plugin, "wakeup_rt") == 0) {
			latency = 1;
		}
		if (fset < 0 && (strcmp(plugin, "function") == 0 ||
				 strcmp(plugin, "function_graph") == 0))
			die("function tracing not configured on this kernel");
		if (!extract)
			set_plugin(plugin);
	}

	if (record || extract) {
		if (!latency)
			start_threads();
		signal(SIGINT, finish);
	}

	if (extract) {
		while (!finished && !trace_empty()) {
			flush_threads();
			sleep(1);
		}
	} else {
		if (!record) {
			update_task_filter();
			exit(0);
		}

		if (run_command)
			run_cmd((argc - optind) - 1, &argv[optind + 1]);
		else {
			update_task_filter();
			/* sleep till we are woken with Ctrl^C */
			printf("Hit Ctrl^C to stop recording\n");
			while (!finished)
				sleep(10);
		}

		disable_tracing();
	}

	stop_threads();

	record_data();
	delete_thread_data();

	printf("Kernel 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");
	for (cpu = 0; cpu < cpu_count; cpu++) {
		trace_seq_init(&s);
		trace_seq_printf(&s, "CPU: %d\n", cpu);
		tracecmd_stat_cpu(&s, cpu);
		trace_seq_do_printf(&s);
		printf("\n");
	}

	exit(0);

	return 0;
}