/*
 * Copyright 2007, Intel Corporation
 *
 * This file is part of PowerTOP
 *
 * This program file 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.
 *
 * 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 in a file named COPYING; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 *
 * Authors:
 * 	Arjan van de Ven <arjan@linux.intel.com>
 */

#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <dirent.h>
#include <libintl.h>
#include <ctype.h>
#include <assert.h>
#include <locale.h>
#include <time.h>
#include <sys/stat.h>

#include "powertop.h"

#define VERSION "1.11"

uint64_t start_usage[8], start_duration[8];
uint64_t last_usage[8], last_duration[8];
char cnames[8][16];

double ticktime = 15.0;

int interrupt_0, total_interrupt;

int showpids = 0;

static int maxcstate = 0;
int topcstate = 0;

int dump = 0;

#define IRQCOUNT 150

struct irqdata {
	int active;
	int number;
	uint64_t count;
	char description[256];
};

struct irqdata interrupts[IRQCOUNT];

#define FREQ_ACPI 3579.545
static unsigned long FREQ;

int nostats;


struct line	*lines;
int		linehead;
int		linesize;
int		linectotal;


double last_bat_cap = 0;
double prev_bat_cap = 0;
time_t last_bat_time = 0;
time_t prev_bat_time = 0;

double displaytime = 0.0;

void push_line(char *string, int count)
{
	int i;

	assert(string != NULL);
	for (i = 0; i < linehead; i++)
		if (strcmp(string, lines[i].string) == 0) {
			lines[i].count += count;
			return;
		}
	if (linehead == linesize)
		lines = realloc (lines, (linesize ? (linesize *= 2) : (linesize = 64)) * sizeof (struct line));
	lines[linehead].string = strdup (string);
	lines[linehead].count = count;
	lines[linehead].pid[0] = 0;
	linehead++;
}

void push_line_pid(char *string, int count, char *pid) 
{
	int i;
	assert(string != NULL);
	for (i = 0; i < linehead; i++)
		if (strcmp(string, lines[i].string) == 0) {
			lines[i].count += count;
			if (pid && strcmp(lines[i].pid, pid)!=0)
				lines[i].pid[0] = 0;
			return;
		}
	if (linehead == linesize)
		lines = realloc (lines, (linesize ? (linesize *= 2) : (linesize = 64)) * sizeof (struct line));
	lines[linehead].string = strdup (string);
	lines[linehead].count = count;
	if (pid)
		strcpy(lines[linehead].pid, pid);
	linehead++;
}

void clear_lines(void)
{
	int i;
	for (i = 0; i < linehead; i++)
		free (lines[i].string);
	free (lines);
	linehead = linesize = 0;
	lines = NULL;
}

void count_lines(void)
{
	uint64_t q = 0;
	int i;
	for (i = 0; i < linehead; i++)
		q += lines[i].count;
	linectotal = q;
}

int update_irq(int irq, uint64_t count, char *name)
{
	int i;
	int firstfree = IRQCOUNT;

	if (!name)
		return 0;

	for (i = 0; i < IRQCOUNT; i++) {
		if (interrupts[i].active && interrupts[i].number == irq) {
			uint64_t oldcount;
			oldcount = interrupts[i].count;
			interrupts[i].count = count;
			return count - oldcount;
		}
		if (!interrupts[i].active && firstfree > i)
			firstfree = i;
	}

	interrupts[firstfree].active = 1;
	interrupts[firstfree].count = count;
	interrupts[firstfree].number = irq;
	strcpy(interrupts[firstfree].description, name);
	if (strcmp(name,"i8042\n")==0)
		strcpy(interrupts[firstfree].description, _("PS/2 keyboard/mouse/touchpad"));
	return count;
}

static void do_proc_irq(void)
{
	FILE *file;
	char line[1024];
	char line2[1024];
	char *name;
	uint64_t delta;
	
	interrupt_0 = 0;
	total_interrupt  = 0;

	file = fopen("/proc/interrupts", "r");
	if (!file)
		return;
	while (!feof(file)) {
		char *c;
		int nr = -1;
		uint64_t count = 0;
		int special = 0;
		memset(line, 0, sizeof(line));
		if (fgets(line, 1024, file) == NULL)
			break;
		c = strchr(line, ':');
		if (!c)
			continue;
		/* deal with NMI and the like.. make up fake nrs */
		if (line[0] != ' ' && (line[0] < '0' || line[0] > '9')) {	
			if (strncmp(line,"NMI:", 4)==0)
				nr=20000;
			if (strncmp(line,"RES:", 4)==0)
				nr=20001;
			if (strncmp(line,"CAL:", 4)==0)
				nr=20002;
			if (strncmp(line,"TLB:", 4)==0)
				nr=20003;
			if (strncmp(line,"TRM:", 4)==0)
				nr=20004;
			if (strncmp(line,"THR:", 4)==0)
				nr=20005;
			if (strncmp(line,"SPU:", 4)==0)
				nr=20006;
			special = 1;
		} else
			nr = strtoull(line, NULL, 10);

		if (nr==-1)
			continue;
		*c = 0;
		c++;
		while (c && strlen(c)) {
			char *newc;
			count += strtoull(c, &newc, 10);
			if (newc == c)
				break;
			c = newc;
		}
		c = strchr(c, ' ');
		if (!c) 
			continue;
		while (c && *c == ' ')
			c++;
		if (!special) {
			c = strchr(c, ' ');
			if (!c) 
				continue;
			while (c && *c == ' ')
				c++;
		}
		name = c;
		delta = update_irq(nr, count, name);
		c = strchr(name, '\n');
		if (c)
			*c = 0;
		if (strcmp(name, "i8042")) { 
			if (special) 
				sprintf(line2, _("   <kernel IPI> : %s"), name);
			else
				sprintf(line2, _("    <interrupt> : %s"), name);
		}
		else
			sprintf(line2, _("    <interrupt> : %s"), _("PS/2 keyboard/mouse/touchpad"));

		if (nr > 0 && delta > 0)
			push_line(line2, delta);
		if (nr==0)
			interrupt_0 = delta;
		else
			total_interrupt += delta;
	}
	fclose(file);
}

static void read_data_acpi(uint64_t * usage, uint64_t * duration)
{
	DIR *dir;
	struct dirent *entry;
	FILE *file = NULL;
	char line[4096];
	char *c;
	int clevel = 0;

	memset(usage, 0, 64);
	memset(duration, 0, 64);

	dir = opendir("/proc/acpi/processor");
	if (!dir)
		return;
	while ((entry = readdir(dir))) {
		if (strlen(entry->d_name) < 3)
			continue;
		sprintf(line, "/proc/acpi/processor/%s/power", entry->d_name);
		file = fopen(line, "r");
		if (!file)
			continue;

		clevel = 0;

		while (!feof(file)) {
			memset(line, 0, 4096);
			if (fgets(line, 4096, file) == NULL)
				break;
			c = strstr(line, "age[");
			if (!c)
				continue;
			c += 4;
			usage[clevel] += 1+strtoull(c, NULL, 10);
			c = strstr(line, "ation[");
			if (!c)
				continue;
			c += 6;
			duration[clevel] += strtoull(c, NULL, 10);

			clevel++;
			if (clevel > maxcstate)
				maxcstate = clevel;

		}
		fclose(file);
	}
	closedir(dir);
}

static void read_data_cpuidle(uint64_t * usage, uint64_t * duration)
{
	DIR *cpudir;
	DIR *dir;
	struct dirent *entry;
	FILE *file = NULL;
	char line[4096];
	char filename[128], *f;
	int len, clevel = 0;

	memset(usage, 0, 64);
	memset(duration, 0, 64);

	cpudir = opendir("/sys/devices/system/cpu");
	if (!cpudir)
		return;

	/* Loop over cpuN entries */
	while ((entry = readdir(cpudir))) {
		if (strlen(entry->d_name) < 3)
			continue;

		if (!isdigit(entry->d_name[3]))
			continue;

		len = sprintf(filename, "/sys/devices/system/cpu/%s/cpuidle",
			      entry->d_name);

		dir = opendir(filename);
		if (!dir)
			return;

		clevel = 0;

		/* For each C-state, there is a stateX directory which
		 * contains a 'usage' and a 'time' (duration) file */
		while ((entry = readdir(dir))) {
			if (strlen(entry->d_name) < 3)
				continue;
			sprintf(filename + len, "/%s/desc", entry->d_name);
			file = fopen(filename, "r");
			if (file) {

				memset(line, 0, 4096);
				f = fgets(line, 4096, file);
				fclose(file);
				if (f == NULL)
					break;

			
				f = strstr(line, "MWAIT ");
				if (f) {
					f += 6;
					clevel = (strtoull(f, NULL, 16)>>4) + 1;
					sprintf(cnames[clevel], "C%i mwait", clevel);
				} else
					sprintf(cnames[clevel], "C%i\t", clevel);

				f = strstr(line, "POLL IDLE");
				if (f) {
					clevel = 0;
					sprintf(cnames[clevel], "%s\t", _("polling"));
				}

				f = strstr(line, "ACPI HLT");
				if (f) {
					clevel = 1;
					sprintf(cnames[clevel], "%s\t", "C1 halt");
				}
			}
			sprintf(filename + len, "/%s/usage", entry->d_name);
			file = fopen(filename, "r");
			if (!file)
				continue;

			memset(line, 0, 4096);
			f = fgets(line, 4096, file);
			fclose(file);
			if (f == NULL)
				break;

			usage[clevel] += 1+strtoull(line, NULL, 10);

			sprintf(filename + len, "/%s/time", entry->d_name);
			file = fopen(filename, "r");
			if (!file)
				continue;
		
			memset(line, 0, 4096);
			f = fgets(line, 4096, file);
			fclose(file);
			if (f == NULL)
				break;

			duration[clevel] += 1+strtoull(line, NULL, 10);

			clevel++;
			if (clevel > maxcstate)
				maxcstate = clevel;
		
		}
		closedir(dir);

	}
	closedir(cpudir);
}

static void read_data(uint64_t * usage, uint64_t * duration)
{
	int r;
	struct stat s;

	/* Then check for CPUidle */
	r = stat("/sys/devices/system/cpu/cpu0/cpuidle", &s);
	if (!r) {
		read_data_cpuidle(usage, duration);
		
		/* perform residency calculations based on usecs */
		FREQ = 1000;
		return;
	}

	/* First, check for ACPI */
	r = stat("/proc/acpi/processor", &s);
	if (!r) {
		read_data_acpi(usage, duration);

		/* perform residency calculations based on ACPI timer */
		FREQ = FREQ_ACPI;
		return;
	}
}

void stop_timerstats(void)
{
	FILE *file;
	file = fopen("/proc/timer_stats", "w");
	if (!file) {
		nostats = 1;
		return;
	}
	fprintf(file, "0\n");
	fclose(file);
}
void start_timerstats(void)
{
	FILE *file;
	file = fopen("/proc/timer_stats", "w");
	if (!file) {
		nostats = 1;
		return;
	}
	fprintf(file, "1\n");
	fclose(file);
}

int line_compare (const void *av, const void *bv)
{
	const struct line	*a = av, *b = bv;
	return b->count - a->count;
}

void sort_lines(void)
{
	qsort (lines, linehead, sizeof (struct line), line_compare);
}



int print_battery_proc_acpi(void)
{
	DIR *dir;
	struct dirent *dirent;
	FILE *file;
	double rate = 0;
	double cap = 0;

	char filename[256];

	dir = opendir("/proc/acpi/battery");
	if (!dir)
		return 0;

	while ((dirent = readdir(dir))) {
		int dontcount = 0;
		double voltage = 0.0;
		double amperes_drawn = 0.0;
		double watts_drawn = 0.0;
		double amperes_left = 0.0;
		double watts_left = 0.0;
		char line[1024];

		if (strlen(dirent->d_name) < 3)
			continue;

		sprintf(filename, "/proc/acpi/battery/%s/state", dirent->d_name);
		file = fopen(filename, "r");
		if (!file)
			continue;
		memset(line, 0, 1024);
		while (fgets(line, 1024, file) != NULL) {
			char *c;
			if (strstr(line, "present:") && strstr(line, "no"))
				break;

			if (strstr(line, "charging state:")
			    && !strstr(line, "discharging"))
				dontcount = 1;
			c = strchr(line, ':');
			if (!c)
				continue;
			c++;

			if (strstr(line, "present voltage")) 
				voltage = strtoull(c, NULL, 10) / 1000.0;
		
			if (strstr(line, "remaining capacity") && strstr(c, "mW"))
				watts_left = strtoull(c, NULL, 10) / 1000.0;

			if (strstr(line, "remaining capacity") && strstr(c, "mAh"))
				amperes_left = strtoull(c, NULL, 10) / 1000.0; 

			if (strstr(line, "present rate") && strstr(c, "mW"))
				watts_drawn = strtoull(c, NULL, 10) / 1000.0 ;

			if (strstr(line, "present rate") && strstr(c, "mA"))
				amperes_drawn = strtoull(c, NULL, 10) / 1000.0;

		}
		fclose(file);
	
		if (!dontcount) {
			rate += watts_drawn + voltage * amperes_drawn;
		}
		cap += watts_left + voltage * amperes_left;
		

	}
	closedir(dir);
	if (prev_bat_cap - cap < 0.001 && rate < 0.001)
		last_bat_time = 0;
	if (!last_bat_time) {
		last_bat_time = prev_bat_time = time(NULL);
		last_bat_cap = prev_bat_cap = cap;
	}
	if (time(NULL) - last_bat_time >= 400) {
		prev_bat_cap = last_bat_cap;
		prev_bat_time = last_bat_time;
		last_bat_time = time(NULL);
		last_bat_cap = cap;
	}

	show_acpi_power_line(rate, cap, prev_bat_cap - cap, time(NULL) - prev_bat_time);
	return 1;
}

int print_battery_proc_pmu(void)
{
	char line[80];
	int i;
	int power_present = 0;
	int num_batteries = 0;
	/* unsigned rem_time_sec = 0; */
	unsigned charge_mAh = 0, max_charge_mAh = 0, voltage_mV = 0;
	int discharge_mA = 0;
	FILE *fd;

	fd = fopen("/proc/pmu/info", "r");
	if (fd == NULL)
		return 0;

	while ( fgets(line, sizeof(line), fd) != NULL )
	{
		if (strncmp("AC Power", line, strlen("AC Power")) == 0)
			sscanf(strchr(line, ':')+2, "%d", &power_present);
		else if (strncmp("Battery count", line, strlen("Battery count")) == 0)
			sscanf(strchr(line, ':')+2, "%d", &num_batteries);
	}
	fclose(fd);

	for (i = 0; i < num_batteries; ++i)
	{
		char file_name[20];
		int flags = 0;
		/* int battery_charging, battery_full; */
		/* unsigned this_rem_time_sec = 0; */
		unsigned this_charge_mAh = 0, this_max_charge_mAh = 0;
		unsigned this_voltage_mV = 0, this_discharge_mA = 0;

		snprintf(file_name, sizeof(file_name), "/proc/pmu/battery_%d", i);
		fd = fopen(file_name, "r");
		if (fd == NULL)
			continue;

		while (fgets(line, sizeof(line), fd) != NULL)
		{
			if (strncmp("flags", line, strlen("flags")) == 0)
				sscanf(strchr(line, ':')+2, "%x", &flags);
			else if (strncmp("charge", line, strlen("charge")) == 0)
				sscanf(strchr(line, ':')+2, "%d", &this_charge_mAh);
			else if (strncmp("max_charge", line, strlen("max_charge")) == 0)
				sscanf(strchr(line, ':')+2, "%d", &this_max_charge_mAh);
			else if (strncmp("voltage", line, strlen("voltage")) == 0)
				sscanf(strchr(line, ':')+2, "%d", &this_voltage_mV);
			else if (strncmp("current", line, strlen("current")) == 0)
				sscanf(strchr(line, ':')+2, "%d", &this_discharge_mA);
			/* else if (strncmp("time rem.", line, strlen("time rem.")) == 0) */
			/*   sscanf(strchr(line, ':')+2, "%d", &this_rem_time_sec); */
		}
		fclose(fd);

		if ( !(flags & 0x1) )
			/* battery isn't present */
			continue;

		/* battery_charging = flags & 0x2; */
		/* battery_full = !battery_charging && power_present; */

		charge_mAh += this_charge_mAh;
		max_charge_mAh += this_max_charge_mAh;
		voltage_mV += this_voltage_mV;
		discharge_mA += this_discharge_mA;
		/* rem_time_sec += this_rem_time_sec; */
	}
	show_pmu_power_line(voltage_mV, charge_mAh, max_charge_mAh,
	                    discharge_mA);
	return 1;
}

void print_battery_sysfs(void)
{
	DIR *dir;
	struct dirent *dirent;
	FILE *file;
	double rate = 0;
	double cap = 0;

	char filename[256];
	
	if (print_battery_proc_acpi())
		return;
	
	if (print_battery_proc_pmu())
		return;

	dir = opendir("/sys/class/power_supply");
	if (!dir) {
		return;
	}

	while ((dirent = readdir(dir))) {
		int dontcount = 0;
		double voltage = 0.0;
		double amperes_drawn = 0.0;
		double watts_drawn = 0.0;
		double watts_left = 0.0;
		char line[1024];

		if (strstr(dirent->d_name, "AC"))
			continue;

		sprintf(filename, "/sys/class/power_supply/%s/present", dirent->d_name);
		file = fopen(filename, "r");
		if (!file)
			continue;
		int s;
		if ((s = getc(file)) != EOF) {
			if (s == 0)
				break;
		}
		fclose(file);

		sprintf(filename, "/sys/class/power_supply/%s/status", dirent->d_name);
		file = fopen(filename, "r");
		if (!file)
			continue;
		memset(line, 0, 1024);
		if (fgets(line, 1024, file) != NULL) {
			if (!strstr(line, "Discharging"))
				dontcount = 1;
		}
		fclose(file);

		sprintf(filename, "/sys/class/power_supply/%s/voltage_now", dirent->d_name);
		file = fopen(filename, "r");
		if (!file)
			continue;
		memset(line, 0, 1024);
		if (fgets(line, 1024, file) != NULL) {
			voltage = strtoull(line, NULL, 10) / 1000000.0;
		}
		fclose(file);

		sprintf(filename, "/sys/class/power_supply/%s/energy_now", dirent->d_name);
		file = fopen(filename, "r");
		watts_left = 1;
		if (!file) {
			sprintf(filename, "/sys/class/power_supply/%s/charge_now", dirent->d_name);
			file = fopen(filename, "r");
			if (!file) 
				continue;

			/* W = A * V */
			watts_left = voltage;
		}
		memset(line, 0, 1024);
		if (fgets(line, 1024, file) != NULL) 
			watts_left *= strtoull(line, NULL, 10) / 1000000.0;
		fclose(file);

		sprintf(filename, "/sys/class/power_supply/%s/current_now", dirent->d_name);
		file = fopen(filename, "r");
		if (!file)
			continue;
		memset(line, 0, 1024);
		if (fgets(line, 1024, file) != NULL) {
			amperes_drawn = strtoull(line, NULL, 10) / 1000000.0;
		}
		fclose(file);
	
		if (!dontcount) {
			rate += watts_drawn + voltage * amperes_drawn;
		}
		cap += watts_left;
		

	}
	closedir(dir);
	if (prev_bat_cap - cap < 0.001 && rate < 0.001)
		last_bat_time = 0;
	if (!last_bat_time) {
		last_bat_time = prev_bat_time = time(NULL);
		last_bat_cap = prev_bat_cap = cap;
	}
	if (time(NULL) - last_bat_time >= 400) {
		prev_bat_cap = last_bat_cap;
		prev_bat_time = last_bat_time;
		last_bat_time = time(NULL);
		last_bat_cap = cap;
	}

	show_acpi_power_line(rate, cap, prev_bat_cap - cap, time(NULL) - prev_bat_time);
}

char cstate_lines[12][200];

void usage()
{
	printf(_("Usage: powertop [OPTION...]\n"));
	printf(_("  -d, --dump            read wakeups once and print list of top offenders\n"));
	printf(_("  -t, --time=DOUBLE     default time to gather data in seconds\n"));
	printf(_("  -h, --help            Show this help message\n"));
	printf(_("  -v, --version         Show version information and exit\n"));
	exit(0);
}

void version()
{
	printf(_("powertop version %s\n"), VERSION);
	exit(0);
}

int main(int argc, char **argv)
{
	char line[1024];
	int ncursesinited=0;
	FILE *file = NULL;
	uint64_t cur_usage[8], cur_duration[8];
	double wakeups_per_second = 0;

	setlocale (LC_ALL, "");
	bindtextdomain ("powertop", "/usr/share/locale");
	textdomain ("powertop");

 	while (1) {
 		static struct option opts[] = {
 			{ "dump", 0, NULL, 'd' },
 			{ "time", 1, NULL, 't' },
 			{ "help", 0, NULL, 'h' },
 			{ "version", 0, NULL, 'v' },
 			{ 0, 0, NULL, 0 }
 		};
 		int index2 = 0, c;
 		
 		c = getopt_long(argc, argv, "dt:hv", opts, &index2);
 		if (c == -1)
 			break;
 		switch (c) {
 		case 'd':
 			dump = 1;
 			break;
 		case 't':
 			ticktime = strtod(optarg, NULL);
 			break;
 		case 'h':
 			usage();
 			break;
 		case 'v':
 			version();
 			break;
 		default:
 			;
 		}
 	}

	if (!dump)
		ticktime = 5.0;
 
	system("/sbin/modprobe cpufreq_stats &> /dev/null");
	read_data(&start_usage[0], &start_duration[0]);


	memcpy(last_usage, start_usage, sizeof(last_usage));
	memcpy(last_duration, start_duration, sizeof(last_duration));

	do_proc_irq();
	do_proc_irq();
	do_cpufreq_stats();
	count_usb_urbs();
	count_usb_urbs();

	memset(cur_usage, 0, sizeof(cur_usage));
	memset(cur_duration, 0, sizeof(cur_duration));
	printf("PowerTOP " VERSION "   (C) 2007, 2008 Intel Corporation \n\n");
	if (geteuid() != 0)
		printf(_("PowerTOP needs to be run as root to collect enough information\n"));
	printf(_("Collecting data for %i seconds \n"), (int)ticktime);
	printf("\n\n");
	print_intel_cstates();
	stop_timerstats();

	while (1) {
		double maxsleep = 0.0;
		int64_t totalticks;
		int64_t totalevents;
		fd_set rfds;
		struct timeval tv;
		int key;

		int i = 0;
		double c0 = 0;
		char *c;


		FD_ZERO(&rfds);
		FD_SET(0, &rfds);
		tv.tv_sec = ticktime;
		tv.tv_usec = (ticktime - tv.tv_sec) * 1000000;;
		do_proc_irq();
		start_timerstats();


		key = select(1, &rfds, NULL, NULL, &tv);

		if (key && tv.tv_sec) ticktime = ticktime - tv.tv_sec - tv.tv_usec/1000000.0;


		stop_timerstats();
		clear_lines();
		do_proc_irq();
		read_data(&cur_usage[0], &cur_duration[0]);

		totalticks = 0;
		totalevents = 0;
		for (i = 0; i < 8; i++)
			if (cur_usage[i]) {
				totalticks += cur_duration[i] - last_duration[i];
				totalevents += cur_usage[i] - last_usage[i];
			}

		if (!dump) {
			if (!ncursesinited) {
				initialize_curses();  
				ncursesinited++;
			}
			setup_windows();
			show_title_bar();
		}

		memset(&cstate_lines, 0, sizeof(cstate_lines));
		topcstate = -4;
		if (totalevents == 0 && maxcstate <= 1) {
			sprintf(cstate_lines[5],_("< Detailed C-state information is not available.>\n"));
		} else {
			double sleept, percentage;;
			c0 = sysconf(_SC_NPROCESSORS_ONLN) * ticktime * 1000 * FREQ - totalticks;
			if (c0 < 0)
				c0 = 0;	/* rounding errors in measurement might make c0 go slightly negative.. this is confusing */
			sprintf(cstate_lines[0], _("Cn\t          Avg residency\n"));

			percentage = c0 * 100.0 / (sysconf(_SC_NPROCESSORS_ONLN) * ticktime * 1000 * FREQ);
			sprintf(cstate_lines[1], _("C0 (cpu running)        (%4.1f%%)\n"), percentage);
			if (percentage > 50)
				topcstate = 0;
			for (i = 0; i < 8; i++)
				if (cur_usage[i]) {
					sleept = (cur_duration[i] - last_duration[i]) / (cur_usage[i] - last_usage[i]
											+ 0.1) / FREQ;
					percentage = (cur_duration[i] -
					      last_duration[i]) * 100 /
					     (sysconf(_SC_NPROCESSORS_ONLN) * ticktime * 1000 * FREQ);

					if (cnames[i][0]==0)
						sprintf(cnames[i],"C%i",i+1);
					sprintf
					    (cstate_lines[2+i], _("%s\t%5.1fms (%4.1f%%)\n"),
					     cnames[i], sleept, percentage);
					if (maxsleep < sleept)
						maxsleep = sleept;
					if (percentage > 50)
						topcstate = i+1;
					
				}
		}
		do_cpufreq_stats();
		show_cstates();
		/* now the timer_stats info */
		memset(line, 0, sizeof(line));
		totalticks = 0;
		file = NULL;
		if (!nostats)
			file = fopen("/proc/timer_stats", "r");
		while (file && !feof(file)) {
			char *count, *pid, *process, *func;
			char line2[1024];
			int cnt;
			int deferrable = 0;
			memset(line, 0, 1024);
			if (fgets(line, 1024, file) == NULL)
				break;
			if (strstr(line, "total events"))
				break;
			c = count = &line[0];
			c = strchr(c, ',');
			if (!c)
				continue;
			*c = 0;
			c++;
			while (*c != 0 && *c == ' ')
				c++;
			pid = c;
			c = strchr(c, ' ');
			if (!c)
				continue;
			*c = 0;
			c++;
			while (*c != 0 && *c == ' ')
				c++;
			process = c;
			c = strchr(c, ' ');
			if (!c)
				continue;
			*c = 0;
			c++;
			while (*c != 0 && *c == ' ')
				c++;
			func = c;
			if (strcmp(process, "insmod") == 0)
				process = _("<kernel module>");
			if (strcmp(process, "modprobe") == 0)
				process = _("<kernel module>");
			if (strcmp(process, "swapper") == 0)
				process = _("<kernel core>");
			c = strchr(c, '\n');
			if (strncmp(func, "tick_nohz_", 10) == 0)
				continue;
			if (strncmp(func, "tick_setup_sched_timer", 20) == 0)
				continue;
			if (strcmp(process, "powertop") == 0)
				continue;
			if (c)
				*c = 0;
			cnt = strtoull(count, &c, 10);
			while (*c != 0) {
				if (*c++ == 'D')
					deferrable = 1;
			}
			if (deferrable)
				continue;
			sprintf(line2, "%15s : %s", process, func);
			push_line_pid(line2, cnt, pid);
		}
		if (file)
			pclose(file);

		if (strstr(line, "total events")) {
			int d;
			d = strtoull(line, NULL, 10) / sysconf(_SC_NPROCESSORS_ONLN);
			if (totalevents == 0) { /* No c-state info available, use timerstats instead */
				totalevents = d * sysconf(_SC_NPROCESSORS_ONLN) + total_interrupt;
				if (d < interrupt_0)
					totalevents += interrupt_0 - d;
			}
			if (d>0 && d < interrupt_0)
				push_line(_("    <interrupt> : extra timer interrupt"), interrupt_0 - d);
		}

	
		if (totalevents && ticktime) {
			wakeups_per_second = totalevents * 1.0 / ticktime / sysconf(_SC_NPROCESSORS_ONLN);
			show_wakeups(wakeups_per_second, ticktime, c0 * 100.0 / (sysconf(_SC_NPROCESSORS_ONLN) * ticktime * 1000 * FREQ) );
		}
		count_usb_urbs();
		print_battery_sysfs();
		count_lines();
		sort_lines();

		displaytime = displaytime - ticktime;

		show_timerstats(nostats, ticktime);

		if (maxsleep < 5.0)
			ticktime = 10;
		else if (maxsleep < 30.0)
			ticktime = 15;
		else if (maxsleep < 100.0)
			ticktime = 20;
		else if (maxsleep < 400.0)
			ticktime = 30;
		else
			ticktime = 45;

		if (key) {
			char keychar;
			int keystroke = fgetc(stdin);
			if (keystroke == EOF)
				exit(EXIT_SUCCESS);

			keychar = toupper(keystroke);
			if (keychar == 'Q')
				exit(EXIT_SUCCESS);
			if (keychar == 'R')
				ticktime = 3;
			if (keychar == suggestion_key && suggestion_activate) {
				suggestion_activate();
				ticktime = 2;
				displaytime = -1.0;
			} else
			if (keychar == 'P')
				showpids = !showpids;
		}

		if (wakeups_per_second < 0)
			ticktime = 2;

		reset_suggestions();

		suggest_kernel_config("CONFIG_USB_SUSPEND", 1,
				    _("Suggestion: Enable the CONFIG_USB_SUSPEND kernel configuration option.\nThis option will automatically disable UHCI USB when not in use, and may\nsave approximately 1 Watt of power."), 20);
		suggest_kernel_config("CONFIG_CPU_FREQ_GOV_ONDEMAND", 1,
				    _("Suggestion: Enable the CONFIG_CPU_FREQ_GOV_ONDEMAND kernel configuration option.\n"
				      "The 'ondemand' CPU speed governor will minimize the CPU power usage while\n" "giving you performance when it is needed."), 5);
		suggest_kernel_config("CONFIG_NO_HZ", 1, _("Suggestion: Enable the CONFIG_NO_HZ kernel configuration option.\nThis option is required to get any kind of longer sleep times in the CPU."), 50);
		suggest_kernel_config("CONFIG_ACPI_BATTERY", 1, _("Suggestion: Enable the CONFIG_ACPI_BATTERY kernel configuration option.\n "
				      "This option is required to get power estimages from PowerTOP"), 5);
		suggest_kernel_config("CONFIG_HPET_TIMER", 1,
				    _("Suggestion: Enable the CONFIG_HPET_TIMER kernel configuration option.\n"
				      "Without HPET support the kernel needs to wake up every 20 milliseconds for \n" "some housekeeping tasks."), 10);
		if (!access("/sys/module/snd_ac97_codec", F_OK) &&
			access("/sys/module/snd_ac97_codec/parameters/power_save", F_OK))
			suggest_kernel_config("CONFIG_SND_AC97_POWER_SAVE", 1,
				    _("Suggestion: Enable the CONFIG_SND_AC97_POWER_SAVE kernel configuration option.\n"
				      "This option will automatically power down your sound codec when not in use,\n"
				      "and can save approximately half a Watt of power."), 20);
		suggest_kernel_config("CONFIG_IRQBALANCE", 0,
				      _("Suggestion: Disable the CONFIG_IRQBALANCE kernel configuration option.\n" "The in-kernel irq balancer is obsolete and wakes the CPU up far more than needed."), 3);
		suggest_kernel_config("CONFIG_CPU_FREQ_STAT", 1,
				    _("Suggestion: Enable the CONFIG_CPU_FREQ_STAT kernel configuration option.\n"
				      "This option allows PowerTOP to show P-state percentages \n" "P-states correspond to CPU frequencies."), 2);
		suggest_kernel_config("CONFIG_INOTIFY", 1,
				    _("Suggestion: Enable the CONFIG_INOTIFY kernel configuration option.\n"
				      "This option allows programs to wait for changes in files and directories\n" 
				      "instead of having to poll for these changes"), 5);


		/* suggest to stop beagle if it shows up in the top 20 and wakes up more than 10 times in the measurement */
		suggest_process_death("beagled : schedule_timeout", "beagled", lines, min(linehead,20), 10.0,
				    _("Suggestion: Disable or remove 'beagle' from your system. \n"
				      "Beagle is the program that indexes for easy desktop search, however it's \n"
				      "not very efficient and costs a significant amount of battery life."), 30);
		suggest_process_death("beagled : futex_wait (hrtimer_wakeup)", "beagled", lines, min(linehead,20), 10.0,
				    _("Suggestion: Disable or remove 'beagle' from your system. \n"
				      "Beagle is the program that indexes for easy desktop search, however it's \n"
				      "not very efficient and costs a significant amount of battery life."), 30);

		/* suggest to stop gnome-power-manager *only* if it shows up in the top 10 and wakes up more than 10 times in the measurement */
		/* note to distribution makers: There is no need to patch this out! */
		/* If you ship a recent enough g-p-m, the warning will not be there, */
		/* and if you ship a really old one the warning is really justified. */
		suggest_process_death("gnome-power-man : schedule_timeout (process_timeout)", "gnome-power-manager", lines, min(linehead,10), 10.0,
				    _("Suggestion: Disable or remove 'gnome-power-manager' from your system. \n"
				      "Older versions of gnome-power-manager wake up far more often than \n"
				      "needed costing you some power."), 5);

		/* suggest to stop pcscd if it shows up in the top 50 and wakes up at all*/
		suggest_process_death("pcscd : ", "pcscd", lines, min(linehead,50), 1.0,
				    _("Suggestion: Disable or remove 'pcscd' from your system. \n"
				      "pcscd tends to keep the USB subsystem out of power save mode\n"
				      "and your processor out of deeper powersave states."), 30);


		/* suggest to stop hal polilng if it shows up in the top 50 and wakes up too much*/
		suggest_process_death("hald-addon-stor : ", "hald-addon-storage", lines, min(linehead,50), 2.0,
				    _( "Suggestion: Disable 'hal' from polling your cdrom with:  \n" 
				       "hal-disable-polling --device /dev/cdrom 'hal' is the component that auto-opens a\n"
				       "window if you plug in a CD but disables SATA power saving from kicking in."), 30);

		/* suggest to kill sealert; it wakes up 10 times/second on a default F7 install*/
		suggest_process_death("/usr/bin/sealer : schedule_timeout (process_timeout)", "-/usr/bin/sealert", lines, min(linehead,20), 20.0,
				    _("Disable the SE-Alert software by removing the 'setroubleshoot-server' rpm\n"
				      "SE-Alert alerts you about SELinux policy violations, but also\n"
				      "has a bug that wakes it up 10 times per second."), 20);


		suggest_bluetooth_off();
		suggest_nmi_watchdog();
		suggest_laptop_mode();
		if (maxsleep > 15.0)
			suggest_hpet();
		suggest_ac97_powersave();
		suggest_wireless_powersave();
		suggest_ondemand_governor();
		suggest_noatime();
		suggest_sata_alpm();
		suggest_powersched();
		suggest_xrandr_TV_off();
		suggest_WOL_off();
		suggest_writeback_time();
		suggest_usb_autosuspend();
		usb_activity_hint();

		if (dump) {
			print_all_suggestions();
			display_usb_activity();
			exit(EXIT_SUCCESS);
		}

		if (!key)
			pick_suggestion();
		show_title_bar();

		fflush(stdout);
		if (!key && ticktime >= 4.8) {	/* quiet down the effects of any IO to xterms */
			FD_ZERO(&rfds);
			FD_SET(0, &rfds);
			tv.tv_sec = 3;
			tv.tv_usec = 0;
			key = select(1, &rfds, NULL, NULL, &tv);
		}

		read_data(&cur_usage[0], &cur_duration[0]);
		memcpy(last_usage, cur_usage, sizeof(last_usage));
		memcpy(last_duration, cur_duration, sizeof(last_duration));


		
	}

	return 0;
}