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