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