/* varcpu : a program to peg CPU usage at around a certain percentage
 *   Ian Wienand <ianw@gelato.unsw.edu.au>
 *   (C) 2005.  Released to public domain
 *
 * This seems to be accurate to within a few percent over time on an
 * otherwise quiet system.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <pthread.h>

#include <glibtop.h>
#include <glibtop/cpu.h>

#define CURRENT 0
#define PREVIOUS 1

static struct cpu_usage_t
{
	double user[2];
	double nice[2];
	double sys[2];
	double free[2];
	double load_avg[10];
	double current_load;
	double sleep;
} cpu_usage;

pthread_mutex_t recalc_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned int current_load_nr;
static int ignore_nice;
static int verbose;

static void inline update_cpu(void)
{
	double user, nice, sys, free;
	double total, c;
	int i,j = 0;

	glibtop_cpu cpu;
	
        glibtop_get_cpu (&cpu);

	cpu_usage.user[PREVIOUS]  = cpu_usage.user[CURRENT];
	cpu_usage.sys[PREVIOUS]   = cpu_usage.sys[CURRENT];
	cpu_usage.free[PREVIOUS]  = cpu_usage.free[CURRENT];
	cpu_usage.nice[PREVIOUS]  = cpu_usage.nice[CURRENT];

	cpu_usage.user[CURRENT] = cpu.user;
	cpu_usage.nice[CURRENT] = cpu.nice;
	cpu_usage.sys[CURRENT]  = cpu.sys;
	cpu_usage.free[CURRENT] = cpu.idle;

	user = (cpu_usage.user[CURRENT] - cpu_usage.user[PREVIOUS]);
	nice = (cpu_usage.nice[CURRENT] - cpu_usage.nice[PREVIOUS]);
	sys  = (cpu_usage.sys[CURRENT] - cpu_usage.sys[PREVIOUS]);
	free = (cpu_usage.free[CURRENT] - cpu_usage.free[PREVIOUS]);

	total = MAX(user + nice + sys + free, 1.0f);

	user  = user  / total;
	nice = nice / total;
	sys  = sys  / total;
	free = free / total;
	
	if (ignore_nice)
		c = user + sys;
	else
		c = user + sys + nice;

	cpu_usage.load_avg[current_load_nr % 10] = CLAMP((100.0f * c), 0.0f, 100.0f);
	current_load_nr++;

	total = 0;
	for(i = 0; i < 10 ; i++)
		if (cpu_usage.load_avg[i] != 0) {
			total += cpu_usage.load_avg[i];
			j++;
		}

	cpu_usage.current_load = total / j;
}

void* hog_thread(void *arg)
{
	/* volatile helps avoid gcc doing anything silly like
	 * optimising away stuff */
	volatile unsigned long i,j;
	
	while (1) {
		for (i = 0 ; i < 10000000UL; i++)
			j = i + 2;

		/* When we are asleep don't recalculate the sleep time */
		pthread_mutex_lock(&recalc_mutex);
		usleep((int)cpu_usage.sleep);
		pthread_mutex_unlock(&recalc_mutex);
		sched_yield();
	}
}

int main(int argc, char *argv[])
{
	
	double target_load, missing_by;
	pthread_t h;

	extern int optind, optopt;
	extern char *optarg;
	int c;
	
	while ((c = getopt(argc, argv, "vnh")) != -1) {
		switch (c) {
		case 'v':
			verbose = 1;
			break;
			
		case 'n':
			ignore_nice = 1;
			break;
			
		case 'h':
			printf("Usage: varcpu [-v] [-n] [-h] target_percent\n"
			       " -v : be more verbose\n"
			       " -n : ignore \"nice\" when calculating\n"
			       " -h : this menu\n"
			       " target_percent : the required CPU utilisation\n"
				);
			exit(0);
		default:
			fprintf(stderr, "Usage: varcpu [-v] [-n]\n");
			exit(1);
		}
	}
	
	if (argc == optind)
	{
		fprintf(stderr, "Please specify a target usage\n");
		exit(1);
	}
		
	target_load = atof(argv[optind]);
	if (target_load < 0 || target_load > 100) {
		fprintf(stderr, "target usage should be > 0 && < 100\n");
		exit(1);
	} else if (verbose)
		printf("Going for a load of %.0f%%\n", target_load); 
	
	/* inital sleep of a half second */
	cpu_usage.sleep = 500000;

	/* This thread works while we sleep */
	pthread_create(&h, NULL, hog_thread, NULL);

	sleep(1);

	while (1) {
		pthread_mutex_lock(&recalc_mutex);

		update_cpu();
		missing_by = (cpu_usage.current_load - target_load) / target_load;
		
		/* If we are doing too much, then sleep less so more
		 * work gets done by the hog thread.  Otherwise, sleep
		 * more so less work gets done.  Only move by half of
		 * the current missing by to smooth out changes.
		 */
		cpu_usage.sleep += (MAX(cpu_usage.sleep, 100) * (0.5f * missing_by));

		if (verbose) {
			printf("Missing by %6.1f%% sleep %10.0fus\r", missing_by*100, cpu_usage.sleep);
			fflush(stdout);
		}

		pthread_mutex_unlock(&recalc_mutex);

		/* Check and update every half a second */
		usleep(500000);
		sched_yield();
	}
}
