/*
 * Copyright 2006, Steven Rostedt
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>

#define THREADS 10
#define SLEEP_TIME 30

int run = 1;
int print;
int printing;
int posix;
int max_prio;
int thread_prio = 40;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

timer_t timerid;

void sig(int sig)
{
	if (print && !printing)
		printf("ping!\n");
}

void do_nanosleep(unsigned long secs, unsigned long nsecs, int print)
{
	struct timespec s, r;
	int ret;

	memset(&r, 0, sizeof(r));
	r.tv_sec = secs;
	r.tv_nsec = nsecs;
	do {
		s = r;
		memset(&r, 0, sizeof(r));
		ret = nanosleep(&s, &r);
		if (print) {
			printing = 1;
			printf("rem: %ld secs %ld nsecs\n",
			       r.tv_sec, r.tv_nsec);
			printing = 0;
		}
	} while(ret);
}

int set_timer(unsigned long secs, unsigned long nsecs)
{
	int ret;

	if (posix) {
		struct itimerspec val;

		memset(&val, 0, sizeof(val));
		val.it_value.tv_nsec = nsecs;
		val.it_interval.tv_sec = 1;
		ret = timer_settime(timerid, 0, &val, NULL);
	} else {
		struct itimerval val;

		memset(&val, 0, sizeof(val));
		val.it_value.tv_usec = nsecs / 1000;
		val.it_interval.tv_sec = 1;
		ret = setitimer(ITIMER_REAL, &val, NULL);
	}
	return ret;
}

void *highfunc(void *dat)
{
	struct sched_param sp;

	memset(&sp,0,sizeof(sp));
	sp.sched_priority = max_prio;
	sched_setscheduler(0,SCHED_FIFO,&sp);

	printf("hello from super thread!\n");

	while (run) {
		pthread_mutex_lock(&mutex);
		pthread_mutex_unlock(&mutex);
		do_nanosleep(0, 800000, 0);
	}

	return NULL;
}

struct func_info {
	int id;
	int threads;
};

void *func(void *dat)
{
	struct func_info *info = dat;
	int id = info->id;
	int threads = info->threads;
	struct sched_param sp;
	int rt = 0;
	unsigned long time = 50000;

	/*
	 * Every 10 threads is realtime.
	 */
	if (!(id % 10)) {
		int prio = thread_prio - id;
		if (prio <= 0)
			prio = 1;
		memset(&sp,0,sizeof(sp));
		sp.sched_priority = prio;
		sched_setscheduler(0,SCHED_FIFO,&sp);
		/*
		 * high priority processes don't lock
		 * they want to preempt those that do.
		 */
		rt = 1;
		/*
		 * We want the timer to go off immediately
		 */
		time = 1000;
	}

	printf("hello from thread %d!\n",id);
	while (run) {
		if (!rt)
			pthread_mutex_lock(&mutex);
		if (set_timer(0, time) < 0)
			perror("timer_settime");
		if (!rt)
			pthread_mutex_unlock(&mutex);
		
		/* sleep 10th ms per thread */
		do_nanosleep(0, 100000 * threads, 0);
	}
	return NULL;
}

void run_test(int secs, int threads)
{
	pthread_t t[threads];
	pthread_t st;
	struct func_info info[threads];
	int i;
	for (i=0; i < threads; i++) {
		info[i].id = i;
		info[i].threads = threads;
		if (pthread_create(&t[i],NULL,func,&info[i])) {
			perror("pthread_creat");
			exit(-1);
		}
	}

	/*
	 * Create the super thread.
	 */
	 if (pthread_create(&st,NULL,highfunc,NULL)) {
		 perror("pthread_creat");
		 exit(-1);
	 }

	 if (secs > 0)
		 do_nanosleep(secs, 0, print);
	 else
		 for (;;)
			 sleep(100);

	 run = 0;
	 for (i=0; i < threads; i++) {
		 pthread_join(t[i],NULL);
	 }
	 pthread_join(st, NULL);
 }

void usage(char **argv)
{
	char *p;
	for (p = argv[0]+strlen(argv[0]); p >= argv[0] && *p != '/'; p--)
		;
	printf("\nusage: %s [-pPh] [-s seconds] [-t threads]\n",p);
	printf("   -p  to print output of signals\n"
	       "   -P  to use posix timer_settime instead of setitimer\n"
	       "   -s  second to run test\n"
	       "   -t threads to run\n"
	       "\n");
	exit(-1);
}

int main (int argc, char **argv)
{
	int c;
	int secs = SLEEP_TIME;
	int threads = THREADS;
	
	while ((c=getopt(argc, argv, "hPpt:s:")) >= 0) {
		switch (c) {
			case 'p':
				print = 1;
				break;
			case 'P':
				posix = 1;
				break;
			case 's':
				secs = atoi(optarg);
				break;
			case 't':
				threads = atoi(optarg);
				break;
			case 'h':
			default:
				usage(argv);
		}
	}
	signal(SIGALRM, sig);
	
	max_prio = sched_get_priority_max(SCHED_FIFO);
	
	if (timer_create(CLOCK_REALTIME, NULL, &timerid) < 0)
		perror("timer_create");
	 
	printf("main program pid=%d\n", getpid());
	
	run_test(secs, threads);
	
	exit(0);
	
	return 0;
}
