[Toybox] [PATCH] fix dmesg default size/level filtering/performance
enh
enh at google.com
Mon Nov 24 19:50:41 PST 2014
On Mon, Nov 24, 2014 at 7:10 PM, Isaac Dunham <ibid.ag at gmail.com> wrote:
> On Mon, Nov 24, 2014 at 05:26:06PM -0800, enh wrote:
>> * the kernel buffer hasn't defaulted to 16KiB for a very long time.
>> i'm not sure why the original code said < 2 rather than <= 0 (or 1KiB,
>> say), but i've left that as it was.
>>
>> * there's also a bug with the filtering out of level markers; they can
>> be more than one digit. (this is true of all kernels, not just Android
>> ones.)
>>
>> * finally, the use of xputc for every character makes the toybox dmesg
>> human-noticeably slow. writing line at a time is orders of magnitude
>> faster/fewer syscalls.
>>
>> here's the patch, which attempts to imitate your style:
>> diff --git a/toys/lsb/dmesg.c b/toys/lsb/dmesg.c
>> index 1003256..d4a2b66 100644
>> --- a/toys/lsb/dmesg.c
>> +++ b/toys/lsb/dmesg.c
>> @@ -15,7 +15,7 @@ config DMESG
>> Print or control the kernel ring buffer.
>>
>> -n Set kernel logging level (1-9).
>> - -s Size of buffer to read (in bytes), default 16384.
>> + -s Size of buffer to read (in bytes), default kernel buffer size.
>> -c Clear the ring buffer after printing.
>> */
>>
>> @@ -34,22 +34,34 @@ void dmesg_main(void)
>> if (toys.optflags & 2) {
>> if (klogctl(8, NULL, TT.level)) error_exit("klogctl");
>> } else {
>> - int size, i, last = '\n';
>> - char *data;
>> + int size, i;
>> + char *data, *end;
>>
>> // Figure out how much data we need, and fetch it.
>> size = TT.size;
>> - if (size<2) size = 16384;
>> + if (size < 2) size = klogctl(10, 0, 0); // SYSLOG_ACTION_SIZE_BUFFER.
>> + if (size < 0) size = 16384;
>> data = xmalloc(size);
>> size = klogctl(3 + (toys.optflags&1), data, size);
>> if (size < 0) error_exit("klogctl");
>>
>> - // Display data, filtering out level markers.
>> + // Display data, filtering out level markers (<\d+>).
>> for (i=0; i<size; ) {
>> - if (last=='\n' && data[i]=='<') i += 3;
>> - else xputc(last = data[i++]);
>> + if (data[i]=='<') {
>> + end = strchr(data+i, '>');
>> + if (end) i = end-data+1;
> I presume the theory here is that log levels might eventually end up going
> above 8, despite the documentation implying otherwise?
never trust the documentation :-) --- as i said in my commentary, this
is the case right now, and not just on Android kernels. example from
my Ubuntu desktop:
~$ dmesg -r | egrep '<[0-9][0-9]>'
<12>[230625.033238] init: anacron main process (848) killed by TERM signal
<12>[230626.308516] init: anacron main process (1996) killed by TERM signal
<12>[230631.510410] init: anacron main process (2439) killed by TERM signal
<12>[231591.145838] init: anacron main process (3557) killed by TERM signal
~$
> And of course everything assumes that < and > are off-limits for the contents.
no, there's a loop invariant that you're always at the start of the
line. strchr stops at the first match. so the assumption is that a
level doesn't contain '>', which is a pretty safe assumption given
that that's the character used to mark the end.
strictly there's another assumption: that the buffer the kernel
returns starts at the beginning of a line. but the old code had that
assumption too, and i've not seen that assumption violated in practice
(despite playing around with small buffer sizes trying to provoke it).
>> + } else {
>> + end = strchr(data+i, '\n');
>> + if (end) {
>> + xwrite(1, data+i, end-(data+i)+1);
>> + i += end - (data+i) + 1;
>
> Somehow this whole bit reminds me of something I wrote not long ago,
> where I took care of unwanted strings by moving the memory contents
> towards the start.
> Applied here, it might look like this:
>
> void memback(long delbytes, char *string)
> {
> // could be adjusted to not need strlen() in our case...
> memmove(string, string + delbytes, strlen(string) + delbytes);
> }
>
> ...
> while ((cur = strchr(cur, '<') != 0) {
> memback(3, cur);
> }
>
>
> This will call memmove() a lot; on the other hand, it can call write once.
yeah, the toolbox dmesg (which is "dmesg -r") only needs to do one big
write, but for default current kernel buffers (128KiB) the difference
between that and one syscall per line is not human-noticeable. judging
by "strace -c dmesg" on the desktop, the competition doesn't bother
either.
>> + } else {
>> + xwrite(1, data+i, size-i);
>> + xputc('\n');
>> + i = size;
>
> I'm guessing you want "xwriteall()" instead of "xwrite()";
> xwriteall retries until everything has been written.
there's no xwriteall function. see the documentation for xwrite.
>> + }
>> + }
>> }
>> - if (last!='\n') xputc('\n');
>> if (CFG_TOYBOX_FREE) free(data);
>> }
>> }
>>
>> i'm assuming no one actually cares about the old Android "implicit -r"
>> behavior. if i get a complaint, adding -r is trivial. (once i learn
>> how to add an option at all :-) )
>
> Adding an option is simple:
>
> -USE_DMESG(NEWTOY(dmesg, "s#n#c", TOYFLAG_BIN)
> +USE_DMESG(NEWTOY(dmesg, "s#n#cr", TOYFLAG_BIN)
> ...
> -n Set kernel logging level (1-9).
> + -r Print the raw message buffer.
> -s Size of buffer to read (in bytes), default 16384.
> ...
> + if (toys.optflags&FLAG_r) {
> + //write the buffer in its entirety
> + }
>
> (I suppose that this could be done with a single call to xwriteall)
> www/code.html has more on how to write things for toybox.
>
>
> And I notice now that dmesg doesn't use the FLAG_* macros, but
> instead hardcodes options by their offset in the optsring.
> It was last touched about 2 years ago just after the FLAG_* macros,
> so that isn't surprising.
cool. i'm assuming given how size-conscious you seem to be that i
shouldn't add -r until someone actually asks for it though?
1416887441.0
More information about the Toybox
mailing list