1 /* $NetBSD: main.c,v 1.28 2010/06/10 06:28:33 dholland 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\
40 Christopher G. Demetriou. All rights reserved.");
41
42 __RCSID("$NetBSD: main.c,v 1.28 2010/06/10 06:28:33 dholland 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 <vis.h>
54 #include <fcntl.h>
55 #include <signal.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 #include "extern.h"
61 #include "pathnames.h"
62
63 static int acct_load(const char *, int);
64 static u_quad_t decode_comp_t(comp_t);
65 static int cmp_comm(const char *, const char *);
66 static int cmp_usrsys(const DBT *, const DBT *);
67 static int cmp_avgusrsys(const DBT *, const DBT *);
68 static int cmp_dkio(const DBT *, const DBT *);
69 static int cmp_avgdkio(const DBT *, const DBT *);
70 static int cmp_cpumem(const DBT *, const DBT *);
71 static int cmp_avgcpumem(const DBT *, const DBT *);
72 static int cmp_calls(const DBT *, const DBT *);
73 static void usage(void) __dead;
74
75 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
76 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
77 int cutoff = 1;
78
79 static const char *dfltargv[] = { _PATH_ACCT };
80 static const int dfltargc = (sizeof(dfltargv)/sizeof(char *));
81
82 /* default to comparing by sum of user + system time */
83 cmpf_t sa_cmp = cmp_usrsys;
84
85 int
main(int argc,char ** argv)86 main(int argc, 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 /* separate 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 usage();
179 /*NOTREACHED*/
180 }
181
182 argc -= optind;
183 argv += optind;
184
185 /* various argument checking */
186 if (fflag && !vflag)
187 errx(1, "only one of -f requires -v");
188 if (fflag && aflag)
189 errx(1, "only one of -a and -v may be specified");
190 /* XXX need more argument checking */
191
192 if (!uflag) {
193 /* initialize tables */
194 if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
195 errx(1, "process accounting initialization failed");
196 if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
197 errx(1, "user accounting initialization failed");
198 }
199
200 if (argc == 0) {
201 argc = dfltargc;
202 argv = __UNCONST(dfltargv);
203 }
204
205 /* for each file specified */
206 for (; argc > 0; argc--, argv++) {
207 int fd;
208
209 /*
210 * load the accounting data from the file.
211 * if it fails, go on to the next file.
212 */
213 fd = acct_load(argv[0], sflag);
214 if (fd < 0)
215 continue;
216
217 if (!uflag && sflag) {
218 #ifndef DEBUG
219 sigset_t nmask, omask;
220 int unmask = 1;
221
222 /*
223 * block most signals so we aren't interrupted during
224 * the update.
225 */
226 if (sigfillset(&nmask) == -1) {
227 warn("sigfillset");
228 unmask = 0;
229 error = 1;
230 }
231 if (unmask &&
232 (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
233 warn("couldn't set signal mask ");
234 unmask = 0;
235 error = 1;
236 }
237 #endif /* DEBUG */
238
239 /*
240 * truncate the accounting data file ASAP, to avoid
241 * losing data. don't worry about errors in updating
242 * the saved stats; better to underbill than overbill,
243 * but we want every accounting record intact.
244 */
245 if (ftruncate(fd, 0) == -1) {
246 warn("couldn't truncate %s", *argv);
247 error = 1;
248 }
249
250 /*
251 * update saved user and process accounting data.
252 * note errors for later.
253 */
254 if (pacct_update() != 0 || usracct_update() != 0)
255 error = 1;
256
257 #ifndef DEBUG
258 /*
259 * restore signals
260 */
261 if (unmask &&
262 (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
263 warn("couldn't restore signal mask");
264 error = 1;
265 }
266 #endif /* DEBUG */
267 }
268
269 /*
270 * close the opened accounting file
271 */
272 if (close(fd) == -1) {
273 warn("close %s", *argv);
274 error = 1;
275 }
276 }
277
278 if (!uflag && !qflag) {
279 /* print any results we may have obtained. */
280 if (!mflag)
281 pacct_print();
282 else
283 usracct_print();
284 }
285
286 if (!uflag) {
287 /* finally, deallocate databases */
288 if (sflag || (!mflag && !qflag))
289 pacct_destroy();
290 if (sflag || (mflag && !qflag))
291 usracct_destroy();
292 }
293
294 exit(error);
295 }
296
297 static int
acct_load(const char * pn,int wr)298 acct_load(const char *pn, int wr)
299 {
300 struct acct ac;
301 struct cmdinfo ci;
302 size_t i;
303 FILE *fp;
304
305 /*
306 * open the file
307 */
308 fp = fopen(pn, wr ? "r+" : "r");
309 if (fp == NULL) {
310 warn("open %s %s", pn, wr ? "for read/write" : "read-only");
311 return (-1);
312 }
313
314 /*
315 * read all we can; don't stat and open because more processes
316 * could exit, and we'd miss them
317 */
318 for (;;) {
319 /* get one accounting entry and punt if there's an error */
320 if (fread(&ac, sizeof(struct acct), 1, fp) != 1) {
321 if (feof(fp))
322 break;
323 if (ferror(fp))
324 warn("error reading %s", pn);
325 else
326 warnx("short read of accounting data in %s",
327 pn);
328 break;
329 }
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((unsigned char)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 %12lluk mem %12llu 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 (fileno(fp));
369 }
370
371 static u_quad_t
decode_comp_t(comp_t comp)372 decode_comp_t(comp_t comp)
373 {
374 u_quad_t rv;
375
376 /*
377 * for more info on the comp_t format, see:
378 * /usr/src/sys/kern/kern_acct.c
379 * /usr/src/sys/sys/acct.h
380 * /usr/src/usr.bin/lastcomm/lastcomm.c
381 */
382 rv = comp & 0x1fff; /* 13 bit fraction */
383 comp >>= 13; /* 3 bit base-8 exponent */
384 while (comp--)
385 rv <<= 3;
386
387 return (rv);
388 }
389
390 /* sort commands, doing the right thing in terms of reversals */
391 static int
cmp_comm(const char * s1,const char * s2)392 cmp_comm(const char *s1, const char *s2)
393 {
394 int rv;
395
396 rv = strcmp(s1, s2);
397 if (rv == 0)
398 rv = -1;
399 return (rflag ? rv : -rv);
400 }
401
402 /* sort by total user and system time */
403 static int
cmp_usrsys(const DBT * d1,const DBT * d2)404 cmp_usrsys(const DBT *d1, const DBT *d2)
405 {
406 struct cmdinfo c1, c2;
407 u_quad_t t1, t2;
408
409 memcpy(&c1, d1->data, sizeof(c1));
410 memcpy(&c2, d2->data, sizeof(c2));
411
412 t1 = c1.ci_utime + c1.ci_stime;
413 t2 = c2.ci_utime + c2.ci_stime;
414
415 if (t1 < t2)
416 return -1;
417 else if (t1 == t2)
418 return (cmp_comm(c1.ci_comm, c2.ci_comm));
419 else
420 return 1;
421 }
422
423 /* sort by average user and system time */
424 static int
cmp_avgusrsys(const DBT * d1,const DBT * d2)425 cmp_avgusrsys(const DBT *d1, const DBT *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
cmp_dkio(const DBT * d1,const DBT * d2)449 cmp_dkio(const DBT *d1, const DBT *d2)
450 {
451 struct cmdinfo c1, c2;
452
453 memcpy(&c1, d1->data, sizeof(c1));
454 memcpy(&c2, d2->data, sizeof(c2));
455
456 if (c1.ci_io < c2.ci_io)
457 return -1;
458 else if (c1.ci_io == c2.ci_io)
459 return (cmp_comm(c1.ci_comm, c2.ci_comm));
460 else
461 return 1;
462 }
463
464 /* sort by average number of disk I/O operations */
465 static int
cmp_avgdkio(const DBT * d1,const DBT * d2)466 cmp_avgdkio(const DBT *d1, const DBT *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
cmp_cpumem(const DBT * d1,const DBT * d2)487 cmp_cpumem(const DBT *d1, const DBT *d2)
488 {
489 struct cmdinfo c1, c2;
490
491 memcpy(&c1, d1->data, sizeof(c1));
492 memcpy(&c2, d2->data, sizeof(c2));
493
494 if (c1.ci_mem < c2.ci_mem)
495 return -1;
496 else if (c1.ci_mem == c2.ci_mem)
497 return (cmp_comm(c1.ci_comm, c2.ci_comm));
498 else
499 return 1;
500 }
501
502 /* sort by the CPU-time average memory usage */
503 static int
cmp_avgcpumem(const DBT * d1,const DBT * d2)504 cmp_avgcpumem(const DBT *d1, const DBT *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
cmp_calls(const DBT * d1,const DBT * d2)529 cmp_calls(const DBT *d1, const DBT *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
544 static void
usage(void)545 usage(void)
546 {
547
548 (void)fprintf(stderr,
549 "usage: %s [-abcdDfijkKlmnqrstu] [-v cutoff] [file ...]\n",
550 getprogname());
551 exit(0);
552 }
553
554 const char *
fmt(const DBT * key)555 fmt(const DBT *key)
556 {
557 static char *buf = NULL;
558 static size_t len = 0;
559 char *nbuf;
560
561 if (len < key->size * 4 + 1) {
562 nbuf = realloc(buf, key->size * 4 + 1);
563 if (!nbuf)
564 err(1, "realloc");
565 buf = nbuf;
566 len = key->size * 4 + 1;
567 }
568 (void)strvisx(buf, key->data, key->size, 0);
569 return buf;
570 }
571