[Toybox] [CLEANUP]vmstat
Rob Landley
rob at landley.net
Fri Dec 27 20:12:07 PST 2013
So I started picking at vmstat a while back, and of course it evolved
into a big rewrite.
First off, the terminal handling is funky. But it's funky upstream too:
If you run "vmstat 1" in ubuntu it detects the terminal height and
reprints the headers once per screen, but if you "vmstat 1 | cat" it
prints them every 25 chars. This is odd: if you "ls | cat" it doesn't
give you -c output for an 80 character screen, it drops down to "ls -1"
behavior. Periodically reprinting the header when you cat the output is
nuts, and there's no standard for what the correct behavior _is_, so the
question is what will I break if I make it stop doing the crazy thing?
Meanwhile, general cleanup:
Remove _num postfix from variable names. (Storing a number is what
integer variables do.)
A bunch of different local variable[2] arrays implies we should have a
structure to group them, so make a struct vmstat_proc. (This information
can probably be shared with "top", so thinking about reusability...)
Every element in said structure is a uint64_t so we can refer to them by
name but _also_ use it as a giant array to do table driven logic. (It's
a bit subtle, but of the kind that deserves a comment.)
Speaking of table driven, still in main():
replace big "%2d %3d %4d" string with an array of lengths. This is used
both for padding the header and for padding the values. (The header
padding doesn't directly buy us much over a spaced string, but it means
if any of the spacings change it only needs to change in one place. I
left the ---string--- one alone because the repetition vs code tradeoff
isn't worth it.)
Up top: collate the three separate /proc reading functions with one big
get_vmstat_proc() that fills out the function working off of a table.
The table is an array of strings, entries that start with / mean open a
new file, the rest mean search for this and expect a decimal long right
after.
I could have tried to eliminate the trailing space and : that's repeated
a lot in the table, but it complicated the code enough it wasn't worth
it. Specifically, searching for "name" and then sscanf(" %d") with the
space in the sscanf opens the possibility of a search for "name "
incorrectly triggering on "names " or similar, I want to match the whole
string, or at least the end of it. Pondered making sure it followed a
newline to ensure whole word match, but the first entry isn't guaranteed
to have a newline and I'm not dragging regexes into it.
Similarly, I could have done the \0 thing instead of the {"one", "two"}
thing, but this table is long and complicated enough I want to be able
to _read_ it. Size vs simple tradeoff, not an obvious answer there. (I
did it both ways in this command actually.)
Grrr. That broken "may be used uninitialized" warning is triggering
twice in get_vmstat_proc() because gcc doesn't understand that the table
entries are parsed in order, and processing the first entry will
initialize "name" (and nothing else uses it before then) and processing
the second will initialize "p" (and nothing else uses it before then),
and it's completely deterministic and the condition it's warning about
CAN'T HAPPEN. But it won't shut up unless we "char *p = p, *name =
name;" just to humor it. (Unlike initializing them to 0, initializing
them to themselves generates no code but lets the compiler know that we
consider them initialized so it should SHUT UP about the stupid warning
it can't comptently generate.)
(I mean yes, the table parser could be fed a bad table. If you look for
a key before specifying a filename, or look for another entry on the
same line as the last key without having had a key yet, it would be
using an uninitialized variable. But the table we feed it, which is a
constant in the same function, doesn't do that. Generating false
positives is not the compiler's job, it's coverity's job. External
"harser than the compiler" code analyzers are as old as "lint", this is
not gcc's problem.)
On to runtime problems! The output spacing is funky due to modern memory
sizes: they allocated 6 digits each (kilobyte units) for swapped, free,
buffered, and cached. Once you have a gigabyte value in any of them, it
uses an extra space and then all the remaining columns are offset a
space, even if they have plenty of blank spaces in front of them. So
track overshoots and deduct from future columns' whitespace budget.
(Yeah, fancy thing upstream isn't doing. But upstream is stale.)
Next up... all these unit conversions are funky and complicated and I
don't understand what's going on. What are the units on any of this?
According to the man page, swap I/O is in kilobytes per second, but disk
I/O is in blocks/second. How big is a block these days, 512 bytes or 4k?
No wait, it doesn't say, that, the man page doesn't _say_ kilobytes for
swap I/O, it says "memory/second" as if _memory_ is a _unit_. So maybe
that's blocks too! No way to tell! Grrrrr...
Very strongly tempted to just normalize them both to kilobytes and add a
command line switch to let you say megabytes. Also, why the heck is disk
I/O special? (What about network filesystems? Is that not I/O?)
Wandered off on a longish tangent trying to figure out the units on the
CPU time usage (it's not
jiffies, it's user_hz, and user_hz/sec is returned by
sysconf(_SC_CLK_TCK)), but we don't need that
because it's just percentage used in each.
Fluff out the help message a lot to explain what the fields are,
including units.
Problem: the middle fields (si so bi bo in cs) don't remotely match what
the other vmstat is producing:
landley at driftwood:~/toybox/clean$ ./vmstat
procs -----------memory---------- ---swap-- -----io---- -system--
----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
1 0 268044 434940 367296 2079916 4 4 30 36 318 750 37
7 60 1
landley at driftwood:~/toybox/clean$ vmstat
procs -----------memory---------- ---swap-- -----io---- -system--
----cpu----
r b swpd free buff cache si so bi bo in cs us sy
id wa
0 0 268044 433932 367304 2079912 9 10 68 81 6 8 35
7 57 1
That's before I started this current round of cleanups: swap out is 4 in
toybox and 10 in ubuntu, that's not even a clean doubling that's just
way different. Hmmm...
$ vmstat && cat /proc/vmstat | grep pswpout && cat /proc/uptime
procs -----------memory---------- ---swap-- -----io---- -system--
----cpu----
r b swpd free buff cache si so bi bo in cs us sy
id wa
0 0 268040 416984 367356 2080192 9 10 68 81 6 9 35
7 57 1
pswpout 1608723
1494710.37 700695.57
landley at driftwood:~/toybox/clean$ python
Python 2.7.3 (default, Sep 26 2013, 20:03:06)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print (1608723*4)/700695.0
9.18358486931
Nope, even using the second uptime value (which isn't documented, but
might be uptime subtracting suspend time? The kernel code implies it's
"idle" time but I dunno what it means by that. I asked on linux-kernel,
http://lkml.indiana.edu/hypermail/linux/kernel/1312.3/00295.html but
nobody's replied yet. It seems like adding up all processors on an SMP
system, which the kernel is doing, would potentially give you a value
GREATER than uptime, but... dunno.)
Anyway, even using that I can't _quite_ match the other uptime because
9.18 doesn't even round up to 10. Except... Hang on, doing the si value:
>>> print (1561842*4)/701092.0
8.91091040833
Which displayed 9 when I did that. So maybe it's _aggressively_ rounding
up, anything greater than an exact match is the next integer? Hmmm...
No, I implemented that and I'm getting "9 9" where the other
is giving "9 10". Based on:
pgpgin 45355001
pgpgout 54524516
/proc/uptime 1645947.71 773585.92
I have no flipping CLUE what the upstream version thinks it's doing.
Right, check in what I've got and research more later.
Rob
More information about the Toybox
mailing list