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