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 52static FILE *acct_load(const char *, int); 53static int cmp_comm(const char *, const char *); 54static int cmp_usrsys(const DBT *, const DBT *); 55static int cmp_avgusrsys(const DBT *, const DBT *); 56static int cmp_dkio(const DBT *, const DBT *); 57static int cmp_avgdkio(const DBT *, const DBT *); 58static int cmp_cpumem(const DBT *, const DBT *); 59static int cmp_avgcpumem(const DBT *, const DBT *); 60static int cmp_calls(const DBT *, const DBT *); 61static void usage(void); 62 63int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag; 64int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag; 65u_quad_t cutoff = 1; 66const char *pdb_file = _PATH_SAVACCT; 67const char *usrdb_file = _PATH_USRACCT; 68 69static char *dfltargv[] = { NULL }; 70static int dfltargc = (sizeof dfltargv/sizeof(char *)); 71 72/* default to comparing by sum of user + system time */ 73cmpf_t sa_cmp = cmp_usrsys; 74 75int 76main(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 294static void 295usage(void) 296{ 297 (void)fprintf(stderr, 298 "usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n"); 299 exit(1); 300} 301 302static FILE * 303acct_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 */ 374static int 375cmp_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 */ 386static int 387cmp_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 */ 407static int 408cmp_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 */ 431static int 432cmp_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 */ 448static int 449cmp_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 */ 469static int 470cmp_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 */ 486static int 487cmp_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 */ 511static int 512cmp_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