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