xref: /freebsd/usr.sbin/sa/main.c (revision d93a896e)
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 #if 0
32 #ifndef lint
33 static const char copyright[] =
34 "@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
35  All rights reserved.\n";
36 #endif
37 #endif
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD$");
40 
41 /*
42  * sa:	system accounting
43  */
44 
45 #include <sys/types.h>
46 #include <sys/acct.h>
47 #include <ctype.h>
48 #include <err.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <signal.h>
52 #include <stdint.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include "extern.h"
58 #include "pathnames.h"
59 
60 static FILE	*acct_load(const char *, int);
61 static int	 cmp_comm(const char *, const char *);
62 static int	 cmp_usrsys(const DBT *, const DBT *);
63 static int	 cmp_avgusrsys(const DBT *, const DBT *);
64 static int	 cmp_dkio(const DBT *, const DBT *);
65 static int	 cmp_avgdkio(const DBT *, const DBT *);
66 static int	 cmp_cpumem(const DBT *, const DBT *);
67 static int	 cmp_avgcpumem(const DBT *, const DBT *);
68 static int	 cmp_calls(const DBT *, const DBT *);
69 static void	 usage(void);
70 
71 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
72 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
73 u_quad_t cutoff = 1;
74 const char *pdb_file = _PATH_SAVACCT;
75 const char *usrdb_file = _PATH_USRACCT;
76 
77 static char	*dfltargv[] = { NULL };
78 static int	dfltargc = (sizeof dfltargv/sizeof(char *));
79 
80 /* default to comparing by sum of user + system time */
81 cmpf_t   sa_cmp = cmp_usrsys;
82 
83 int
84 main(int argc, char **argv)
85 {
86 	FILE *f;
87 	char pathacct[] = _PATH_ACCT;
88 	int ch, error = 0;
89 
90 	dfltargv[0] = pathacct;
91 
92 	while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -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 'P':
152 				/* specify program database summary file */
153 				pdb_file = optarg;
154 				break;
155 			case 'q':
156 				/* quiet; error messages only */
157 				qflag = 1;
158 				break;
159 			case 'r':
160 				/* reverse order of sort */
161 				rflag = 1;
162 				break;
163 			case 's':
164 				/* merge accounting file into summaries */
165 				sflag = 1;
166 				break;
167 			case 't':
168 				/* report ratio of user and system times */
169 				tflag = 1;
170 				break;
171 			case 'u':
172 				/* first, print uid and command name */
173 				uflag = 1;
174 				break;
175 			case 'U':
176 				/* specify user database summary file */
177 				usrdb_file = optarg;
178 				break;
179 			case 'v':
180 				/* cull junk */
181 				vflag = 1;
182 				cutoff = atoi(optarg);
183 				break;
184 			case '?':
185 	                default:
186 				usage();
187 		}
188 
189 	argc -= optind;
190 	argv += optind;
191 
192 	/* various argument checking */
193 	if (fflag && !vflag)
194 		errx(1, "only one of -f requires -v");
195 	if (fflag && aflag)
196 		errx(1, "only one of -a and -v may be specified");
197 	/* XXX need more argument checking */
198 
199 	if (!uflag) {
200 		/* initialize tables */
201 		if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
202 			errx(1, "process accounting initialization failed");
203 		if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
204 			errx(1, "user accounting initialization failed");
205 	}
206 
207 	if (argc == 0) {
208 		argc = dfltargc;
209 		argv = dfltargv;
210 	}
211 
212 	/* for each file specified */
213 	for (; argc > 0; argc--, argv++) {
214 		/*
215 		 * load the accounting data from the file.
216 		 * if it fails, go on to the next file.
217 		 */
218 		f = acct_load(argv[0], sflag);
219 		if (f == NULL)
220 			continue;
221 
222 		if (!uflag && sflag) {
223 #ifndef DEBUG
224 			sigset_t nmask, omask;
225 			int unmask = 1;
226 
227 			/*
228 			 * block most signals so we aren't interrupted during
229 			 * the update.
230 			 */
231 			if (sigfillset(&nmask) == -1) {
232 				warn("sigfillset");
233 				unmask = 0;
234 				error = 1;
235 			}
236 			if (unmask &&
237 			    (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
238 				warn("couldn't set signal mask");
239 				unmask = 0;
240 				error = 1;
241 			}
242 #endif /* DEBUG */
243 
244 			/*
245 			 * truncate the accounting data file ASAP, to avoid
246 			 * losing data.  don't worry about errors in updating
247 			 * the saved stats; better to underbill than overbill,
248 			 * but we want every accounting record intact.
249 			 */
250 			if (ftruncate(fileno(f), 0) == -1) {
251 				warn("couldn't truncate %s", *argv);
252 				error = 1;
253 			}
254 
255 			/*
256 			 * update saved user and process accounting data.
257 			 * note errors for later.
258 			 */
259 			if (pacct_update() != 0 || usracct_update() != 0)
260 				error = 1;
261 
262 #ifndef DEBUG
263 			/*
264 			 * restore signals
265 			 */
266 			if (unmask &&
267 			    (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
268 				warn("couldn't restore signal mask");
269 				error = 1;
270 			}
271 #endif /* DEBUG */
272 		}
273 
274 		/*
275 		 * close the opened accounting file
276 		 */
277 		if (fclose(f) == EOF) {
278 			warn("fclose %s", *argv);
279 			error = 1;
280 		}
281 	}
282 
283 	if (!uflag && !qflag) {
284 		/* print any results we may have obtained. */
285 		if (!mflag)
286 			pacct_print();
287 		else
288 			usracct_print();
289 	}
290 
291 	if (!uflag) {
292 		/* finally, deallocate databases */
293 		if (sflag || (!mflag && !qflag))
294 			pacct_destroy();
295 		if (sflag || (mflag && !qflag))
296 			usracct_destroy();
297 	}
298 
299 	exit(error);
300 }
301 
302 static void
303 usage(void)
304 {
305 	(void)fprintf(stderr,
306 		"usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n");
307 	exit(1);
308 }
309 
310 static FILE *
311 acct_load(const char *pn, int wr)
312 {
313 	struct acctv3 ac;
314 	struct cmdinfo ci;
315 	ssize_t rv;
316 	FILE *f;
317 	int i;
318 
319 	/*
320 	 * open the file
321 	 */
322 	f = fopen(pn, wr ? "r+" : "r");
323 	if (f == NULL) {
324 		warn("open %s %s", pn, wr ? "for read/write" : "read-only");
325 		return (NULL);
326 	}
327 
328 	/*
329 	 * read all we can; don't stat and open because more processes
330 	 * could exit, and we'd miss them
331 	 */
332 	while (1) {
333 		/* get one accounting entry and punt if there's an error */
334 		rv = readrec_forward(f, &ac);
335 		if (rv != 1) {
336 			if (rv == EOF)
337 				warn("error reading %s", pn);
338 			break;
339 		}
340 
341 		/* decode it */
342 		ci.ci_calls = 1;
343 		for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0';
344 		    i++) {
345 			char c = ac.ac_comm[i];
346 
347 			if (!isascii(c) || iscntrl(c)) {
348 				ci.ci_comm[i] = '?';
349 				ci.ci_flags |= CI_UNPRINTABLE;
350 			} else
351 				ci.ci_comm[i] = c;
352 		}
353 		if (ac.ac_flagx & AFORK)
354 			ci.ci_comm[i++] = '*';
355 		ci.ci_comm[i++] = '\0';
356 		ci.ci_etime = ac.ac_etime;
357 		ci.ci_utime = ac.ac_utime;
358 		ci.ci_stime = ac.ac_stime;
359 		ci.ci_uid = ac.ac_uid;
360 		ci.ci_mem = ac.ac_mem;
361 		ci.ci_io = ac.ac_io;
362 
363 		if (!uflag) {
364 			/* and enter it into the usracct and pacct databases */
365 			if (sflag || (!mflag && !qflag))
366 				pacct_add(&ci);
367 			if (sflag || (mflag && !qflag))
368 				usracct_add(&ci);
369 		} else if (!qflag)
370 			printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n",
371 			    ci.ci_uid,
372 			    (ci.ci_utime + ci.ci_stime) / 1000000,
373 			    ci.ci_mem, ci.ci_io,
374 			    ci.ci_comm);
375 	}
376 
377 	/* Finally, return the file stream for possible truncation. */
378 	return (f);
379 }
380 
381 /* sort commands, doing the right thing in terms of reversals */
382 static int
383 cmp_comm(const char *s1, const char *s2)
384 {
385 	int rv;
386 
387 	rv = strcmp(s1, s2);
388 	if (rv == 0)
389 		rv = -1;
390 	return (rflag ? rv : -rv);
391 }
392 
393 /* sort by total user and system time */
394 static int
395 cmp_usrsys(const DBT *d1, const DBT *d2)
396 {
397 	struct cmdinfo c1, c2;
398 	double t1, t2;
399 
400 	memcpy(&c1, d1->data, sizeof(c1));
401 	memcpy(&c2, d2->data, sizeof(c2));
402 
403 	t1 = c1.ci_utime + c1.ci_stime;
404 	t2 = c2.ci_utime + c2.ci_stime;
405 
406 	if (t1 < t2)
407 		return -1;
408 	else if (t1 == t2)
409 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
410 	else
411 		return 1;
412 }
413 
414 /* sort by average user and system time */
415 static int
416 cmp_avgusrsys(const DBT *d1, const DBT *d2)
417 {
418 	struct cmdinfo c1, c2;
419 	double t1, t2;
420 
421 	memcpy(&c1, d1->data, sizeof(c1));
422 	memcpy(&c2, d2->data, sizeof(c2));
423 
424 	t1 = c1.ci_utime + c1.ci_stime;
425 	t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
426 
427 	t2 = c2.ci_utime + c2.ci_stime;
428 	t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
429 
430 	if (t1 < t2)
431 		return -1;
432 	else if (t1 == t2)
433 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
434 	else
435 		return 1;
436 }
437 
438 /* sort by total number of disk I/O operations */
439 static int
440 cmp_dkio(const DBT *d1, const DBT *d2)
441 {
442 	struct cmdinfo c1, c2;
443 
444 	memcpy(&c1, d1->data, sizeof(c1));
445 	memcpy(&c2, d2->data, sizeof(c2));
446 
447 	if (c1.ci_io < c2.ci_io)
448 		return -1;
449 	else if (c1.ci_io == c2.ci_io)
450 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
451 	else
452 		return 1;
453 }
454 
455 /* sort by average number of disk I/O operations */
456 static int
457 cmp_avgdkio(const DBT *d1, const DBT *d2)
458 {
459 	struct cmdinfo c1, c2;
460 	double n1, n2;
461 
462 	memcpy(&c1, d1->data, sizeof(c1));
463 	memcpy(&c2, d2->data, sizeof(c2));
464 
465 	n1 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1);
466 	n2 = c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1);
467 
468 	if (n1 < n2)
469 		return -1;
470 	else if (n1 == n2)
471 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
472 	else
473 		return 1;
474 }
475 
476 /* sort by the cpu-storage integral */
477 static int
478 cmp_cpumem(const DBT *d1, const DBT *d2)
479 {
480 	struct cmdinfo c1, c2;
481 
482 	memcpy(&c1, d1->data, sizeof(c1));
483 	memcpy(&c2, d2->data, sizeof(c2));
484 
485 	if (c1.ci_mem < c2.ci_mem)
486 		return -1;
487 	else if (c1.ci_mem == c2.ci_mem)
488 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
489 	else
490 		return 1;
491 }
492 
493 /* sort by the cpu-time average memory usage */
494 static int
495 cmp_avgcpumem(const DBT *d1, const DBT *d2)
496 {
497 	struct cmdinfo c1, c2;
498 	double t1, t2;
499 	double n1, n2;
500 
501 	memcpy(&c1, d1->data, sizeof(c1));
502 	memcpy(&c2, d2->data, sizeof(c2));
503 
504 	t1 = c1.ci_utime + c1.ci_stime;
505 	t2 = c2.ci_utime + c2.ci_stime;
506 
507 	n1 = c1.ci_mem / (t1 ? t1 : 1);
508 	n2 = c2.ci_mem / (t2 ? t2 : 1);
509 
510 	if (n1 < n2)
511 		return -1;
512 	else if (n1 == n2)
513 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
514 	else
515 		return 1;
516 }
517 
518 /* sort by the number of invocations */
519 static int
520 cmp_calls(const DBT *d1, const DBT *d2)
521 {
522 	struct cmdinfo c1, c2;
523 
524 	memcpy(&c1, d1->data, sizeof(c1));
525 	memcpy(&c2, d2->data, sizeof(c2));
526 
527 	if (c1.ci_calls < c2.ci_calls)
528 		return -1;
529 	else if (c1.ci_calls == c2.ci_calls)
530 		return (cmp_comm(c1.ci_comm, c2.ci_comm));
531 	else
532 		return 1;
533 }
534