Signed-off-by: Mattias Andrée <maand...@kth.se> --- Makefile | 1 + TODO | 2 +- vmstat.c | 321 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 vmstat.c
diff --git a/Makefile b/Makefile index b526421..e1dab9b 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,7 @@ BIN = \ umount \ unshare \ uptime \ + vmstat \ vtallow \ watch \ who diff --git a/TODO b/TODO index 21f5c20..6dd69d3 100644 --- a/TODO +++ b/TODO @@ -27,7 +27,7 @@ tabs taskset top tput -vmstat +vmstat [-sdDpS] Misc ==== diff --git a/vmstat.c b/vmstat.c new file mode 100644 index 0000000..d97ee10 --- /dev/null +++ b/vmstat.c @@ -0,0 +1,321 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" + +struct vm { + intmax_t cpu_user; + intmax_t cpu_nice; + intmax_t cpu_system; + intmax_t cpu_idle; + intmax_t cpu_iowait; + intmax_t cpu_irq; + intmax_t cpu_softirq; + intmax_t cpu_steal; + intmax_t cpu_guest; + intmax_t cpu_guest_nice; + intmax_t cpu_unknown; + intmax_t page_in; + intmax_t page_out; + intmax_t swap_in; + intmax_t swap_out; + intmax_t intr; + intmax_t ctxt_switches; + intmax_t processes; + intmax_t proc_running; + intmax_t proc_blocked; + intmax_t mem_free; + intmax_t buffers; + intmax_t cached; + intmax_t active; + intmax_t inactive; + intmax_t swap_total; + intmax_t swap_free; + intmax_t sreclaimable; +}; + +static intmax_t kb_per_page; +static intmax_t hz; + +static int +stpstarts(char *str, const char *head, char **end) +{ + size_t n = strlen(head); + if (!strncmp(str, head, n)) { + *end = &str[n]; + return 1; + } + return 0; +} + +static intmax_t +read_ints(const char *s, intmax_t *arr, size_t n) +{ + const char *beginning = s; + intmax_t rest = 0; + size_t tmp; + int negative; + + memset(arr, 0, n * sizeof(*arr)); + + for (; n--; arr++) { + while (*s && !isdigit(*s)) + s++; + negative = (s != beginning && s[-1] == '-'); + while (isdigit(*s)) + *arr = *arr * 10 + (*s++ - '0'); + if (negative) + *arr = -*arr; + } + + for (; *s; rest += tmp) { + tmp = 0; + while (*s && !isdigit(*s)) + s++; + negative = (s != beginning && s[-1] == '-'); + while (isdigit(*s)) + tmp = tmp * 10 + (*s++ - '0'); + if (negative) + tmp = -tmp; + } + + return rest; +} + +static void +load_vm(struct vm *s) +{ + static intmax_t debt = 0; + + int have_page = 0, have_swap = 0; + char *line = NULL, *p; + size_t size = 0; + ssize_t len; + FILE *fp; + + memset(s, 0, sizeof(*s)); + + fp = fopen("/proc/stat", "r"); + if (!fp) + eprintf("fopen /proc/stat:"); + while ((len = getline(&line, &size, fp)) >= 0) { + if (stpstarts(line, "cpu ", &p)) { + s->cpu_unknown = read_ints(p, &s->cpu_user, 10); + } else if (stpstarts(line, "page ", &p)) { + read_ints(p, &s->page_in, 2); + have_page = 1; + } else if (stpstarts(line, "swap ", &p)) { + read_ints(p, &s->swap_in, 2); + have_swap = 1; + } else if (stpstarts(line, "intr ", &p)) { + read_ints(p, &s->intr, 1); + } else if (stpstarts(line, "ctxt ", &p)) { + read_ints(p, &s->ctxt_switches, 1); + } else if (stpstarts(line, "processes ", &p)) { + read_ints(p, &s->processes, 1); + } else if (stpstarts(line, "proc_running ", &p)) { + read_ints(p, &s->proc_running, 1); + } else if (stpstarts(line, "proc_blocked ", &p)) { + read_ints(p, &s->proc_blocked, 1); + } + } + if (ferror(fp)) + eprintf("getline /proc/stat:"); + fclose(fp); + + if (!have_page || !have_swap) { + fp = fopen("/proc/vmstat", "r"); + if (!fp) + eprintf("fopen /proc/vmstat:"); + while ((len = getline(&line, &size, fp)) >= 0) { + if (!have_page && stpstarts(line, "pgpgin ", &p)) + read_ints(p, &s->page_in, 1); + else if (!have_page && stpstarts(line, "pgpgout ", &p)) + read_ints(p, &s->page_out, 1); + else if (!have_swap && stpstarts(line, "pswpin ", &p)) + read_ints(p, &s->swap_in, 1); + else if (!have_swap && stpstarts(line, "pswpout ", &p)) + read_ints(p, &s->swap_out, 1); + } + if (ferror(fp)) + eprintf("getline /proc/vmstat:"); + fclose(fp); + } + + fp = fopen("/proc/meminfo", "r"); + if (!fp) + eprintf("fopen /proc/meminfo:"); + while ((len = getline(&line, &size, fp)) >= 0) { + if (stpstarts(line, "MemFree:", &p)) + read_ints(p, &s->mem_free, 1); + else if (stpstarts(line, "Buffers:", &p)) + read_ints(p, &s->buffers, 1); + else if (stpstarts(line, "Cached:", &p)) + read_ints(p, &s->cached, 1); + else if (stpstarts(line, "Active:", &p)) + read_ints(p, &s->active, 1); + else if (stpstarts(line, "Inactive:", &p)) + read_ints(p, &s->inactive, 1); + else if (stpstarts(line, "SwapTotal:", &p)) + read_ints(p, &s->swap_total, 1); + else if (stpstarts(line, "SwapFree:", &p)) + read_ints(p, &s->swap_free, 1); + else if (stpstarts(line, "SReclaimable:", &p)) + read_ints(p, &s->sreclaimable, 1); + } + if (ferror(fp)) + eprintf("getline /proc/meminfo:"); + fclose(fp); + + /* yes, this is actually needed */ + s->cpu_idle += debt; + debt = 0; + if (s->cpu_idle < 0) { + debt = s->cpu_idle; + s->cpu_idle = 0; + } + + free(line); +} + +static void +print_vm(struct vm *s1, struct vm *s0, int active_mem, int timestamp, int print_header) +{ + struct vm s = *s1; + intmax_t ticks; + char timezone[21]; + char timestr[21]; + struct tm *tm; + time_t now; + size_t n; + + ticks = s.cpu_user -= s0->cpu_user; + ticks += s.cpu_nice -= s0->cpu_nice; + ticks += s.cpu_system -= s0->cpu_system; + ticks += s.cpu_idle -= s0->cpu_idle; + ticks += s.cpu_iowait -= s0->cpu_iowait; + ticks += s.cpu_irq -= s0->cpu_irq; + ticks += s.cpu_softirq -= s0->cpu_softirq; + ticks += s.cpu_steal -= s0->cpu_steal; + ticks += s.cpu_guest -= s0->cpu_guest; + ticks += s.cpu_guest_nice -= s0->cpu_guest_nice; + ticks += s.cpu_unknown -= s0->cpu_unknown; + s.processes -= s0->processes; + s.intr -= s0->intr; + s.ctxt_switches -= s0->ctxt_switches; + s.page_in -= s0->page_in; + s.page_out -= s0->page_out; + + s.cpu_user += s.cpu_nice; + s.cpu_guest += s0->cpu_guest_nice; + s.cpu_idle += !ticks; + ticks += !ticks; + + if (timestamp) { + now = time(NULL); + tm = localtime(&now); + strftime(timestr, sizeof(timestr), " %Y-%m-%d %H:%M:%S", tm); + strftime(timezone, sizeof(timezone), "%Z", tm); + n = strlen(timezone) + 1; + memmove(&timezone[sizeof(timezone) - n], timezone, n); + memset(timezone, ' ', sizeof(timezone) - n); + } + +#define PERCENT(X) ((X) * 100 + ticks / 2) / ticks +#define HERTZ(X) ((X) * hz + ticks / 2) / ticks + + if (!print_header) + goto print; + printf("----procs---- -------------------memory------------------ ---swap-- -----io---- --system- --------------cpu--------------%s\n", + timestamp ? " -----timestamp-----" : ""); + printf(" r b fk swpd free " "%s " "%s si so bi bo in cs us sy id wa in si st gt%s\n", + active_mem ? "inact" : " buff", active_mem ? "active" : " cache", timestamp ? timezone : ""); +print: + printf("%2ji %2ji %7ji %10ji " "%10ji " "%10ji " "%10ji %4ji %4ji %5ji %5ji ""%4ji %4ji %3ji %3ji %3ji %3ji %3ji %3ji %3ji %3ji%s\n", + s.proc_running, s.proc_blocked, s.processes, + s.swap_total - s.swap_free, s.mem_free, active_mem ? s.inactive : s.buffers, active_mem ? s.active : s.cached + s.sreclaimable, + HERTZ(s.swap_in * kb_per_page), HERTZ(s.swap_out * kb_per_page), + HERTZ(s.page_in), HERTZ(s.page_out), + HERTZ(s.intr), HERTZ(s.ctxt_switches), + PERCENT(s.cpu_user), PERCENT(s.cpu_system), PERCENT(s.cpu_idle), + PERCENT(s.cpu_iowait), PERCENT(s.cpu_irq), PERCENT(s.cpu_softirq), + PERCENT(s.cpu_steal), PERCENT(s.cpu_guest), + timestamp ? timestr : ""); + +#undef PERCENT +#undef HERTZ +} + +static void +usage(void) +{ + eprintf("usage: %s [-ant] [delay [count]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + static struct vm vm[2]; + static struct timespec delay; + char *end; + double tmp; + unsigned long long int count = 0, i = 0; + int one_header = 0; + int active_mem = 0; + int timestamp = 0; + + ARGBEGIN { + case 'a': + active_mem = 1; + break; + case 'n': + one_header = 1; + break; + case 't': + timestamp = 1; + break; + case 'w': + /* Ignored for compatibility (allow output to be wider then 80 columns) */ + break; + default: + usage(); + } ARGEND; + + if (argc > 2) + usage(); + + kb_per_page = (intmax_t)(sysconf(_SC_PAGESIZE) / 1024); + hz = (intmax_t)sysconf(_SC_CLK_TCK); + + if (argc) { + errno = 0; + tmp = strtod(argv[0], &end); + if (errno || *end || tmp <= 0) + eprintf("%s: not a valid positive number\n", argv[0]); + delay.tv_sec = (time_t)tmp; + tmp = (tmp - (double)delay.tv_sec) * 1000000000.; + delay.tv_nsec = (long int)tmp; + if (delay.tv_nsec > 999999999L) + delay.tv_nsec = 999999999L; + + if (argc > 1) + count = (unsigned long long int)atoll(argv[1]); + } + + goto beginning; + for (; argc && (argc < 2 || i < count); i++) { + clock_nanosleep(CLOCK_MONOTONIC, 0, &delay, NULL); + beginning: + load_vm(&vm[i & 1]); + print_vm(&vm[i & 1], &vm[~i & 1], active_mem, timestamp, one_header ? !i : (i % 50 == 0)); + } + + return 0; +} -- 2.11.1