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.2 1995/05/30 03:51:39 rgrimes 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 char 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("%6lu %12.2f 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 c1 = (struct cmdinfo *) d1->data; 405 c2 = (struct cmdinfo *) d2->data; 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 c1 = (struct cmdinfo *) d1->data; 427 c2 = (struct cmdinfo *) d2->data; 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 c1 = (struct cmdinfo *) d1->data; 451 c2 = (struct cmdinfo *) d2->data; 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 c1 = (struct cmdinfo *) d1->data; 470 c2 = (struct cmdinfo *) d2->data; 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 c1 = (struct cmdinfo *) d1->data; 491 c2 = (struct cmdinfo *) d2->data; 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 c1 = (struct cmdinfo *) d1->data; 511 c2 = (struct cmdinfo *) d2->data; 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 c1 = (struct cmdinfo *) d1->data; 535 c2 = (struct cmdinfo *) d2->data; 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