Home | History | Annotate | Line # | Download | only in sa
main.c revision 1.7
      1 /*
      2  * Copyright (c) 1994 Christopher G. Demetriou
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  * 3. All advertising materials mentioning features or use of this software
     14  *    must display the following acknowledgement:
     15  *      This product includes software developed by Christopher G. Demetriou.
     16  * 4. The name of the author may not be used to endorse or promote products
     17  *    derived from this software without specific prior written permission
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include <sys/cdefs.h>
     32 #ifndef lint
     33 __COPYRIGHT("@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
     34  All rights reserved.\n");
     35 
     36 __RCSID("$NetBSD: main.c,v 1.7 1997/10/18 03:57:25 lukem Exp $");
     37 #endif
     38 
     39 /*
     40  * sa:	system accounting
     41  */
     42 
     43 #include <sys/types.h>
     44 #include <sys/acct.h>
     45 #include <ctype.h>
     46 #include <err.h>
     47 #include <fcntl.h>
     48 #include <signal.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>
     52 #include <unistd.h>
     53 #include "extern.h"
     54 #include "pathnames.h"
     55 
     56 static int	acct_load	__P((char *, int));
     57 static u_quad_t	decode_comp_t	__P((comp_t));
     58 static int	cmp_comm	__P((const char *, const char *));
     59 static int	cmp_usrsys	__P((const DBT *, const DBT *));
     60 static int	cmp_avgusrsys	__P((const DBT *, const DBT *));
     61 static int	cmp_dkio	__P((const DBT *, const DBT *));
     62 static int	cmp_avgdkio	__P((const DBT *, const DBT *));
     63 static int	cmp_cpumem	__P((const DBT *, const DBT *));
     64 static int	cmp_avgcpumem	__P((const DBT *, const DBT *));
     65 static int	cmp_calls	__P((const DBT *, const DBT *));
     66 
     67 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
     68 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
     69 int cutoff = 1;
     70 
     71 static char	*dfltargv[] = { _PATH_ACCT };
     72 static int	dfltargc = (sizeof(dfltargv)/sizeof(char *));
     73 
     74 /* default to comparing by sum of user + system time */
     75 cmpf_t   sa_cmp = cmp_usrsys;
     76 
     77 int
     78 main(argc, argv)
     79 	int argc;
     80 	char **argv;
     81 {
     82 	int ch;
     83 	int error;
     84 
     85 	error = 0;
     86 	while ((ch = getopt(argc, argv, "abcdDfijkKlmnqrstuv:")) != -1)
     87 		switch (ch) {
     88 			case 'a':
     89 				/* print all commands */
     90 				aflag = 1;
     91 				break;
     92 			case 'b':
     93 				/* sort by per-call user/system time average */
     94 				bflag = 1;
     95 				sa_cmp = cmp_avgusrsys;
     96 				break;
     97 			case 'c':
     98 				/* print percentage total time */
     99 				cflag = 1;
    100 				break;
    101 			case 'd':
    102 				/* sort by averge number of disk I/O ops */
    103 				dflag = 1;
    104 				sa_cmp = cmp_avgdkio;
    105 				break;
    106 			case 'D':
    107 				/* print and sort by total disk I/O ops */
    108 				Dflag = 1;
    109 				sa_cmp = cmp_dkio;
    110 				break;
    111 			case 'f':
    112 				/* force no interactive threshold comprison */
    113 				fflag = 1;
    114 				break;
    115 			case 'i':
    116 				/* do not read in summary file */
    117 				iflag = 1;
    118 				break;
    119 			case 'j':
    120 				/* instead of total minutes, give sec/call */
    121 				jflag = 1;
    122 				break;
    123 			case 'k':
    124 				/* sort by cpu-time average memory usage */
    125 				kflag = 1;
    126 				sa_cmp = cmp_avgcpumem;
    127 				break;
    128 			case 'K':
    129 				/* print and sort by cpu-storage integral */
    130 				sa_cmp = cmp_cpumem;
    131 				Kflag = 1;
    132 				break;
    133 			case 'l':
    134 				/* seperate system and user time */
    135 				lflag = 1;
    136 				break;
    137 			case 'm':
    138 				/* print procs and time per-user */
    139 				mflag = 1;
    140 				break;
    141 			case 'n':
    142 				/* sort by number of calls */
    143 				sa_cmp = cmp_calls;
    144 				break;
    145 			case 'q':
    146 				/* quiet; error messages only */
    147 				qflag = 1;
    148 				break;
    149 			case 'r':
    150 				/* reverse order of sort */
    151 				rflag = 1;
    152 				break;
    153 			case 's':
    154 				/* merge accounting file into summaries */
    155 				sflag = 1;
    156 				break;
    157 			case 't':
    158 				/* report ratio of user and system times */
    159 				tflag = 1;
    160 				break;
    161 			case 'u':
    162 				/* first, print uid and command name */
    163 				uflag = 1;
    164 				break;
    165 			case 'v':
    166 				/* cull junk */
    167 				vflag = 1;
    168 				cutoff = atoi(optarg);
    169 				break;
    170 			case '?':
    171 	                default:
    172 				(void)fprintf(stderr,
    173 				    "usage: sa [-abcdDfijkKlmnqrstu] [-v cutoff] [file ...]\n");
    174 				exit(1);
    175 		}
    176 
    177 	argc -= optind;
    178 	argv += optind;
    179 
    180 	/* various argument checking */
    181 	if (fflag && !vflag)
    182 		errx(1, "only one of -f requires -v");
    183 	if (fflag && aflag)
    184 		errx(1, "only one of -a and -v may be specified");
    185 	/* XXX need more argument checking */
    186 
    187 	if (!uflag) {
    188 		/* initialize tables */
    189 		if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
    190 			errx(1, "process accounting initialization failed");
    191 		if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
    192 			errx(1, "user accounting initialization failed");
    193 	}
    194 
    195 	if (argc == 0) {
    196 		argc = dfltargc;
    197 		argv = dfltargv;
    198 	}
    199 
    200 	/* for each file specified */
    201 	for (; argc > 0; argc--, argv++) {
    202 		int	fd;
    203 
    204 		/*
    205 		 * load the accounting data from the file.
    206 		 * if it fails, go on to the next file.
    207 		 */
    208 		fd = acct_load(argv[0], sflag);
    209 		if (fd < 0)
    210 			continue;
    211 
    212 		if (!uflag && sflag) {
    213 #ifndef DEBUG
    214 			sigset_t nmask, omask;
    215 			int unmask = 1;
    216 
    217 			/*
    218 			 * block most signals so we aren't interrupted during
    219 			 * the update.
    220 			 */
    221 			if (sigfillset(&nmask) == -1) {
    222 				warn("sigfillset");
    223 				unmask = 0;
    224 				error = 1;
    225 			}
    226 			if (unmask &&
    227 			    (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
    228 				warn("couldn't set signal mask ");
    229 				unmask = 0;
    230 				error = 1;
    231 			}
    232 #endif /* DEBUG */
    233 
    234 			/*
    235 			 * truncate the accounting data file ASAP, to avoid
    236 			 * losing data.  don't worry about errors in updating
    237 			 * the saved stats; better to underbill than overbill,
    238 			 * but we want every accounting record intact.
    239 			 */
    240 			if (ftruncate(fd, 0) == -1) {
    241 				warn("couldn't truncate %s", *argv);
    242 				error = 1;
    243 			}
    244 
    245 			/*
    246 			 * update saved user and process accounting data.
    247 			 * note errors for later.
    248 			 */
    249 			if (pacct_update() != 0 || usracct_update() != 0)
    250 				error = 1;
    251 
    252 #ifndef DEBUG
    253 			/*
    254 			 * restore signals
    255 			 */
    256 			if (unmask &&
    257 			    (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
    258 				warn("couldn't restore signal mask");
    259 				error = 1;
    260 			}
    261 #endif /* DEBUG */
    262 		}
    263 
    264 		/*
    265 		 * close the opened accounting file
    266 		 */
    267 		if (close(fd) == -1) {
    268 			warn("close %s", *argv);
    269 			error = 1;
    270 		}
    271 	}
    272 
    273 	if (!uflag && !qflag) {
    274 		/* print any results we may have obtained. */
    275 		if (!mflag)
    276 			pacct_print();
    277 		else
    278 			usracct_print();
    279 	}
    280 
    281 	if (!uflag) {
    282 		/* finally, deallocate databases */
    283 		if (sflag || (!mflag && !qflag))
    284 			pacct_destroy();
    285 		if (sflag || (mflag && !qflag))
    286 			usracct_destroy();
    287 	}
    288 
    289 	exit(error);
    290 }
    291 
    292 static int
    293 acct_load(pn, wr)
    294 	char *pn;
    295 	int wr;
    296 {
    297 	struct acct ac;
    298 	struct cmdinfo ci;
    299 	ssize_t rv;
    300 	int fd, i;
    301 
    302 	/*
    303 	 * open the file
    304 	 */
    305 	fd = open(pn, wr ? O_RDWR : O_RDONLY, 0);
    306 	if (fd == -1) {
    307 		warn("open %s %s", pn, wr ? "for read/write" : "read-only");
    308 		return (-1);
    309 	}
    310 
    311 	/*
    312 	 * read all we can; don't stat and open because more processes
    313 	 * could exit, and we'd miss them
    314 	 */
    315 	while (1) {
    316 		/* get one accounting entry and punt if there's an error */
    317 		rv = read(fd, &ac, sizeof(struct acct));
    318 		if (rv == -1)
    319 			warn("error reading %s", pn);
    320 		else if (rv > 0 && rv < sizeof(struct acct))
    321 			warnx("short read of accounting data in %s", pn);
    322 		if (rv != sizeof(struct acct))
    323 			break;
    324 
    325 		/* decode it */
    326 		ci.ci_calls = 1;
    327 		for (i = 0; i < sizeof(ac.ac_comm) && ac.ac_comm[i] != '\0';
    328 		    i++) {
    329 			char c = ac.ac_comm[i];
    330 
    331 			if (!isascii(c) || iscntrl(c)) {
    332 				ci.ci_comm[i] = '?';
    333 				ci.ci_flags |= CI_UNPRINTABLE;
    334 			} else
    335 				ci.ci_comm[i] = c;
    336 		}
    337 		if (ac.ac_flag & AFORK)
    338 			ci.ci_comm[i++] = '*';
    339 		ci.ci_comm[i++] = '\0';
    340 		ci.ci_etime = decode_comp_t(ac.ac_etime);
    341 		ci.ci_utime = decode_comp_t(ac.ac_utime);
    342 		ci.ci_stime = decode_comp_t(ac.ac_stime);
    343 		ci.ci_uid = ac.ac_uid;
    344 		ci.ci_mem = ac.ac_mem;
    345 		ci.ci_io = decode_comp_t(ac.ac_io) / AHZ;
    346 
    347 		if (!uflag) {
    348 			/* and enter it into the usracct and pacct databases */
    349 			if (sflag || (!mflag && !qflag))
    350 				pacct_add(&ci);
    351 			if (sflag || (mflag && !qflag))
    352 				usracct_add(&ci);
    353 		} else if (!qflag)
    354 			printf("%6u %12.2f cpu %12quk mem %12qu io %s\n",
    355 			    ci.ci_uid,
    356 			    (ci.ci_utime + ci.ci_stime) / (double) AHZ,
    357 			    ci.ci_mem, ci.ci_io, ci.ci_comm);
    358 	}
    359 
    360 	/* finally, return the file descriptor for possible truncation */
    361 	return (fd);
    362 }
    363 
    364 static u_quad_t
    365 decode_comp_t(comp)
    366 	comp_t comp;
    367 {
    368 	u_quad_t rv;
    369 
    370 	/*
    371 	 * for more info on the comp_t format, see:
    372 	 *	/usr/src/sys/kern/kern_acct.c
    373 	 *	/usr/src/sys/sys/acct.h
    374 	 *	/usr/src/usr.bin/lastcomm/lastcomm.c
    375 	 */
    376 	rv = comp & 0x1fff;	/* 13 bit fraction */
    377 	comp >>= 13;		/* 3 bit base-8 exponent */
    378 	while (comp--)
    379 		rv <<= 3;
    380 
    381 	return (rv);
    382 }
    383 
    384 /* sort commands, doing the right thing in terms of reversals */
    385 static int
    386 cmp_comm(s1, s2)
    387 	const char *s1, *s2;
    388 {
    389 	int rv;
    390 
    391 	rv = strcmp(s1, s2);
    392 	if (rv == 0)
    393 		rv = -1;
    394 	return (rflag ? rv : -rv);
    395 }
    396 
    397 /* sort by total user and system time */
    398 static int
    399 cmp_usrsys(d1, d2)
    400 	const DBT *d1, *d2;
    401 {
    402 	struct cmdinfo c1, c2;
    403 	u_quad_t t1, t2;
    404 
    405 	memcpy(&c1, d1->data, sizeof(c1));
    406 	memcpy(&c2, d2->data, sizeof(c2));
    407 
    408 	t1 = c1.ci_utime + c1.ci_stime;
    409 	t2 = c2.ci_utime + c2.ci_stime;
    410 
    411 	if (t1 < t2)
    412 		return -1;
    413 	else if (t1 == t2)
    414 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    415 	else
    416 		return 1;
    417 }
    418 
    419 /* sort by average user and system time */
    420 static int
    421 cmp_avgusrsys(d1, d2)
    422 	const DBT *d1, *d2;
    423 {
    424 	struct cmdinfo c1, c2;
    425 	double t1, t2;
    426 
    427 	memcpy(&c1, d1->data, sizeof(c1));
    428 	memcpy(&c2, d2->data, sizeof(c2));
    429 
    430 	t1 = c1.ci_utime + c1.ci_stime;
    431 	t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
    432 
    433 	t2 = c2.ci_utime + c2.ci_stime;
    434 	t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
    435 
    436 	if (t1 < t2)
    437 		return -1;
    438 	else if (t1 == t2)
    439 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    440 	else
    441 		return 1;
    442 }
    443 
    444 /* sort by total number of disk I/O operations */
    445 static int
    446 cmp_dkio(d1, d2)
    447 	const DBT *d1, *d2;
    448 {
    449 	struct cmdinfo c1, c2;
    450 
    451 	memcpy(&c1, d1->data, sizeof(c1));
    452 	memcpy(&c2, d2->data, sizeof(c2));
    453 
    454 	if (c1.ci_io < c2.ci_io)
    455 		return -1;
    456 	else if (c1.ci_io == c2.ci_io)
    457 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    458 	else
    459 		return 1;
    460 }
    461 
    462 /* sort by average number of disk I/O operations */
    463 static int
    464 cmp_avgdkio(d1, d2)
    465 	const DBT *d1, *d2;
    466 {
    467 	struct cmdinfo c1, c2;
    468 	double n1, n2;
    469 
    470 	memcpy(&c1, d1->data, sizeof(c1));
    471 	memcpy(&c2, d2->data, sizeof(c2));
    472 
    473 	n1 = (double) c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1);
    474 	n2 = (double) c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1);
    475 
    476 	if (n1 < n2)
    477 		return -1;
    478 	else if (n1 == n2)
    479 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    480 	else
    481 		return 1;
    482 }
    483 
    484 /* sort by the cpu-storage integral */
    485 static int
    486 cmp_cpumem(d1, d2)
    487 	const DBT *d1, *d2;
    488 {
    489 	struct cmdinfo c1, c2;
    490 
    491 	memcpy(&c1, d1->data, sizeof(c1));
    492 	memcpy(&c2, d2->data, sizeof(c2));
    493 
    494 	if (c1.ci_mem < c2.ci_mem)
    495 		return -1;
    496 	else if (c1.ci_mem == c2.ci_mem)
    497 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    498 	else
    499 		return 1;
    500 }
    501 
    502 /* sort by the cpu-time average memory usage */
    503 static int
    504 cmp_avgcpumem(d1, d2)
    505 	const DBT *d1, *d2;
    506 {
    507 	struct cmdinfo c1, c2;
    508 	u_quad_t t1, t2;
    509 	double n1, n2;
    510 
    511 	memcpy(&c1, d1->data, sizeof(c1));
    512 	memcpy(&c2, d2->data, sizeof(c2));
    513 
    514 	t1 = c1.ci_utime + c1.ci_stime;
    515 	t2 = c2.ci_utime + c2.ci_stime;
    516 
    517 	n1 = (double) c1.ci_mem / (double) (t1 ? t1 : 1);
    518 	n2 = (double) c2.ci_mem / (double) (t2 ? t2 : 1);
    519 
    520 	if (n1 < n2)
    521 		return -1;
    522 	else if (n1 == n2)
    523 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    524 	else
    525 		return 1;
    526 }
    527 
    528 /* sort by the number of invocations */
    529 static int
    530 cmp_calls(d1, d2)
    531 	const DBT *d1, *d2;
    532 {
    533 	struct cmdinfo c1, c2;
    534 
    535 	memcpy(&c1, d1->data, sizeof(c1));
    536 	memcpy(&c2, d2->data, sizeof(c2));
    537 
    538 	if (c1.ci_calls < c2.ci_calls)
    539 		return -1;
    540 	else if (c1.ci_calls == c2.ci_calls)
    541 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
    542 	else
    543 		return 1;
    544 }
    545