diff options
22 files changed, 2990 insertions, 0 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index bd8cf275e2..018eec7115 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -214,6 +214,7 @@ COMMON_SUBDIRS = \ cmd/plockstat \ cmd/pools \ cmd/power \ + cmd/powertop \ cmd/ppgsz \ cmd/praudit \ cmd/prctl \ diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index 153cc86f1b..c9b15faf8d 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -293,6 +293,7 @@ COMMON_SUBDIRS= \ policykit \ pools \ power \ + powertop \ ppgsz \ pg \ plockstat \ diff --git a/usr/src/cmd/powertop/Makefile b/usr/src/cmd/powertop/Makefile new file mode 100644 index 0000000000..65860399af --- /dev/null +++ b/usr/src/cmd/powertop/Makefile @@ -0,0 +1,55 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# cmd/powertop/Makefile +# + +PROG = powertop + +include ../Makefile.cmd + +$(64ONLY)SUBDIRS= $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET = all +install := TARGET = install +clean := TARGET = clean +clobber := TARGET = clobber +lint := TARGET = lint + +.KEEP_STATE: + +all: $(SUBDIRS) + +clean clobber lint: $(SUBDIRS) + +install: $(SUBDIRS) + -$(RM) $(ROOTPROG) + -$(LN) $(ISAEXEC) $(ROOTPROG) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../Makefile.targ diff --git a/usr/src/cmd/powertop/Makefile.com b/usr/src/cmd/powertop/Makefile.com new file mode 100644 index 0000000000..8408a3b528 --- /dev/null +++ b/usr/src/cmd/powertop/Makefile.com @@ -0,0 +1,56 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +PROG = powertop +OBJS = $(PROG).o display.o battery.o cpufreq.o cpuidle.o events.o util.o suggestions.o +SRCS = $(OBJS:%.o=../%.c) + +include ../../Makefile.cmd + +CFLAGS += $(CCVERBOSE) +CFLAGS64 += $(CCVERBOSE) + +LDLIBS += -lcurses -ldtrace -lkstat + +FILEMODE = 0555 +GROUP = bin + +CLEANFILES += $(OBJS) + +.KEEP_STATE: + +all: $(PROG) + +$(PROG): $(OBJS) + $(LINK.c) -o $@ $(OBJS) $(LDLIBS) + $(POST_PROCESS) +clean: + $(RM) $(CLEANFILES) + +lint: lint_SRCS + +%.o: ../%.c + $(COMPILE.c) $< + +include ../../Makefile.targ diff --git a/usr/src/cmd/powertop/amd64/Makefile b/usr/src/cmd/powertop/amd64/Makefile new file mode 100644 index 0000000000..d99d30ca4c --- /dev/null +++ b/usr/src/cmd/powertop/amd64/Makefile @@ -0,0 +1,28 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com +include ../../Makefile.cmd.64 + +install: all $(ROOTPROG64) diff --git a/usr/src/cmd/powertop/battery.c b/usr/src/cmd/powertop/battery.c new file mode 100644 index 0000000000..66d899bb66 --- /dev/null +++ b/usr/src/cmd/powertop/battery.c @@ -0,0 +1,223 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <string.h> +#include <kstat.h> +#include <errno.h> +#include "powertop.h" + +typedef struct battery_state { + uint32_t exist; + uint32_t power_unit; + uint32_t bst_state; + double present_rate; + double remain_cap; + double last_cap; +} battery_state_t; + +battery_state_t battery; + +static int battery_stat_snapshot(void); + +#define mW2W(value) ((value) / 1000) + +void +print_battery(void) +{ + int err; + + (void) memset(&battery, 0, sizeof (battery_state_t)); + + /* + * The return value of battery_stat_snapsho() can be used for + * debug or to show/hide the acpi power line. We currently don't + * make the distinction of a system that runs only on AC and one + * that runs on battery but has no kstat battery info. + * + * We still display the estimate power usage for systems + * running on AC with a fully charged battery because some + * batteries may still consume power. + * + * If battery_mod_lookup() didn't find a kstat battery module, don't + * bother trying to take the snapshot + */ + if (kstat_batt_idx > 0) { + if ((err = battery_stat_snapshot()) < 0) + pt_error("%s : battery kstat not found %d\n", __FILE__, + err); + } + + show_acpi_power_line(battery.exist, battery.present_rate, + battery.remain_cap, battery.last_cap, battery.bst_state); +} + +static int +battery_stat_snapshot(void) +{ + kstat_ctl_t *kc; + kstat_t *ksp; + kstat_named_t *knp; + + kc = kstat_open(); + + /* + * power unit: + * 0 - Capacity information is reported in [mWh] and + * charge/discharge rate information in [mW] + * 1 - Capacity information is reported in [mAh] and + * charge/discharge rate information in [mA]. + */ + ksp = kstat_lookup(kc, kstat_batt_mod[kstat_batt_idx], 0, + "battery BIF0"); + + if (ksp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + (void) kstat_read(kc, ksp, NULL); + knp = kstat_data_lookup(ksp, "bif_unit"); + + if (knp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + battery.power_unit = knp->value.ui32; + + /* + * Present rate: + * the power or current being supplied or accepted + * through the battery's terminal + */ + ksp = kstat_lookup(kc, kstat_batt_mod[kstat_batt_idx], 0, + "battery BST0"); + + if (ksp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + (void) kstat_read(kc, ksp, NULL); + knp = kstat_data_lookup(ksp, "bst_rate"); + + if (knp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + if (knp->value.ui32 == 0xFFFFFFFF) + battery.present_rate = 0; + else { + battery.exist = 1; + battery.present_rate = mW2W((double)(knp->value.ui32)); + } + + /* + * Last Full charge capacity: + * Predicted battery capacity when fully charged. + */ + ksp = kstat_lookup(kc, kstat_batt_mod[kstat_batt_idx], 0, + "battery BIF0"); + + if (ksp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + (void) kstat_read(kc, ksp, NULL); + knp = kstat_data_lookup(ksp, "bif_last_cap"); + + if (knp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + battery.last_cap = mW2W((double)(knp->value.ui32)); + + /* + * Remaining capacity: + * the estimated remaining battery capacity + */ + ksp = kstat_lookup(kc, kstat_batt_mod[kstat_batt_idx], 0, + "battery BST0"); + + if (ksp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + (void) kstat_read(kc, ksp, NULL); + knp = kstat_data_lookup(ksp, "bst_rem_cap"); + + if (knp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + battery.remain_cap = mW2W((double)(knp->value.ui32)); + + /* + * Battery State: + * Bit0 - 1 : discharging + * Bit1 - 1 : charging + * Bit2 - 1 : critical energy state + */ + ksp = kstat_lookup(kc, kstat_batt_mod[kstat_batt_idx], 0, + "battery BST0"); + + if (ksp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + (void) kstat_read(kc, ksp, NULL); + knp = kstat_data_lookup(ksp, "bst_state"); + + if (knp == NULL) { + (void) kstat_close(kc); + return (-1); + } + + battery.bst_state = knp->value.ui32; + + (void) kstat_close(kc); + + return (0); +} diff --git a/usr/src/cmd/powertop/cpufreq.c b/usr/src/cmd/powertop/cpufreq.c new file mode 100644 index 0000000000..18bd393665 --- /dev/null +++ b/usr/src/cmd/powertop/cpufreq.c @@ -0,0 +1,425 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <stdlib.h> +#include <string.h> +#include <dtrace.h> +#include <kstat.h> +#include <errno.h> +#include "powertop.h" + +#define HZ2MHZ(speed) ((speed) / 1000000) + +static uint64_t max_cpufreq = 0; +static dtrace_hdl_t *g_dtp; + +/* + * Enabling PM through /etc/power.conf + * See suggest_p_state() + */ +static char default_conf[] = "/etc/power.conf"; +static char default_pmconf[] = "/usr/sbin/pmconfig"; +static char cpupm_enable[] = " echo cpupm enable >> /etc/power.conf"; +static char cpupm_treshold[] = " echo cpu-threshold 1s >> /etc/power.conf"; + +/* + * Buffer containing DTrace program to track CPU frequency transitions + */ +static const char *pt_cpufreq_dtrace_prog = +"" +"hrtime_t last[int];" +"" +"BEGIN" +"{" +" begin = timestamp;" +"}" +"" +":::cpu-change-speed" +"/last[((cpudrv_devstate_t *)arg0)->cpu_id] != 0/" +"{" +" this->cpu = ((cpudrv_devstate_t *)arg0)->cpu_id;" +" this->oldspeed = ((cpudrv_pm_t *)arg1)->cur_spd->speed;" +" @times[this->cpu, this->oldspeed] = sum(timestamp - last[this->cpu]);" +" last[this->cpu] = timestamp;" +"}" +":::cpu-change-speed" +"/last[((cpudrv_devstate_t *)arg0)->cpu_id] == 0/" +"{" +" this->cpu = ((cpudrv_devstate_t *)arg0)->cpu_id;" +" this->oldspeed = ((cpudrv_pm_t *)arg1)->cur_spd->speed;" +" @times[this->cpu, this->oldspeed] = sum(timestamp - begin);" +" last[this->cpu] = timestamp;" +"}"; + +static int pt_cpufreq_snapshot(void); +static int pt_cpufreq_dtrace_walk(const dtrace_aggdata_t *, void *); + +/* + * Perform setup necessary to enumerate and track CPU speed changes + */ +int +pt_cpufreq_stat_prepare(void) +{ + dtrace_prog_t *prog; + dtrace_proginfo_t info; + dtrace_optval_t statustime; + + kstat_ctl_t *kc; + kstat_t *ksp; + kstat_named_t *knp; + + pstate_info_t *state; + char *s, *token; + int err; + + state = pstate_info; + cpu_power_states = calloc((size_t)g_ncpus, sizeof (cpu_power_info_t)); + + /* + * Enumerate the CPU frequencies + */ + if ((kc = kstat_open()) == NULL) + return (errno); + + ksp = kstat_lookup(kc, "cpu_info", cpu_table[0], NULL); + + if (ksp == NULL) + return (errno); + + (void) kstat_read(kc, ksp, NULL); + + knp = kstat_data_lookup(ksp, "supported_frequencies_Hz"); + s = knp->value.str.addr.ptr; + + npstates = 0; + + for (token = strtok(s, ":"), s = NULL; + NULL != token && npstates < NSTATES; + token = strtok(NULL, ":")) { + + state->speed = HZ2MHZ(atoll(token)); + + if (state->speed > max_cpufreq) + max_cpufreq = state->speed; + + state->total_time = (uint64_t)0; + + npstates++; + state++; + } + + if (token != NULL) + pt_error("%s : exceeded NSTATES\n", __FILE__); + + (void) kstat_close(kc); + + /* + * Return if speed transition is not supported + */ + if (npstates < 2) + return (-1); + + /* + * Setup DTrace to look for CPU frequency changes + */ + if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) { + pt_error("%s : cannot open dtrace library: %s\n", __FILE__, + dtrace_errmsg(NULL, err)); + return (-2); + } + if ((prog = dtrace_program_strcompile(g_dtp, pt_cpufreq_dtrace_prog, + DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) { + pt_error("%s : cpu-change-speed probe unavailable\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_program_exec(g_dtp, prog, &info) == -1) { + pt_error("%s : failed to enable speed probe\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_setopt(g_dtp, "aggsize", "128k") == -1) { + pt_error("%s : failed to set speed 'aggsize'\n", __FILE__); + } + if (dtrace_setopt(g_dtp, "aggrate", "0") == -1) { + pt_error("%s : failed to set speed 'aggrate'\n", __FILE__); + } + if (dtrace_setopt(g_dtp, "aggpercpu", 0) == -1) { + pt_error("%s : failed to set speed 'aggpercpu'\n", __FILE__); + } + if (dtrace_go(g_dtp) != 0) { + pt_error("%s : failed to start speed observation", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_getopt(g_dtp, "statusrate", &statustime) == -1) { + pt_error("%s : failed to get speed 'statusrate'\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + + return (0); +} + +/* + * The DTrace probes have already been enabled, and are tracking + * CPU speed transitions. Take a snapshot of the aggregations, and + * look for any CPUs that have made a speed transition over the last + * sampling interval. Note that the aggregations may be empty if no + * speed transitions took place over the last interval. In that case, + * notate that we have already accounted for the time, so that when + * we do encounter a speed transition in a future sampling interval + * we can subtract that time back out. + */ +int +pt_cpufreq_stat_collect(double interval) +{ + int cpu, i, ret; + uint64_t speed; + hrtime_t duration; + cpu_power_info_t *cpu_pow; + + /* + * Zero out the interval time reported by DTrace for + * this interval + */ + for (i = 0; i < npstates; i++) + pstate_info[i].total_time = 0; + + for (i = 0; i < g_ncpus; i++) + cpu_power_states[i].dtrace_time = 0; + + if (dtrace_status(g_dtp) == -1) + return (-1); + + if (dtrace_aggregate_snap(g_dtp) != 0) + pt_error("%s : failed to add to stats aggregation", __FILE__); + + if (dtrace_aggregate_walk_keyvarsorted(g_dtp, pt_cpufreq_dtrace_walk, + NULL) != 0) + pt_error("%s : failed to sort stats aggregation", __FILE__); + + dtrace_aggregate_clear(g_dtp); + + if ((ret = pt_cpufreq_snapshot()) != 0) { + pt_error("%s : failed to add to stats aggregation", __FILE__); + return (ret); + } + + for (cpu = 0; cpu < g_ncpus; cpu++) { + cpu_pow = &cpu_power_states[cpu]; + + speed = cpu_pow->current_pstate; + + duration = (hrtime_t)((interval * NANOSEC)) - + cpu_pow->dtrace_time; + + for (i = 0; i < npstates; i++) { + if (pstate_info[i].speed == speed) { + pstate_info[i].total_time += duration; + cpu_pow->time_accounted += duration; + } + } + } + + return (0); +} + +/* + * Take a snapshot of each CPU's speed by looking through the cpu_info kstats. + */ +static int +pt_cpufreq_snapshot(void) +{ + kstat_ctl_t *kc; + kstat_t *ksp; + kstat_named_t *knp; + int cpu; + cpu_power_info_t *state; + + if ((kc = kstat_open()) == NULL) + return (errno); + + for (cpu = 0; cpu < g_ncpus; cpu++) { + ksp = kstat_lookup(kc, "cpu_info", cpu_table[cpu], NULL); + if (ksp == NULL) { + pt_error("%s : couldn't find cpu_info kstat for CPU " + "%d\n", __FILE__, cpu); + (void) kstat_close(kc); + return (1); + } + + if (kstat_read(kc, ksp, NULL) == -1) { + pt_error("%s : couldn't read cpu_info kstat for " + "CPU %d\n", __FILE__, cpu); + (void) kstat_close(kc); + return (2); + } + + knp = kstat_data_lookup(ksp, "current_clock_Hz"); + if (knp == NULL) { + pt_error("%s : couldn't find current_clock_Hz " + "kstat for CPU %d\n", __FILE__, cpu); + (void) kstat_close(kc); + return (3); + } + + state = &cpu_power_states[cpu]; + state->current_pstate = HZ2MHZ(knp->value.ui64); + } + + if (kstat_close(kc) != 0) + pt_error("%s : couldn't close kstat\n", __FILE__); + + return (0); +} + +/* + * DTrace aggregation walker that sorts through a snapshot of the + * aggregation data collected during firings of the cpu-change-speed + * probe. + */ +/*ARGSUSED*/ +static int +pt_cpufreq_dtrace_walk(const dtrace_aggdata_t *data, void *arg) +{ + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + dtrace_recdesc_t *cpu_rec, *speed_rec; + cpu_power_info_t *cpu_pow; + int32_t cpu; + uint64_t speed; + hrtime_t dt_state_time = 0; + int i; + + if (strcmp(aggdesc->dtagd_name, "times") == 0) { + cpu_rec = &aggdesc->dtagd_rec[1]; + speed_rec = &aggdesc->dtagd_rec[2]; + + for (i = 0; i < g_ncpus; i++) { + /* LINTED - alignment */ + dt_state_time += *((hrtime_t *)(data->dtada_percpu[i])); + } + + /* LINTED - alignment */ + cpu = *(int32_t *)(data->dtada_data + cpu_rec->dtrd_offset); + /* LINTED - alignment */ + speed = *(uint64_t *)(data->dtada_data + + speed_rec->dtrd_offset); + + if (speed == 0) { + speed = max_cpufreq; + } + + /* + * We have an aggregation record for "cpu" being at "speed" + * for an interval of "n" nanoseconds. The reported interval + * may exceed the powertop sampling interval, since we only + * notice during potentially infrequent firings of the + * "speed change" DTrace probe. In this case powertop would + * have already accounted for the portions of the interval + * that happened during prior powertop sampings, so subtract + * out time already accounted. + */ + cpu_pow = &cpu_power_states[cpu]; + + for (i = 0; i < npstates; i++) { + if (pstate_info[i].speed == speed) { + if (cpu_pow->time_accounted > 0) { + if (dt_state_time == 0) + continue; + if (dt_state_time > + cpu_pow->time_accounted) { + dt_state_time -= + cpu_pow->time_accounted; + cpu_pow->time_accounted = 0; + } + } + pstate_info[i].total_time += dt_state_time; + cpu_pow->dtrace_time += dt_state_time; + } + } + } + return (DTRACE_AGGWALK_NEXT); +} + +/* + * Used as a suggestion, sets PM in /etc/power.conf and + * a 1sec threshold, then calls /usr/sbin/pmconfig + */ +void +enable_p_state(void) +{ + (void) system(cpupm_enable); + (void) system(cpupm_treshold); + (void) system(default_pmconf); +} + +/* + * Checks if PM is enabled in /etc/power.conf, enabling if not + */ +void +suggest_p_state(void) +{ + char line[1024]; + FILE *file; + + /* + * Return if speed transition is not supported + */ + if (npstates < 2) + return; + + file = fopen(default_conf, "r"); + + if (!file) + return; + + (void) memset(line, 0, 1024); + + while (fgets(line, 1023, file)) { + if (strstr(line, "cpupm")) { + if (strstr(line, "enable")) { + (void) fclose(file); + return; + } + } + } + + add_suggestion("Suggestion: enable CPU power management by " + "pressing the P key", 40, 'P', "P - Enable p-state", + enable_p_state); + + (void) fclose(file); +} diff --git a/usr/src/cmd/powertop/cpuidle.c b/usr/src/cmd/powertop/cpuidle.c new file mode 100644 index 0000000000..7682ae0425 --- /dev/null +++ b/usr/src/cmd/powertop/cpuidle.c @@ -0,0 +1,216 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <string.h> +#include <dtrace.h> +#include "powertop.h" + +static dtrace_hdl_t *g_dtp; + +/* + * Buffer containing DTrace program to track CPU idle state transitions + */ +static const char *pt_cpuidle_dtrace_prog = +":::idle-state-transition" +"/arg0 != 0/" +"{" +" self->start = timestamp;" +" self->state = arg0;" +"}" +"" +":::idle-state-transition" +"/arg0 == 0 && self->start/" +"{" +" @number[self->state] = count();" +" @times[self->state] = sum((timestamp - self->start)/1000000);" +" self->start = 0;" +" self->state = 0;" +"}"; + +static int pt_cpuidle_dtrace_walk(const dtrace_aggdata_t *, void *); + +/* + * Perform setup necessary to track CPU idle state transitions + */ +int +pt_cpuidle_stat_prepare(void) +{ + dtrace_prog_t *prog; + dtrace_proginfo_t info; + dtrace_optval_t statustime; + int err; + + if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) { + pt_error("%s : cannot open dtrace library: %s\n", __FILE__, + dtrace_errmsg(NULL, err)); + return (-1); + } + if ((prog = dtrace_program_strcompile(g_dtp, pt_cpuidle_dtrace_prog, + DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) { + pt_error("%s : C-State DTrace probes unavailable\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_program_exec(g_dtp, prog, &info) == -1) { + pt_error("%s : failed to enable C State probes\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_setopt(g_dtp, "aggsize", "128k") == -1) { + pt_error("%s : failed to set C-state 'aggsize'\n", __FILE__); + } + if (dtrace_setopt(g_dtp, "aggrate", "0") == -1) { + pt_error("%s : failed to set C-state'aggrate'\n", __FILE__); + } + if (dtrace_setopt(g_dtp, "aggpercpu", 0) == -1) { + pt_error("%s : failed to set C-state 'aggpercpu'\n", __FILE__); + } + if (dtrace_go(g_dtp) != 0) { + pt_error("%s : failed to start C-state observation", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_getopt(g_dtp, "statusrate", &statustime) == -1) { + pt_error("%s : failed to get C-state 'statusrate'\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + return (0); +} + +/* + * The DTrace probes have been enabled, and are tracking CPU idle state + * transitions. Take a snapshot of the aggregations, and invoke the aggregation + * walker to process any records. The walker does most of the accounting work + * chalking up time spent into the cstate_info structure. + */ +int +pt_cpuidle_stat_collect(double interval) +{ + int i; + hrtime_t t = 0; + + /* + * Zero out the interval time reported by DTrace for + * this interval + */ + for (i = 0; i < NSTATES; i++) { + cstate_info[i].total_time = 0; + cstate_info[i].events = 0; + } + + /* + * Assume that all the time spent in this interval will + * be the default "0" state. The DTrace walker will reallocate + * time out of the default bucket as it processes aggregation + * records for time spent in other states. + */ + cstate_info[0].total_time = (long)(interval * g_ncpus * 1000); + + if (dtrace_status(g_dtp) == -1) + return (-1); + + if (dtrace_aggregate_snap(g_dtp) != 0) + pt_error("%s : failed to add to aggregation", __FILE__); + + if (dtrace_aggregate_walk_keyvarsorted(g_dtp, pt_cpuidle_dtrace_walk, + NULL) != 0) + pt_error("%s : failed to sort aggregation", __FILE__); + + dtrace_aggregate_clear(g_dtp); + + /* + * Populate cstate_info with the correct amount of time spent + * in each C state and update the number of C states in max_cstate + */ + total_c_time = 0; + for (i = 0; i < NSTATES; i++) { + if (cstate_info[i].total_time > 0) { + total_c_time += cstate_info[i].total_time; + if (i > max_cstate) + max_cstate = i; + if (cstate_info[i].last_time > t) { + t = cstate_info[i].last_time; + longest_cstate = i; + } + } + } + + return (0); +} + +/* + * DTrace aggregation walker that sorts through a snapshot of data records + * collected during firings of the idle-state-transition probe. + * + * XXX A way of querying the current idle state for a CPU is needed in addition + * to logic similar to that in cpufreq.c + */ +/*ARGSUSED*/ +static int +pt_cpuidle_dtrace_walk(const dtrace_aggdata_t *data, void *arg) +{ + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + dtrace_recdesc_t *rec; + uint64_t n = 0; + int32_t state; + int i; + + rec = &aggdesc->dtagd_rec[1]; + /* LINTED - alignment */ + state = *(int32_t *)(data->dtada_data + rec->dtrd_offset); + + if (strcmp(aggdesc->dtagd_name, "number") == 0) { + for (i = 0; i < g_ncpus; i++) { + /* LINTED - alignment */ + n += *((uint64_t *)(data->dtada_percpu[i])); + } + total_events += n; + cstate_info[state].events += n; + } + else + if (strcmp(aggdesc->dtagd_name, "times") == 0) { + for (i = 0; i < g_ncpus; i++) { + /* LINTED - alignment */ + n += *((uint64_t *)(data->dtada_percpu[i])); + } + cstate_info[state].last_time = n; + cstate_info[state].total_time += n; + if (cstate_info[0].total_time >= n) + cstate_info[0].total_time -= n; + } + + return (DTRACE_AGGWALK_NEXT); +} diff --git a/usr/src/cmd/powertop/display.c b/usr/src/cmd/powertop/display.c new file mode 100644 index 0000000000..322cd1c613 --- /dev/null +++ b/usr/src/cmd/powertop/display.c @@ -0,0 +1,401 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <stdlib.h> +#include <string.h> +#include <curses.h> +#include "powertop.h" + +static WINDOW *title_bar_window; +static WINDOW *cstate_window; +static WINDOW *wakeup_window; +static WINDOW *acpi_power_window; +static WINDOW *eventstat_window; +static WINDOW *suggestion_window; +static WINDOW *status_bar_window; + +#define print(win, y, x, fmt, args...) \ + if (dump) \ + (void) printf(fmt, ## args); \ + else \ + (void) mvwprintw(win, y, x, fmt, ## args); + +char status_bar_slots[10][40]; +int maxx, maxy; + +static void +zap_windows(void) +{ + if (title_bar_window) { + (void) delwin(title_bar_window); + title_bar_window = NULL; + } + if (cstate_window) { + (void) delwin(cstate_window); + cstate_window = NULL; + } + if (wakeup_window) { + (void) delwin(wakeup_window); + wakeup_window = NULL; + } + if (acpi_power_window) { + (void) delwin(acpi_power_window); + acpi_power_window = NULL; + } + if (eventstat_window) { + (void) delwin(eventstat_window); + eventstat_window = NULL; + } + if (suggestion_window) { + (void) delwin(suggestion_window); + suggestion_window = NULL; + } + if (status_bar_window) { + (void) delwin(status_bar_window); + status_bar_window = NULL; + } +} + +void +cleanup_curses(void) +{ + (void) endwin(); +} + +/* + * This part was re-written to be human readable and easy to modify. Please + * try to keep it that way and help us save some time. + * + * Friendly reminder: + * subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x) + */ +void +setup_windows(void) +{ + /* + * These variables are used to properly set the initial y position and + * number of lines in each subwindow, as the number of supported CPU + * states affects their placement. + */ + int cstate_lines, event_lines, pos_y; + + getmaxyx(stdscr, maxy, maxx); + + zap_windows(); + + cstate_lines = TITLE_LINE + max((max_cstate+1), npstates); + + pos_y = 0; + title_bar_window = subwin(stdscr, SINGLE_LINE_SW, maxx, pos_y, 0); + + pos_y += NEXT_LINE + BLANK_LINE; + cstate_window = subwin(stdscr, cstate_lines, maxx, pos_y, 0); + + pos_y += cstate_lines + BLANK_LINE; + wakeup_window = subwin(stdscr, SINGLE_LINE_SW, maxx, pos_y, 0); + + pos_y += NEXT_LINE; + acpi_power_window = subwin(stdscr, SINGLE_LINE_SW, maxx, pos_y, 0); + + pos_y += NEXT_LINE + BLANK_LINE; + event_lines = maxy - SINGLE_LINE_SW - NEXT_LINE - LENGTH_SUGG_SW - + pos_y; + eventstat_window = subwin(stdscr, event_lines, maxx, pos_y, 0); + + pos_y += event_lines + NEXT_LINE; + suggestion_window = subwin(stdscr, SINGLE_LINE_SW, maxx, pos_y, 0); + + pos_y += BLANK_LINE + NEXT_LINE; + status_bar_window = subwin(stdscr, SINGLE_LINE_SW, maxx, pos_y, 0); + + (void) strcpy(status_bar_slots[0], _(" Q - Quit ")); + (void) strcpy(status_bar_slots[1], _(" R - Refresh ")); + + (void) werase(stdscr); + (void) wrefresh(status_bar_window); +} + +void +initialize_curses(void) +{ + (void) initscr(); + (void) start_color(); + + /* + * Enable keyboard mapping + */ + (void) keypad(stdscr, TRUE); + + /* + * Tell curses not to do NL->CR/NL on output + */ + (void) nonl(); + + /* + * Take input chars one at a time, no wait for \n + */ + (void) cbreak(); + + /* + * Dont echo input + */ + (void) noecho(); + + /* + * Turn off cursor + */ + (void) curs_set(0); + + (void) init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); + (void) init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE); + (void) init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED); + (void) init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED); + (void) init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW); + (void) init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN); + (void) init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE); + (void) init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK); + + (void) atexit(cleanup_curses); +} + +void +show_title_bar(void) +{ + int i, x = 0, y = 0; + char title_pad[10]; + + (void) wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); + (void) wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); + (void) werase(title_bar_window); + + (void) snprintf(title_pad, 10, "%%%ds", + (maxx - strlen(TITLE))/2 + strlen(TITLE)); + /* LINTED: E_SEC_PRINTF_VAR_FMT */ + print(title_bar_window, y, x, title_pad, TITLE); + + (void) wrefresh(title_bar_window); + (void) werase(status_bar_window); + + for (i = 0; i < 10; i++) { + if (strlen(status_bar_slots[i]) == 0) + continue; + (void) wattron(status_bar_window, A_REVERSE); + print(status_bar_window, y, x, "%s", status_bar_slots[i]); + (void) wattroff(status_bar_window, A_REVERSE); + x += strlen(status_bar_slots[i]) + 1; + } + (void) wnoutrefresh(status_bar_window); +} + +void +show_cstates(void) +{ + char c[100]; + int i; + double total_pstates = 0.0, avg, res; + + if (!dump) { + (void) werase(cstate_window); + (void) wattrset(cstate_window, COLOR_PAIR(PT_COLOR_DEFAULT)); + (void) wbkgd(cstate_window, COLOR_PAIR(PT_COLOR_DEFAULT)); + } + + print(cstate_window, 0, 0, "%s", "Cn\t\t\tAvg residency\n"); + + res = (((double)cstate_info[0].total_time / total_c_time)) * 100; + (void) sprintf(c, "C0 (cpu running)\t\t(%.1f%%)\n", (float)res); + print(cstate_window, 1, 0, "%s", c); + + for (i = 1; i <= max_cstate; i++) { + /* + * In situations where the load is too intensive, the system + * might not transition at all. + */ + if (cstate_info[i].events > 0) + avg = (((double)cstate_info[i].total_time/g_ncpus)/ + cstate_info[i].events); + else + avg = 0; + + res = ((double)cstate_info[i].total_time/total_c_time) * 100; + + (void) sprintf(c, "C%d\t\t\t%.1fms\t(%.1f%%)\n", i, (float)avg, + (float)res); + print(cstate_window, i + 1, 0, "%s", c); + } + + print(cstate_window, 0, 48, "%s", "P-states (frequencies)\n"); + + if (npstates < 2) { + (void) sprintf(c, "%4lu Mhz\t%.1f%%", + (long)pstate_info[0].speed, 100.0); + print(cstate_window, 1, 48, "%s\n", c); + } else { + for (i = 0; i < npstates; i++) { + total_pstates += (double)(pstate_info[i].total_time/ + g_ncpus/1000000); + } + + for (i = 0; i < npstates; i++) { + (void) sprintf(c, "%4lu Mhz\t%.1f%%", + (long)pstate_info[i].speed, + 100 * (pstate_info[i].total_time/g_ncpus/1000000 + /total_pstates)); + print(cstate_window, i+1, 48, "%s\n", c); + } + } + if (!dump) + (void) wnoutrefresh(cstate_window); +} + +void +show_acpi_power_line(uint32_t flag, double rate, double rem_cap, double cap, + uint32_t state) +{ + char buffer[1024]; + + (void) sprintf(buffer, _("no ACPI power usage estimate available")); + + if (!dump) + (void) werase(acpi_power_window); + if (flag) { + char *c; + (void) sprintf(buffer, "Power usage (ACPI estimate): %.3fW", + rate); + (void) strcat(buffer, " "); + c = &buffer[strlen(buffer)]; + switch (state) { + case 0: + (void) sprintf(c, "(running on AC power, fully " + "charged)"); + break; + case 1: + (void) sprintf(c, "(discharging: %3.1f hours)", + rem_cap/rate); + break; + case 2: + (void) sprintf(c, "(charging: %3.1f hours)", + (cap - rem_cap)/rate); + break; + case 4: + (void) sprintf(c, "(##critically low battery power##)"); + break; + } + + } + print(acpi_power_window, 0, 0, "%s\n", buffer); + if (!dump) + (void) wnoutrefresh(acpi_power_window); +} + +void +show_wakeups(double interval) +{ + char c[100]; + + if (!dump) { + (void) werase(wakeup_window); + (void) wbkgd(wakeup_window, COLOR_PAIR(PT_COLOR_RED)); + (void) wattron(wakeup_window, A_BOLD); + } + (void) sprintf(c, "Wakeups-from-idle per second: %4.1f\tinterval: " + "%.1fs", (double)(total_events/interval), interval); + print(wakeup_window, 0, 0, "%s\n", c); + if (!dump) + (void) wnoutrefresh(wakeup_window); +} + +void +show_eventstats(double interval) +{ + char c[100]; + int i; + double events; + event_info_t *p_event = event_info; + + if (!dump) { + (void) werase(eventstat_window); + (void) wattrset(eventstat_window, COLOR_PAIR(PT_COLOR_DEFAULT)); + (void) wbkgd(eventstat_window, COLOR_PAIR(PT_COLOR_DEFAULT)); + } + + /* + * Sort the event report list + */ + if (top_events > EVENT_NUM_MAX) + top_events = EVENT_NUM_MAX; + + qsort((void *)event_info, top_events, sizeof (event_info_t), + event_compare); + + print(eventstat_window, 0, 0, "%s", "Top causes for wakeups:\n"); + + for (i = 0; i < top_events; i++, p_event++) { + + if (total_events > 0) + events = (double)p_event->total_count/ + (double)total_events; + else + events = 0; + + (void) sprintf(c, "%4.1f%% (%5.1f)", 100 * events, + (double)p_event->total_count/interval); + print(eventstat_window, i+1, 0, "%s", c); + print(eventstat_window, i+1, 16, "%20s :", + p_event->offender_name); + print(eventstat_window, i+1, 40, "%-64s\n", + p_event->offense_name); + } + if (!dump) + (void) wnoutrefresh(eventstat_window); +} + +void +show_suggestion(char *sug) +{ + (void) werase(suggestion_window); + print(suggestion_window, 0, 0, "%s", sug); + (void) wnoutrefresh(suggestion_window); +} + +void +update_windows(void) +{ + (void) doupdate(); +} diff --git a/usr/src/cmd/powertop/events.c b/usr/src/cmd/powertop/events.c new file mode 100644 index 0000000000..6abbeb003b --- /dev/null +++ b/usr/src/cmd/powertop/events.c @@ -0,0 +1,321 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <string.h> +#include <stdlib.h> +#include <dtrace.h> +#include "powertop.h" + +static dtrace_hdl_t *g_dtp; +/* + * DTrace scripts for observing interrupts, callouts and cyclic events + * that cause CPU activity. Such activity prevents the processor from + * entering lower power states and reducing power consumption. + * + * g_prog is the default script + */ +static const char *g_prog = +"interrupt-complete" +"/arg0 != NULL && arg3 !=0/" +"{" +" this->devi = (struct dev_info *)arg0;" +" @interrupts[stringof(`devnamesp[this->devi->devi_major].dn_name)," +" this->devi->devi_instance] = count();" +"}" +"" +"sdt:::callout-start" +"/(caddr_t)((callout_t *)arg0)->c_func == (caddr_t)&`setrun/" +"{" +" this->thr = (kthread_t *)(((callout_t *)arg0)->c_arg);" +" @events_u[stringof(this->thr->t_procp->p_user.u_comm)] = count();" +"}" +"" +"sdt:::callout-start" +"/(caddr_t)((callout_t *)arg0)->c_func != (caddr_t)&`setrun/" +"{" +" @events_k[(caddr_t)((callout_t *)arg0)->c_func] = count();" +"}" +"" +"sdt:::cyclic-start" +"/(caddr_t)((cyclic_t *)arg0)->cy_handler == (caddr_t)&`clock/" +"{" +" @events_k[(caddr_t)((cyclic_t *)arg0)->cy_handler] = count();" +"}" +"" +"sysinfo:::xcalls" +"/pid != $pid/" +"{" +" @events_x[execname] = sum(arg0);" +"}"; + +/* + * g_prog_V is enabled through the -v option, it includes cyclic events + * in the report, allowing a complete view of system activity + */ +static const char *g_prog_v = +"interrupt-complete" +"/arg0 != NULL && arg3 !=0/" +"{" +" this->devi = (struct dev_info *)arg0;" +" @interrupts[stringof(`devnamesp[this->devi->devi_major].dn_name)," +" this->devi->devi_instance] = count();" +"}" +"" +"sdt:::callout-start" +"/(caddr_t)((callout_t *)arg0)->c_func == (caddr_t)&`setrun/" +"{" +" this->thr = (kthread_t *)(((callout_t *)arg0)->c_arg);" +" @events_u[stringof(this->thr->t_procp->p_user.u_comm)] = count();" +"}" +"" +"sdt:::callout-start" +"/(caddr_t)((callout_t *)arg0)->c_func != (caddr_t)&`setrun/" +"{" +" @events_k[(caddr_t)((callout_t *)arg0)->c_func] = count();" +"}" +"" +"sdt:::cyclic-start" +"/(caddr_t)((cyclic_t *)arg0)->cy_handler != (caddr_t)&`dtrace_state_deadman &&" +" (caddr_t)((cyclic_t *)arg0)->cy_handler != (caddr_t)&`dtrace_state_clean/" +"{" +" @events_k[(caddr_t)((cyclic_t *)arg0)->cy_handler] = count();" +"}" +"" +"sysinfo:::xcalls" +"/pid != $pid/" +"{" +" @events_x[execname] = sum(arg0);" +"}"; + +/*ARGSUSED*/ +static int +walk(const dtrace_aggdata_t *data, void *arg) +{ + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + dtrace_recdesc_t *rec1, *rec2; + dtrace_syminfo_t dts; + char *offense_name; + uint64_t offender_addr; + int32_t *instance; + int i; + uint64_t n = 0; + GElf_Sym sym; + + if (top_events >= EVENT_NUM_MAX) + return (0); + + rec1 = &aggdesc->dtagd_rec[1]; + rec2 = &aggdesc->dtagd_rec[2]; + + /* + * Report interrupts + */ + if (strcmp(aggdesc->dtagd_name, "interrupts") == 0) { + offense_name = data->dtada_data + rec1->dtrd_offset; + + /* LINTED - alignment */ + instance = (int32_t *)(data->dtada_data + rec2->dtrd_offset); + (void) snprintf((char *)(p_event->offender_name), + EVENT_NAME_MAX, "%s", "<interrupt>"); + (void) snprintf((char *)(p_event->offense_name), EVENT_NAME_MAX, + "%s#%d", offense_name, *instance); + /* + * Report kernel events + */ + } else if (strcmp(aggdesc->dtagd_name, "events_k") == 0) { + + (void) snprintf((char *)(p_event->offender_name), + EVENT_NAME_MAX, "%s", "<kernel>"); + + /* + * Casting offender_addr to the wrong type will cause + * dtrace_lookup_by_addr to return 0 and the report + * to show an address instead of a name. + */ + switch (bit_depth) { + case 32: + /* LINTED - alignment */ + offender_addr = *(uint32_t *)(data->dtada_data + + rec1->dtrd_offset); + break; + case 64: + /* LINTED - alignment */ + offender_addr = *(uint64_t *)(data->dtada_data + + rec1->dtrd_offset); + break; + } + + /* + * We have the address of the kernel callout. + * Try to resolve it into a meaningful symbol + */ + if (dtrace_lookup_by_addr(g_dtp, offender_addr, + &sym, &dts) == 0) { + (void) snprintf((char *)(p_event->offense_name), + EVENT_NAME_MAX, "%s`%s", dts.dts_object, + dts.dts_name); + } else { + (void) snprintf((char *)(p_event->offense_name), + EVENT_NAME_MAX, "0x%llx", offender_addr); + } + /* + * Report user events + */ + } else if (strcmp(aggdesc->dtagd_name, "events_u") == 0) { + offense_name = data->dtada_data + rec1->dtrd_offset; + + (void) snprintf((char *)(p_event->offender_name), + EVENT_NAME_MAX, "%s", offense_name); + (void) snprintf((char *)(p_event->offense_name), + EVENT_NAME_MAX, "<scheduled timeout expiration>"); + /* + * Report cross calls + */ + } else if (strcmp(aggdesc->dtagd_name, "events_x") == 0) { + offense_name = data->dtada_data + rec1->dtrd_offset; + + (void) snprintf((char *)(p_event->offender_name), + EVENT_NAME_MAX, "%s", offense_name); + (void) snprintf((char *)(p_event->offense_name), + EVENT_NAME_MAX, "<cross calls>"); + /* + * Report unknown events + */ + } else { + (void) snprintf((char *)(p_event->offender_name), + EVENT_NAME_MAX, "%s", "<unknown>"); + (void) snprintf((char *)(p_event->offense_name), + EVENT_NAME_MAX, "%s", "<unknown>"); + } + + for (i = 0; i < g_ncpus; i++) + /* LINTED - alignment */ + n += *((uint64_t *)(data->dtada_percpu[i])); + + p_event->total_count = n; + + p_event++; + top_events++; + + return (DTRACE_AGGWALK_NEXT); +} + +int +pt_events_stat_prepare(void) +{ + dtrace_prog_t *prog; + dtrace_proginfo_t info; + int err; + dtrace_optval_t statustime; + + p_event = event_info; + + if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) { + pt_error("%s : cannot open dtrace library: %s\n", __FILE__, + dtrace_errmsg(NULL, err)); + return (-1); + } + + /* + * Execute different scripts (defined above) depending on + * user specified options. Default mode has event_mode empty + */ + switch (event_mode) { + default: + if ((prog = dtrace_program_strcompile(g_dtp, g_prog, + DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) { + pt_error("%s : failed to compile g_prog\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + break; + case 'v': + if ((prog = dtrace_program_strcompile(g_dtp, g_prog_v, + DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) { + pt_error("%s : failed to compile g_prog_v\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + break; + } + + if (dtrace_program_exec(g_dtp, prog, &info) == -1) { + pt_error("%s : failed to enable probes\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_setopt(g_dtp, "aggsize", "128k") == -1) { + pt_error("%s : failed to set 'aggsize'\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_setopt(g_dtp, "aggrate", "0") == -1) { + pt_error("%s : failed to set 'aggrate'\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_setopt(g_dtp, "aggpercpu", 0) == -1) { + pt_error("%s : failed to set 'aggpercpu'\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_go(g_dtp) != 0) { + pt_error("%s : dtrace_go() failed\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + if (dtrace_getopt(g_dtp, "statusrate", &statustime) == -1) { + pt_error("%s : failed to get 'statusrate'\n", __FILE__); + return (dtrace_errno(g_dtp)); + } + return (0); +} + +int +pt_events_stat_collect(void) +{ + p_event = event_info; + top_events = 0; + + if (dtrace_status(g_dtp) == -1) + return (-1); + + if (dtrace_aggregate_snap(g_dtp) != 0) + pt_error("%s : failed to add to aggregate", __FILE__); + + if (dtrace_aggregate_walk_keyvarsorted(g_dtp, walk, NULL) != 0) + pt_error("%s : failed to sort aggregate", __FILE__); + + dtrace_aggregate_clear(g_dtp); + + return (0); +} diff --git a/usr/src/cmd/powertop/i386/Makefile b/usr/src/cmd/powertop/i386/Makefile new file mode 100644 index 0000000000..eadb23ed43 --- /dev/null +++ b/usr/src/cmd/powertop/i386/Makefile @@ -0,0 +1,27 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +install: all $(ROOTPROG32) diff --git a/usr/src/cmd/powertop/powertop.c b/usr/src/cmd/powertop/powertop.c new file mode 100644 index 0000000000..22e2307926 --- /dev/null +++ b/usr/src/cmd/powertop/powertop.c @@ -0,0 +1,335 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <getopt.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <locale.h> +#include "powertop.h" + +int g_ncpus; +processorid_t *cpu_table; +const int true = 1; + +int +main(int argc, char **argv) +{ + hrtime_t last, now; + uint_t features = 0, user_interval = 0; + int ncursesinited = 0, index2 = 0, c, ret, dump_count = 0; + double last_time; + char *endptr; + + static struct option opts[] = { + { "dump", 1, NULL, 'd' }, + { "time", 1, NULL, 't' }, + { "help", 0, NULL, 'h' }, + { "verbose", 0, NULL, 'v' }, + { 0, 0, NULL, 0 } + }; + + (void) setlocale(LC_ALL, ""); + (void) bindtextdomain("powertop", "/usr/share/locale"); + (void) textdomain("powertop"); + + pt_set_progname(argv[0]); + + if ((bit_depth = get_bit_depth()) < 0) + exit(EXIT_FAILURE); + + ticktime = ticktime_usr = INTERVAL_DEFAULT; + displaytime = 0.0; + dump = 0; + event_mode = ' '; + max_cstate = 0; + + while ((c = getopt_long(argc, argv, "d:vt:h", opts, &index2)) != EOF) { + if (c == -1) + break; + + switch (c) { + case 'd': + if (dump) + usage(); + + dump = 1; + dump_count = (int)strtod(optarg, &endptr); + + if (dump_count <= 0 || *endptr != NULL) + usage(); + break; + case 't': + if (user_interval) + usage(); + + user_interval = 1; + ticktime = ticktime_usr = (double)strtod(optarg, + &endptr); + + if (*endptr != NULL || ticktime < 1 || + ticktime > INTERVAL_MAX) + usage(); + break; + case 'v': + if (event_mode == 'v') + usage(); + + event_mode = 'v'; + break; + case 'h': + default: + usage(); + return (EXIT_USAGE); + } + } + + if (optind < argc) { + usage(); + } + + (void) printf("%s (C) 2008 Intel Corporation\n\n", TITLE); + + /* + * Enumerate the system's CPUs + * Populate cpu_table, g_ncpus + */ + enumerate_cpus(); + + /* + * If the system is running on battery, find out what's + * the kstat module for it + */ + battery_mod_lookup(); + + /* Prepare C-state statistics */ + ret = pt_cpuidle_stat_prepare(); + if (ret == 0) + features |= FEATURE_CSTATE; + else + /* + * PowerTop was unable to run a DTrace program, + * most likely for lack of permissions. + */ + exit(EXIT_FAILURE); + + /* Prepare P-state statistics */ + if (pt_cpufreq_stat_prepare() == 0) + features |= FEATURE_PSTATE; + + /* Prepare event statistics */ + if (pt_events_stat_prepare() != -1) + features |= FEATURE_EVENTS; + + (void) printf(_("Collecting data for %.2f second(s) \n"), + (float)ticktime); + + last = gethrtime(); + + while (true) { + fd_set rfds; + struct timeval tv; + int key, reinit = 0; + char keychar; + + /* + * Sleep for a while waiting either for input (if we're not + * in dump mode) or for the timeout to elapse + */ + FD_ZERO(&rfds); + FD_SET(0, &rfds); + + tv.tv_sec = (long)ticktime; + tv.tv_usec = (long)((ticktime - tv.tv_sec) * 1000000); + + if (!dump) + key = select(1, &rfds, NULL, NULL, &tv); + else + key = select(1, NULL, NULL, NULL, &tv); + + now = gethrtime(); + + g_interval = (double)(now - last)/NANOSEC; + last = now; + + top_events = 0; + total_events = 0; + + (void) memset(event_info, EVENT_NUM_MAX * sizeof (event_info_t), + 0); + (void) memset(cstate_info, 2 * sizeof (state_info_t), 0); + + /* Collect idle state transition stats */ + if (features & FEATURE_CSTATE && + pt_cpuidle_stat_collect(g_interval) < 0) { + /* Reinitialize C-state statistics */ + if (pt_cpuidle_stat_prepare() != 0) + exit(EXIT_FAILURE); + + reinit = 1; + } + + /* Collect frequency change stats */ + if (features & FEATURE_PSTATE && + pt_cpufreq_stat_collect(g_interval) < 0) { + /* Reinitialize P-state statistics */ + if (pt_cpufreq_stat_prepare() != 0) + exit(EXIT_FAILURE); + + reinit = 1; + } + + /* Collect event statistics */ + if (features & FEATURE_EVENTS && + pt_events_stat_collect() < 0) { + /* Reinitialize event statistics */ + if (pt_events_stat_prepare() != 0) + exit(EXIT_FAILURE); + + reinit = 1; + } + + if (reinit) + continue; + + /* + * Initialize curses if we're not dumping and + * haven't already done it + */ + if (!dump) { + if (!ncursesinited) { + initialize_curses(); + ncursesinited++; + } + setup_windows(); + show_title_bar(); + } + + /* Show CPU power states */ + if (features & FEATURE_CSTATE) + show_cstates(); + + /* Show wakeups events affecting PM */ + if (features & FEATURE_EVENTS) { + show_wakeups(g_interval); + show_eventstats(g_interval); + } + + print_battery(); + + displaytime = displaytime - ticktime; + + if (key && !dump) { + keychar = toupper(fgetc(stdin)); + + switch (keychar) { + case 'Q': + cleanup_curses(); + exit(EXIT_SUCCESS); + break; + case 'R': + ticktime = 3; + break; + } + if (keychar == suggestion_key && suggestion_activate) { + suggestion_activate(); + displaytime = -1.0; + } + } + reset_suggestions(); + + /* suggests PM */ + if (geteuid() == 0) { + suggest_p_state(); + } else { + suggest_as_root(); + } + + if (dump_count) + dump_count--; + + /* Exits if user requested a dump */ + if (dump && !dump_count) { + print_all_suggestions(); + exit(EXIT_SUCCESS); + } + + /* No key pressed, will suggest something */ + if (!key && !dump_count) + pick_suggestion(); + + /* Refresh display */ + if (!dump) { + show_title_bar(); + update_windows(); + } + + /* + * Update the interval based on how long the CPU was in the + * longest c-state during the last snapshot. If the user + * specified an interval we skip this bit and keep it fixed. + */ + last_time = (((double)cstate_info[longest_cstate].total_time/ + g_ncpus)/cstate_info[longest_cstate].events); + + if (!user_interval) + if (last_time < INTERVAL_DEFAULT || + (total_events/ticktime) < 1) + ticktime = INTERVAL_DEFAULT; + else + ticktime = INTERVAL_UPDATE(last_time); + + /* + * Restore user specified interval after a refresh + */ + if (keychar == 'R' && user_interval) + ticktime = ticktime_usr; + } + return (EXIT_SUCCESS); +} + +void +suggest_as_root(void) +{ + add_suggestion("Suggestion: run as root to get suggestions" + " for reducing system power consumption", 40, NULL, NULL, + NULL); +} diff --git a/usr/src/cmd/powertop/powertop.h b/usr/src/cmd/powertop/powertop.h new file mode 100644 index 0000000000..f1dee36dbe --- /dev/null +++ b/usr/src/cmd/powertop/powertop.h @@ -0,0 +1,277 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#ifndef __INCLUDE_GUARD_POWERTOP_H_ +#define __INCLUDE_GUARD_POWERTOP_H_ + +#include <sys/types.h> +#include <libintl.h> +#include <sys/processor.h> + +#define max(A, B) (((A) < (B)) ? (B) : (A)) + +#define _(STRING) gettext(STRING) + +#define TITLE "OpenSolaris PowerTOP version 1.1" + +/* + * Exit values. stdlib.h defines EXIT_SUCCESS as 0 and + * EXIT_FAILURE as 1 + */ +#define EXIT_USAGE 2 + +/* + * PowerTop Features + * These may not be available everywhere + */ +#define FEATURE_CSTATE 0x1 +#define FEATURE_PSTATE 0x2 +#define FEATURE_EVENTS 0x4 + +#define BIT_DEPTH_BUF 10 + +#define INTERVAL_DEFAULT 5.0 +#define INTERVAL_MAX 100.0 +#define INTERVAL_UPDATE(l) \ + ((l/INTERVAL_DEFAULT) * INTERVAL_DEFAULT + INTERVAL_DEFAULT) + +#define STATE_NAME_MAX 16 +#define EVENT_NAME_MAX 64 +#define EVENT_NUM_MAX 100 +#define NSTATES 32 + +/* + * Display colors + */ +#define PT_COLOR_DEFAULT 1 +#define PT_COLOR_HEADER_BAR 2 +#define PT_COLOR_ERROR 3 +#define PT_COLOR_RED 4 +#define PT_COLOR_YELLOW 5 +#define PT_COLOR_GREEN 6 +#define PT_COLOR_BRIGHT 7 +#define PT_COLOR_BLUE 8 + +/* + * Constants for setup_windows() + */ +#define SINGLE_LINE_SW 1 +#define LENGTH_SUGG_SW 2 +#define TITLE_LINE 1 +#define BLANK_LINE 1 +#define NEXT_LINE 1 + +/* + * Structures and typedefs + */ +struct line { + char *string; + int count; +}; + +typedef struct event_info { + char offender_name[EVENT_NAME_MAX]; + char offense_name[EVENT_NAME_MAX]; + uint64_t total_count; +} event_info_t; + +/* + * P/C state information + */ +typedef struct state_info { + char name[STATE_NAME_MAX]; + hrtime_t total_time; + hrtime_t last_time; + double events; +} state_info_t; + +typedef struct pstate_info { + uint64_t speed; + hrtime_t total_time; +} pstate_info_t; + +typedef struct cpu_power_info { + uint64_t current_pstate; + hrtime_t time_accounted; + hrtime_t dtrace_time; +} cpu_power_info_t; + +typedef void (suggestion_func)(void); + +/* + * Global variables + */ +double displaytime; + +int bit_depth; + +/* + * Event accounting + */ +int total_events; +int top_events; + +/* + * Interval + */ +double ticktime, ticktime_usr; +double g_interval; + +/* + * Command line arguments + */ +int dump; +char event_mode; + +/* + * Event info array + */ +event_info_t event_info[EVENT_NUM_MAX]; +event_info_t *p_event; + +/* + * Lookup table, sequential CPU id to Solaris CPU id + */ +processorid_t *cpu_table; + +/* + * Number of idle/frequency states + */ +int npstates; +int max_cstate; +int longest_cstate; + +/* + * Total time, used to display different idle states + */ +hrtime_t total_c_time; + +/* + * P/C state info arrays + */ +state_info_t cstate_info[NSTATES]; +pstate_info_t pstate_info[NSTATES]; + +/* + * Per CPU power state information + */ +cpu_power_info_t *cpu_power_states; + +/* + * Extern declarations + */ +extern struct line *lines; +extern int linehead; +extern int linesize; +extern int linectotal; + +extern int g_ncpus; + +/* + * kstat's battery module + */ +extern char *kstat_batt_mod[3]; +extern uint_t kstat_batt_idx; + +extern int topcstate; +extern int topfreq; +extern int dump; + +extern char *prog; + +extern char status_bar_slots[10][40]; + +extern const int true, false; + +extern char suggestion_key; +extern suggestion_func *suggestion_activate; + +/* + * Suggestions related + */ +extern void suggest_p_state(void); +extern void suggest_as_root(void); + +/* + * See util.c + */ +extern void pt_error(char *, ...); +extern void pt_set_progname(char *); +extern void enumerate_cpus(void); +extern void usage(void); +extern int get_bit_depth(void); +extern void battery_mod_lookup(void); +extern int event_compare(const void *, const void *); + +/* + * Display/curses related + */ +extern void show_title_bar(void); +extern void setup_windows(void); +extern void initialize_curses(void); +extern void show_acpi_power_line(uint32_t flag, double rate, + double rem_cap, double cap, uint32_t state); +extern void show_cstates(); +extern void show_wakeups(double interval); +extern void show_eventstats(double interval); +extern void show_suggestion(char *sug); +extern void cleanup_curses(void); +extern void update_windows(void); + +/* + * Suggestions + */ +extern void pick_suggestion(void); +extern void add_suggestion(char *text, int weight, char key, + char *keystring, suggestion_func *func); +extern void reset_suggestions(void); +extern void print_all_suggestions(void); +extern void print_battery(void); + +/* + * DTrace stats + */ +extern int pt_cpufreq_stat_prepare(void); +extern int pt_cpufreq_stat_collect(double interval); +extern int pt_cpuidle_stat_prepare(void); +extern int pt_cpuidle_stat_collect(double interval); +extern int pt_events_stat_prepare(void); +extern int pt_events_stat_collect(void); + +#endif /* __INCLUDE_GUARD_POWERTOP_H_ */ diff --git a/usr/src/cmd/powertop/sparcv9/Makefile b/usr/src/cmd/powertop/sparcv9/Makefile new file mode 100644 index 0000000000..d99d30ca4c --- /dev/null +++ b/usr/src/cmd/powertop/sparcv9/Makefile @@ -0,0 +1,28 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com +include ../../Makefile.cmd.64 + +install: all $(ROOTPROG64) diff --git a/usr/src/cmd/powertop/suggestions.c b/usr/src/cmd/powertop/suggestions.c new file mode 100644 index 0000000000..22995b26f7 --- /dev/null +++ b/usr/src/cmd/powertop/suggestions.c @@ -0,0 +1,181 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "powertop.h" + +char suggestion_key; +suggestion_func *suggestion_activate; + +struct suggestion; + +struct suggestion { + struct suggestion *next; + + char *string; + int weight; + char key; + char *keystring; + + suggestion_func *func; +}; + +static struct suggestion *suggestions; +static int total_weight; + +static char previous[1024]; + +void +reset_suggestions(void) +{ + struct suggestion *ptr; + + ptr = suggestions; + + while (ptr) { + struct suggestion *next; + + next = ptr->next; + free(ptr->string); + free(ptr->keystring); + free(ptr); + ptr = next; + } + + suggestions = NULL; + (void) strcpy(status_bar_slots[8], ""); + + suggestion_key = -1; + suggestion_activate = NULL; + total_weight = 0; +} + +void +add_suggestion(char *text, int weight, char key, char *keystring, + suggestion_func *func) +{ + struct suggestion *new; + + if (!text) + return; + + new = malloc(sizeof (struct suggestion)); + + if (!new) + return; + + (void) memset(new, 0, sizeof (struct suggestion)); + + new->string = strdup(text); + new->weight = weight; + new->key = key; + + if (keystring) + new->keystring = strdup(keystring); + + new->next = suggestions; + new->func = func; + suggestions = new; + total_weight += weight; +} + +void +pick_suggestion(void) +{ + int weight, value, running = 0; + struct suggestion *ptr; + + (void) strcpy(status_bar_slots[8], ""); + suggestion_key = -1; + suggestion_activate = NULL; + + if (total_weight == 0 || suggestions == NULL) { + show_suggestion(""); + return; + } + + weight = total_weight; + + if (strlen(previous) && displaytime > 0.0) + weight += 50; + + value = rand() % weight; + ptr = suggestions; + + while (ptr) { + running += ptr->weight; + + if (strcmp(ptr->string, previous) == 0 && displaytime > 0.0) + running += 50; + + if (running > value) { + if (ptr->keystring) + (void) strncpy(status_bar_slots[8], + ptr->keystring, 40); + + suggestion_key = ptr->key; + suggestion_activate = ptr->func; + + show_suggestion(ptr->string); + + if (strcmp(ptr->string, previous)) { + displaytime = 30.0; + (void) strcpy(previous, ptr->string); + } + return; + } + ptr = ptr->next; + } + + show_suggestion(""); + (void) memset(previous, 0, sizeof (previous)); + displaytime = -1.0; +} + +void +print_all_suggestions(void) +{ + struct suggestion *ptr; + + for (ptr = suggestions; ptr; ptr = ptr->next) + (void) printf("\n%s\n", ptr->string); +} diff --git a/usr/src/cmd/powertop/util.c b/usr/src/cmd/powertop/util.c new file mode 100644 index 0000000000..35bd160f30 --- /dev/null +++ b/usr/src/cmd/powertop/util.c @@ -0,0 +1,179 @@ +/* + * Copyright 2008, Intel Corporation + * Copyright 2008, Sun Microsystems, Inc + * + * 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> + * Eric C Saxe <eric.saxe@sun.com> + * Aubrey Li <aubrey.li@intel.com> + */ + +/* + * GPL Disclaimer + * + * For the avoidance of doubt, except that if any license choice other + * than GPL or LGPL is available it will apply instead, Sun elects to + * use only the General Public License version 2 (GPLv2) at this time + * for any software where a choice of GPL license versions is made + * available with the language indicating that GPLv2 or any later + * version may be used, or where a choice of which version of the GPL + * is applied is otherwise unspecified. + */ + +#include <stdarg.h> +#include <stdlib.h> +#include <libgen.h> +#include <unistd.h> +#include <strings.h> +#include <sys/systeminfo.h> +#include <kstat.h> +#include <errno.h> +#include "powertop.h" + +static char PROG_FMT[] = "%s: "; +static char ERR_FMT[] = ": %s"; +static char *progname; + +char *kstat_batt_mod[3] = {"NULL", "battery", "acpi_drv"}; +uint_t kstat_batt_idx; + +void +pt_set_progname(char *name) +{ + progname = basename(name); +} + +/*PRINTFLIKE1*/ +void +pt_error(char *format, ...) +{ + int err = errno; + va_list alist; + + if (progname != NULL) + (void) fprintf(stderr, PROG_FMT, progname); + + va_start(alist, format); + (void) vfprintf(stderr, format, alist); + va_end(alist); + + if (strchr(format, '\n') == NULL) + (void) fprintf(stderr, gettext(ERR_FMT), strerror(err)); +} + +void +enumerate_cpus(void) +{ + int cpuid; + int ncpus = 0; + int max, cpus_conf; + + max = sysconf(_SC_CPUID_MAX); + cpus_conf = sysconf(_SC_NPROCESSORS_CONF); + cpu_table = malloc(cpus_conf * sizeof (processorid_t)); + + for (cpuid = 0; cpuid < max; cpuid++) { + if (p_online(cpuid, P_STATUS) != -1) { + cpu_table[ncpus] = cpuid; + ncpus++; + } + } + g_ncpus = ncpus; +} + +void +usage(void) +{ + (void) fprintf(stderr, "%s (C) 2008 Intel Corporation\n\n", TITLE); + (void) fprintf(stderr, "Usage: powertop [option]\n"); + (void) fprintf(stderr, " -d, --dump [count] Read wakeups count " + "times and print list of top offenders\n"); + (void) fprintf(stderr, " -t, --time [interval] Default time to gather " + "data in seconds [1-100s]\n"); + (void) fprintf(stderr, " -v, --verbose Verbose mode, reports " + "kernel cyclic activity\n"); + (void) fprintf(stderr, " -h, --help Show this help " + "message\n"); + + exit(EXIT_USAGE); +} + +int +get_bit_depth(void) +{ + /* + * This little routine was derived from isainfo.c to look up + * the system's bit depth. It feeds a 10 byte long buffer to + * sysinfo (we only need the first word, sysinfo truncates and + * \0 terminates the rest) from which we figure out which isa + * we're running on. + */ + char buf[BIT_DEPTH_BUF]; + + if (sysinfo(SI_ARCHITECTURE_64, buf, BIT_DEPTH_BUF) == -1) + if (sysinfo(SI_ARCHITECTURE_32, buf, BIT_DEPTH_BUF) == -1) + return (-2); + + if (strcmp(buf, "sparc") == 0 || strcmp(buf, "i386") == 0) + return (32); + + if (strcmp(buf, "sparcv9") == 0 || strcmp(buf, "amd64") == 0) + return (64); + + return (-3); +} + +/* + * Checks if the kstat module for battery information is present and + * whether it's called 'battery' or 'acpi_drv' + */ +void +battery_mod_lookup(void) +{ + kstat_ctl_t *kc = kstat_open(); + + if (kstat_lookup(kc, kstat_batt_mod[1], 0, NULL)) + kstat_batt_idx = 1; + else + if (kstat_lookup(kc, kstat_batt_mod[2], 0, NULL)) + kstat_batt_idx = 2; + else + kstat_batt_idx = 0; + + (void) kstat_close(kc); +} + +/* + * Simple integer comparison routine for the event report qsort(3C). + */ +int +event_compare(const void *p1, const void *p2) +{ + event_info_t i = *((event_info_t *)p1); + event_info_t j = *((event_info_t *)p2); + + if (i.total_count > j.total_count) + return (-1); + + if (i.total_count < j.total_count) + return (1); + + return (0); +} diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile index 9c69626855..a150618fe3 100644 --- a/usr/src/pkgdefs/Makefile +++ b/usr/src/pkgdefs/Makefile @@ -340,6 +340,7 @@ COMMON_SUBDIRS= \ SUNWpool \ SUNWpoold \ SUNWpoolr \ + SUNWpowertop \ SUNWppm \ SUNWpppd \ SUNWpppdu \ diff --git a/usr/src/pkgdefs/SUNWpowertop/Makefile b/usr/src/pkgdefs/SUNWpowertop/Makefile new file mode 100644 index 0000000000..7a1708facd --- /dev/null +++ b/usr/src/pkgdefs/SUNWpowertop/Makefile @@ -0,0 +1,37 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +DATAFILES += depend + +LICENSEFILES = $(GPLV2) + +.KEEP_STATE: + +all: $(FILES) +install: all pkg + +include ../Makefile.targ diff --git a/usr/src/pkgdefs/SUNWpowertop/pkginfo.tmpl b/usr/src/pkgdefs/SUNWpowertop/pkginfo.tmpl new file mode 100644 index 0000000000..f9d37cbf47 --- /dev/null +++ b/usr/src/pkgdefs/SUNWpowertop/pkginfo.tmpl @@ -0,0 +1,50 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWpowertop" +NAME="PowerTOP tool" +ARCH="ISA" +VERSION="ONVERS,REV=0.0.0" +CATEGORY="system" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +DESC="PowerTOP tool" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="usr" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +CLASSES="none" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="false" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWpowertop/prototype_com b/usr/src/pkgdefs/SUNWpowertop/prototype_com new file mode 100644 index 0000000000..709b7eebf4 --- /dev/null +++ b/usr/src/pkgdefs/SUNWpowertop/prototype_com @@ -0,0 +1,46 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# packaging files +i pkginfo +i copyright +i depend +# +# source locations relative to the prototype file +# +# SUNWpowertop +# +d none usr 0755 root sys +d none usr/bin 0755 root bin +l none usr/bin/powertop=../../usr/lib/isaexec diff --git a/usr/src/pkgdefs/SUNWpowertop/prototype_i386 b/usr/src/pkgdefs/SUNWpowertop/prototype_i386 new file mode 100644 index 0000000000..38a8af0c76 --- /dev/null +++ b/usr/src/pkgdefs/SUNWpowertop/prototype_i386 @@ -0,0 +1,52 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# +# List files which are I386 specific here +# +# source locations relative to the prototype file +# +# +# SUNWpowertop +# +d none usr/bin/i86 755 root bin +f none usr/bin/i86/powertop 555 root bin +d none usr/bin/amd64 755 root bin +f none usr/bin/amd64/powertop 555 root bin diff --git a/usr/src/pkgdefs/SUNWpowertop/prototype_sparc b/usr/src/pkgdefs/SUNWpowertop/prototype_sparc new file mode 100644 index 0000000000..91c2626d92 --- /dev/null +++ b/usr/src/pkgdefs/SUNWpowertop/prototype_sparc @@ -0,0 +1,50 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWpowertop +# +d none usr/bin/sparcv9 755 root bin +f none usr/bin/sparcv9/powertop 555 root bin |