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