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 #if 0 32 #ifndef lint 33 static const char copyright[] = 34 "@(#) Copyright (c) 1994 Christopher G. Demetriou\n\ 35 All rights reserved.\n"; 36 #endif 37 #endif 38 #include <sys/cdefs.h> 39 __FBSDID("$FreeBSD$"); 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 <stdint.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <unistd.h> 56 #include "extern.h" 57 #include "pathnames.h" 58 59 static int acct_load(char *, int); 60 static u_quad_t decode_comp_t(comp_t); 61 static int cmp_comm(const char *, const char *); 62 static int cmp_usrsys(const DBT *, const DBT *); 63 static int cmp_avgusrsys(const DBT *, const DBT *); 64 static int cmp_dkio(const DBT *, const DBT *); 65 static int cmp_avgdkio(const DBT *, const DBT *); 66 static int cmp_cpumem(const DBT *, const DBT *); 67 static int cmp_avgcpumem(const DBT *, const DBT *); 68 static int cmp_calls(const DBT *, const DBT *); 69 static void usage(void); 70 71 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag; 72 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag; 73 u_quad_t cutoff = 1; 74 75 static char *dfltargv[] = { NULL }; 76 static int dfltargc = (sizeof dfltargv/sizeof(char *)); 77 78 /* default to comparing by sum of user + system time */ 79 cmpf_t sa_cmp = cmp_usrsys; 80 81 int 82 main(int argc, char **argv) 83 { 84 char ch; 85 char pathacct[] = _PATH_ACCT; 86 int error = 0; 87 88 dfltargv[0] = pathacct; 89 90 while ((ch = getopt(argc, argv, "abcdDfijkKlmnqrstuv:")) != -1) 91 switch (ch) { 92 case 'a': 93 /* print all commands */ 94 aflag = 1; 95 break; 96 case 'b': 97 /* sort by per-call user/system time average */ 98 bflag = 1; 99 sa_cmp = cmp_avgusrsys; 100 break; 101 case 'c': 102 /* print percentage total time */ 103 cflag = 1; 104 break; 105 case 'd': 106 /* sort by averge number of disk I/O ops */ 107 dflag = 1; 108 sa_cmp = cmp_avgdkio; 109 break; 110 case 'D': 111 /* print and sort by total disk I/O ops */ 112 Dflag = 1; 113 sa_cmp = cmp_dkio; 114 break; 115 case 'f': 116 /* force no interactive threshold comprison */ 117 fflag = 1; 118 break; 119 case 'i': 120 /* do not read in summary file */ 121 iflag = 1; 122 break; 123 case 'j': 124 /* instead of total minutes, give sec/call */ 125 jflag = 1; 126 break; 127 case 'k': 128 /* sort by cpu-time average memory usage */ 129 kflag = 1; 130 sa_cmp = cmp_avgcpumem; 131 break; 132 case 'K': 133 /* print and sort by cpu-storage integral */ 134 sa_cmp = cmp_cpumem; 135 Kflag = 1; 136 break; 137 case 'l': 138 /* separate system and user time */ 139 lflag = 1; 140 break; 141 case 'm': 142 /* print procs and time per-user */ 143 mflag = 1; 144 break; 145 case 'n': 146 /* sort by number of calls */ 147 sa_cmp = cmp_calls; 148 break; 149 case 'q': 150 /* quiet; error messages only */ 151 qflag = 1; 152 break; 153 case 'r': 154 /* reverse order of sort */ 155 rflag = 1; 156 break; 157 case 's': 158 /* merge accounting file into summaries */ 159 sflag = 1; 160 break; 161 case 't': 162 /* report ratio of user and system times */ 163 tflag = 1; 164 break; 165 case 'u': 166 /* first, print uid and command name */ 167 uflag = 1; 168 break; 169 case 'v': 170 /* cull junk */ 171 vflag = 1; 172 cutoff = atoi(optarg); 173 break; 174 case '?': 175 default: 176 usage(); 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 void 295 usage() 296 { 297 (void)fprintf(stderr, 298 "usage: sa [-abcdDfijkKlmnqrstu] [-v cutoff] [file ...]\n"); 299 exit(1); 300 } 301 302 static int 303 acct_load(pn, wr) 304 char *pn; 305 int wr; 306 { 307 struct acct ac; 308 struct cmdinfo ci; 309 ssize_t rv; 310 int fd, i; 311 312 /* 313 * open the file 314 */ 315 fd = open(pn, wr ? O_RDWR : O_RDONLY, 0); 316 if (fd == -1) { 317 warn("open %s %s", pn, wr ? "for read/write" : "read-only"); 318 return (-1); 319 } 320 321 /* 322 * read all we can; don't stat and open because more processes 323 * could exit, and we'd miss them 324 */ 325 while (1) { 326 /* get one accounting entry and punt if there's an error */ 327 rv = read(fd, &ac, sizeof(struct acct)); 328 if (rv == -1) 329 warn("error reading %s", pn); 330 else if (rv > 0 && rv < (int)sizeof(struct acct)) 331 warnx("short read of accounting data in %s", pn); 332 if (rv != sizeof(struct acct)) 333 break; 334 335 /* decode it */ 336 ci.ci_calls = 1; 337 for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0'; 338 i++) { 339 char c = ac.ac_comm[i]; 340 341 if (!isascii(c) || iscntrl(c)) { 342 ci.ci_comm[i] = '?'; 343 ci.ci_flags |= CI_UNPRINTABLE; 344 } else 345 ci.ci_comm[i] = c; 346 } 347 if (ac.ac_flag & AFORK) 348 ci.ci_comm[i++] = '*'; 349 ci.ci_comm[i++] = '\0'; 350 ci.ci_etime = decode_comp_t(ac.ac_etime); 351 ci.ci_utime = decode_comp_t(ac.ac_utime); 352 ci.ci_stime = decode_comp_t(ac.ac_stime); 353 ci.ci_uid = ac.ac_uid; 354 ci.ci_mem = ac.ac_mem; 355 ci.ci_io = decode_comp_t(ac.ac_io) / AHZ; 356 357 if (!uflag) { 358 /* and enter it into the usracct and pacct databases */ 359 if (sflag || (!mflag && !qflag)) 360 pacct_add(&ci); 361 if (sflag || (mflag && !qflag)) 362 usracct_add(&ci); 363 } else if (!qflag) 364 printf("%6lu %12.2f cpu %12juk mem %12ju io %s\n", 365 ci.ci_uid, 366 (ci.ci_utime + ci.ci_stime) / (double) AHZ, 367 (uintmax_t)ci.ci_mem, (uintmax_t)ci.ci_io, 368 ci.ci_comm); 369 } 370 371 /* finally, return the file descriptor for possible truncation */ 372 return (fd); 373 } 374 375 static u_quad_t 376 decode_comp_t(comp) 377 comp_t comp; 378 { 379 u_quad_t rv; 380 381 /* 382 * for more info on the comp_t format, see: 383 * /usr/src/sys/kern/kern_acct.c 384 * /usr/src/sys/sys/acct.h 385 * /usr/src/usr.bin/lastcomm/lastcomm.c 386 */ 387 rv = comp & 0x1fff; /* 13 bit fraction */ 388 comp >>= 13; /* 3 bit base-8 exponent */ 389 while (comp--) 390 rv <<= 3; 391 392 return (rv); 393 } 394 395 /* sort commands, doing the right thing in terms of reversals */ 396 static int 397 cmp_comm(s1, s2) 398 const char *s1, *s2; 399 { 400 int rv; 401 402 rv = strcmp(s1, s2); 403 if (rv == 0) 404 rv = -1; 405 return (rflag ? rv : -rv); 406 } 407 408 /* sort by total user and system time */ 409 static int 410 cmp_usrsys(d1, d2) 411 const DBT *d1, *d2; 412 { 413 struct cmdinfo c1, c2; 414 u_quad_t t1, t2; 415 416 memcpy(&c1, d1->data, sizeof(c1)); 417 memcpy(&c2, d2->data, sizeof(c2)); 418 419 t1 = c1.ci_utime + c1.ci_stime; 420 t2 = c2.ci_utime + c2.ci_stime; 421 422 if (t1 < t2) 423 return -1; 424 else if (t1 == t2) 425 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 426 else 427 return 1; 428 } 429 430 /* sort by average user and system time */ 431 static int 432 cmp_avgusrsys(d1, d2) 433 const DBT *d1, *d2; 434 { 435 struct cmdinfo c1, c2; 436 double t1, t2; 437 438 memcpy(&c1, d1->data, sizeof(c1)); 439 memcpy(&c2, d2->data, sizeof(c2)); 440 441 t1 = c1.ci_utime + c1.ci_stime; 442 t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1); 443 444 t2 = c2.ci_utime + c2.ci_stime; 445 t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1); 446 447 if (t1 < t2) 448 return -1; 449 else if (t1 == t2) 450 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 451 else 452 return 1; 453 } 454 455 /* sort by total number of disk I/O operations */ 456 static int 457 cmp_dkio(d1, d2) 458 const DBT *d1, *d2; 459 { 460 struct cmdinfo c1, c2; 461 462 memcpy(&c1, d1->data, sizeof(c1)); 463 memcpy(&c2, d2->data, sizeof(c2)); 464 465 if (c1.ci_io < c2.ci_io) 466 return -1; 467 else if (c1.ci_io == c2.ci_io) 468 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 469 else 470 return 1; 471 } 472 473 /* sort by average number of disk I/O operations */ 474 static int 475 cmp_avgdkio(d1, d2) 476 const DBT *d1, *d2; 477 { 478 struct cmdinfo c1, c2; 479 double n1, n2; 480 481 memcpy(&c1, d1->data, sizeof(c1)); 482 memcpy(&c2, d2->data, sizeof(c2)); 483 484 n1 = (double) c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1); 485 n2 = (double) c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1); 486 487 if (n1 < n2) 488 return -1; 489 else if (n1 == n2) 490 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 491 else 492 return 1; 493 } 494 495 /* sort by the cpu-storage integral */ 496 static int 497 cmp_cpumem(d1, d2) 498 const DBT *d1, *d2; 499 { 500 struct cmdinfo c1, c2; 501 502 memcpy(&c1, d1->data, sizeof(c1)); 503 memcpy(&c2, d2->data, sizeof(c2)); 504 505 if (c1.ci_mem < c2.ci_mem) 506 return -1; 507 else if (c1.ci_mem == c2.ci_mem) 508 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 509 else 510 return 1; 511 } 512 513 /* sort by the cpu-time average memory usage */ 514 static int 515 cmp_avgcpumem(d1, d2) 516 const DBT *d1, *d2; 517 { 518 struct cmdinfo c1, c2; 519 u_quad_t t1, t2; 520 double n1, n2; 521 522 memcpy(&c1, d1->data, sizeof(c1)); 523 memcpy(&c2, d2->data, sizeof(c2)); 524 525 t1 = c1.ci_utime + c1.ci_stime; 526 t2 = c2.ci_utime + c2.ci_stime; 527 528 n1 = (double) c1.ci_mem / (double) (t1 ? t1 : 1); 529 n2 = (double) c2.ci_mem / (double) (t2 ? t2 : 1); 530 531 if (n1 < n2) 532 return -1; 533 else if (n1 == n2) 534 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 535 else 536 return 1; 537 } 538 539 /* sort by the number of invocations */ 540 static int 541 cmp_calls(d1, d2) 542 const DBT *d1, *d2; 543 { 544 struct cmdinfo c1, c2; 545 546 memcpy(&c1, d1->data, sizeof(c1)); 547 memcpy(&c2, d2->data, sizeof(c2)); 548 549 if (c1.ci_calls < c2.ci_calls) 550 return -1; 551 else if (c1.ci_calls == c2.ci_calls) 552 return (cmp_comm(c1.ci_comm, c2.ci_comm)); 553 else 554 return 1; 555 } 556