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