1251 lines
31 KiB
C
1251 lines
31 KiB
C
/*
|
|
* 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 <ctype.h>
|
|
#include <assert.h>
|
|
#include <locale.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "powertop.h"
|
|
|
|
#define VERSION "1.11-1"
|
|
|
|
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;
|
|
int reset_pm_stats = 0;
|
|
|
|
static int cpu_count = 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;
|
|
c = strchr(name, '\n');
|
|
if (c)
|
|
*c = 0;
|
|
|
|
delta = update_irq(nr, count, name);
|
|
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"));
|
|
//don't special-case interrupt 0 as it's not the system timer on Android
|
|
#ifdef PLATFORM_NO_INT0
|
|
if (delta > 0)
|
|
push_line(line2, delta);
|
|
total_interrupt += delta;
|
|
#else
|
|
if (nr > 0 && delta > 0)
|
|
push_line(line2, delta);
|
|
if (nr==0)
|
|
interrupt_0 = delta;
|
|
else
|
|
total_interrupt += delta;
|
|
#endif
|
|
}
|
|
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;
|
|
int numcpus = 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 = snprintf(filename, 128, "/sys/devices/system/cpu/%s/online",
|
|
entry->d_name);
|
|
file = fopen(filename, "r");
|
|
if (file) {
|
|
f = fgets(line, 4096, file);
|
|
fclose(file);
|
|
if (f != NULL) {
|
|
if (*f == '1')
|
|
numcpus++;
|
|
else
|
|
continue;
|
|
}
|
|
}
|
|
len = snprintf(filename, 128, "/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);
|
|
if (numcpus)
|
|
cpu_count = numcpus;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void reset_msm_pm_stats(void)
|
|
{
|
|
FILE *file;
|
|
file = fopen("/proc/msm_pm_stats", "w");
|
|
if (!file)
|
|
return;
|
|
fprintf(file, "reset\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(_(" -r, --reset Reset PM stats data\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' },
|
|
{ "reset", 0, NULL, 'r' },
|
|
{ "pids", 0, NULL, 'p' },
|
|
{ "help", 0, NULL, 'h' },
|
|
{ "version", 0, NULL, 'v' },
|
|
{ 0, 0, NULL, 0 }
|
|
};
|
|
int index2 = 0, c;
|
|
|
|
c = getopt_long(argc, argv, "dt:rphv", opts, &index2);
|
|
if (c == -1)
|
|
break;
|
|
switch (c) {
|
|
case 'd':
|
|
dump = 1;
|
|
break;
|
|
case 't':
|
|
ticktime = strtod(optarg, NULL);
|
|
break;
|
|
case 'r':
|
|
reset_pm_stats = 1;
|
|
break;
|
|
case 'p':
|
|
showpids = 1;
|
|
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 num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
|
|
int i = 0;
|
|
double c0 = 0;
|
|
char *c;
|
|
|
|
|
|
FD_ZERO(&rfds);
|
|
#ifndef NO_NCURSES
|
|
/* Do not check for stdin (fd: 0)
|
|
* This would cause select to return immediately
|
|
* when the USB is re-attached on ADB shell.
|
|
*/
|
|
FD_SET(0, &rfds);
|
|
#endif
|
|
tv.tv_sec = ticktime;
|
|
tv.tv_usec = (ticktime - tv.tv_sec) * 1000000;;
|
|
if (reset_pm_stats)
|
|
reset_msm_pm_stats();
|
|
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();
|
|
msm_pm_stats();
|
|
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;
|
|
if (cpu_count)
|
|
num_cpus = cpu_count;
|
|
c0 = num_cpus * 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 / (num_cpus * 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 /
|
|
(num_cpus * 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();
|
|
show_msm_pm_stats();
|
|
/* 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);
|
|
#ifdef PLATFORM_NO_INT0
|
|
totalevents = total_interrupt;
|
|
#endif
|
|
if (strstr(line, "total events")) {
|
|
int d;
|
|
d = strtoull(line, NULL, 10) / num_cpus;
|
|
if (totalevents == 0) { /* No c-state info available, use timerstats instead */
|
|
totalevents = d * num_cpus + 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 / num_cpus;
|
|
show_wakeups(wakeups_per_second, ticktime, c0 * 100.0 / (num_cpus * 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);
|
|
#ifndef NO_NCURSES
|
|
/* Do not handle EOF when not using ncurses as
|
|
* the shell would pass the EOF to the app causing
|
|
* it to stop.
|
|
*/
|
|
if (keystroke == EOF)
|
|
exit(EXIT_SUCCESS);
|
|
#endif
|
|
|
|
keychar = toupper(keystroke);
|
|
if (keychar == 'Q')
|
|
exit(EXIT_SUCCESS);
|
|
if (keychar == 'R')
|
|
ticktime = 3;
|
|
#ifndef NO_SUGGESTIONS
|
|
if (keychar == suggestion_key && suggestion_activate) {
|
|
suggestion_activate();
|
|
ticktime = 2;
|
|
displaytime = -1.0;
|
|
} else
|
|
#endif
|
|
if (keychar == 'P')
|
|
showpids = !showpids;
|
|
|
|
}
|
|
|
|
if (wakeups_per_second < 0)
|
|
ticktime = 2;
|
|
|
|
|
|
#ifndef NO_SUGGESTIONS
|
|
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();
|
|
#endif
|
|
if (dump) {
|
|
#ifndef NO_SUGGESTIONS
|
|
print_all_suggestions();
|
|
display_usb_activity();
|
|
#endif
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
#ifndef NO_SUGGESTIONS
|
|
if (!key)
|
|
pick_suggestion();
|
|
#endif
|
|
#ifndef NO_NCURSES
|
|
show_title_bar();
|
|
#endif
|
|
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;
|
|
}
|