1 /*- 2 * SPDX-License-Identifier: BSD-4-Clause 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 by Christopher G. Demetriou. 18 * 4. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * sa: system accounting 35 */ 36 37 #include <sys/types.h> 38 #include <sys/acct.h> 39 #include <ctype.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <signal.h> 44 #include <stdint.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include "extern.h" 50 #include "pathnames.h" 51 52 static FILE *acct_load(const char *, int); 53 static int cmp_comm(const char *, const char *); 54 static int cmp_usrsys(const DBT *, const DBT *); 55 static int cmp_avgusrsys(const DBT *, const DBT *); 56 static int cmp_dkio(const DBT *, const DBT *); 57 static int cmp_avgdkio(const DBT *, const DBT *); 58 static int cmp_cpumem(const DBT *, const DBT *); 59 static int cmp_avgcpumem(const DBT *, const DBT *); 60 static int cmp_calls(const DBT *, const DBT *); 61 static void usage(void); 62 63 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag; 64 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag; 65 u_quad_t cutoff = 1; 66 const char *pdb_file = _PATH_SAVACCT; 67 const char *usrdb_file = _PATH_USRACCT; 68 69 static char *dfltargv[] = { NULL }; 70 static int dfltargc = (sizeof dfltargv/sizeof(char *)); 71 72 /* default to comparing by sum of user + system time */ 73 cmpf_t sa_cmp = cmp_usrsys; 74 75 int 76 main(int argc, char **argv) 77 { 78 FILE *f; 79 char pathacct[] = _PATH_ACCT; 80 int ch, error = 0; 81 82 dfltargv[0] = pathacct; 83 84 while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -1) 85 switch (ch) { 86 case 'a': 87 /* print all commands */ 88 aflag = 1; 89 break; 90 case 'b': 91 /* sort by per-call user/system time average */ 92 bflag = 1; 93 sa_cmp = cmp_avgusrsys; 94 break; 95 case 'c': 96 /* print percentage total time */ 97 cflag = 1; 98 break; 99 case 'd': 100 /* sort by averge number of disk I/O ops */ 101 dflag = 1; 102 sa_cmp = cmp_avgdkio; 103 break; 104 case 'D': 105 /* print and sort by total disk I/O ops */ 106 Dflag = 1; 107 sa_cmp = cmp_dkio; 108 break; 109 case 'f': 110 /* force no interactive threshold comprison */ 111 fflag = 1; 112 break; 113 case 'i': 114 /* do not read in summary file */ 115 iflag = 1; 116 break; 117 case 'j': 118 /* instead of total minutes, give sec/call */ 119 jflag = 1; 120 break; 121 case 'k': 122 /* sort by cpu-time average memory usage */ 123 kflag = 1; 124 sa_cmp = cmp_avgcpumem; 125 break; 126 case 'K': 127 /* print and sort by cpu-storage integral */ 128 sa_cmp = cmp_cpumem; 129 Kflag = 1; 130 break; 131 case 'l': 132 /* separate system and user time */ 133 lflag = 1; 134 break; 135 case 'm': 136 /* print procs and time per-user */ 137 mflag = 1; 138 break; 139 case 'n': 140 /* sort by number of calls */ 141 sa_cmp = cmp_calls; 142 break; 143 case 'P': 144 /* specify program database summary file */ 145 pdb_file = optarg; 146 break; 147 case 'q': 148 /* quiet; error messages only */ 149 qflag = 1; 150 break; 151 case 'r': 152 /* reverse order of sort */ 153 rflag = 1; 154 break; 155 case 's': 156 /* merge accounting file into summaries */ 157 sflag = 1; 158 break; 159 case 't': 160 /* report ratio of user and system times */ 161 tflag = 1; 162 break; 163 case 'u': 164 /* first, print uid and command name */ 165 uflag = 1; 166 break; 167 case 'U': 168 /* specify user database summary file */ 169 usrdb_file = optarg; 170 break; 171 case 'v': 172 /* cull junk */ 173 vflag = 1; 174 cutoff = atoi(optarg); 175 break; 176 case '?': 177 default: 178 usage(); 179 } 180 181 argc -= optind; 182 argv += optind; 183 184 /* various argument checking */ 185 if (fflag && !vflag) 186 errx(1, "only one of -f requires -v"); 187 if (fflag && aflag) 188 errx(1, "only one of -a and -v may be specified"); 189 /* XXX need more argument checking */ 190 191 if (!uflag) { 192 /* initialize tables */ 193 if ((sflag || (!mflag && !qflag)) && pacct_init() != 0) 194 errx(1, "process accounting initialization failed"); 195 if ((sflag || (mflag && !qflag)) && usracct_init() != 0) 196 errx(1, "user accounting initialization failed"); 197 } 198 199 if (argc == 0) { 200 argc = dfltargc; 201 argv = dfltargv; 202 } 203 204 /* for each file specified */ 205 for (; argc > 0; argc--, argv++) { 206 /* 207 * load the accounting data from the file. 208 * if it fails, go on to the next file. 209 */ 210 f = acct_load(argv[0], sflag); 211 if (f == NULL) 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(fileno(f), 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 (fclose(f) == EOF) { 270 warn("fclose %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(void) 296 { 297 (void)fprintf(stderr, 298 "usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n"); 299 exit(1); 300 } 301 302 static FILE * 303 acct_load(const char *pn, int wr) 304 { 305 struct acctv3 ac; 306 struct cmdinfo ci; 307 ssize_t rv; 308 FILE *f; 309 int i; 310 311 /* 312 * open the file 313 */ 314 f = fopen(pn, wr ? "r+" : "r"); 315 if (f == NULL) { 316 warn("open %s %s", pn, wr ? "for read/write" : "read-only"); 317 return (NULL); 318 } 319 320 /* 321 * read all we can; don't stat and open because more processes 322 * could exit, and we'd miss them 323 */ 324 while (1) { 325 /* get one accounting entry and punt if there's an error */ 326 rv = readrec_forward(f, &ac); 327 if (rv != 1) { 328 if (rv == EOF) 329 warn("error reading %s", pn); 330 break; 331 } 332 333 /* decode it */ 334 ci.ci_calls = 1; 335 for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0'; 336 i++) { 337 char c = ac.ac_comm[i]; 338 339 if (!isascii(c) || iscntrl(c)) { 340 ci.ci_comm[i] = '?'; 341 ci.ci_flags |= CI_UNPRINTABLE; 342 } else 343 ci.ci_comm[i] = c; 344 } 345 if (ac.ac_flagx & AFORK) 346 ci.ci_comm[i++] = '*'; 347 ci.ci_comm[i++] = '\0'; 348 ci.ci_etime = ac.ac_etime; 349 ci.ci_utime = ac.ac_utime; 350 ci.ci_stime = ac.ac_stime; 351 ci.ci_uid = ac.ac_uid; 352 ci.ci_mem = ac.ac_mem; 353 ci.ci_io = ac.ac_io; 354 355 if (!uflag) { 356 /* and enter it into the usracct and pacct databases */ 357 if (sflag || (!mflag && !qflag)) 358 pacct_add(&ci); 359 if (sflag || (mflag && !qflag)) 360 usracct_add(&ci); 361 } else if (!qflag) 362 printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n", 363 ci.ci_uid, 364 (ci.ci_utime + ci.ci_stime) / 1000000, 365 ci.ci_mem, ci.ci_io, 366 ci.ci_comm); 367 } 368 369 /* Finally, return the file stream for possible truncation. */ 370 return (f); 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 double 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 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1); 458 n2 = 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 double 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 = c1.ci_mem / (t1 ? t1 : 1); 500 n2 = c2.ci_mem / (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