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