static char *sccsid
= "@(#)sa.c 4.5 (Berkeley) 83/10/14";
* 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
* Also added the -f flag, to force no interactive threshold
* compression with the -v flag.
/* interpret command time accounting */
#define NC sizeof(acctbuf.ac_comm)
#define NAMELG (sizeof(utmp.ut_name)+1)
char name
[NC
]; /* this is <\001><user id><\000> */
#define us_cnt oldu.Us_cnt
#define us_ctime oldu.Us_ctime
#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.
int maxuser
; /* highest uid from /etc/passwd, + 10 for slop*/
int (*cmp
)(); /* compares 2 cells; set to appropriate func */
* 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
* The hash table is segmented, and dynamically extendable.
* Realize that the hash table and accounting information is kept
* 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
* 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 ISPROCESS(tp) (tp->p.name[0] && (tp->p.name[0] != USERKEY))
#define ISUSER(tp) (tp->p.name[0] && (tp->p.name[0] == USERKEY))
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*/
* Iterate through all symbols in the symbol table in declaration
* struct allocbox *allocwalk;
* 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 = allocwalk->nextalloc) \
for (walkpointer = &allocwalk->tabslots[0],\
ubpointer = &allocwalk->tabslots[TABDALLOP], \
ubpointer = ubpointer > ( (cell *)alloctail) \
walkpointer < ubpointer; \
#define TABCHUNKS(allocwalk, tabptr, size) \
for (allocwalk = allochead; \
allocwalk = allocwalk->nextalloc) \
(tabptr = &allocwalk->tabslots[0]), \
( (&allocwalk->tabslots[TABDALLOP]) \
? (nexttab - tabptr) : TABDALLOP \
#define PROCESSITERATE(allocwalk, walkpointer, ubpointer) \
DECLITERATE(allocwalk, walkpointer, ubpointer) \
if (ISPROCESS(walkpointer))
#define USERITERATE(allocwalk, walkpointer, ubpointer) \
DECLITERATE(allocwalk, walkpointer, ubpointer) \
* 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.
* Hash table segments and manager
struct hashdallop
*h_next
;
struct hashdallop
*htab
; /* head of the list */
int htabinstall
; /* install the symbol */
* 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.
#define USRACCT "./usracct"
#define SAVACCT "./savacct"
#define USRACCT "/usr/adm/usracct"
#define SAVACCT "/usr/adm/savacct"
#define ACCT "/usr/adm/acct"
* The threshold is built up from digits in the argv ;
* will build a value of thres of 101.
* If the threshold is zero after processing argv, it is set to 1
extern tcmp(), ncmp(), bcmp(), dcmp(), Dcmp(), kcmp(), Kcmp();
register struct allocbox
*allocwalk
;
int i
, j
, size
, nchunks
, smallest
;
struct chunkdesc
*chunkvector
;
maxuser
= USERSLOP
+ getmaxuid();
for(i
=1; argv
[0][i
]; i
++)
thres
= thres
* 10 + (argv
[0][i
]-'0');
fflg
++; /* force v option; no tty interaction */
PROCESSITERATE(allocwalk
, tp
, ub
){
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
;
if ((ff
= fopen(USRACCT
, "w")) != NULL
) {
static struct user ZeroUser
= {0};
* Write out just enough user slots,
* filling with zero slots for users that
* 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
);
fwrite((char *)&(ZeroUser
.oldu
),
sizeof(struct Olduser
),1,ff
);
if ((ff
= fopen(SAVACCT
, "w")) == NULL
) {
PROCESSITERATE(allocwalk
, tp
, ub
)
fwrite((char *)&(tp
->p
), sizeof(struct process
), 1, ff
);
column(ncom
, treal
, tcpu
, tsys
, timem
, tio
);
* the fragmented table is sorted by sorting each fragment
TABCHUNKS(allocwalk
, tp
, size
){
qsort(tp
, size
, sizeof(cell
), cellcmp
);
chunkvector
= (struct chunkdesc
*)calloc(nchunks
,
sizeof(struct chunkdesc
));
TABCHUNKS(allocwalk
, tp
, size
){
chunkvector
[nchunks
].chunk_tp
= tp
;
chunkvector
[nchunks
].chunk_n
= size
;
* Find the smallest element at the head of the queues.
for (i
= 1; i
< nchunks
; i
++){
if (cellcmp(chunkvector
[i
].chunk_tp
,
chunkvector
[smallest
].chunk_tp
) < 0)
tp
= chunkvector
[smallest
].chunk_tp
++;
* If this queue is drained, drop the chunk count,
* and readjust the queues.
if (--chunkvector
[smallest
].chunk_n
== 0){
for (i
= smallest
; i
< nchunks
; i
++)
chunkvector
[i
] = chunkvector
[i
+1];
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 */
register struct user
*up
;
getnames(); /* fetches all of the names! */
for (i
= 0; i
< maxuser
; i
++) {
if ( (up
= wasuser(i
)) != 0){
printf("%-8s", up
->us_name
);
printf("%7u %9.2fcpu %10.0ftio %12.0fk*sec\n",
up
->us_cnt
, up
->us_ctime
/ 60,
printf("%8.2f%%", 100.*n
/ncom
);
col(n
, 3600*(b
/(b
+c
)), tcpu
+tsys
, "u/s");
col(n
, b
+c
, tcpu
+tsys
, "cp");
printf("%8.1f", a
/(b
+c
), "re/cp");
printf("%10.0favio", e
/(n
?n
:1));
printf("%10.0fk", d
/(2*((b
+c
)!=0.0?(b
+c
):1.0)));
printf("%10.0fk*sec", d
/(2*60));
printf("%11.2f%s", a
/(n
*60.), cp
); else
printf("%11.2f%s", a
/3600., cp
);
printf("%8.2f%%", 100.*a
/m
);
register struct user
*up
;
printf("Only 1 file with -s\n");
if ((ff
= fopen(f
, "r"))==NULL
) {
printf("Can't open %s\n", f
);
while (fread((char *)&fbuf
, sizeof(fbuf
), 1, ff
) == 1) {
if (++nrecords
% 1000 == 0)
printf("Input record from %s number %d\n",
if (fbuf
.ac_comm
[0]==0) {
for (cp
= fbuf
.ac_comm
; cp
< &fbuf
.ac_comm
[NC
]; cp
++) {
if (c
&& (c
< ' ' || c
>= 0200)) {
if (fbuf
.ac_flag
&AFORK
) {
for (cp
=fbuf
.ac_comm
; cp
< &fbuf
.ac_comm
[NC
]; cp
++)
x
= expand(fbuf
.ac_utime
) + expand(fbuf
.ac_stime
);
printf("%3d %6d cpu %8u mem %6d io %.14s\n",
fbuf
.ac_uid
, x
, y
, z
, fbuf
.ac_comm
);
up
= finduser(fbuf
.ac_uid
);
continue; /* preposterous user id */
tp
= enter(fbuf
.ac_comm
);
x
= expand(fbuf
.ac_etime
);
x
= expand(fbuf
.ac_utime
);
x
= expand(fbuf
.ac_stime
);
* Generalized cell compare routine, to cast out users
if(p1
->p
.count
== p2
->p
.count
)
return(p1
->p
.count
- p2
->p
.count
);
return(p2
->p
.count
- p1
->p
.count
);
f1
= sum(p1
)/p1
->p
.count
;
f2
= sum(p2
)/p2
->p
.count
;
if (p1
->p
.imem
< p2
->p
.imem
) {
if (p1
->p
.imem
> p2
->p
.imem
) {
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);
a1
= p1
->p
.io
/ (p1
->p
.count
?p1
->p
.count
:1);
a2
= p2
->p
.io
/ (p2
->p
.count
?p2
->p
.count
:1);
if (p1
->p
.io
< p2
->p
.io
) {
if (p1
->p
.io
> p2
->p
.io
) {
return( p
->p
.cput
+ p
->p
.syst
);
register struct user
*up
;
if ((f
= fopen(SAVACCT
, "r")) == NULL
)
while (fread((char *)&tbuf
, sizeof(struct process
), 1, f
) == 1) {
tp
->p
.count
= tbuf
.count
;
tp
->p
.realt
= tbuf
.realt
;
if ((f
= fopen(USRACCT
, "r")) == NULL
)
fread((char *)&(userbuf
.oldu
), sizeof(struct Olduser
), 1, f
) == 1;
continue; /* preposterous user id */
register struct allocbox
*allocwalk
;
register cell
*tp
, *ub
, *junkp
;
printf("Categorizing commands used %d times or fewer as **junk**\n",
junkp
= enter("**junk**");
PROCESSITERATE(allocwalk
, tp
, ub
){
if (tp
->p
.name
[0] && tp
->p
.count
<= thres
) {
printf("%.14s--", tp
->p
.name
);
if (fflg
|| ((c
=getchar())=='y')) {
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
;
static char UserKey
[NAMELG
+ 2];
sprintf(UserKey
+1, "%04x", uid
);
* Only call this if you really want to insert it in the table!
fprintf(stderr
, "Preposterous user id, %d: ignored\n", uid
);
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.
register struct user
*tp
;
register struct passwd
*pw
;
struct passwd
*getpwent();
if ( (tp
= wasuser(pw
->pw_uid
)) != 0)
strncpy(tp
->us_name
, pw
->pw_name
, NAMELG
);
register struct user
*tp
;
register struct passwd
*pw
;
struct passwd
*getpwent();
htaballoc(); /* get the first part of the hash table */
#define ALLOCQTY sizeof (struct allocbox)
newbox
= (struct allocbox
*)calloc(1, ALLOCQTY
);
nexttab
= &newbox
->tabslots
[0];
allochead
= alloctail
= newbox
;
alloctail
->nextalloc
= newbox
;
printf("##Accounting table slot # %d\n", ntabs
);
register struct hashdallop
*new;
printf("%%%New hash table chunk allocated, number %d\n", ++ntables
);
new = (struct hashdallop
*)calloc(1, sizeof (struct hashdallop
));
else { /* add AFTER the 1st slot */
new->h_next
= htab
->h_next
;
#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.
register char *from
, *to
;
register int len
, nprobes
;
static struct hashdallop
*hdallop
, *emptyhd
;
static cell
**emptyslot
, **hp_ub
;
for (nprobes
= 0, from
= name
, len
= 0;
nprobes
<<= 2, nprobes
+= *from
++, len
++)
nprobes
+= from
[-1] << 5;
for (hdallop
= htab
; hdallop
!= 0; hdallop
= hdallop
->h_next
){
for (hp
= &(hdallop
->h_tab
[initialprobe
]),
hp_ub
= &(hdallop
->h_tab
[NHASH
]);
(*hp
) && (nprobes
< NHASH
);
hp
-= (hp
>= hp_ub
) ? NHASH
:0,
for (len
= 0; (len
<NC
) && *from
; len
++)
if (len
>= NC
) /*both are maximal length*/
if (*to
== 0) /*assert *from == 0*/
if (*hp
== 0 && emptyslot
== 0 &&
hdallop
->h_nused
< HASHCLOGGED
) {
hdallop
= htab
->h_next
; /* aren't we smart! */
hp
= &hdallop
->h_tab
[initialprobe
];
for(len
= 0, from
= name
, to
= (*hp
)->p
.name
; (len
<NC
); len
++)
if ((*to
++ = *from
++) == '\0')