13125Sdg/* 23125Sdg * Copyright (c) 1994 Christopher G. Demetriou 33125Sdg * All rights reserved. 43125Sdg * 53125Sdg * Redistribution and use in source and binary forms, with or without 63125Sdg * modification, are permitted provided that the following conditions 73125Sdg * are met: 83125Sdg * 1. Redistributions of source code must retain the above copyright 93125Sdg * notice, this list of conditions and the following disclaimer. 103125Sdg * 2. Redistributions in binary form must reproduce the above copyright 113125Sdg * notice, this list of conditions and the following disclaimer in the 123125Sdg * documentation and/or other materials provided with the distribution. 133125Sdg * 3. All advertising materials mentioning features or use of this software 143125Sdg * must display the following acknowledgement: 153125Sdg * This product includes software developed by Christopher G. Demetriou. 163125Sdg * 4. The name of the author may not be used to endorse or promote products 173125Sdg * derived from this software without specific prior written permission 183125Sdg * 193125Sdg * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 203125Sdg * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 213125Sdg * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 223125Sdg * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 233125Sdg * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 243125Sdg * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 253125Sdg * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 263125Sdg * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 273125Sdg * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 283125Sdg * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 293125Sdg */ 303125Sdg 31114601Sobrien#if 0 3230425Scharnier#ifndef lint 3330425Scharnierstatic const char copyright[] = 343125Sdg"@(#) Copyright (c) 1994 Christopher G. Demetriou\n\ 353125Sdg All rights reserved.\n"; 363125Sdg#endif 37114601Sobrien#endif 38114601Sobrien#include <sys/cdefs.h> 39114601Sobrien__FBSDID("$FreeBSD$"); 403125Sdg 413125Sdg/* 423125Sdg * sa: system accounting 433125Sdg */ 443125Sdg 453125Sdg#include <sys/types.h> 463125Sdg#include <sys/acct.h> 473125Sdg#include <ctype.h> 483125Sdg#include <err.h> 49169857Sdds#include <errno.h> 503125Sdg#include <fcntl.h> 513125Sdg#include <signal.h> 52100107Sdes#include <stdint.h> 533125Sdg#include <stdio.h> 543125Sdg#include <stdlib.h> 5513558Smpp#include <string.h> 563125Sdg#include <unistd.h> 573125Sdg#include "extern.h" 583125Sdg#include "pathnames.h" 593125Sdg 60169857Sddsstatic FILE *acct_load(const char *, int); 61169857Sddsstatic int cmp_comm(const char *, const char *); 62169857Sddsstatic int cmp_usrsys(const DBT *, const DBT *); 63169857Sddsstatic int cmp_avgusrsys(const DBT *, const DBT *); 64169857Sddsstatic int cmp_dkio(const DBT *, const DBT *); 65169857Sddsstatic int cmp_avgdkio(const DBT *, const DBT *); 66169857Sddsstatic int cmp_cpumem(const DBT *, const DBT *); 67169857Sddsstatic int cmp_avgcpumem(const DBT *, const DBT *); 68169857Sddsstatic int cmp_calls(const DBT *, const DBT *); 69169857Sddsstatic void usage(void); 703125Sdg 713125Sdgint aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag; 723125Sdgint Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag; 7399829Salfredu_quad_t cutoff = 1; 74169670Sddsconst char *pdb_file = _PATH_SAVACCT; 75169670Sddsconst char *usrdb_file = _PATH_USRACCT; 763125Sdg 7799829Salfredstatic char *dfltargv[] = { NULL }; 783125Sdgstatic int dfltargc = (sizeof dfltargv/sizeof(char *)); 793125Sdg 803125Sdg/* default to comparing by sum of user + system time */ 813125Sdgcmpf_t sa_cmp = cmp_usrsys; 823125Sdg 833125Sdgint 8499829Salfredmain(int argc, char **argv) 853125Sdg{ 86169857Sdds FILE *f; 8799829Salfred char pathacct[] = _PATH_ACCT; 88124830Sgrehan int ch, error = 0; 893125Sdg 9099829Salfred dfltargv[0] = pathacct; 9199829Salfred 92169670Sdds while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -1) 933125Sdg switch (ch) { 943125Sdg case 'a': 953125Sdg /* print all commands */ 963125Sdg aflag = 1; 973125Sdg break; 983125Sdg case 'b': 993125Sdg /* sort by per-call user/system time average */ 1003125Sdg bflag = 1; 1013125Sdg sa_cmp = cmp_avgusrsys; 1023125Sdg break; 1033125Sdg case 'c': 1043125Sdg /* print percentage total time */ 1053125Sdg cflag = 1; 1063125Sdg break; 1073125Sdg case 'd': 1083125Sdg /* sort by averge number of disk I/O ops */ 1093125Sdg dflag = 1; 1103125Sdg sa_cmp = cmp_avgdkio; 1113125Sdg break; 1123125Sdg case 'D': 1133125Sdg /* print and sort by total disk I/O ops */ 1143125Sdg Dflag = 1; 1153125Sdg sa_cmp = cmp_dkio; 1163125Sdg break; 1173125Sdg case 'f': 1183125Sdg /* force no interactive threshold comprison */ 1193125Sdg fflag = 1; 1203125Sdg break; 1213125Sdg case 'i': 1223125Sdg /* do not read in summary file */ 1233125Sdg iflag = 1; 1243125Sdg break; 1253125Sdg case 'j': 1263125Sdg /* instead of total minutes, give sec/call */ 1273125Sdg jflag = 1; 1283125Sdg break; 1293125Sdg case 'k': 1303125Sdg /* sort by cpu-time average memory usage */ 1313125Sdg kflag = 1; 1323125Sdg sa_cmp = cmp_avgcpumem; 1333125Sdg break; 1343125Sdg case 'K': 1353125Sdg /* print and sort by cpu-storage integral */ 1363125Sdg sa_cmp = cmp_cpumem; 1373125Sdg Kflag = 1; 1383125Sdg break; 1393125Sdg case 'l': 14072091Sasmodai /* separate system and user time */ 1413125Sdg lflag = 1; 1423125Sdg break; 1433125Sdg case 'm': 1443125Sdg /* print procs and time per-user */ 1453125Sdg mflag = 1; 1463125Sdg break; 1473125Sdg case 'n': 1483125Sdg /* sort by number of calls */ 1493125Sdg sa_cmp = cmp_calls; 1503125Sdg break; 151169670Sdds case 'P': 152169670Sdds /* specify program database summary file */ 153169670Sdds pdb_file = optarg; 154169670Sdds break; 1553125Sdg case 'q': 1563125Sdg /* quiet; error messages only */ 1573125Sdg qflag = 1; 1583125Sdg break; 1593125Sdg case 'r': 1603125Sdg /* reverse order of sort */ 1613125Sdg rflag = 1; 1623125Sdg break; 1633125Sdg case 's': 1643125Sdg /* merge accounting file into summaries */ 1653125Sdg sflag = 1; 1663125Sdg break; 1673125Sdg case 't': 1683125Sdg /* report ratio of user and system times */ 1693125Sdg tflag = 1; 1703125Sdg break; 1713125Sdg case 'u': 1723125Sdg /* first, print uid and command name */ 1733125Sdg uflag = 1; 1743125Sdg break; 175169670Sdds case 'U': 176169670Sdds /* specify user database summary file */ 177169670Sdds usrdb_file = optarg; 178169670Sdds break; 1793125Sdg case 'v': 1803125Sdg /* cull junk */ 1813125Sdg vflag = 1; 1823125Sdg cutoff = atoi(optarg); 1833125Sdg break; 1843125Sdg case '?': 1853125Sdg default: 18630425Scharnier usage(); 1873125Sdg } 1883125Sdg 1893125Sdg argc -= optind; 1903125Sdg argv += optind; 1913125Sdg 1923125Sdg /* various argument checking */ 1933125Sdg if (fflag && !vflag) 1943125Sdg errx(1, "only one of -f requires -v"); 1953125Sdg if (fflag && aflag) 1963125Sdg errx(1, "only one of -a and -v may be specified"); 1973125Sdg /* XXX need more argument checking */ 1983125Sdg 1993125Sdg if (!uflag) { 2003125Sdg /* initialize tables */ 2013125Sdg if ((sflag || (!mflag && !qflag)) && pacct_init() != 0) 2023125Sdg errx(1, "process accounting initialization failed"); 2033125Sdg if ((sflag || (mflag && !qflag)) && usracct_init() != 0) 2043125Sdg errx(1, "user accounting initialization failed"); 2053125Sdg } 2063125Sdg 2073125Sdg if (argc == 0) { 2083125Sdg argc = dfltargc; 2093125Sdg argv = dfltargv; 2103125Sdg } 2113125Sdg 2123125Sdg /* for each file specified */ 2133125Sdg for (; argc > 0; argc--, argv++) { 2143125Sdg /* 2153125Sdg * load the accounting data from the file. 2163125Sdg * if it fails, go on to the next file. 2173125Sdg */ 218169857Sdds f = acct_load(argv[0], sflag); 219169857Sdds if (f == NULL) 2203125Sdg continue; 2213125Sdg 2223125Sdg if (!uflag && sflag) { 2233125Sdg#ifndef DEBUG 2243125Sdg sigset_t nmask, omask; 2253125Sdg int unmask = 1; 2263125Sdg 2273125Sdg /* 2283125Sdg * block most signals so we aren't interrupted during 2293125Sdg * the update. 2303125Sdg */ 2313125Sdg if (sigfillset(&nmask) == -1) { 2323125Sdg warn("sigfillset"); 2333125Sdg unmask = 0; 2343125Sdg error = 1; 2353125Sdg } 2368857Srgrimes if (unmask && 2373125Sdg (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) { 23830425Scharnier warn("couldn't set signal mask"); 2393125Sdg unmask = 0; 2403125Sdg error = 1; 2413125Sdg } 2423125Sdg#endif /* DEBUG */ 2433125Sdg 2443125Sdg /* 2453125Sdg * truncate the accounting data file ASAP, to avoid 2463125Sdg * losing data. don't worry about errors in updating 2473125Sdg * the saved stats; better to underbill than overbill, 2483125Sdg * but we want every accounting record intact. 2493125Sdg */ 250169857Sdds if (ftruncate(fileno(f), 0) == -1) { 25199829Salfred warn("couldn't truncate %s", *argv); 2523125Sdg error = 1; 2533125Sdg } 2543125Sdg 2553125Sdg /* 2563125Sdg * update saved user and process accounting data. 2573125Sdg * note errors for later. 2583125Sdg */ 2593125Sdg if (pacct_update() != 0 || usracct_update() != 0) 2603125Sdg error = 1; 2613125Sdg 2623125Sdg#ifndef DEBUG 2633125Sdg /* 2643125Sdg * restore signals 2653125Sdg */ 2663125Sdg if (unmask && 2673125Sdg (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) { 2683125Sdg warn("couldn't restore signal mask"); 2693125Sdg error = 1; 2703125Sdg } 2713125Sdg#endif /* DEBUG */ 2723125Sdg } 2733125Sdg 2743125Sdg /* 2753125Sdg * close the opened accounting file 2763125Sdg */ 277169857Sdds if (fclose(f) == EOF) { 278169857Sdds warn("fclose %s", *argv); 2793125Sdg error = 1; 2803125Sdg } 2813125Sdg } 2823125Sdg 2833125Sdg if (!uflag && !qflag) { 2843125Sdg /* print any results we may have obtained. */ 2853125Sdg if (!mflag) 2863125Sdg pacct_print(); 2873125Sdg else 2883125Sdg usracct_print(); 2893125Sdg } 2903125Sdg 2913125Sdg if (!uflag) { 2923125Sdg /* finally, deallocate databases */ 2933125Sdg if (sflag || (!mflag && !qflag)) 2943125Sdg pacct_destroy(); 2953125Sdg if (sflag || (mflag && !qflag)) 2963125Sdg usracct_destroy(); 2973125Sdg } 2983125Sdg 2993125Sdg exit(error); 3003125Sdg} 3013125Sdg 30230425Scharnierstatic void 303201227Sedusage(void) 30430425Scharnier{ 30530425Scharnier (void)fprintf(stderr, 306169670Sdds "usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n"); 30730425Scharnier exit(1); 30830425Scharnier} 30930425Scharnier 310169857Sddsstatic FILE * 311141638Sdelphijacct_load(const char *pn, int wr) 3123125Sdg{ 313169857Sdds struct acctv2 ac; 3143125Sdg struct cmdinfo ci; 3153125Sdg ssize_t rv; 316169857Sdds FILE *f; 317169857Sdds int i; 3183125Sdg 3193125Sdg /* 3203125Sdg * open the file 3213125Sdg */ 322169857Sdds f = fopen(pn, wr ? "r+" : "r"); 323169857Sdds if (f == NULL) { 3243125Sdg warn("open %s %s", pn, wr ? "for read/write" : "read-only"); 325169857Sdds return (NULL); 3263125Sdg } 3273125Sdg 3283125Sdg /* 3293125Sdg * read all we can; don't stat and open because more processes 3303125Sdg * could exit, and we'd miss them 3313125Sdg */ 3323125Sdg while (1) { 3333125Sdg /* get one accounting entry and punt if there's an error */ 334169857Sdds rv = readrec_forward(f, &ac); 335169857Sdds if (rv != 1) { 336169857Sdds if (rv == EOF) 337169857Sdds warn("error reading %s", pn); 3383125Sdg break; 339169857Sdds } 3403125Sdg 3413125Sdg /* decode it */ 3423125Sdg ci.ci_calls = 1; 34399829Salfred for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0'; 3443125Sdg i++) { 3453125Sdg char c = ac.ac_comm[i]; 3463125Sdg 3473125Sdg if (!isascii(c) || iscntrl(c)) { 3483125Sdg ci.ci_comm[i] = '?'; 3493125Sdg ci.ci_flags |= CI_UNPRINTABLE; 3503125Sdg } else 3513125Sdg ci.ci_comm[i] = c; 3523125Sdg } 353169857Sdds if (ac.ac_flagx & AFORK) 3543125Sdg ci.ci_comm[i++] = '*'; 3553125Sdg ci.ci_comm[i++] = '\0'; 356169857Sdds ci.ci_etime = ac.ac_etime; 357169857Sdds ci.ci_utime = ac.ac_utime; 358169857Sdds ci.ci_stime = ac.ac_stime; 3593125Sdg ci.ci_uid = ac.ac_uid; 3603125Sdg ci.ci_mem = ac.ac_mem; 361169857Sdds ci.ci_io = ac.ac_io; 3623125Sdg 3633125Sdg if (!uflag) { 3643125Sdg /* and enter it into the usracct and pacct databases */ 3653125Sdg if (sflag || (!mflag && !qflag)) 3663125Sdg pacct_add(&ci); 3673125Sdg if (sflag || (mflag && !qflag)) 3683125Sdg usracct_add(&ci); 3693125Sdg } else if (!qflag) 370169857Sdds printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n", 3713125Sdg ci.ci_uid, 372169857Sdds (ci.ci_utime + ci.ci_stime) / 1000000, 373169857Sdds ci.ci_mem, ci.ci_io, 374100107Sdes ci.ci_comm); 3753125Sdg } 3763125Sdg 377169857Sdds /* Finally, return the file stream for possible truncation. */ 378169857Sdds return (f); 3793125Sdg} 3803125Sdg 3813125Sdg/* sort commands, doing the right thing in terms of reversals */ 3823125Sdgstatic int 383141638Sdelphijcmp_comm(const char *s1, const char *s2) 3843125Sdg{ 3853125Sdg int rv; 3863125Sdg 3873125Sdg rv = strcmp(s1, s2); 3883125Sdg if (rv == 0) 3893125Sdg rv = -1; 3903125Sdg return (rflag ? rv : -rv); 3913125Sdg} 3923125Sdg 3933125Sdg/* sort by total user and system time */ 3943125Sdgstatic int 395141638Sdelphijcmp_usrsys(const DBT *d1, const DBT *d2) 3963125Sdg{ 39767642Sgallatin struct cmdinfo c1, c2; 398169857Sdds double t1, t2; 3993125Sdg 40067642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 40167642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 4023125Sdg 40367642Sgallatin t1 = c1.ci_utime + c1.ci_stime; 40467642Sgallatin t2 = c2.ci_utime + c2.ci_stime; 4053125Sdg 4063125Sdg if (t1 < t2) 4073125Sdg return -1; 4083125Sdg else if (t1 == t2) 40967642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 4103125Sdg else 4113125Sdg return 1; 4123125Sdg} 4133125Sdg 4143125Sdg/* sort by average user and system time */ 4153125Sdgstatic int 416141638Sdelphijcmp_avgusrsys(const DBT *d1, const DBT *d2) 4173125Sdg{ 41867642Sgallatin struct cmdinfo c1, c2; 4193125Sdg double t1, t2; 4203125Sdg 42167642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 42267642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 4233125Sdg 42467642Sgallatin t1 = c1.ci_utime + c1.ci_stime; 42567642Sgallatin t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1); 4263125Sdg 42767642Sgallatin t2 = c2.ci_utime + c2.ci_stime; 42867642Sgallatin t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1); 4293125Sdg 4303125Sdg if (t1 < t2) 4313125Sdg return -1; 4323125Sdg else if (t1 == t2) 43367642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 4343125Sdg else 4353125Sdg return 1; 4363125Sdg} 4373125Sdg 4383125Sdg/* sort by total number of disk I/O operations */ 4393125Sdgstatic int 440141638Sdelphijcmp_dkio(const DBT *d1, const DBT *d2) 4413125Sdg{ 44267642Sgallatin struct cmdinfo c1, c2; 4433125Sdg 44467642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 44567642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 4463125Sdg 44767642Sgallatin if (c1.ci_io < c2.ci_io) 4483125Sdg return -1; 44967642Sgallatin else if (c1.ci_io == c2.ci_io) 45067642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 4513125Sdg else 4523125Sdg return 1; 4533125Sdg} 4543125Sdg 4553125Sdg/* sort by average number of disk I/O operations */ 4563125Sdgstatic int 457141638Sdelphijcmp_avgdkio(const DBT *d1, const DBT *d2) 4583125Sdg{ 45967642Sgallatin struct cmdinfo c1, c2; 4603125Sdg double n1, n2; 4613125Sdg 46267642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 46367642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 4643125Sdg 465169857Sdds n1 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1); 466169857Sdds n2 = c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1); 4673125Sdg 4683125Sdg if (n1 < n2) 4693125Sdg return -1; 4703125Sdg else if (n1 == n2) 47167642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 4723125Sdg else 4733125Sdg return 1; 4743125Sdg} 4753125Sdg 4763125Sdg/* sort by the cpu-storage integral */ 4773125Sdgstatic int 478141638Sdelphijcmp_cpumem(const DBT *d1, const DBT *d2) 4793125Sdg{ 48067642Sgallatin struct cmdinfo c1, c2; 4813125Sdg 48267642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 48367642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 4843125Sdg 48567642Sgallatin if (c1.ci_mem < c2.ci_mem) 4863125Sdg return -1; 48767642Sgallatin else if (c1.ci_mem == c2.ci_mem) 48867642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 4893125Sdg else 4903125Sdg return 1; 4913125Sdg} 4923125Sdg 4933125Sdg/* sort by the cpu-time average memory usage */ 4943125Sdgstatic int 495141638Sdelphijcmp_avgcpumem(const DBT *d1, const DBT *d2) 4963125Sdg{ 49767642Sgallatin struct cmdinfo c1, c2; 498169857Sdds double t1, t2; 4993125Sdg double n1, n2; 5003125Sdg 50167642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 50267642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 5033125Sdg 50467642Sgallatin t1 = c1.ci_utime + c1.ci_stime; 50567642Sgallatin t2 = c2.ci_utime + c2.ci_stime; 5063125Sdg 507169857Sdds n1 = c1.ci_mem / (t1 ? t1 : 1); 508169857Sdds n2 = c2.ci_mem / (t2 ? t2 : 1); 5093125Sdg 5103125Sdg if (n1 < n2) 5113125Sdg return -1; 5123125Sdg else if (n1 == n2) 51367642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 5143125Sdg else 5153125Sdg return 1; 5163125Sdg} 5173125Sdg 5183125Sdg/* sort by the number of invocations */ 5193125Sdgstatic int 520141638Sdelphijcmp_calls(const DBT *d1, const DBT *d2) 5213125Sdg{ 52267642Sgallatin struct cmdinfo c1, c2; 5233125Sdg 52467642Sgallatin memcpy(&c1, d1->data, sizeof(c1)); 52567642Sgallatin memcpy(&c2, d2->data, sizeof(c2)); 5263125Sdg 52767642Sgallatin if (c1.ci_calls < c2.ci_calls) 5283125Sdg return -1; 52967642Sgallatin else if (c1.ci_calls == c2.ci_calls) 53067642Sgallatin return (cmp_comm(c1.ci_comm, c2.ci_comm)); 5313125Sdg else 5323125Sdg return 1; 5333125Sdg} 534