[BACK]Return to main.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / systat

File: [local] / src / usr.bin / systat / main.c (download)

Revision 1.77, Sun Dec 4 18:01:57 2022 UTC (17 months, 2 weeks ago) by cheloha
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD
Changes since 1.76: +2 -3 lines

systat(1): vmstat: measure elapsed time with clock_gettime(2) instead of ticks

The vmstat view in systat(1) should not use statclock() ticks to count
elapsed time.  First, ticks are low resolution.  Second, the statclock
is sometimes randomized, so each tick is not necessarily of equal
length.  Third, we're counting ticks from every CPU on the system, so
every rate in the view is divided by the number of CPUs.  For example,
on an amd64 system with 8 CPUs you currently see:

     200 clock

... when the true clock interrupt rate on that system is 1600.

Instead, measure elapsed time with clock_gettime(2).  Use CLOCK_UPTIME
here so we exclude time when the system is suspended.  With this
change we no longer need "stathz" or "hertz".  We can also get rid of
the anachronistic secondary clock failure test.

Prompted by dlg@ and jmatthew@.  deraadt@ says this has been in snaps
since 2022-11-21; no complaints.

Link: https://marc.info/?l=openbsd-tech&m=166898960831136&w=2

ok dlg@ deraadt@

/* $OpenBSD: main.c,v 1.77 2022/12/04 18:01:57 cheloha Exp $	 */
/*
 * Copyright (c) 2001, 2007 Can Erkin Acar
 * Copyright (c) 2001 Daniel Hartmeier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <sys/types.h>
#include <sys/sysctl.h>


#include <ctype.h>
#include <curses.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <utmp.h>

#include "engine.h"
#include "systat.h"

#define TIMEPOS (80 - 8 - 20 - 1)

double	dellave;

kvm_t	*kd;
char	*nlistf = NULL;
char	*memf = NULL;
double	avenrun[3];
double	naptime = 5.0;
int	verbose = 1;		/* to report kvm read errs */
int	nflag = 1;
int	ut, hz;
char    hostname[HOST_NAME_MAX+1];
WINDOW  *wnd;
int	CMDLINE;
char	timebuf[26];
char	uloadbuf[TIMEPOS];


int  ucount(void);
void usage(void);
double strtodnum(const char *, double, double, const char **);

/* command prompt */

void cmd_delay(const char *);
void cmd_count(const char *);
void cmd_compat(const char *);

struct command cm_compat = {"Command", cmd_compat};
struct command cm_delay = {"Seconds to delay", cmd_delay};
struct command cm_count = {"Number of lines to display", cmd_count};


/* display functions */

int
print_header(void)
{
	time_t now;
	int start = dispstart + 1, end = dispstart + maxprint;
	char tmpbuf[TIMEPOS];
	char header[MAX_LINE_BUF];

	if (end > num_disp)
		end = num_disp;

	tb_start();

	if (!paused) {
		char *ctim;

		getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0]));

		snprintf(uloadbuf, sizeof(uloadbuf),
		    "%4d users Load %.2f %.2f %.2f", 
		    ucount(), avenrun[0], avenrun[1], avenrun[2]);

		time(&now);
		ctim = ctime(&now);
		ctim[11+8] = '\0';
		strlcpy(timebuf, ctim + 11, sizeof(timebuf));
	}

	if (num_disp && (start > 1 || end != num_disp))
		snprintf(tmpbuf, sizeof(tmpbuf),
		    "%s (%u-%u of %u) %s", uloadbuf, start, end, num_disp,
		    paused ? "PAUSED" : "");
	else
		snprintf(tmpbuf, sizeof(tmpbuf), 
		    "%s %s", uloadbuf,
		    paused ? "PAUSED" : "");
		
	snprintf(header, sizeof(header), "%-*s %19.19s %s", TIMEPOS - 1,
	    tmpbuf, hostname, timebuf);

	if (rawmode)
		printf("\n\n%s\n", header);
	else
		mvprintw(0, 0, "%s", header);

	return (1);
}

/* compatibility functions, rearrange later */
void
error(const char *fmt, ...)
{
	va_list ap;
	char buf[MAX_LINE_BUF];

	va_start(ap, fmt);
	vsnprintf(buf, sizeof buf, fmt, ap);
	va_end(ap);

	message_set(buf);
}

void
nlisterr(struct nlist namelist[])
{
	int i, n;

	n = 0;
	clear();
	mvprintw(2, 10, "systat: nlist: can't find following symbols:");
	for (i = 0;
	    namelist[i].n_name != NULL && *namelist[i].n_name != '\0'; i++)
		if (namelist[i].n_value == 0)
			mvprintw(2 + ++n, 10, "%s", namelist[i].n_name);
	move(CMDLINE, 0);
	clrtoeol();
	refresh();
	endwin();
	exit(1);
}

void
die(void)
{
	if (!rawmode)
		endwin();
	exit(0);
}


int
prefix(char *s1, char *s2)
{

	while (*s1 == *s2) {
		if (*s1 == '\0')
			return (1);
		s1++, s2++;
	}
	return (*s1 == '\0');
}

/* calculate number of users on the system */
int
ucount(void)
{
	int nusers = 0;
	struct	utmp utmp;

	if (ut < 0)
		return (0);
	lseek(ut, (off_t)0, SEEK_SET);
	while (read(ut, &utmp, sizeof(utmp)))
		if (utmp.ut_name[0] != '\0')
			nusers++;

	return (nusers);
}

/* main program functions */

void
usage(void)
{
	extern char *__progname;
	fprintf(stderr, "usage: %s [-aBbhiNn] [-d count] "
	    "[-s delay] [-w width] [view] [delay]\n", __progname);
	exit(1);
}

void
show_view(void)
{
	if (rawmode)
		return;

	tb_start();
	tbprintf("%s %g", curr_view->name, naptime);
	tb_end();
	message_set(tmp_buf);
}

void
add_view_tb(field_view *v)
{
	if (curr_view == v)
		tbprintf("[%s] ", v->name);
	else
		tbprintf("%s ", v->name);
}

void
show_help(void)
{
	if (rawmode)
		return;

	tb_start();
	foreach_view(add_view_tb);
	tb_end();
	message_set(tmp_buf);
}

void
add_order_tb(order_type *o)
{
	if (curr_view->mgr->order_curr == o)
		tbprintf("[%s%s(%c)] ", o->name,
		    o->func != NULL && sortdir == -1 ? "^" : "",
		    (char) o->hotkey);
	else
		tbprintf("%s(%c) ", o->name, (char) o->hotkey);
}

void
show_order(void)
{
	if (rawmode)
		return;

	tb_start();
	if (foreach_order(add_order_tb) == -1) {
		tbprintf("No orders available");
	}
	tb_end();
	message_set(tmp_buf);
}

void
cmd_compat(const char *buf)
{
	const char *s;

	if (strcasecmp(buf, "help") == 0) {
		message_toggle(MESSAGE_HELP);
		return;
	}
	if (strcasecmp(buf, "quit") == 0 || strcasecmp(buf, "q") == 0) {
		gotsig_close = 1;
		return;
	}
	if (strcasecmp(buf, "stop") == 0) {
		paused = 1;
		gotsig_alarm = 1;
		return;
	}
	if (strncasecmp(buf, "start", 5) == 0) {
		paused = 0;
		gotsig_alarm = 1;
		cmd_delay(buf + 5);
		return;
	}
	if (strncasecmp(buf, "order", 5) == 0) {
		message_toggle(MESSAGE_ORDER);
		return;
	}
	if (strncasecmp(buf, "human", 5) == 0) {
		humanreadable = !humanreadable;
		return;
	}

	for (s = buf; *s && strchr("0123456789+-.eE", *s) != NULL; s++)
		;
	if (*s) {
		if (set_view(buf))
			error("Invalid/ambiguous view: %s", buf);
	} else
		cmd_delay(buf);
}

void
cmd_delay(const char *buf)
{
	double del;
	const char *errstr;

	if (buf[0] == '\0')
		return;
	del = strtodnum(buf, 0, UINT32_MAX / 1000000, &errstr);
	if (errstr != NULL)
		error("s: \"%s\": delay value is %s", buf, errstr);
	else {
		refresh_delay(del);
		gotsig_alarm = 1;
		naptime = del;
	}
}

void
cmd_count(const char *buf)
{
	const char *errstr;

	maxprint = strtonum(buf, 1, lines - HEADER_LINES, &errstr);
	if (errstr)
		maxprint = lines - HEADER_LINES;
}


int
keyboard_callback(int ch)
{
	switch (ch) {
	case '?':
		/* FALLTHROUGH */
	case 'h':
		message_toggle(MESSAGE_HELP);
		break;
	case CTRL_G:
		message_toggle(MESSAGE_VIEW);
		break;
	case 'l':
		command_set(&cm_count, NULL);
		break;
	case 's':
		command_set(&cm_delay, NULL);
		break;
	case ',':
		separate_thousands = !separate_thousands;
		gotsig_alarm = 1;
		break;
	case ':':
		command_set(&cm_compat, NULL);
		break;
	default:
		return 0;
	};

	return 1;
}

void
initialize(void)
{
	engine_initialize();

	initvmstat();
	initpigs();
	initifstat();
	initiostat();
	initsensors();
	initmembufs();
	initnetstat();
	initswap();
	initpftop();
	initpf();
	initpool();
	initmalloc();
	initnfs();
	initcpu();
	inituvm();
}

void
gethz(void)
{
	struct clockinfo cinf;
	size_t  size = sizeof(cinf);
	int	mib[2];

	mib[0] = CTL_KERN;
	mib[1] = KERN_CLOCKRATE;
	if (sysctl(mib, 2, &cinf, &size, NULL, 0) == -1)
		return;
	hz = cinf.hz;
}

#define	INVALID		1
#define	TOOSMALL	2
#define	TOOLARGE	3

double
strtodnum(const char *nptr, double minval, double maxval, const char **errstrp)
{
	double d = 0;
	int error = 0;
	char *ep;
	struct errval {
		const char *errstr;
		int err;
	} ev[4] = {
		{ NULL,		0 },
		{ "invalid",	EINVAL },
		{ "too small",	ERANGE },
		{ "too large",	ERANGE },
	};

	ev[0].err = errno;
	errno = 0;
	if (minval > maxval) {
		error = INVALID;
	} else {
		d = strtod(nptr, &ep);
		if (nptr == ep || *ep != '\0')
			error = INVALID;
		else if ((d == -HUGE_VAL && errno == ERANGE) || d < minval)
			error = TOOSMALL;
		else if ((d == HUGE_VAL && errno == ERANGE) || d > maxval)
			error = TOOLARGE;
	}
	if (errstrp != NULL)
		*errstrp = ev[error].errstr;
	errno = ev[error].err;
	if (error)
		d = 0;

	return (d);
}

int
main(int argc, char *argv[])
{
	char errbuf[_POSIX2_LINE_MAX];
	const char *errstr;
	extern char *optarg;
	extern int optind;
	double delay = 5, del;

	char *viewstr = NULL;

	gid_t gid;
	int countmax = 0;
	int maxlines = 0;

	int ch;

	ut = open(_PATH_UTMP, O_RDONLY);
	if (ut == -1) {
		warn("No utmp");
	}

	kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);

	gid = getgid();
	if (setresgid(gid, gid, gid) == -1)
		err(1, "setresgid");

	while ((ch = getopt(argc, argv, "BNabd:hins:w:")) != -1) {
		switch (ch) {
		case 'a':
			maxlines = -1;
			break;
		case 'B':
			averageonly = 1;
			if (countmax < 2)
				countmax = 2;
			/* FALLTHROUGH */
		case 'b':
			rawmode = 1;
			interactive = 0;
			break;
		case 'd':
			countmax = strtonum(optarg, 1, INT_MAX, &errstr);
			if (errstr)
				errx(1, "-d %s: %s", optarg, errstr);
			break;
		case 'h':
			humanreadable = 1;
			break;
		case 'i':
			interactive = 1;
			break;
		case 'N':
			nflag = 0;
			break;
		case 'n':
			/* this is a noop, -n is the default */
			nflag = 1;
			break;
		case 's':
			delay = strtodnum(optarg, 0, UINT32_MAX / 1000000,
			    &errstr);
			if (errstr != NULL)
				errx(1, "-s \"%s\": delay value is %s", optarg,
				    errstr);
			break;
		case 'w':
			rawwidth = strtonum(optarg, 1, MAX_LINE_BUF-1, &errstr);
			if (errstr)
				errx(1, "-w %s: %s", optarg, errstr);
			break;
		default:
			usage();
			/* NOTREACHED */
		}
	}

	if (kd == NULL)
		warnx("kvm_openfiles: %s", errbuf);

	argc -= optind;
	argv += optind;

	if (argc == 1) {
		del = strtodnum(argv[0], 0, UINT32_MAX / 1000000, &errstr);
		if (errstr != NULL)
			viewstr = argv[0];
		else
			delay = del;
	} else if (argc == 2) {
		viewstr = argv[0];
		delay = strtodnum(argv[1], 0, UINT32_MAX / 1000000, &errstr);
		if (errstr != NULL)
			errx(1, "\"%s\": delay value is %s", argv[1], errstr);
	}

	refresh_delay(delay);
	naptime = delay;

	gethostname(hostname, sizeof (hostname));
	gethz();

	initialize();

	set_order(NULL);
	if (viewstr && set_view(viewstr)) {
		fprintf(stderr, "Unknown/ambiguous view name: %s\n", viewstr);
		return 1;
	}

	if (check_termcap()) {
		rawmode = 1;
		interactive = 0;
	}

	setup_term(maxlines);

	if (unveil("/", "r") == -1)
		err(1, "unveil /");
	if (unveil(NULL, NULL) == -1)
		err(1, "unveil");

	if (rawmode && countmax == 0)
		countmax = 1;

	gotsig_alarm = 1;

	engine_loop(countmax);

	return 0;
}