1178 lines
23 KiB
C
1178 lines
23 KiB
C
#ifndef lint
|
|
static char *sccsid = "@(#)sa.c 1.1 94/10/31 SMI"; /* from UCB 4.9 12/12/84 */
|
|
#endif
|
|
|
|
/*
|
|
* Extensive modifications to internal data structures
|
|
* to allow arbitrary number of different commands and users added.
|
|
*
|
|
* Also allowed the digit option on the -v flag (interactive
|
|
* threshold compress) to be a digit string, so one can
|
|
* set the threshold > 9.
|
|
*
|
|
* Also added the -f flag, to force no interactive threshold
|
|
* compression with the -v flag.
|
|
*
|
|
* Robert Henry
|
|
* UC Berkeley
|
|
* 31jan81
|
|
*/
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/acct.h>
|
|
#include <signal.h>
|
|
#include <utmp.h>
|
|
#include <pwd.h>
|
|
|
|
extern char *calloc();
|
|
|
|
#define AHZ 64 /* this used to be defined in acct.h */
|
|
/* interpret command time accounting */
|
|
|
|
#define NC sizeof (acctbuf.ac_comm)
|
|
|
|
struct acct acctbuf;
|
|
int lflg;
|
|
int cflg;
|
|
int Dflg;
|
|
int dflg;
|
|
int iflg;
|
|
int jflg;
|
|
int Kflg;
|
|
int kflg;
|
|
int nflg;
|
|
int aflg;
|
|
int rflg;
|
|
int oflg;
|
|
int tflg;
|
|
int vflg;
|
|
int fflg;
|
|
int uflg;
|
|
int thres;
|
|
int sflg;
|
|
int bflg;
|
|
int mflg;
|
|
|
|
struct utmp utmp;
|
|
#define NAMELG (sizeof (utmp.ut_name)+1)
|
|
|
|
struct Olduser{
|
|
int Us_cnt;
|
|
double Us_ctime;
|
|
double Us_io;
|
|
double Us_imem;
|
|
};
|
|
|
|
struct user {
|
|
char name[NC]; /* this is <\001><user id><\000> */
|
|
struct Olduser oldu;
|
|
char us_name[NAMELG];
|
|
};
|
|
#define us_cnt oldu.Us_cnt
|
|
#define us_ctime oldu.Us_ctime
|
|
#define us_io oldu.Us_io
|
|
#define us_imem oldu.Us_imem
|
|
|
|
/*
|
|
* We protect ourselves from preposterous user id's by looking
|
|
* through the passwd file for the highest uid allocated, and
|
|
* then adding 10 to that.
|
|
* This prevents the user structure from growing too large.
|
|
*/
|
|
#define MAXUID ((uid_t)-1) /* Max. representable uid */
|
|
#define USERSLOP 10
|
|
u_int maxuser; /* highest uid from /etc/passwd, + 10 for slop */
|
|
|
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
struct process {
|
|
char name[NC];
|
|
int count;
|
|
double realt;
|
|
double cput;
|
|
double syst;
|
|
double imem;
|
|
double io;
|
|
};
|
|
|
|
union Tab{
|
|
struct process p;
|
|
struct user u;
|
|
};
|
|
|
|
typedef union Tab cell;
|
|
|
|
int (*cmp)(); /* compares 2 cells; set to appropriate func */
|
|
cell *enter();
|
|
struct user *finduser();
|
|
struct user *wasuser();
|
|
uid_t getmaxuid();
|
|
|
|
/*
|
|
* Table elements are keyed by the name of the file exec'ed.
|
|
* Because on large systems, many files can be exec'ed,
|
|
* a static table size may grow to be too large.
|
|
*
|
|
* Table elements are allocated in chunks dynamically, linked
|
|
* together so that they may be retrieved sequentially.
|
|
*
|
|
* An index into the table structure is provided by hashing through
|
|
* a seperate hash table.
|
|
* The hash table is segmented, and dynamically extendable.
|
|
* Realize that the hash table and accounting information is kept
|
|
* in different segments!
|
|
*
|
|
* We have a linked list of hash table segments; within each
|
|
* segment we use a quadratic rehash that touches no more than 1/2
|
|
* of the buckets in the hash table when probing.
|
|
* If the probe does not find the desired symbol, it moves to the
|
|
* next segment, or allocates a new segment.
|
|
*
|
|
* Hash table segments are kept on the linked list with the first
|
|
* segment always first (that will probably contain the
|
|
* most frequently executed commands) and
|
|
* the last added segment immediately after the first segment,
|
|
* to hopefully gain something by locality of reference.
|
|
*
|
|
* We store the per user information in the same structure as
|
|
* the per exec'ed file information. This allows us to use the
|
|
* same managers for both, as the number of user id's may be very
|
|
* large.
|
|
* User information is keyed by the first character in the name
|
|
* being a '\001', followed by four bytes of (long extended)
|
|
* user id number, followed by a null byte.
|
|
* The actual user names are kept in a seperate field of the
|
|
* user structure, and is filled in upon demand later.
|
|
* Iteration through all users by low user id to high user id
|
|
* is done by just probing the table, which is gross.
|
|
*/
|
|
#define USERKEY '\001'
|
|
#define ISPROCESS(tp) (tp->p.name[0] && (tp->p.name[0] != USERKEY))
|
|
#define ISUSER(tp) (tp->p.name[0] && (tp->p.name[0] == USERKEY))
|
|
|
|
#define TABDALLOP 500
|
|
struct allocbox{
|
|
struct allocbox *nextalloc;
|
|
cell tabslots[TABDALLOP];
|
|
};
|
|
|
|
struct allocbox *allochead; /* head of chunk list */
|
|
struct allocbox *alloctail; /* tail */
|
|
struct allocbox *newbox; /* for creating a new chunk */
|
|
cell *nexttab; /* next table element that is free */
|
|
int tabsleft; /* slots left in current chunk */
|
|
int ntabs;
|
|
/*
|
|
* Iterate through all symbols in the symbol table in declaration
|
|
* order.
|
|
* struct allocbox *allocwalk;
|
|
* cell *sp, *ub;
|
|
*
|
|
* sp points to the desired item, allocwalk and ub are there
|
|
* to make the iteration go.
|
|
*/
|
|
|
|
#define DECLITERATE(allocwalk, walkpointer, ubpointer) \
|
|
for (allocwalk = allochead; \
|
|
allocwalk != 0; \
|
|
allocwalk = allocwalk->nextalloc) \
|
|
for (walkpointer = &allocwalk->tabslots[0], \
|
|
ubpointer = &allocwalk->tabslots[TABDALLOP], \
|
|
ubpointer = ubpointer > ( (cell *)alloctail) \
|
|
? nexttab : ubpointer; \
|
|
walkpointer < ubpointer; \
|
|
walkpointer++ )
|
|
|
|
#define TABCHUNKS(allocwalk, tabptr, size) \
|
|
for (allocwalk = allochead; \
|
|
allocwalk != 0; \
|
|
allocwalk = allocwalk->nextalloc) \
|
|
if ( \
|
|
(tabptr = &allocwalk->tabslots[0]), \
|
|
(size = \
|
|
( (&allocwalk->tabslots[TABDALLOP]) \
|
|
> ((cell *)alloctail) \
|
|
) \
|
|
? (nexttab - tabptr) : TABDALLOP \
|
|
), \
|
|
1 \
|
|
)
|
|
#define PROCESSITERATE(allocwalk, walkpointer, ubpointer) \
|
|
DECLITERATE(allocwalk, walkpointer, ubpointer) \
|
|
if (ISPROCESS(walkpointer))
|
|
|
|
#define USERITERATE(allocwalk, walkpointer, ubpointer) \
|
|
DECLITERATE(allocwalk, walkpointer, ubpointer) \
|
|
if (ISUSER(walkpointer))
|
|
/*
|
|
* When we have to sort the segmented accounting table, we
|
|
* create a vector of sorted queues that is merged
|
|
* to sort the entire accounting table.
|
|
*/
|
|
struct chunkdesc {
|
|
cell *chunk_tp;
|
|
int chunk_n;
|
|
};
|
|
|
|
/*
|
|
* Hash table segments and manager
|
|
*/
|
|
#define NHASH 1103
|
|
struct hashdallop {
|
|
int h_nused;
|
|
struct hashdallop *h_next;
|
|
cell *h_tab[NHASH];
|
|
};
|
|
struct hashdallop *htab; /* head of the list */
|
|
int htabinstall; /* install the symbol */
|
|
|
|
double treal;
|
|
double tcpu;
|
|
double tsys;
|
|
double tio;
|
|
double timem;
|
|
char *sname;
|
|
double ncom;
|
|
double expand();
|
|
char *getname();
|
|
|
|
/*
|
|
* usracct saves records of type Olduser.
|
|
* There is one record for every possible uid less than
|
|
* the largest uid seen in the previous usracct or in savacct.
|
|
* uid's that had no activity correspond to zero filled slots;
|
|
* thus one can index the file and get the user record out.
|
|
* It would be better to save only user information for users
|
|
* that the system knows about to save space, but that is not
|
|
* upward compatabile with the old system.
|
|
*
|
|
* In the old version of sa, uid's greater than 999 were not handled
|
|
* properly; this system will do that.
|
|
*/
|
|
|
|
#ifdef DEBUG
|
|
#define USRACCT "./usracct"
|
|
#define SAVACCT "./savacct"
|
|
#define ACCT "./pacct"
|
|
#else
|
|
#define USRACCT "/var/adm/usracct"
|
|
#define SAVACCT "/var/adm/savacct"
|
|
#define ACCT "/var/adm/pacct"
|
|
#endif DEBUG
|
|
|
|
|
|
char *usracct = USRACCT;
|
|
char *savacct = SAVACCT;
|
|
|
|
int cellcmp();
|
|
/*
|
|
* The threshold is built up from digits in the argv;
|
|
* eg, -v1s0u1
|
|
* will build a value of thres of 101.
|
|
*
|
|
* If the threshold is zero after processing argv, it is set to 1
|
|
*/
|
|
int thres = 0;
|
|
int htabinstall = 1;
|
|
int (*cmp)();
|
|
|
|
/* we assume pagesize is at least 1k */
|
|
int pgdiv;
|
|
#define pgtok(x) ((x) / pgdiv)
|
|
|
|
extern tcmp(), ncmp(), Bcmp(), dcmp(), Dcmp(), kcmp(), Kcmp();
|
|
extern double sum();
|
|
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
FILE *ff;
|
|
double ft;
|
|
register struct allocbox *allocwalk;
|
|
register cell *tp, *ub;
|
|
int i, j, size, nchunks, smallest;
|
|
struct chunkdesc *chunkvector;
|
|
cell *junkp = 0;
|
|
|
|
pgdiv = getpagesize() / 1024;
|
|
if (pgdiv == 0)
|
|
pgdiv = 1;
|
|
maxuser = USERSLOP + getmaxuid();
|
|
if (maxuser > MAXUID)
|
|
maxuser = MAXUID;
|
|
|
|
tabinit();
|
|
cmp = tcmp;
|
|
if (argc > 1 && argv[1][0]=='-') {
|
|
argv++;
|
|
argc--;
|
|
for (i = 1; argv[0][i]; i++)
|
|
switch (argv[0][i]) {
|
|
|
|
case 'o':
|
|
oflg++;
|
|
break;
|
|
|
|
case 'i':
|
|
iflg++;
|
|
break;
|
|
|
|
case 'b':
|
|
bflg++;
|
|
cmp = Bcmp;
|
|
break;
|
|
|
|
case 'l':
|
|
lflg++;
|
|
break;
|
|
|
|
case 'c':
|
|
cflg++;
|
|
break;
|
|
|
|
case 'd':
|
|
dflg++;
|
|
cmp = dcmp;
|
|
break;
|
|
|
|
case 'D':
|
|
Dflg++;
|
|
cmp = Dcmp;
|
|
break;
|
|
|
|
case 'j':
|
|
jflg++;
|
|
break;
|
|
|
|
case 'k':
|
|
kflg++;
|
|
cmp = kcmp;
|
|
break;
|
|
|
|
case 'K':
|
|
Kflg++;
|
|
cmp = Kcmp;
|
|
break;
|
|
|
|
case 'n':
|
|
nflg++;
|
|
cmp = ncmp;
|
|
break;
|
|
|
|
case 'a':
|
|
aflg++;
|
|
break;
|
|
|
|
case 'r':
|
|
rflg++;
|
|
break;
|
|
|
|
case 't':
|
|
tflg++;
|
|
break;
|
|
|
|
case 's':
|
|
sflg++;
|
|
aflg++;
|
|
break;
|
|
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
thres = thres * 10 + (argv[0][i]-'0');
|
|
break;
|
|
|
|
case 'v':
|
|
vflg++;
|
|
break;
|
|
|
|
case 'f':
|
|
fflg++; /* force v option; no tty interaction */
|
|
break;
|
|
|
|
case 'u':
|
|
uflg++;
|
|
break;
|
|
|
|
case 'm':
|
|
mflg++;
|
|
break;
|
|
|
|
case 'U':
|
|
case 'S':
|
|
if (i != 1 || argv[0][2]) { /* gross! */
|
|
fprintf(stderr, "-U and -S options must be separate\n");
|
|
exit(1);
|
|
}
|
|
argc++, argv--; /* backup - yuk */
|
|
goto doUS;
|
|
|
|
default:
|
|
fprintf(stderr, "Invalid option %c\n", argv[0][1]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
#define optfile(f) {if (argc < 2) \
|
|
{ fprintf(stderr, "Missing filename\n"); exit(1); } \
|
|
argc--, argv++; f = argv[0]; }
|
|
|
|
doUS:
|
|
for (argc--, argv++; argc && argv[0][0] == '-'; argc--, argv++) {
|
|
switch (argv[0][1]) {
|
|
case 'U':
|
|
optfile(usracct);
|
|
break;
|
|
|
|
case 'S':
|
|
optfile(savacct);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Invalid option %c\n", argv[0][1]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (thres == 0)
|
|
thres = 1;
|
|
if (iflg==0)
|
|
init();
|
|
if (argc<1)
|
|
doacct(ACCT);
|
|
else while (argc--)
|
|
doacct(*argv++);
|
|
if (uflg) {
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* cleanup pass
|
|
* put junk together
|
|
*/
|
|
|
|
if (vflg)
|
|
strip();
|
|
if (!aflg)
|
|
PROCESSITERATE(allocwalk, tp, ub) {
|
|
for (j = 0; j < NC; j++)
|
|
if (tp->p.name[j] == '?')
|
|
goto yes;
|
|
if (tp->p.count != 1)
|
|
continue;
|
|
yes:
|
|
if (junkp == 0)
|
|
junkp = enter("***other");
|
|
junkp->p.count += tp->p.count;
|
|
junkp->p.realt += tp->p.realt;
|
|
junkp->p.cput += tp->p.cput;
|
|
junkp->p.syst += tp->p.syst;
|
|
junkp->p.imem += tp->p.imem;
|
|
junkp->p.io += tp->p.io;
|
|
tp->p.name[0] = 0;
|
|
}
|
|
if (sflg) {
|
|
static struct user ZeroUser = {0};
|
|
struct user *up;
|
|
uid_t uid;
|
|
|
|
(void)signal(SIGINT, SIG_IGN);
|
|
if ((ff = fopen(usracct, "w")) == NULL) {
|
|
perror(usracct);
|
|
exit(1);
|
|
}
|
|
/*
|
|
* Write out just enough user slots,
|
|
* filling with zero slots for users that
|
|
* weren't found.
|
|
* The file can be indexed directly by uid
|
|
* to get the correct record.
|
|
*/
|
|
for (uid = 0; uid != maxuser; uid++) {
|
|
if ( (up = wasuser(uid)) != 0)
|
|
fwrite((char *)&up->oldu,
|
|
sizeof (struct Olduser), 1, ff);
|
|
else
|
|
fseek(ff, (long)(sizeof (struct Olduser)), 1);
|
|
}
|
|
fwrite((char *)&ZeroUser.oldu,
|
|
sizeof (struct Olduser), 1, ff);
|
|
|
|
if ((ff = fopen(savacct, "w")) == NULL) {
|
|
perror(savacct);
|
|
exit(1);
|
|
}
|
|
PROCESSITERATE(allocwalk, tp, ub)
|
|
fwrite((char *)&(tp->p), sizeof (struct process), 1, ff);
|
|
fclose(ff);
|
|
creat(sname, 0644);
|
|
(void)signal(SIGINT, SIG_DFL);
|
|
}
|
|
/*
|
|
* sort and print
|
|
*/
|
|
if (mflg) {
|
|
printmoney();
|
|
exit(0);
|
|
}
|
|
column(ncom, treal, tcpu, tsys, timem, tio);
|
|
printf("\n");
|
|
|
|
/*
|
|
* the fragmented table is sorted by sorting each fragment
|
|
* and then merging.
|
|
*/
|
|
nchunks = 0;
|
|
TABCHUNKS(allocwalk, tp, size) {
|
|
qsort((char *)tp, size, sizeof (cell), cellcmp);
|
|
nchunks ++;
|
|
}
|
|
chunkvector = (struct chunkdesc *)calloc(
|
|
(u_int)MAX(nchunks, 1), sizeof (struct chunkdesc));
|
|
if (chunkvector == (struct chunkdesc *)NULL) {
|
|
fprintf(stderr, "sa: out of memory\n");
|
|
exit(1);
|
|
}
|
|
nchunks = 0;
|
|
TABCHUNKS(allocwalk, tp, size) {
|
|
chunkvector[nchunks].chunk_tp = tp;
|
|
chunkvector[nchunks].chunk_n = size;
|
|
nchunks++;
|
|
}
|
|
for (; nchunks; ) {
|
|
/*
|
|
* Find the smallest element at the head of the queues.
|
|
*/
|
|
smallest = 0;
|
|
for (i = 1; i < nchunks; i++) {
|
|
if (cellcmp(chunkvector[i].chunk_tp,
|
|
chunkvector[smallest].chunk_tp) < 0)
|
|
smallest = i;
|
|
}
|
|
tp = chunkvector[smallest].chunk_tp++;
|
|
/*
|
|
* If this queue is drained, drop the chunk count,
|
|
* and readjust the queues.
|
|
*/
|
|
if (--chunkvector[smallest].chunk_n == 0) {
|
|
nchunks--;
|
|
for (i = smallest; i < nchunks; i++)
|
|
chunkvector[i] = chunkvector[i+1];
|
|
}
|
|
if (ISPROCESS(tp)) {
|
|
ft = tp->p.count;
|
|
column(ft, tp->p.realt, tp->p.cput,
|
|
tp->p.syst, tp->p.imem, tp->p.io);
|
|
printf(" %.14s\n", tp->p.name);
|
|
}
|
|
} /* iterate to merge the lists */
|
|
exit(0);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
printmoney()
|
|
{
|
|
register uid_t i;
|
|
register struct user *up;
|
|
|
|
getnames(); /* fetches all of the names! */
|
|
for (i = 0; i != maxuser; i++) {
|
|
if ((up = wasuser(i)) != 0) {
|
|
if (up->us_cnt) {
|
|
if (up->us_name[0])
|
|
printf("%-8s", up->us_name);
|
|
else
|
|
printf("%-8d", i);
|
|
printf("%7u %9.2fcpu %10.0ftio %12.0fk*sec\n",
|
|
up->us_cnt, up->us_ctime / 60,
|
|
up->us_io,
|
|
up->us_imem / AHZ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
column(n, a, b, c, d, e)
|
|
double n, a, b, c, d, e;
|
|
{
|
|
|
|
printf("%8.0f", n);
|
|
if (cflg) {
|
|
if (n == ncom)
|
|
printf("%9s", ""); else
|
|
printf("%8.2f%%", 100.*n/ncom);
|
|
}
|
|
col(n, a, treal, "re");
|
|
if (oflg)
|
|
col(n, 60*AHZ*(b/(b+c)), tcpu+tsys, "u/s");
|
|
else if (lflg) {
|
|
col(n, b, tcpu, "u");
|
|
col(n, c, tsys, "s");
|
|
} else
|
|
col(n, b+c, tcpu+tsys, "cp");
|
|
if (tflg)
|
|
printf("%8.1fre/cp", a/(b+c));
|
|
if (dflg || !Dflg)
|
|
printf("%10.0favio", e/(n?n:1));
|
|
else
|
|
printf("%10.0ftio", e);
|
|
if (kflg || !Kflg)
|
|
printf("%10.0fk", d/((b+c)!=0.0?(b+c):1.0));
|
|
else
|
|
printf("%10.0fk*sec", d/AHZ);
|
|
}
|
|
|
|
col(n, a, m, cp)
|
|
double n, a, m;
|
|
char *cp;
|
|
{
|
|
|
|
if (jflg)
|
|
printf("%11.2f%s", a/(n*(double)AHZ), cp); else
|
|
printf("%11.2f%s", a/(60.*(double)AHZ), cp);
|
|
if (cflg) {
|
|
if (a == m)
|
|
printf("%9s", ""); else
|
|
printf("%8.2f%%", 100.*a/m);
|
|
}
|
|
}
|
|
|
|
doacct(f)
|
|
char *f;
|
|
{
|
|
FILE *ff;
|
|
long y, z;
|
|
double x;
|
|
struct acct fbuf;
|
|
register char *cp;
|
|
register struct user *up;
|
|
register cell *tp;
|
|
#ifdef DEBUG
|
|
int nrecords = 0;
|
|
#endif DEBUG
|
|
|
|
if (sflg && sname) {
|
|
printf("Only 1 file with -s\n");
|
|
exit(1);
|
|
}
|
|
if (sflg)
|
|
sname = f;
|
|
if ((ff = fopen(f, "r"))==NULL) {
|
|
printf("Can't open %s\n", f);
|
|
return;
|
|
}
|
|
while (fread((char *)&fbuf, sizeof (fbuf), 1, ff) == 1) {
|
|
#ifdef DEBUG
|
|
if (++nrecords % 1000 == 0)
|
|
printf("Input record from %s number %d\n",
|
|
f, nrecords);
|
|
#endif DEBUG
|
|
if (fbuf.ac_comm[0]==0) {
|
|
fbuf.ac_comm[0] = '?';
|
|
}
|
|
for (cp = fbuf.ac_comm; *cp && cp < &fbuf.ac_comm[NC]; cp++)
|
|
if (!isascii(*cp) || iscntrl(*cp))
|
|
*cp = '?';
|
|
if (cp == fbuf.ac_comm)
|
|
*cp++ = '?';
|
|
if (fbuf.ac_flag&AFORK) {
|
|
if (cp >= &fbuf.ac_comm[NC])
|
|
cp = &fbuf.ac_comm[NC-1];
|
|
*cp++ = '*';
|
|
}
|
|
if (cp < &fbuf.ac_comm[NC])
|
|
*cp = '\0';
|
|
x = expand(fbuf.ac_utime) + expand(fbuf.ac_stime);
|
|
y = pgtok((u_short)fbuf.ac_mem);
|
|
z = expand(fbuf.ac_io) / AHZ;
|
|
if (uflg) {
|
|
printf("%3d %6.2f cpu %8luk mem %6ld io %.*s\n",
|
|
fbuf.ac_uid, x/(double)AHZ, y, z, NC, fbuf.ac_comm);
|
|
continue;
|
|
}
|
|
up = finduser(fbuf.ac_uid);
|
|
if (up == 0)
|
|
continue; /* preposterous user id */
|
|
up->us_cnt++;
|
|
up->us_ctime += x/(double)AHZ;
|
|
up->us_imem += x * y;
|
|
up->us_io += z;
|
|
ncom += 1.0;
|
|
|
|
tp = enter(fbuf.ac_comm);
|
|
tp->p.imem += x * y;
|
|
timem += x * y;
|
|
tp->p.count++;
|
|
x = expand(fbuf.ac_etime);
|
|
tp->p.realt += x;
|
|
treal += x;
|
|
x = expand(fbuf.ac_utime);
|
|
tp->p.cput += x;
|
|
tcpu += x;
|
|
x = expand(fbuf.ac_stime);
|
|
tp->p.syst += x;
|
|
tsys += x;
|
|
tp->p.io += z;
|
|
tio += z;
|
|
}
|
|
fclose(ff);
|
|
}
|
|
|
|
/*
|
|
* Generalized cell compare routine, to cast out users
|
|
*/
|
|
cellcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
if (ISPROCESS(p1)) {
|
|
if (ISPROCESS(p2))
|
|
return ((*cmp)(p1, p2));
|
|
return (-1);
|
|
}
|
|
if (ISPROCESS(p2))
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
ncmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
|
|
if (p1->p.count == p2->p.count)
|
|
return (tcmp(p1, p2));
|
|
if (rflg)
|
|
return (p1->p.count - p2->p.count);
|
|
return (p2->p.count - p1->p.count);
|
|
}
|
|
|
|
Bcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
double f1, f2;
|
|
double sum();
|
|
|
|
f1 = sum(p1)/p1->p.count;
|
|
f2 = sum(p2)/p2->p.count;
|
|
if (f1 < f2) {
|
|
if (rflg)
|
|
return (-1);
|
|
return (1);
|
|
}
|
|
if (f1 > f2) {
|
|
if (rflg)
|
|
return (1);
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
Kcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
|
|
if (p1->p.imem < p2->p.imem) {
|
|
if (rflg)
|
|
return (-1);
|
|
return (1);
|
|
}
|
|
if (p1->p.imem > p2->p.imem) {
|
|
if (rflg)
|
|
return (1);
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
kcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
double a1, a2;
|
|
|
|
a1 = p1->p.imem / ((p1->p.cput+p1->p.syst)?(p1->p.cput+p1->p.syst):1);
|
|
a2 = p2->p.imem / ((p2->p.cput+p2->p.syst)?(p2->p.cput+p2->p.syst):1);
|
|
if (a1 < a2) {
|
|
if (rflg)
|
|
return (-1);
|
|
return (1);
|
|
}
|
|
if (a1 > a2) {
|
|
if (rflg)
|
|
return (1);
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
dcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
double a1, a2;
|
|
|
|
a1 = p1->p.io / (p1->p.count?p1->p.count:1);
|
|
a2 = p2->p.io / (p2->p.count?p2->p.count:1);
|
|
if (a1 < a2) {
|
|
if (rflg)
|
|
return (-1);
|
|
return (1);
|
|
}
|
|
if (a1 > a2) {
|
|
if (rflg)
|
|
return (1);
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
Dcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
|
|
if (p1->p.io < p2->p.io) {
|
|
if (rflg)
|
|
return (-1);
|
|
return (1);
|
|
}
|
|
if (p1->p.io > p2->p.io) {
|
|
if (rflg)
|
|
return (1);
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
tcmp(p1, p2)
|
|
cell *p1, *p2;
|
|
{
|
|
extern double sum();
|
|
double f1, f2;
|
|
|
|
f1 = sum(p1);
|
|
f2 = sum(p2);
|
|
if (f1 < f2) {
|
|
return (rflg ? -1 : 1);
|
|
} else if (f1 > f2) {
|
|
return (rflg ? 1 : -1);
|
|
} else
|
|
return (0);
|
|
}
|
|
|
|
double
|
|
sum(p)
|
|
cell *p;
|
|
{
|
|
|
|
if (p->p.name[0] == 0)
|
|
return (0.0);
|
|
return (p->p.cput + p->p.syst);
|
|
}
|
|
|
|
init()
|
|
{
|
|
struct user userbuf;
|
|
struct process tbuf;
|
|
register cell *tp;
|
|
register struct user *up;
|
|
uid_t uid;
|
|
FILE *f;
|
|
|
|
if ((f = fopen(savacct, "r")) == NULL)
|
|
goto gshm;
|
|
while (fread((char *)&tbuf, sizeof (struct process), 1, f) == 1) {
|
|
tp = enter(tbuf.name);
|
|
ncom += tbuf.count;
|
|
tp->p.count = tbuf.count;
|
|
treal += tbuf.realt;
|
|
tp->p.realt = tbuf.realt;
|
|
tcpu += tbuf.cput;
|
|
tp->p.cput = tbuf.cput;
|
|
tsys += tbuf.syst;
|
|
tp->p.syst = tbuf.syst;
|
|
tio += tbuf.io;
|
|
tp->p.io = tbuf.io;
|
|
timem += tbuf.imem;
|
|
tp->p.imem = tbuf.imem;
|
|
}
|
|
fclose(f);
|
|
gshm:
|
|
if ((f = fopen(usracct, "r")) == NULL)
|
|
return;
|
|
for (uid = 0;
|
|
fread((char *)&(userbuf.oldu), sizeof (struct Olduser), 1, f) == 1;
|
|
uid++) {
|
|
if (userbuf.us_cnt) {
|
|
up = finduser(uid);
|
|
if (up == 0)
|
|
continue; /* preposterous user id */
|
|
up->oldu = userbuf.oldu;
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
strip()
|
|
{
|
|
int c;
|
|
register struct allocbox *allocwalk;
|
|
register cell *tp, *ub, *junkp;
|
|
|
|
if (fflg)
|
|
printf("Categorizing commands used %d times or fewer as **junk**\n",
|
|
thres);
|
|
junkp = enter("**junk**");
|
|
PROCESSITERATE(allocwalk, tp, ub) {
|
|
if (tp->p.name[0] && tp->p.count <= thres) {
|
|
if (!fflg)
|
|
printf("%.14s--", tp->p.name);
|
|
if (fflg || ((c=getchar())=='y')) {
|
|
tp->p.name[0] = '\0';
|
|
junkp->p.count += tp->p.count;
|
|
junkp->p.realt += tp->p.realt;
|
|
junkp->p.cput += tp->p.cput;
|
|
junkp->p.syst += tp->p.syst;
|
|
junkp->p.imem += tp->p.imem;
|
|
junkp->p.io += tp->p.io;
|
|
}
|
|
if (!fflg)
|
|
while (c && c!='\n')
|
|
c = getchar();
|
|
}
|
|
}
|
|
}
|
|
|
|
double
|
|
expand(t)
|
|
unsigned t;
|
|
{
|
|
register double nt;
|
|
|
|
nt = t&017777;
|
|
t >>= 13;
|
|
while (t!=0) {
|
|
t--;
|
|
nt *= 8;
|
|
}
|
|
return (nt);
|
|
}
|
|
|
|
static char UserKey[NAMELG + 2];
|
|
|
|
char *
|
|
makekey(uid)
|
|
uid_t uid;
|
|
{
|
|
sprintf(UserKey+1, "%04x", uid);
|
|
UserKey[0] = USERKEY;
|
|
return (UserKey);
|
|
}
|
|
|
|
struct user *
|
|
wasuser(uid)
|
|
uid_t uid;
|
|
{
|
|
struct user *tp;
|
|
|
|
htabinstall = 0;
|
|
tp = finduser(uid);
|
|
htabinstall = 1;
|
|
return (tp);
|
|
}
|
|
|
|
/*
|
|
* Only call this if you really want to insert it in the table!
|
|
*/
|
|
struct user *
|
|
finduser(uid)
|
|
uid_t uid;
|
|
{
|
|
|
|
if (uid > maxuser) {
|
|
fprintf(stderr, "User id %d not in passwd: ignored\n", uid);
|
|
return (0);
|
|
}
|
|
return ((struct user*)enter(makekey(uid)));
|
|
}
|
|
|
|
/*
|
|
* Set the names of all users in the password file.
|
|
* We will later not print those that didn't do anything.
|
|
*/
|
|
getnames()
|
|
{
|
|
register struct user *tp;
|
|
register struct passwd *pw;
|
|
struct passwd *getpwent();
|
|
|
|
setpwent();
|
|
while (pw = getpwent()) {
|
|
/* use first name in passwd file for duplicate uid's */
|
|
if ((tp = wasuser((uid_t)pw->pw_uid)) != 0 && !isalpha(tp->us_name[0]))
|
|
strncpy(tp->us_name, pw->pw_name, NAMELG);
|
|
}
|
|
endpwent();
|
|
}
|
|
|
|
uid_t
|
|
getmaxuid()
|
|
{
|
|
register struct passwd *pw;
|
|
struct passwd *getpwent();
|
|
uid_t maxuid = 0;
|
|
|
|
setpwent();
|
|
while (pw = getpwent()) {
|
|
/* Negative numbers converted to unsigned numbers */
|
|
if ((uid_t) pw->pw_uid > maxuid) {
|
|
maxuid = (uid_t) pw->pw_uid;
|
|
}
|
|
}
|
|
endpwent();
|
|
return (maxuid);
|
|
}
|
|
|
|
tabinit()
|
|
{
|
|
allochead = 0;
|
|
alloctail = 0;
|
|
nexttab = 0;
|
|
tabsleft = 0;
|
|
htab = 0;
|
|
ntabs = 0;
|
|
htaballoc(); /* get the first part of the hash table */
|
|
}
|
|
|
|
#define ALLOCQTY sizeof (struct allocbox)
|
|
cell *
|
|
taballoc()
|
|
{
|
|
|
|
if (tabsleft == 0) {
|
|
newbox = (struct allocbox *)calloc(1, ALLOCQTY);
|
|
if (newbox == (struct allocbox *)NULL) {
|
|
fprintf(stderr, "sa: out of memory\n");
|
|
exit(1);
|
|
}
|
|
tabsleft = TABDALLOP;
|
|
nexttab = &newbox->tabslots[0];
|
|
if (alloctail == 0) {
|
|
allochead = alloctail = newbox;
|
|
} else {
|
|
alloctail->nextalloc = newbox;
|
|
alloctail = newbox;
|
|
}
|
|
}
|
|
--tabsleft;
|
|
++ntabs;
|
|
#ifdef DEBUG
|
|
if (ntabs % 100 == 0)
|
|
printf("##Accounting table slot # %d\n", ntabs);
|
|
#endif DEBUG
|
|
return (nexttab++);
|
|
}
|
|
|
|
htaballoc()
|
|
{
|
|
register struct hashdallop *new;
|
|
#ifdef DEBUG
|
|
static int ntables = 0;
|
|
|
|
printf("%%%New hash table chunk allocated, number %d\n", ++ntables);
|
|
#endif DEBUG
|
|
new = (struct hashdallop *)calloc(1, sizeof (struct hashdallop));
|
|
if (new == (struct hashdallop *)NULL) {
|
|
fprintf(stderr, "sa: out of memory\n");
|
|
exit(1);
|
|
}
|
|
if (htab == 0)
|
|
htab = new;
|
|
else { /* add AFTER the 1st slot */
|
|
new->h_next = htab->h_next;
|
|
htab->h_next = new;
|
|
}
|
|
}
|
|
|
|
#define HASHCLOGGED (NHASH / 2)
|
|
/*
|
|
* Lookup a symbol passed in as the argument.
|
|
*
|
|
* We take pains to avoid function calls; this function
|
|
* is called quite frequently, and the calling overhead
|
|
* contributes significantly to the overall execution speed of sa.
|
|
*/
|
|
cell *
|
|
enter(name)
|
|
char *name;
|
|
{
|
|
static int initialprobe;
|
|
register cell **hp;
|
|
register char *from, *to;
|
|
register int len, nprobes;
|
|
static struct hashdallop *hdallop, *emptyhd;
|
|
static cell **emptyslot, **hp_ub;
|
|
|
|
emptyslot = 0;
|
|
for (nprobes = 0, from = name, len = 0;
|
|
*from && len < NC;
|
|
nprobes <<= 2, nprobes += *from++, len++)
|
|
continue;
|
|
nprobes += from[-1] << 5;
|
|
nprobes %= NHASH;
|
|
if (nprobes < 0)
|
|
nprobes += NHASH;
|
|
|
|
initialprobe = nprobes;
|
|
for (hdallop = htab; hdallop != 0; hdallop = hdallop->h_next) {
|
|
for (hp = &(hdallop->h_tab[initialprobe]),
|
|
nprobes = 1,
|
|
hp_ub = &(hdallop->h_tab[NHASH]);
|
|
(*hp) && (nprobes < NHASH);
|
|
hp += nprobes,
|
|
hp -= (hp >= hp_ub) ? NHASH : 0,
|
|
nprobes += 2)
|
|
{
|
|
from = name;
|
|
to = (*hp)->p.name;
|
|
|
|
for (len = 0; (len<NC) && *from; len++)
|
|
if (*from++ != *to++)
|
|
goto nextprobe;
|
|
if (len >= NC) /* both are maximal length */
|
|
return (*hp);
|
|
if (*to == 0) /* assert *from == 0 */
|
|
return (*hp);
|
|
nextprobe: ;
|
|
}
|
|
if (*hp == 0 && emptyslot == 0 &&
|
|
hdallop->h_nused < HASHCLOGGED) {
|
|
emptyslot = hp;
|
|
emptyhd = hdallop;
|
|
}
|
|
}
|
|
if (emptyslot == 0) {
|
|
htaballoc();
|
|
hdallop = htab->h_next; /* aren't we smart! */
|
|
hp = &hdallop->h_tab[initialprobe];
|
|
} else {
|
|
hdallop = emptyhd;
|
|
hp = emptyslot;
|
|
}
|
|
if (htabinstall) {
|
|
*hp = taballoc();
|
|
hdallop->h_nused++;
|
|
for (len = 0, from = name, to = (*hp)->p.name; (len<NC); len++)
|
|
if ((*to++ = *from++) == '\0')
|
|
break;
|
|
return (*hp);
|
|
}
|
|
return (0);
|
|
}
|