main.c revision 1.4 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
32 static char copright[] =
33 "@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
34 All rights reserved.\n";
35
36 static char rcsid[] = "$Id: main.c,v 1.4 1995/03/22 15:56:29 mycroft 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
55 static int acct_load __P((char *, int));
56 static u_quad_t decode_comp_t __P((comp_t));
57 static int cmp_comm __P((const char *, const char *));
58 static int cmp_usrsys __P((const DBT *, const DBT *));
59 static int cmp_avgusrsys __P((const DBT *, const DBT *));
60 static int cmp_dkio __P((const DBT *, const DBT *));
61 static int cmp_avgdkio __P((const DBT *, const DBT *));
62 static int cmp_cpumem __P((const DBT *, const DBT *));
63 static int cmp_avgcpumem __P((const DBT *, const DBT *));
64 static int cmp_calls __P((const DBT *, const DBT *));
65
66 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
67 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
68 int cutoff = 1;
69
70 static char *dfltargv[] = { _PATH_ACCT };
71 static int dfltargc = (sizeof(dfltargv)/sizeof(char *));
72
73 /* default to comparing by sum of user + system time */
74 cmpf_t sa_cmp = cmp_usrsys;
75
76 int
77 main(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
290 static int
291 acct_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
362 static u_quad_t
363 decode_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 */
383 static int
384 cmp_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 */
396 static int
397 cmp_usrsys(d1, d2)
398 const DBT *d1, *d2;
399 {
400 struct cmdinfo c1, c2;
401 u_quad_t t1, t2;
402
403 memcpy(&c1, d1->data, sizeof(c1));
404 memcpy(&c2, d2->data, sizeof(c2));
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 */
418 static int
419 cmp_avgusrsys(d1, d2)
420 const DBT *d1, *d2;
421 {
422 struct cmdinfo c1, c2;
423 double t1, t2;
424
425 memcpy(&c1, d1->data, sizeof(c1));
426 memcpy(&c2, d2->data, sizeof(c2));
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 */
443 static int
444 cmp_dkio(d1, d2)
445 const DBT *d1, *d2;
446 {
447 struct cmdinfo c1, c2;
448
449 memcpy(&c1, d1->data, sizeof(c1));
450 memcpy(&c2, d2->data, sizeof(c2));
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 */
461 static int
462 cmp_avgdkio(d1, d2)
463 const DBT *d1, *d2;
464 {
465 struct cmdinfo c1, c2;
466 double n1, n2;
467
468 memcpy(&c1, d1->data, sizeof(c1));
469 memcpy(&c2, d2->data, sizeof(c2));
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 */
483 static int
484 cmp_cpumem(d1, d2)
485 const DBT *d1, *d2;
486 {
487 struct cmdinfo c1, c2;
488
489 memcpy(&c1, d1->data, sizeof(c1));
490 memcpy(&c2, d2->data, sizeof(c2));
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 */
501 static int
502 cmp_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 memcpy(&c1, d1->data, sizeof(c1));
510 memcpy(&c2, d2->data, sizeof(c2));
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 */
527 static int
528 cmp_calls(d1, d2)
529 const DBT *d1, *d2;
530 {
531 struct cmdinfo c1, c2;
532
533 memcpy(&c1, d1->data, sizeof(c1));
534 memcpy(&c2, d2->data, sizeof(c2));
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 }
543