Re: apmd(8): poll timer miscalculation

2018-06-25 Thread sunil+bugs
sunil+b...@nimmagadda.net wrote:
> sunil+b...@nimmagadda.net wrote:
> > >Synopsis:  apmd(8) poll timer off by 10x
> > >Category:  system
> > >Environment:
> > System  : OpenBSD 6.3
> > Details : OpenBSD 6.3-current (GENERIC.MP) #54: Wed May 30 23:03:50 
> > MDT 2018
> >  
> > dera...@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> > 
> > Architecture: OpenBSD.amd64
> > Machine : amd64
> > >Description:
> > With apmd_flags="-Az10", expected apmd(8) to suspend when
> > battery is at 10%, however, it didn't check in time and
> > laptop ran of out power.
> > >How-To-Repeat:
> > Disconnect A/C adapter and run with -z percent greater
> > than current estimated battery life reported by apm(8);
> > poll every minute, for example...
> > # rcctl stop apmd
> > # apmd -A -z90 -t60
> > should suspend in a minute, however, it suspends after 10
> > minutes.
> > >Fix:
> > The following diff...
> > 
> > 1. Provides a dedicated timer that fires every 10 seconds
> > instead of relying on EVFILT_READ freqency.
> > 
> > 2. Increments a counter and checks against timeout value,
> > if it exceeds, invokes auto-action.
> > 
> > 3. Wraps a few long lines that exceed 80 cols upon code
> > shuffle.
> 
> Revised diff without whitespace changes and...
> 
> 1. While querying for changed objects using kevent(2), request changes
> for all objects registered instead of just 1 and handle them.
> 

Ping! Could someone familiar with apmd(8) please take a look and
comment; thanks.

Prior discussion on bugs@ about the problem...
https://marc.info/?t=15078412582=1=2

> Index: apmd.c
> ===
> RCS file: /cvs/src/usr.sbin/apmd/apmd.c,v
> retrieving revision 1.81
> diff -u -p -w -r1.81 apmd.c
> --- apmd.c15 Oct 2017 15:14:49 -  1.81
> +++ apmd.c8 Jun 2018 10:16:53 -
> @@ -56,6 +56,9 @@
>  #define AUTO_SUSPEND 1
>  #define AUTO_HIBERNATE 2
>  
> +#define TIMO (10*60) /* 10 minutes */
> +#define TIMER_ID 1
> +
>  const char apmdev[] = _PATH_APM_CTLDEV;
>  const char sockfile[] = _PATH_APM_SOCKET;
>  
> @@ -341,8 +344,6 @@ hibernate(int ctl_fd)
>   ioctl(ctl_fd, APM_IOC_HIBERNATE, 0);
>  }
>  
> -#define TIMO (10*60) /* 10 minutes */
> -
>  int
>  main(int argc, char *argv[])
>  {
> @@ -353,13 +354,12 @@ main(int argc, char *argv[])
>   int statonly = 0;
>   int powerstatus = 0, powerbak = 0, powerchange = 0;
>   int noacsleep = 0;
> - struct timespec ts = {TIMO, 0}, sts = {0, 0};
>   struct apm_power_info pinfo;
> - time_t apmtimeout = 0;
> + time_t apmtimeout = TIMO, counter = 0;
>   const char *sockname = sockfile;
>   const char *errstr;
>   int kq, nchanges;
> - struct kevent ev[2];
> + struct kevent ev[3];
>   int ncpu_mib[2] = { CTL_HW, HW_NCPU };
>   int ncpu;
>   size_t ncpu_sz = sizeof(ncpu);
> @@ -379,8 +379,8 @@ main(int argc, char *argv[])
>   sockname = optarg;
>   break;
>   case 't':
> - ts.tv_sec = strtoul(optarg, NULL, 0);
> - if (ts.tv_sec == 0)
> + apmtimeout = strtoul(optarg, NULL, 0);
> + if (apmtimeout == 0)
>   usage();
>   break;
>   case 's':   /* status only */
> @@ -466,57 +466,30 @@ main(int argc, char *argv[])
>  
>   EV_SET([0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
>   0, 0, NULL);
> + EV_SET([1], TIMER_ID, EVFILT_TIMER, EV_ADD, 0, 1, NULL);
>   if (ctl_fd == -1)
> - nchanges = 1;
> + nchanges = 2;
>   else {
> - EV_SET([1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
> + EV_SET([2], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
>   EV_CLEAR, 0, 0, NULL);
> - nchanges = 2;
> + nchanges = 3;
>   }
> - if (kevent(kq, ev, nchanges, NULL, 0, ) < 0)
> + if (kevent(kq, ev, nchanges, NULL, 0, NULL) < 0)
>   error("kevent", NULL);
>  
>   if (sysctl(ncpu_mib, 2, , _sz, NULL, 0) < 0)
>   error("cannot read hw.ncpu", NULL);
>  
>   for (;;) {
> - int rv;
> + int i, rv;
>  
> - sts = ts;
> -
> - apmtimeout += 1;
> - if ((rv = kevent(kq, NULL, 0, ev, 1, )) < 0)
> + if ((rv = kevent(kq, NULL, 0, ev, nchanges, NULL)) < 0)
>   break;
>  
> - if (apmtimeout >= ts.tv_sec) {
> - apmtimeout = 0;
> -
> - /* wakeup for timeout: take status */
> - powerbak = power_status(ctl_fd, 0, );
> - if (powerstatus != powerbak) {
> -  

Re: apmd(8): poll timer miscalculation

2018-06-08 Thread sunil+bugs
sunil+b...@nimmagadda.net wrote:
> >Synopsis:apmd(8) poll timer off by 10x
> >Category:system
> >Environment:
>   System  : OpenBSD 6.3
>   Details : OpenBSD 6.3-current (GENERIC.MP) #54: Wed May 30 23:03:50 
> MDT 2018
>
> dera...@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> 
>   Architecture: OpenBSD.amd64
>   Machine : amd64
> >Description:
> With apmd_flags="-Az10", expected apmd(8) to suspend when
> battery is at 10%, however, it didn't check in time and
> laptop ran of out power.
> >How-To-Repeat:
> Disconnect A/C adapter and run with -z percent greater
> than current estimated battery life reported by apm(8);
> poll every minute, for example...
>   # rcctl stop apmd
>   # apmd -A -z90 -t60
> should suspend in a minute, however, it suspends after 10
> minutes.
> >Fix:
>   The following diff...
> 
> 1. Provides a dedicated timer that fires every 10 seconds
> instead of relying on EVFILT_READ freqency.
> 
> 2. Increments a counter and checks against timeout value,
> if it exceeds, invokes auto-action.
> 
> 3. Wraps a few long lines that exceed 80 cols upon code
> shuffle.

Revised diff without whitespace changes and...

1. While querying for changed objects using kevent(2), request changes
for all objects registered instead of just 1 and handle them.

Index: apmd.c
===
RCS file: /cvs/src/usr.sbin/apmd/apmd.c,v
retrieving revision 1.81
diff -u -p -w -r1.81 apmd.c
--- apmd.c  15 Oct 2017 15:14:49 -  1.81
+++ apmd.c  8 Jun 2018 10:16:53 -
@@ -56,6 +56,9 @@
 #define AUTO_SUSPEND 1
 #define AUTO_HIBERNATE 2
 
+#define TIMO (10*60)   /* 10 minutes */
+#define TIMER_ID 1
+
 const char apmdev[] = _PATH_APM_CTLDEV;
 const char sockfile[] = _PATH_APM_SOCKET;
 
@@ -341,8 +344,6 @@ hibernate(int ctl_fd)
ioctl(ctl_fd, APM_IOC_HIBERNATE, 0);
 }
 
-#define TIMO (10*60)   /* 10 minutes */
-
 int
 main(int argc, char *argv[])
 {
@@ -353,13 +354,12 @@ main(int argc, char *argv[])
int statonly = 0;
int powerstatus = 0, powerbak = 0, powerchange = 0;
int noacsleep = 0;
-   struct timespec ts = {TIMO, 0}, sts = {0, 0};
struct apm_power_info pinfo;
-   time_t apmtimeout = 0;
+   time_t apmtimeout = TIMO, counter = 0;
const char *sockname = sockfile;
const char *errstr;
int kq, nchanges;
-   struct kevent ev[2];
+   struct kevent ev[3];
int ncpu_mib[2] = { CTL_HW, HW_NCPU };
int ncpu;
size_t ncpu_sz = sizeof(ncpu);
@@ -379,8 +379,8 @@ main(int argc, char *argv[])
sockname = optarg;
break;
case 't':
-   ts.tv_sec = strtoul(optarg, NULL, 0);
-   if (ts.tv_sec == 0)
+   apmtimeout = strtoul(optarg, NULL, 0);
+   if (apmtimeout == 0)
usage();
break;
case 's':   /* status only */
@@ -466,57 +466,30 @@ main(int argc, char *argv[])
 
EV_SET([0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
0, 0, NULL);
+   EV_SET([1], TIMER_ID, EVFILT_TIMER, EV_ADD, 0, 1, NULL);
if (ctl_fd == -1)
-   nchanges = 1;
+   nchanges = 2;
else {
-   EV_SET([1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
+   EV_SET([2], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
EV_CLEAR, 0, 0, NULL);
-   nchanges = 2;
+   nchanges = 3;
}
-   if (kevent(kq, ev, nchanges, NULL, 0, ) < 0)
+   if (kevent(kq, ev, nchanges, NULL, 0, NULL) < 0)
error("kevent", NULL);
 
if (sysctl(ncpu_mib, 2, , _sz, NULL, 0) < 0)
error("cannot read hw.ncpu", NULL);
 
for (;;) {
-   int rv;
+   int i, rv;
 
-   sts = ts;
-
-   apmtimeout += 1;
-   if ((rv = kevent(kq, NULL, 0, ev, 1, )) < 0)
+   if ((rv = kevent(kq, NULL, 0, ev, nchanges, NULL)) < 0)
break;
 
-   if (apmtimeout >= ts.tv_sec) {
-   apmtimeout = 0;
-
-   /* wakeup for timeout: take status */
-   powerbak = power_status(ctl_fd, 0, );
-   if (powerstatus != powerbak) {
-   powerstatus = powerbak;
-   powerchange = 1;
-   }
-
-   if (!powerstatus && autoaction &&
-   autolimit > (int)pinfo.battery_life) {
-   syslog(LOG_NOTICE,
-   

Re: apmd(8): poll timer miscalculation

2018-06-08 Thread sunil+bugs
Scott Cheloha  wrote:
> On Thu, Jun 07, 2018 at 11:27:34AM +, sunil+b...@nimmagadda.net wrote:
> > >Synopsis:  apmd(8) poll timer off by 10x
> > >Category:  system
> > >Environment:
> > System  : OpenBSD 6.3
> > Details : OpenBSD 6.3-current (GENERIC.MP) #54: Wed May 30 23:03:50 
> > MDT 2018
> >  
> > dera...@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> > 
> > Architecture: OpenBSD.amd64
> > Machine : amd64
> > >Description:
> > With apmd_flags="-Az10", expected apmd(8) to suspend when
> > battery is at 10%, however, it didn't check in time and
> > laptop ran of out power.
> > >How-To-Repeat:
> > Disconnect A/C adapter and run with -z percent greater
> > than current estimated battery life reported by apm(8);
> > poll every minute, for example...
> > # rcctl stop apmd
> > # apmd -A -z90 -t60
> > should suspend in a minute, however, it suspends after 10
> > minutes.
> > >Fix:
> > The following diff...
> > 
> > 1. Provides a dedicated timer that fires every 10 seconds
> > instead of relying on EVFILT_READ freqency.
> > 
> > 2. Increments a counter and checks against timeout value,
> > if it exceeds, invokes auto-action.
> > 
> > 3. Wraps a few long lines that exceed 80 cols upon code
> > shuffle.
> 
> I don't think we need to introduce an additional timer, or do so much
> code shufflin' here, to fix your issue.  The problem seems to be that
> apmtimeout is incremented once per iteration but must meet or exceed
> ts.tv_sec to trigger a status check, so the period for battery status
> checks is at least n^2 seconds.
> 
> I'm pretty sure this was unintentional, though it makes it unlikely
> that apmd will catch a low battery percentage and suspend the machine
> before the battery is totally exhausted.  Especially since, by default,
> n = 600.
> 
> Here's a minimal diff that checks if we timed out on return from kevent.
> There's additional cleanup that this change implies, but I've left it out
> for the moment.
> 
> Of note is that an event for either of the descriptors resets the
> timeout, regardless of how long it's been since we checked the battery
> status: this is effectively the current behavior.  If people want, we
> can add logic to decrement the maximum timeout accordingly on each
> iteration and reset it when kevent truly times out.  This sounds closer
> to what the manpage advertises for the -t option.
> 
> Caveat: I'm unfamiliar with apmd(8) and I don't have time just this
> second scour the change log to figure out why the behavior is what it
> is now.  Someone more familiar with the code will need to corroborate
> what I've said and the attached diff.
> 
> That said, feel free to try this diff in the meantime.  Does this work
> for you?

No, it doesn't. kevent(2) never times out (so rv is never zero) as
there is data available on ctl_fd every 10 seconds.

> 
> Anyone more familiar with apmd(8) wanna chime in here?
> 
> --
> Scott Cheloha
> 
> Index: usr.sbin/apmd/apmd.c
> ===
> RCS file: /cvs/src/usr.sbin/apmd/apmd.c,v
> retrieving revision 1.81
> diff -u -p -r1.81 apmd.c
> --- usr.sbin/apmd/apmd.c  15 Oct 2017 15:14:49 -  1.81
> +++ usr.sbin/apmd/apmd.c  7 Jun 2018 20:26:11 -
> @@ -488,7 +488,7 @@ main(int argc, char *argv[])
>   if ((rv = kevent(kq, NULL, 0, ev, 1, )) < 0)
>   break;
>  
> - if (apmtimeout >= ts.tv_sec) {
> + if (rv == 0) {
>   apmtimeout = 0;
>  
>   /* wakeup for timeout: take status */



Re: apmd(8): poll timer miscalculation

2018-06-07 Thread Scott Cheloha
On Thu, Jun 07, 2018 at 11:27:34AM +, sunil+b...@nimmagadda.net wrote:
> >Synopsis:apmd(8) poll timer off by 10x
> >Category:system
> >Environment:
>   System  : OpenBSD 6.3
>   Details : OpenBSD 6.3-current (GENERIC.MP) #54: Wed May 30 23:03:50 
> MDT 2018
>
> dera...@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP
> 
>   Architecture: OpenBSD.amd64
>   Machine : amd64
> >Description:
> With apmd_flags="-Az10", expected apmd(8) to suspend when
> battery is at 10%, however, it didn't check in time and
> laptop ran of out power.
> >How-To-Repeat:
> Disconnect A/C adapter and run with -z percent greater
> than current estimated battery life reported by apm(8);
> poll every minute, for example...
>   # rcctl stop apmd
>   # apmd -A -z90 -t60
> should suspend in a minute, however, it suspends after 10
> minutes.
> >Fix:
>   The following diff...
> 
> 1. Provides a dedicated timer that fires every 10 seconds
> instead of relying on EVFILT_READ freqency.
> 
> 2. Increments a counter and checks against timeout value,
> if it exceeds, invokes auto-action.
> 
> 3. Wraps a few long lines that exceed 80 cols upon code
> shuffle.

I don't think we need to introduce an additional timer, or do so much
code shufflin' here, to fix your issue.  The problem seems to be that
apmtimeout is incremented once per iteration but must meet or exceed
ts.tv_sec to trigger a status check, so the period for battery status
checks is at least n^2 seconds.

I'm pretty sure this was unintentional, though it makes it unlikely
that apmd will catch a low battery percentage and suspend the machine
before the battery is totally exhausted.  Especially since, by default,
n = 600.

Here's a minimal diff that checks if we timed out on return from kevent.
There's additional cleanup that this change implies, but I've left it out
for the moment.

Of note is that an event for either of the descriptors resets the
timeout, regardless of how long it's been since we checked the battery
status: this is effectively the current behavior.  If people want, we
can add logic to decrement the maximum timeout accordingly on each
iteration and reset it when kevent truly times out.  This sounds closer
to what the manpage advertises for the -t option.

Caveat: I'm unfamiliar with apmd(8) and I don't have time just this
second scour the change log to figure out why the behavior is what it
is now.  Someone more familiar with the code will need to corroborate
what I've said and the attached diff.

That said, feel free to try this diff in the meantime.  Does this work
for you?

Anyone more familiar with apmd(8) wanna chime in here?

--
Scott Cheloha

Index: usr.sbin/apmd/apmd.c
===
RCS file: /cvs/src/usr.sbin/apmd/apmd.c,v
retrieving revision 1.81
diff -u -p -r1.81 apmd.c
--- usr.sbin/apmd/apmd.c15 Oct 2017 15:14:49 -  1.81
+++ usr.sbin/apmd/apmd.c7 Jun 2018 20:26:11 -
@@ -488,7 +488,7 @@ main(int argc, char *argv[])
if ((rv = kevent(kq, NULL, 0, ev, 1, )) < 0)
break;
 
-   if (apmtimeout >= ts.tv_sec) {
+   if (rv == 0) {
apmtimeout = 0;
 
/* wakeup for timeout: take status */



apmd(8): poll timer miscalculation

2018-06-07 Thread sunil+bugs
>Synopsis:  apmd(8) poll timer off by 10x
>Category:  system
>Environment:
System  : OpenBSD 6.3
Details : OpenBSD 6.3-current (GENERIC.MP) #54: Wed May 30 23:03:50 
MDT 2018
 
dera...@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP

Architecture: OpenBSD.amd64
Machine : amd64
>Description:
With apmd_flags="-Az10", expected apmd(8) to suspend when
battery is at 10%, however, it didn't check in time and
laptop ran of out power.
>How-To-Repeat:
Disconnect A/C adapter and run with -z percent greater
than current estimated battery life reported by apm(8);
poll every minute, for example...
# rcctl stop apmd
# apmd -A -z90 -t60
should suspend in a minute, however, it suspends after 10
minutes.
>Fix:
The following diff...

1. Provides a dedicated timer that fires every 10 seconds
instead of relying on EVFILT_READ freqency.

2. Increments a counter and checks against timeout value,
if it exceeds, invokes auto-action.

3. Wraps a few long lines that exceed 80 cols upon code
shuffle.

Index: apmd.c
===
RCS file: /cvs/src/usr.sbin/apmd/apmd.c,v
retrieving revision 1.81
diff -u -p -w -r1.81 apmd.c
--- apmd.c  15 Oct 2017 15:14:49 -  1.81
+++ apmd.c  7 Jun 2018 10:56:06 -
@@ -56,6 +56,9 @@
 #define AUTO_SUSPEND 1
 #define AUTO_HIBERNATE 2
 
+#define TIMO (10*60)   /* 10 minutes */
+#define TIMER_ID 1
+
 const char apmdev[] = _PATH_APM_CTLDEV;
 const char sockfile[] = _PATH_APM_SOCKET;
 
@@ -341,8 +344,6 @@ hibernate(int ctl_fd)
ioctl(ctl_fd, APM_IOC_HIBERNATE, 0);
 }
 
-#define TIMO (10*60)   /* 10 minutes */
-
 int
 main(int argc, char *argv[])
 {
@@ -353,13 +354,12 @@ main(int argc, char *argv[])
int statonly = 0;
int powerstatus = 0, powerbak = 0, powerchange = 0;
int noacsleep = 0;
-   struct timespec ts = {TIMO, 0}, sts = {0, 0};
struct apm_power_info pinfo;
-   time_t apmtimeout = 0;
+   time_t apmtimeout = TIMO, counter = 0;
const char *sockname = sockfile;
const char *errstr;
int kq, nchanges;
-   struct kevent ev[2];
+   struct kevent ev[3];
int ncpu_mib[2] = { CTL_HW, HW_NCPU };
int ncpu;
size_t ncpu_sz = sizeof(ncpu);
@@ -379,8 +379,8 @@ main(int argc, char *argv[])
sockname = optarg;
break;
case 't':
-   ts.tv_sec = strtoul(optarg, NULL, 0);
-   if (ts.tv_sec == 0)
+   apmtimeout = strtoul(optarg, NULL, 0);
+   if (apmtimeout == 0)
usage();
break;
case 's':   /* status only */
@@ -466,14 +466,15 @@ main(int argc, char *argv[])
 
EV_SET([0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
0, 0, NULL);
+   EV_SET([1], TIMER_ID, EVFILT_TIMER, EV_ADD, 0, 1, NULL);
if (ctl_fd == -1)
-   nchanges = 1;
+   nchanges = 2;
else {
-   EV_SET([1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
+   EV_SET([2], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
EV_CLEAR, 0, 0, NULL);
-   nchanges = 2;
+   nchanges = 3;
}
-   if (kevent(kq, ev, nchanges, NULL, 0, ) < 0)
+   if (kevent(kq, ev, nchanges, NULL, 0, NULL) < 0)
error("kevent", NULL);
 
if (sysctl(ncpu_mib, 2, , _sz, NULL, 0) < 0)
@@ -482,41 +483,14 @@ main(int argc, char *argv[])
for (;;) {
int rv;
 
-   sts = ts;
-
-   apmtimeout += 1;
-   if ((rv = kevent(kq, NULL, 0, ev, 1, )) < 0)
+   if ((rv = kevent(kq, NULL, 0, ev, 1, NULL)) < 0)
break;
 
-   if (apmtimeout >= ts.tv_sec) {
-   apmtimeout = 0;
-
-   /* wakeup for timeout: take status */
-   powerbak = power_status(ctl_fd, 0, );
-   if (powerstatus != powerbak) {
-   powerstatus = powerbak;
-   powerchange = 1;
-   }
-
-   if (!powerstatus && autoaction &&
-   autolimit > (int)pinfo.battery_life) {
-   syslog(LOG_NOTICE,
-   "estimated battery life %d%%, "
-   "autoaction limit set to %d%% .",
-   pinfo.battery_life,
-   autolimit
-   );
-
-   if (autoaction == AUTO_SUSPEND)