Added xten to list of directories
[unix-history] / usr.sbin / sa / main.c
CommitLineData
7e7051a8
GR
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#ifndef LINT
32static char copright[] =
33"@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
34 All rights reserved.\n";
35
36static char rcsid[] = "$Id: main.c,v 1.1 1994/03/24 18:41:51 cgd Exp $";
37#endif
38
39/*
40 * sa: system accounting
41 */
42
43#include <sys/types.h>
44#include <sys/acct.h>
45#include <ctype.h>
46#include <err.h>
47#include <fcntl.h>
48#include <signal.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <unistd.h>
52#include "extern.h"
53#include "pathnames.h"
54
55static int acct_load __P((char *, int));
56static u_quad_t decode_comp_t __P((comp_t));
57static int cmp_comm __P((const char *, const char *));
58static int cmp_usrsys __P((const DBT *, const DBT *));
59static int cmp_avgusrsys __P((const DBT *, const DBT *));
60static int cmp_dkio __P((const DBT *, const DBT *));
61static int cmp_avgdkio __P((const DBT *, const DBT *));
62static int cmp_cpumem __P((const DBT *, const DBT *));
63static int cmp_avgcpumem __P((const DBT *, const DBT *));
64static int cmp_calls __P((const DBT *, const DBT *));
65
66int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
67int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
68int cutoff = 1;
69
70static char *dfltargv[] = { _PATH_ACCT };
71static int dfltargc = (sizeof dfltargv/sizeof(char *));
72
73/* default to comparing by sum of user + system time */
74cmpf_t sa_cmp = cmp_usrsys;
75
76int
77main(argc, argv)
78 int argc;
79 char **argv;
80{
81 char ch;
82 int error;
83
84 while ((ch = getopt(argc, argv, "abcdDfijkKlmnqrstuv:")) != -1)
85 switch (ch) {
86 case 'a':
87 /* print all commands */
88 aflag = 1;
89 break;
90 case 'b':
91 /* sort by per-call user/system time average */
92 bflag = 1;
93 sa_cmp = cmp_avgusrsys;
94 break;
95 case 'c':
96 /* print percentage total time */
97 cflag = 1;
98 break;
99 case 'd':
100 /* sort by averge number of disk I/O ops */
101 dflag = 1;
102 sa_cmp = cmp_avgdkio;
103 break;
104 case 'D':
105 /* print and sort by total disk I/O ops */
106 Dflag = 1;
107 sa_cmp = cmp_dkio;
108 break;
109 case 'f':
110 /* force no interactive threshold comprison */
111 fflag = 1;
112 break;
113 case 'i':
114 /* do not read in summary file */
115 iflag = 1;
116 break;
117 case 'j':
118 /* instead of total minutes, give sec/call */
119 jflag = 1;
120 break;
121 case 'k':
122 /* sort by cpu-time average memory usage */
123 kflag = 1;
124 sa_cmp = cmp_avgcpumem;
125 break;
126 case 'K':
127 /* print and sort by cpu-storage integral */
128 sa_cmp = cmp_cpumem;
129 Kflag = 1;
130 break;
131 case 'l':
132 /* seperate system and user time */
133 lflag = 1;
134 break;
135 case 'm':
136 /* print procs and time per-user */
137 mflag = 1;
138 break;
139 case 'n':
140 /* sort by number of calls */
141 sa_cmp = cmp_calls;
142 break;
143 case 'q':
144 /* quiet; error messages only */
145 qflag = 1;
146 break;
147 case 'r':
148 /* reverse order of sort */
149 rflag = 1;
150 break;
151 case 's':
152 /* merge accounting file into summaries */
153 sflag = 1;
154 break;
155 case 't':
156 /* report ratio of user and system times */
157 tflag = 1;
158 break;
159 case 'u':
160 /* first, print uid and command name */
161 uflag = 1;
162 break;
163 case 'v':
164 /* cull junk */
165 vflag = 1;
166 cutoff = atoi(optarg);
167 break;
168 case '?':
169 default:
170 (void)fprintf(stderr,
171 "usage: sa [-abcdDfijkKlmnqrstu] [-v cutoff] [file ...]\n");
172 exit(1);
173 }
174
175 argc -= optind;
176 argv += optind;
177
178 /* various argument checking */
179 if (fflag && !vflag)
180 errx(1, "only one of -f requires -v");
181 if (fflag && aflag)
182 errx(1, "only one of -a and -v may be specified");
183 /* XXX need more argument checking */
184
185 if (!uflag) {
186 /* initialize tables */
187 if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
188 errx(1, "process accounting initialization failed");
189 if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
190 errx(1, "user accounting initialization failed");
191 }
192
193 if (argc == 0) {
194 argc = dfltargc;
195 argv = dfltargv;
196 }
197
198 /* for each file specified */
199 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 fd = acct_load(argv[0], sflag);
207 if (fd < 0)
208 continue;
209
210 if (!uflag && sflag) {
211#ifndef DEBUG
212 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 if (sigfillset(&nmask) == -1) {
220 warn("sigfillset");
221 unmask = 0;
222 error = 1;
223 }
224 if (unmask &&
225 (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 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 if (pacct_update() != 0 || usracct_update() != 0)
248 error = 1;
249
250#ifndef DEBUG
251 /*
252 * restore signals
253 */
254 if (unmask &&
255 (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
256 warn("couldn't restore signal mask");
257 error = 1;
258 }
259#endif /* DEBUG */
260 }
261
262 /*
263 * close the opened accounting file
264 */
265 if (close(fd) == -1) {
266 warn("close %s", argv);
267 error = 1;
268 }
269 }
270
271 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 if (!uflag) {
280 /* finally, deallocate databases */
281 if (sflag || (!mflag && !qflag))
282 pacct_destroy();
283 if (sflag || (mflag && !qflag))
284 usracct_destroy();
285 }
286
287 exit(error);
288}
289
290static int
291acct_load(pn, wr)
292 char *pn;
293 int wr;
294{
295 struct acct ac;
296 struct cmdinfo ci;
297 ssize_t rv;
298 int fd, i;
299
300 /*
301 * open the file
302 */
303 fd = open(pn, wr ? O_RDWR : O_RDONLY, 0);
304 if (fd == -1) {
305 warn("open %s %s", pn, wr ? "for read/write" : "read-only");
306 return (-1);
307 }
308
309 /*
310 * read all we can; don't stat and open because more processes
311 * could exit, and we'd miss them
312 */
313 while (1) {
314 /* get one accounting entry and punt if there's an error */
315 rv = read(fd, &ac, sizeof(struct acct));
316 if (rv == -1)
317 warn("error reading %s", pn);
318 else if (rv > 0 && rv < sizeof(struct acct))
319 warnx("short read of accounting data in %s", pn);
320 if (rv != sizeof(struct acct))
321 break;
322
323 /* decode it */
324 ci.ci_calls = 1;
325 for (i = 0; i < sizeof ac.ac_comm && ac.ac_comm[i] != '\0';
326 i++) {
327 char c = ac.ac_comm[i];
328
329 if (!isascii(c) || iscntrl(c)) {
330 ci.ci_comm[i] = '?';
331 ci.ci_flags |= CI_UNPRINTABLE;
332 } else
333 ci.ci_comm[i] = c;
334 }
335 if (ac.ac_flag & AFORK)
336 ci.ci_comm[i++] = '*';
337 ci.ci_comm[i++] = '\0';
338 ci.ci_etime = decode_comp_t(ac.ac_etime);
339 ci.ci_utime = decode_comp_t(ac.ac_utime);
340 ci.ci_stime = decode_comp_t(ac.ac_stime);
341 ci.ci_uid = ac.ac_uid;
342 ci.ci_mem = ac.ac_mem;
343 ci.ci_io = decode_comp_t(ac.ac_io) / AHZ;
344
345 if (!uflag) {
346 /* and enter it into the usracct and pacct databases */
347 if (sflag || (!mflag && !qflag))
348 pacct_add(&ci);
349 if (sflag || (mflag && !qflag))
350 usracct_add(&ci);
351 } else if (!qflag)
352 printf("%6u %12.2lf cpu %12quk mem %12qu io %s\n",
353 ci.ci_uid,
354 (ci.ci_utime + ci.ci_stime) / (double) AHZ,
355 ci.ci_mem, ci.ci_io, ci.ci_comm);
356 }
357
358 /* finally, return the file descriptor for possible truncation */
359 return (fd);
360}
361
362static u_quad_t
363decode_comp_t(comp)
364 comp_t comp;
365{
366 u_quad_t rv;
367
368 /*
369 * for more info on the comp_t format, see:
370 * /usr/src/sys/kern/kern_acct.c
371 * /usr/src/sys/sys/acct.h
372 * /usr/src/usr.bin/lastcomm/lastcomm.c
373 */
374 rv = comp & 0x1fff; /* 13 bit fraction */
375 comp >>= 13; /* 3 bit base-8 exponent */
376 while (comp--)
377 rv <<= 3;
378
379 return (rv);
380}
381
382/* sort commands, doing the right thing in terms of reversals */
383static int
384cmp_comm(s1, s2)
385 const char *s1, *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 */
396static int
397cmp_usrsys(d1, d2)
398 const DBT *d1, *d2;
399{
400 struct cmdinfo *c1, *c2;
401 u_quad_t t1, t2;
402
403 c1 = (struct cmdinfo *) d1->data;
404 c2 = (struct cmdinfo *) d2->data;
405
406 t1 = c1->ci_utime + c1->ci_stime;
407 t2 = c2->ci_utime + c2->ci_stime;
408
409 if (t1 < t2)
410 return -1;
411 else if (t1 == t2)
412 return (cmp_comm(c1->ci_comm, c2->ci_comm));
413 else
414 return 1;
415}
416
417/* sort by average user and system time */
418static int
419cmp_avgusrsys(d1, d2)
420 const DBT *d1, *d2;
421{
422 struct cmdinfo *c1, *c2;
423 double t1, t2;
424
425 c1 = (struct cmdinfo *) d1->data;
426 c2 = (struct cmdinfo *) d2->data;
427
428 t1 = c1->ci_utime + c1->ci_stime;
429 t1 /= (double) (c1->ci_calls ? c1->ci_calls : 1);
430
431 t2 = c2->ci_utime + c2->ci_stime;
432 t2 /= (double) (c2->ci_calls ? c2->ci_calls : 1);
433
434 if (t1 < t2)
435 return -1;
436 else if (t1 == t2)
437 return (cmp_comm(c1->ci_comm, c2->ci_comm));
438 else
439 return 1;
440}
441
442/* sort by total number of disk I/O operations */
443static int
444cmp_dkio(d1, d2)
445 const DBT *d1, *d2;
446{
447 struct cmdinfo *c1, *c2;
448
449 c1 = (struct cmdinfo *) d1->data;
450 c2 = (struct cmdinfo *) d2->data;
451
452 if (c1->ci_io < c2->ci_io)
453 return -1;
454 else if (c1->ci_io == c2->ci_io)
455 return (cmp_comm(c1->ci_comm, c2->ci_comm));
456 else
457 return 1;
458}
459
460/* sort by average number of disk I/O operations */
461static int
462cmp_avgdkio(d1, d2)
463 const DBT *d1, *d2;
464{
465 struct cmdinfo *c1, *c2;
466 double n1, n2;
467
468 c1 = (struct cmdinfo *) d1->data;
469 c2 = (struct cmdinfo *) d2->data;
470
471 n1 = (double) c1->ci_io / (double) (c1->ci_calls ? c1->ci_calls : 1);
472 n2 = (double) c2->ci_io / (double) (c2->ci_calls ? c2->ci_calls : 1);
473
474 if (n1 < n2)
475 return -1;
476 else if (n1 == n2)
477 return (cmp_comm(c1->ci_comm, c2->ci_comm));
478 else
479 return 1;
480}
481
482/* sort by the cpu-storage integral */
483static int
484cmp_cpumem(d1, d2)
485 const DBT *d1, *d2;
486{
487 struct cmdinfo *c1, *c2;
488
489 c1 = (struct cmdinfo *) d1->data;
490 c2 = (struct cmdinfo *) d2->data;
491
492 if (c1->ci_mem < c2->ci_mem)
493 return -1;
494 else if (c1->ci_mem == c2->ci_mem)
495 return (cmp_comm(c1->ci_comm, c2->ci_comm));
496 else
497 return 1;
498}
499
500/* sort by the cpu-time average memory usage */
501static int
502cmp_avgcpumem(d1, d2)
503 const DBT *d1, *d2;
504{
505 struct cmdinfo *c1, *c2;
506 u_quad_t t1, t2;
507 double n1, n2;
508
509 c1 = (struct cmdinfo *) d1->data;
510 c2 = (struct cmdinfo *) d2->data;
511
512 t1 = c1->ci_utime + c1->ci_stime;
513 t2 = c2->ci_utime + c2->ci_stime;
514
515 n1 = (double) c1->ci_mem / (double) (t1 ? t1 : 1);
516 n2 = (double) c2->ci_mem / (double) (t2 ? t2 : 1);
517
518 if (n1 < n2)
519 return -1;
520 else if (n1 == n2)
521 return (cmp_comm(c1->ci_comm, c2->ci_comm));
522 else
523 return 1;
524}
525
526/* sort by the number of invocations */
527static int
528cmp_calls(d1, d2)
529 const DBT *d1, *d2;
530{
531 struct cmdinfo *c1, *c2;
532
533 c1 = (struct cmdinfo *) d1->data;
534 c2 = (struct cmdinfo *) d2->data;
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}