GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/sa/main.c Lines: 74 217 34.1 %
Date: 2017-11-13 Branches: 48 144 33.3 %

Line Branch Exec Source
1
/*	$OpenBSD: main.c,v 1.16 2016/08/14 22:29:01 krw Exp $	*/
2
/*
3
 * Copyright (c) 1994 Christopher G. Demetriou
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 * 3. All advertising materials mentioning features or use of this software
15
 *    must display the following acknowledgement:
16
 *      This product includes software developed by Christopher G. Demetriou.
17
 * 4. The name of the author may not be used to endorse or promote products
18
 *    derived from this software without specific prior written permission
19
 *
20
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
 */
31
32
/*
33
 * sa:	system accounting
34
 */
35
36
#include <sys/types.h>
37
#include <sys/acct.h>
38
#include <ctype.h>
39
#include <err.h>
40
#include <fcntl.h>
41
#include <signal.h>
42
#include <stdio.h>
43
#include <stdlib.h>
44
#include <string.h>
45
#include <unistd.h>
46
#include "extern.h"
47
#include "pathnames.h"
48
49
static int	acct_load(char *, int);
50
static uint64_t	decode_comp_t(comp_t);
51
static int	cmp_comm(const char *, const char *);
52
static int	cmp_usrsys(const DBT *, const DBT *);
53
static int	cmp_avgusrsys(const DBT *, const DBT *);
54
static int	cmp_dkio(const DBT *, const DBT *);
55
static int	cmp_avgdkio(const DBT *, const DBT *);
56
static int	cmp_cpumem(const DBT *, const DBT *);
57
static int	cmp_avgcpumem(const DBT *, const DBT *);
58
static int	cmp_calls(const DBT *, const DBT *);
59
60
int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
61
int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
62
int cutoff = 1;
63
64
static char	*dfltargv[] = { _PATH_ACCT };
65
static int	dfltargc = (sizeof(dfltargv)/sizeof(char *));
66
67
/* default to comparing by sum of user + system time */
68
cmpf_t   sa_cmp = cmp_usrsys;
69
70
int
71
main(int argc, char **argv)
72
{
73
	int ch;
74
	int error = 0;
75
10
	const char *errstr;
76
	extern char *__progname;
77
78
5
	if (pledge("stdio rpath wpath cpath getpw flock", NULL) == -1)
79
		err(1, "pledge");
80
81
30
	while ((ch = getopt(argc, argv, "abcdDfijkKlmnqrstuv:")) != -1)
82





10
		switch (ch) {
83
		case 'a':
84
			/* print all commands */
85
			aflag = 1;
86
			break;
87
		case 'b':
88
			/* sort by per-call user/system time average */
89
			bflag = 1;
90
			sa_cmp = cmp_avgusrsys;
91
			break;
92
		case 'c':
93
			/* print percentage total time */
94
			cflag = 1;
95
			break;
96
		case 'd':
97
			/* sort by averge number of disk I/O ops */
98
			dflag = 1;
99
			sa_cmp = cmp_avgdkio;
100
			break;
101
		case 'D':
102
			/* print and sort by total disk I/O ops */
103
			Dflag = 1;
104
			sa_cmp = cmp_dkio;
105
			break;
106
		case 'f':
107
			/* force no interactive threshold comprison */
108
			fflag = 1;
109
			break;
110
		case 'i':
111
			/* do not read in summary file */
112
			iflag = 1;
113
			break;
114
		case 'j':
115
			/* instead of total minutes, give sec/call */
116
			jflag = 1;
117
			break;
118
		case 'k':
119
			/* sort by cpu-time average memory usage */
120
			kflag = 1;
121
			sa_cmp = cmp_avgcpumem;
122
			break;
123
		case 'K':
124
			/* print and sort by cpu-storage integral */
125
			sa_cmp = cmp_cpumem;
126
			Kflag = 1;
127
			break;
128
		case 'l':
129
			/* separate system and user time */
130
			lflag = 1;
131
			break;
132
		case 'm':
133
			/* print procs and time per-user */
134
			mflag = 1;
135
			break;
136
		case 'n':
137
			/* sort by number of calls */
138
			sa_cmp = cmp_calls;
139
			break;
140
		case 'q':
141
			/* quiet; error messages only */
142
5
			qflag = 1;
143
5
			break;
144
		case 'r':
145
			/* reverse order of sort */
146
			rflag = 1;
147
			break;
148
		case 's':
149
			/* merge accounting file into summaries */
150
5
			sflag = 1;
151
5
			break;
152
		case 't':
153
			/* report ratio of user and system times */
154
			tflag = 1;
155
			break;
156
		case 'u':
157
			/* first, print uid and command name */
158
			uflag = 1;
159
			break;
160
		case 'v':
161
			/* cull junk */
162
			vflag = 1;
163
			cutoff = strtonum(optarg, 1, INT_MAX, &errstr);
164
			if (errstr)
165
				errx(1, "-v %s: %s", optarg, errstr);
166
			break;
167
		case '?':
168
		default:
169
			(void)fprintf(stderr,
170
			    "usage: %s [-abcDdfijKklmnqrstu] [-v cutoff]"
171
			    " [file ...]\n", __progname);
172
			exit(1);
173
		}
174
175
5
	argc -= optind;
176
5
	argv += optind;
177
178
	/* various argument checking */
179
5
	if (fflag && !vflag)
180
		errx(1, "only one of -f requires -v");
181
5
	if (fflag && aflag)
182
		errx(1, "only one of -a and -v may be specified");
183
	/* XXX need more argument checking */
184
185
5
	if (!uflag) {
186
		/* initialize tables */
187

10
		if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
188
			errx(1, "process accounting initialization failed");
189

10
		if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
190
			errx(1, "user accounting initialization failed");
191
	}
192
193
5
	if (argc == 0) {
194
5
		argc = dfltargc;
195
		argv = dfltargv;
196
5
	}
197
198
	/* for each file specified */
199
20
	for (; argc > 0; argc--, argv++) {
200
		int	fd;
201
202
		/*
203
		 * load the accounting data from the file.
204
		 * if it fails, go on to the next file.
205
		 */
206
5
		fd = acct_load(argv[0], sflag);
207
5
		if (fd < 0)
208
			continue;
209
210
5
		if (!uflag && sflag) {
211
#ifndef DEBUG
212
5
			sigset_t nmask, omask;
213
			int unmask = 1;
214
215
			/*
216
			 * block most signals so we aren't interrupted during
217
			 * the update.
218
			 */
219
5
			if (sigfillset(&nmask) == -1) {
220
				warn("sigfillset");
221
				unmask = 0;
222
				error = 1;
223
			}
224

10
			if (unmask &&
225
5
			    (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
226
				warn("couldn't set signal mask ");
227
				unmask = 0;
228
				error = 1;
229
			}
230
#endif /* DEBUG */
231
232
			/*
233
			 * truncate the accounting data file ASAP, to avoid
234
			 * losing data.  don't worry about errors in updating
235
			 * the saved stats; better to underbill than overbill,
236
			 * but we want every accounting record intact.
237
			 */
238
5
			if (ftruncate(fd, 0) == -1) {
239
				warn("couldn't truncate %s", *argv);
240
				error = 1;
241
			}
242
243
			/*
244
			 * update saved user and process accounting data.
245
			 * note errors for later.
246
			 */
247

10
			if (pacct_update() != 0 || usracct_update() != 0)
248
				error = 1;
249
250
#ifndef DEBUG
251
			/*
252
			 * restore signals
253
			 */
254

10
			if (unmask &&
255
5
			    (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
256
				warn("couldn't restore signal mask");
257
				error = 1;
258
			}
259
#endif /* DEBUG */
260
5
		}
261
262
		/*
263
		 * close the opened accounting file
264
		 */
265
5
		if (close(fd) == -1) {
266
			warn("close %s", *argv);
267
			error = 1;
268
		}
269
5
	}
270
271
5
	if (!uflag && !qflag) {
272
		/* print any results we may have obtained. */
273
		if (!mflag)
274
			pacct_print();
275
		else
276
			usracct_print();
277
	}
278
279
5
	if (!uflag) {
280
		/* finally, deallocate databases */
281

5
		if (sflag || (!mflag && !qflag))
282
5
			pacct_destroy();
283

5
		if (sflag || (mflag && !qflag))
284
5
			usracct_destroy();
285
	}
286
287
	exit(error);
288
}
289
290
static int
291
acct_load(char *pn, int wr)
292
{
293
10
	struct acct ac;
294
5
	struct cmdinfo ci;
295
	ssize_t rv;
296
	int fd, i;
297
298
	/*
299
	 * open the file
300
	 */
301
5
	fd = open(pn, wr ? O_RDWR : O_RDONLY, 0);
302
5
	if (fd == -1) {
303
		warn("open %s %s", pn, wr ? "for read/write" : "read-only");
304
		return (-1);
305
	}
306
307
	/*
308
	 * read all we can; don't stat and open because more processes
309
	 * could exit, and we'd miss them
310
	 */
311
298128
	while (1) {
312
		/* get one accounting entry and punt if there's an error */
313
298128
		rv = read(fd, &ac, sizeof(struct acct));
314
298128
		if (rv == -1)
315
			warn("error reading %s", pn);
316
298128
		else if (rv > 0 && rv < sizeof(struct acct))
317
			warnx("short read of accounting data in %s", pn);
318
298128
		if (rv != sizeof(struct acct))
319
			break;
320
321
		/* decode it */
322
298123
		ci.ci_calls = 1;
323

3607377
		for (i = 0; i < sizeof(ac.ac_comm) && ac.ac_comm[i] != '\0';
324
904719
		    i++) {
325
			unsigned char c = ac.ac_comm[i];
326
327

1809438
			if (!isascii(c) || iscntrl(c)) {
328
				ci.ci_comm[i] = '?';
329
				ci.ci_flags |= CI_UNPRINTABLE;
330
			} else
331
904719
				ci.ci_comm[i] = c;
332
		}
333
298123
		if (ac.ac_flag & AFORK)
334
32448
			ci.ci_comm[i++] = '*';
335
298123
		ci.ci_comm[i++] = '\0';
336
298123
		ci.ci_etime = decode_comp_t(ac.ac_etime);
337
298123
		ci.ci_utime = decode_comp_t(ac.ac_utime);
338
298123
		ci.ci_stime = decode_comp_t(ac.ac_stime);
339
298123
		ci.ci_uid = ac.ac_uid;
340
298123
		ci.ci_mem = ac.ac_mem;
341
298123
		ci.ci_io = decode_comp_t(ac.ac_io) / AHZ;
342
343
298123
		if (!uflag) {
344
			/* and enter it into the usracct and pacct databases */
345

298123
			if (sflag || (!mflag && !qflag))
346
298123
				pacct_add(&ci);
347

298123
			if (sflag || (mflag && !qflag))
348
298123
				usracct_add(&ci);
349
		} else if (!qflag)
350
			printf("%6u %12.2f cpu %12lluk mem %12llu io %s\n",
351
			    ci.ci_uid,
352
			    (ci.ci_utime + ci.ci_stime) / (double) AHZ,
353
			    ci.ci_mem, ci.ci_io, ci.ci_comm);
354
	}
355
356
	/* finally, return the file descriptor for possible truncation */
357
5
	return (fd);
358
5
}
359
360
static uint64_t
361
decode_comp_t(comp_t comp)
362
{
363
	uint64_t rv;
364
365
	/*
366
	 * for more info on the comp_t format, see:
367
	 *	/usr/src/sys/kern/kern_acct.c
368
	 *	/usr/src/sys/sys/acct.h
369
	 *	/usr/src/usr.bin/lastcomm/lastcomm.c
370
	 */
371
2384984
	rv = comp & 0x1fff;	/* 13 bit fraction */
372
1192492
	comp >>= 13;		/* 3 bit base-8 exponent */
373
2386510
	while (comp--)
374
763
		rv <<= 3;
375
376
1192492
	return (rv);
377
}
378
379
/* sort commands, doing the right thing in terms of reversals */
380
static int
381
cmp_comm(const char *s1, const char *s2)
382
{
383
	int rv;
384
385
	rv = strcmp(s1, s2);
386
	if (rv == 0)
387
		rv = -1;
388
	return (rflag ? rv : -rv);
389
}
390
391
/* sort by total user and system time */
392
static int
393
cmp_usrsys(const DBT *d1, const DBT *d2)
394
{
395
	struct cmdinfo c1, c2;
396
	uint64_t t1, t2;
397
398
	memcpy(&c1, d1->data, sizeof(c1));
399
	memcpy(&c2, d2->data, sizeof(c2));
400
401
	t1 = c1.ci_utime + c1.ci_stime;
402
	t2 = c2.ci_utime + c2.ci_stime;
403
404
	if (t1 < t2)
405
		return -1;
406
	else if (t1 == t2)
407
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
408
	else
409
		return 1;
410
}
411
412
/* sort by average user and system time */
413
static int
414
cmp_avgusrsys(const DBT *d1, const DBT *d2)
415
{
416
	struct cmdinfo c1, c2;
417
	double t1, t2;
418
419
	memcpy(&c1, d1->data, sizeof(c1));
420
	memcpy(&c2, d2->data, sizeof(c2));
421
422
	t1 = c1.ci_utime + c1.ci_stime;
423
	t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
424
425
	t2 = c2.ci_utime + c2.ci_stime;
426
	t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
427
428
	if (t1 < t2)
429
		return -1;
430
	else if (t1 == t2)
431
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
432
	else
433
		return 1;
434
}
435
436
/* sort by total number of disk I/O operations */
437
static int
438
cmp_dkio(const DBT *d1, const DBT *d2)
439
{
440
	struct cmdinfo c1, c2;
441
442
	memcpy(&c1, d1->data, sizeof(c1));
443
	memcpy(&c2, d2->data, sizeof(c2));
444
445
	if (c1.ci_io < c2.ci_io)
446
		return -1;
447
	else if (c1.ci_io == c2.ci_io)
448
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
449
	else
450
		return 1;
451
}
452
453
/* sort by average number of disk I/O operations */
454
static int
455
cmp_avgdkio(const DBT *d1, const DBT *d2)
456
{
457
	struct cmdinfo c1, c2;
458
	double n1, n2;
459
460
	memcpy(&c1, d1->data, sizeof(c1));
461
	memcpy(&c2, d2->data, sizeof(c2));
462
463
	n1 = (double) c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1);
464
	n2 = (double) c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1);
465
466
	if (n1 < n2)
467
		return -1;
468
	else if (n1 == n2)
469
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
470
	else
471
		return 1;
472
}
473
474
/* sort by the cpu-storage integral */
475
static int
476
cmp_cpumem(const DBT *d1, const DBT *d2)
477
{
478
	struct cmdinfo c1, c2;
479
480
	memcpy(&c1, d1->data, sizeof(c1));
481
	memcpy(&c2, d2->data, sizeof(c2));
482
483
	if (c1.ci_mem < c2.ci_mem)
484
		return -1;
485
	else if (c1.ci_mem == c2.ci_mem)
486
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
487
	else
488
		return 1;
489
}
490
491
/* sort by the cpu-time average memory usage */
492
static int
493
cmp_avgcpumem(const DBT *d1, const DBT *d2)
494
{
495
	struct cmdinfo c1, c2;
496
	uint64_t t1, t2;
497
	double n1, n2;
498
499
	memcpy(&c1, d1->data, sizeof(c1));
500
	memcpy(&c2, d2->data, sizeof(c2));
501
502
	t1 = c1.ci_utime + c1.ci_stime;
503
	t2 = c2.ci_utime + c2.ci_stime;
504
505
	n1 = (double) c1.ci_mem / (double) (t1 ? t1 : 1);
506
	n2 = (double) c2.ci_mem / (double) (t2 ? t2 : 1);
507
508
	if (n1 < n2)
509
		return -1;
510
	else if (n1 == n2)
511
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
512
	else
513
		return 1;
514
}
515
516
/* sort by the number of invocations */
517
static int
518
cmp_calls(const DBT *d1, const DBT *d2)
519
{
520
	struct cmdinfo c1, c2;
521
522
	memcpy(&c1, d1->data, sizeof(c1));
523
	memcpy(&c2, d2->data, sizeof(c2));
524
525
	if (c1.ci_calls < c2.ci_calls)
526
		return -1;
527
	else if (c1.ci_calls == c2.ci_calls)
528
		return (cmp_comm(c1.ci_comm, c2.ci_comm));
529
	else
530
		return 1;
531
}