On Fri, Dec 09, 2022 at 12:10:59PM +0100, Alexandre Ratchov wrote: > This diff adds an option to display variables periodically. Basically > it replaces this usage: > > while sleep 1; do audioctl play.errors; done > > by > > audioctl -w 1 play.errors > > The purpose of above audioctl commands is to debug underruns, so we > don't want to fork a new process and reopen the device. This would > trigger longer kernel code-paths and may cause additional underruns > than the ones being investigated. > > OK?
I like the idea, but I would like to tweak some things. - Add [-w wait] to the first synoptic form in the manpage. It's legal to do e.g. # audioctl -w 1 - Call the variable "wait" in audioctl.c to match the manpage. - Update usagestr to mention [-w wait]. - When polling variables periodically, it's better to use setitimer(2) and sigsuspend(2) instead of sleep(3). setitimer(2) keeps the period from drifting. - Let the user SIGINT (^C) out of the program without returning an error to the shell. I'm unsure about this one, but it seems logical to give the user a way to gracefully terminate the program. You say in the manpage that the program will continue printing until it is interrupted. Index: audioctl.8 =================================================================== RCS file: /cvs/src/usr.bin/audioctl/audioctl.8,v retrieving revision 1.4 diff -u -p -r1.4 audioctl.8 --- audioctl.8 23 Apr 2020 00:16:59 -0000 1.4 +++ audioctl.8 9 Dec 2022 18:41:45 -0000 @@ -35,8 +35,10 @@ .Sh SYNOPSIS .Nm audioctl .Op Fl f Ar file +.Op Fl w wait .Nm audioctl .Op Fl n +.Op Fl w wait .Op Fl f Ar file .Ar name ... .Nm audioctl @@ -59,6 +61,12 @@ The default is Suppress printing of the variable name. .It Fl q Suppress all output when setting a variable. +.It Fl w Ar wait +Pause +.Ar wait +seconds between each display. +.Nm +will display variables until it is interrupted. .It Ar name Ns = Ns Ar value Attempt to set the specified variable .Ar name @@ -130,10 +138,10 @@ audio control devices audio devices .El .Sh EXAMPLES -Display the number of bytes of silence inserted during play buffer -underruns since device started: +Once per second, display the number of bytes of silence inserted +during play buffer underruns since device started: .Bd -literal -offset indent -# audioctl play.errors +# audioctl -w 1 play.errors .Ed .Pp Use signed 24-bit samples and 44100Hz sample rate: Index: audioctl.c =================================================================== RCS file: /cvs/src/usr.bin/audioctl/audioctl.c,v retrieving revision 1.43 diff -u -p -r1.43 audioctl.c --- audioctl.c 12 Jul 2021 15:09:19 -0000 1.43 +++ audioctl.c 9 Dec 2022 18:41:45 -0000 @@ -16,9 +16,11 @@ */ #include <sys/types.h> #include <sys/ioctl.h> +#include <sys/time.h> #include <sys/audioio.h> #include <fcntl.h> #include <limits.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> @@ -43,6 +45,7 @@ struct field { #define STR 2 #define ENC 3 int type; + int show; int set; } fields[] = { {"name", &rname.name, NULL, STR}, @@ -63,11 +66,24 @@ struct field { }; const char usagestr[] = - "usage: audioctl [-f file]\n" - " audioctl [-n] [-f file] name ...\n" + "usage: audioctl [-f file] [-w wait]\n" + " audioctl [-n] [-f file] [-w wait] name ...\n" " audioctl [-nq] [-f file] name=value ...\n"; int fd, show_names = 1, quiet = 0; +unsigned int wait; +volatile sig_atomic_t uninterrupted = 1; + +void +handle_alrm(int signo) +{ +} + +void +handle_int(int signo) +{ + uninterrupted = 0; +} /* * parse encoding string (examples: s8, u8, s16, s16le, s24be ...) @@ -194,24 +210,15 @@ parse_field(struct field *f, void *addr, void audio_main(int argc, char **argv) { + struct itimerval itv; struct field *f; char *lhs, *rhs; + sigset_t empty; int set = 0; - if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1) - err(1, "AUDIO_GETSTATUS"); - if (ioctl(fd, AUDIO_GETDEV, &rname) == -1) - err(1, "AUDIO_GETDEV"); - if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1) - err(1, "AUDIO_GETPAR"); - if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1) - err(1, "AUDIO_GETPOS"); if (argc == 0) { - for (f = fields; f->name != NULL; f++) { - printf("%s=", f->name); - print_field(f, f->raddr); - printf("\n"); - } + for (f = fields; f->name != NULL; f++) + f->show = 1; } AUDIO_INITPAR(&wpar); for (; argc > 0; argc--, argv++) { @@ -231,15 +238,51 @@ audio_main(int argc, char **argv) parse_field(f, f->waddr, rhs); f->set = 1; set = 1; - } else { + } else + f->show = 1; + } + + if (set && wait) + errx(1, "the -w flag is incompatible with variable assignment"); + + if (wait != 0) { + if (signal(SIGALRM, handle_alrm) == SIG_ERR) + err(1, "signal"); + if (signal(SIGINT, handle_int) == SIG_ERR) + err(1, "signal"); + sigemptyset(&empty); + itv.it_value.tv_sec = wait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + err(1, "setitimer"); + } + + while (uninterrupted) { + if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1) + err(1, "AUDIO_GETSTATUS"); + if (ioctl(fd, AUDIO_GETDEV, &rname) == -1) + err(1, "AUDIO_GETDEV"); + if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1) + err(1, "AUDIO_GETPAR"); + if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1) + err(1, "AUDIO_GETPOS"); + for (f = fields; f->name != NULL; f++) { + if (!f->show) + continue; if (show_names) printf("%s=", f->name); print_field(f, f->raddr); printf("\n"); } + if (wait == 0) + break; + sigsuspend(&empty); } + if (!set) return; + if (ioctl(fd, AUDIO_SETPAR, &wpar) == -1) err(1, "AUDIO_SETPAR"); if (ioctl(fd, AUDIO_GETPAR, &wpar) == -1) @@ -261,9 +304,10 @@ int main(int argc, char **argv) { char *path = "/dev/audioctl0"; + const char *errstr; int c; - while ((c = getopt(argc, argv, "anf:q")) != -1) { + while ((c = getopt(argc, argv, "anf:qw:")) != -1) { switch (c) { case 'a': /* ignored, compat */ break; @@ -275,6 +319,11 @@ main(int argc, char **argv) break; case 'q': quiet = 1; + break; + case 'w': + wait = strtonum(optarg, 1, UINT_MAX, &errstr); + if (errstr != NULL) + errx(1, "wait is %s: %s", errstr, optarg); break; default: fputs(usagestr, stderr);