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
33static 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
60static FILE	*acct_load(const char *, int);
61static int	 cmp_comm(const char *, const char *);
62static int	 cmp_usrsys(const DBT *, const DBT *);
63static int	 cmp_avgusrsys(const DBT *, const DBT *);
64static int	 cmp_dkio(const DBT *, const DBT *);
65static int	 cmp_avgdkio(const DBT *, const DBT *);
66static int	 cmp_cpumem(const DBT *, const DBT *);
67static int	 cmp_avgcpumem(const DBT *, const DBT *);
68static int	 cmp_calls(const DBT *, const DBT *);
69static void	 usage(void);
70
71int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
72int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
73u_quad_t cutoff = 1;
74const char *pdb_file = _PATH_SAVACCT;
75const char *usrdb_file = _PATH_USRACCT;
76
77static char	*dfltargv[] = { NULL };
78static int	dfltargc = (sizeof dfltargv/sizeof(char *));
79
80/* default to comparing by sum of user + system time */
81cmpf_t   sa_cmp = cmp_usrsys;
82
83int
84main(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
302static void
303usage(void)
304{
305	(void)fprintf(stderr,
306		"usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n");
307	exit(1);
308}
309
310static FILE *
311acct_load(const char *pn, int wr)
312{
313	struct acctv2 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 */
382static int
383cmp_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 */
394static int
395cmp_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 */
415static int
416cmp_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 */
439static int
440cmp_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 */
456static int
457cmp_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 */
477static int
478cmp_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 */
494static int
495cmp_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 */
519static int
520cmp_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