On Wed, 01 May 2024 23:54:52 +0200, "Nathaniel Griswold" <n...@fastmail.com> wrote: > > Interesting, maybe i'll test on it. >
I've played with this patch a bit more today, as result I've inlined an updated version to end of this email. > > Regarding estimated life time: > > > > Battery state: high, 66% remaining, 152 minutes life estimate > > AC adapter state: not connected > > Performance adjustment mode: powersaving (400 MHz) > > > > which is like 2x from usual numbers. > > > > This is for idle current usage? > > I tried disabling cores in my bios down to 3 CPUs and did comparisons and i > didn't really notice a savings. Yep, it is. With a few reboot and restart heavy application like chrome with ton of tabs, rebuilding whole kernel a few times on battery (!), it running 3 hours 10 minutes, and I've started with ~95% battery, not 100%. A bit history from logs regarding apmd: 1. Snapshot's kernel: Apr 28 13:32:23 matebook apmd: battery status: CRITICAL. external power status: not connected. estimated battery life 14% (11 minutes life time estimate) 2. Solene's patch: May 1 11:52:28 matebook apmd: battery status: CRITICAL. external power status: not connected. estimated battery life 14% (14 minutes life time estimate) 3. Attached patch: Battery state: CRITICAL, 13% remaining, 31 minutes life estimate AC adapter state: not connected Performance adjustment mode: powersaving (400 MHz) Thus, the current version quite comfortable to use. diff --git sys/kern/sched_bsd.c sys/kern/sched_bsd.c index 25b221c1ee2..c01bb93d94f 100644 --- sys/kern/sched_bsd.c +++ sys/kern/sched_bsd.c @@ -65,8 +65,11 @@ void update_loadavg(void *); void schedcpu(void *); uint32_t decay_aftersleep(uint32_t, uint32_t); +extern struct cpuset sched_all_cpus; extern struct cpuset sched_idle_cpus; +extern int sched_smt; + /* * constants for averages over 1, 5, and 15 minutes when sampling at * 5 second intervals. @@ -573,6 +576,7 @@ void (*cpu_setperf)(int); #define PERFPOL_MANUAL 0 #define PERFPOL_AUTO 1 #define PERFPOL_HIGH 2 +#define PERFPOL_POWERSAVING 4 int perflevel = 100; int perfpolicy = PERFPOL_AUTO; @@ -583,7 +587,9 @@ int perfpolicy = PERFPOL_AUTO; #include <sys/sysctl.h> void setperf_auto(void *); +void setperf_powersaving(void *); struct timeout setperf_to = TIMEOUT_INITIALIZER(setperf_auto, NULL); +struct timeout setperf_to_powersaving = TIMEOUT_INITIALIZER(setperf_powersaving, NULL); extern int hw_power; void @@ -653,6 +659,101 @@ faster: timeout_add_msec(&setperf_to, 100); } +void +setperf_powersaving(void *v) +{ + static uint64_t *idleticks, *totalticks; + static int downbeats; + int i, j = 0; + int speedup = 0; + CPU_INFO_ITERATOR cii; + struct cpu_info *ci, *firstoffline = NULL, *lastidle = NULL; + uint64_t idle, total, allidle = 0, alltotal = 0; + + if (perfpolicy != PERFPOL_POWERSAVING) + goto recover; + + if (cpu_setperf == NULL) + goto recover; + + if (hw_power) + goto recover; + + if (!idleticks) + if (!(idleticks = mallocarray(ncpusfound, sizeof(*idleticks), + M_DEVBUF, M_NOWAIT | M_ZERO))) + return; + if (!totalticks) + if (!(totalticks = mallocarray(ncpusfound, sizeof(*totalticks), + M_DEVBUF, M_NOWAIT | M_ZERO))) { + free(idleticks, M_DEVBUF, + sizeof(*idleticks) * ncpusfound); + return; + } + CPU_INFO_FOREACH(cii, ci) { + if (!cpu_is_online(ci)) { + if (!firstoffline && (sched_smt || ci->ci_smt_id == 0)) + firstoffline = ci; + continue; + } + total = 0; + for (i = 0; i < CPUSTATES; i++) { + total += ci->ci_schedstate.spc_cp_time[i]; + } + total -= totalticks[j]; + idle = ci->ci_schedstate.spc_cp_time[CP_IDLE] - idleticks[j]; + if (idle < total / 3) + speedup = 1; + alltotal += total; + allidle += idle; + idleticks[j] += idle; + totalticks[j] += total; + /* it shoul keep at least one CPU online */ + if (j++ && cpuset_isset(&sched_idle_cpus, ci)) + lastidle = ci; + } + if (allidle < alltotal / 3) + speedup = 1; + if (speedup) + /* twice as long here because we check every 200ms */ + downbeats = 1; + + if (speedup && perflevel != 100) { + perflevel = 100; + cpu_setperf(perflevel); + } else if (speedup && firstoffline) { + atomic_clearbits_int(&firstoffline->ci_schedstate.spc_schedflags, + SPCF_SHOULDHALT | SPCF_HALTED); + cpuset_add(&sched_all_cpus, firstoffline); + need_resched(firstoffline); + } else if (!speedup && perflevel != 0 && --downbeats <= 0) { + perflevel = 0; + cpu_setperf(perflevel); + } else if (!speedup && lastidle) { + cpuset_del(&sched_all_cpus, lastidle); + atomic_setbits_int(&lastidle->ci_schedstate.spc_schedflags, + SPCF_SHOULDHALT); + need_resched(lastidle); + } + + /* every 200ms to have a better resolution of the load */ + timeout_add_msec(&setperf_to_powersaving, 200); + return; + +recover: + CPU_INFO_FOREACH(cii, ci) { + if (!cpu_is_online(ci)) { + if (sched_smt || ci->ci_smt_id == 0) { + atomic_clearbits_int(&ci->ci_schedstate.spc_schedflags, + SPCF_SHOULDHALT | SPCF_HALTED); + cpuset_add(&sched_all_cpus, ci); + need_resched(ci); + } + } + } +} + + int sysctl_hwsetperf(void *oldp, size_t *oldlenp, void *newp, size_t newlen) { @@ -691,6 +792,9 @@ sysctl_hwperfpolicy(void *oldp, size_t *oldlenp, void *newp, size_t newlen) case PERFPOL_AUTO: strlcpy(policy, "auto", sizeof(policy)); break; + case PERFPOL_POWERSAVING: + strlcpy(policy, "powersaving", sizeof(policy)); + break; case PERFPOL_HIGH: strlcpy(policy, "high", sizeof(policy)); break; @@ -709,6 +813,8 @@ sysctl_hwperfpolicy(void *oldp, size_t *oldlenp, void *newp, size_t newlen) perfpolicy = PERFPOL_MANUAL; else if (strcmp(policy, "auto") == 0) perfpolicy = PERFPOL_AUTO; + else if (strcmp(policy, "powersaving") == 0) + perfpolicy = PERFPOL_POWERSAVING; else if (strcmp(policy, "high") == 0) perfpolicy = PERFPOL_HIGH; else @@ -716,6 +822,8 @@ sysctl_hwperfpolicy(void *oldp, size_t *oldlenp, void *newp, size_t newlen) if (perfpolicy == PERFPOL_AUTO) { timeout_add_msec(&setperf_to, 200); + } else if (perfpolicy == PERFPOL_POWERSAVING) { + timeout_add_msec(&setperf_to_powersaving, 200); } else if (perfpolicy == PERFPOL_HIGH) { perflevel = 100; cpu_setperf(perflevel); diff --git usr.sbin/apmd/apm-proto.h usr.sbin/apmd/apm-proto.h index 867d0afbd70..166618e996f 100644 --- usr.sbin/apmd/apm-proto.h +++ usr.sbin/apmd/apm-proto.h @@ -38,6 +38,7 @@ enum apm_action { SETPERF_LOW, SETPERF_HIGH, SETPERF_AUTO, + SETPERF_POWERSAVING, }; enum apm_state { @@ -51,6 +52,7 @@ enum apm_perfmode { PERF_NONE = -1, PERF_MANUAL, PERF_AUTO, + PERF_POWERSAVING, }; struct apm_command { diff --git usr.sbin/apmd/apmd.8 usr.sbin/apmd/apmd.8 index 7c02bb10c8a..4647ed3e126 100644 --- usr.sbin/apmd/apmd.8 +++ usr.sbin/apmd/apmd.8 @@ -34,7 +34,7 @@ .Nd Advanced Power Management daemon .Sh SYNOPSIS .Nm apmd -.Op Fl AadHLs +.Op Fl AadHLPs .Op Fl f Ar devname .Op Fl S Ar sockname .Op Fl t Ar seconds @@ -94,6 +94,9 @@ Start in manual performance adjustment mode, initialising .Va hw.setperf to 0. +.It Fl P +.Nm +in auto mode use powersaving policy when on battery. .It Fl S Ar sockname Specify an alternate socket name, .Ar sockname . diff --git usr.sbin/apmd/apmd.c usr.sbin/apmd/apmd.c index f29d5c9a081..b6d8c5e8aa6 100644 --- usr.sbin/apmd/apmd.c +++ usr.sbin/apmd/apmd.c @@ -58,6 +58,7 @@ #define AUTO_HIBERNATE 2 int debug = 0; +int use_powersaving = 0; extern char *__progname; @@ -100,7 +101,7 @@ void usage(void) { fprintf(stderr, - "usage: %s [-AadHLs] [-f devname] [-S sockname] [-t seconds] " + "usage: %s [-AadHLPs] [-f devname] [-S sockname] [-t seconds] " "[-Z percent] [-z percent]\n", __progname); exit(1); } @@ -139,6 +140,10 @@ power_status(int fd, int force, struct apm_power_info *pinfo) static struct apm_power_info last; int acon = 0, priority = LOG_NOTICE; + int perfpol_mib[] = { CTL_HW, HW_PERFPOLICY }; + char perfpol[32]; + size_t perfpol_sz = sizeof(perfpol); + if (fd == -1) { if (pinfo) { bstate.battery_state = 255; @@ -151,11 +156,19 @@ power_status(int fd, int force, struct apm_power_info *pinfo) return 0; } + if (sysctl(perfpol_mib, 2, perfpol, &perfpol_sz, NULL, 0) == -1) + logmsg(LOG_INFO, "cannot read hw.perfpolicy"); + if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) { /* various conditions under which we report status: something changed * enough since last report, or asked to force a print */ - if (bstate.ac_state == APM_AC_ON) + if (bstate.ac_state == APM_AC_ON) { acon = 1; + if(use_powersaving && strcmp(perfpol, "powersaving") == 0) + setperfpolicy("auto"); + } else if(use_powersaving && strcmp(perfpol, "auto") == 0) + setperfpolicy("powersaving"); + if (bstate.battery_state == APM_BATT_CRITICAL && bstate.battery_state != last.battery_state) priority = LOG_EMERG; @@ -282,6 +295,11 @@ handle_client(int sock_fd, int ctl_fd) logmsg(LOG_NOTICE, "setting hw.perfpolicy to high"); setperfpolicy("high"); break; + case SETPERF_POWERSAVING: + reply.newstate = NORMAL; + logmsg(LOG_NOTICE, "setting hw.perfpolicy to powersaving"); + setperfpolicy("powersaving"); + break; case SETPERF_AUTO: reply.newstate = NORMAL; logmsg(LOG_NOTICE, "setting hw.perfpolicy to auto"); @@ -299,8 +317,10 @@ handle_client(int sock_fd, int ctl_fd) if (strcmp(perfpol, "manual") == 0 || strcmp(perfpol, "high") == 0) { reply.perfmode = PERF_MANUAL; - } else if (strcmp(perfpol, "auto") == 0) + } else if (strcmp(perfpol, "auto") == 0) { reply.perfmode = PERF_AUTO; + } else if (strcmp(perfpol, "powersaving") == 0) + reply.perfmode = PERF_POWERSAVING; } if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) == -1) { @@ -423,7 +443,7 @@ main(int argc, char *argv[]) struct kevent ev[2]; int doperf = PERF_NONE; - while ((ch = getopt(argc, argv, "aACdHLsf:t:S:z:Z:")) != -1) + while ((ch = getopt(argc, argv, "aACdHLPsf:t:S:z:Z:")) != -1) switch(ch) { case 'a': noacsleep = 1; @@ -459,6 +479,9 @@ main(int argc, char *argv[]) doperf = PERF_MANUAL; setperfpolicy("low"); break; + case 'P': + use_powersaving = 1; + break; case 'H': if (doperf != PERF_NONE) usage(); diff --git usr.sbin/apmd/apmsubr.c usr.sbin/apmd/apmsubr.c index 6504fe823bb..a1dbcfb2c61 100644 --- usr.sbin/apmd/apmsubr.c +++ usr.sbin/apmd/apmsubr.c @@ -79,6 +79,8 @@ perf_mode(int mode) return "manual"; case PERF_AUTO: return "auto"; + case PERF_POWERSAVING: + return "powersaving"; default: return "invalid"; } -- wbr, Kirill