xref: /original-bsd/usr.sbin/sa/sa.c (revision e1db577d)
1 /*-
2  * Copyright (c) 1991 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.proprietary.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1991 The Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)sa.c	4.14 (Berkeley) 05/06/91";
16 #endif /* not lint */
17 
18 /*
19  *	Extensive modifications to internal data structures
20  *	to allow arbitrary number of different commands and users added.
21  *
22  *	Also allowed the digit option on the -v flag (interactive
23  *	threshold compress) to be a digit string, so one can
24  *	set the threshold > 9.
25  *
26  *	Also added the -f flag, to force no interactive threshold
27  *	compression with the -v flag.
28  *
29  *	Robert Henry
30  *	UC Berkeley
31  *	31jan81
32  */
33 #include <stdio.h>
34 #include <ctype.h>
35 #include <sys/types.h>
36 #include <sys/acct.h>
37 #include <signal.h>
38 #include <utmp.h>
39 #include <pwd.h>
40 #include "pathnames.h"
41 
42 /* interpret command time accounting */
43 
44 #define	NC	sizeof(acctbuf.ac_comm)
45 
46 struct acct acctbuf;
47 int	lflg;
48 int	cflg;
49 int	Dflg;
50 int	dflg;
51 int	iflg;
52 int	jflg;
53 int	Kflg;
54 int	kflg;
55 int	nflg;
56 int	aflg;
57 int	rflg;
58 int	oflg;
59 int	tflg;
60 int	vflg;
61 int	fflg;
62 int	uflg;
63 int	thres;
64 int	sflg;
65 int	bflg;
66 int	mflg;
67 
68 struct	utmp	utmp;
69 #define	NAMELG	(sizeof(utmp.ut_name)+1)
70 
71 struct 	Olduser{
72 	int	Us_cnt;
73 	double	Us_ctime;
74 	double	Us_io;
75 	double	Us_imem;
76 };
77 
78 struct	user {
79 	char	name[NC];		/* this is <\001><user id><\000> */
80 	struct	Olduser	oldu;
81 	char	us_name[NAMELG];
82 };
83 #define	us_cnt		oldu.Us_cnt
84 #define	us_ctime	oldu.Us_ctime
85 #define	us_io		oldu.Us_io
86 #define	us_imem		oldu.Us_imem
87 
88 /*
89  *	We protect ourselves from preposterous user id's by looking
90  *	through the passwd file for the highest uid allocated, and
91  *	then adding 10 to that.
92  *	This prevents the user structure from growing too large.
93  */
94 #define	USERSLOP	10
95 int	maxuser;		/* highest uid from /etc/passwd, + 10 for slop*/
96 
97 struct	process {
98 	char	name[NC];
99 	int	count;
100 	double	realt;
101 	double	cput;
102 	double	syst;
103 	double	imem;
104 	double	io;
105 };
106 
107 union	Tab{
108 	struct	process	p;
109 	struct	user	u;
110 };
111 
112 typedef	union Tab cell;
113 
114 int	(*cmp)();	/* compares 2 cells; set to appropriate func */
115 cell	*enter();
116 struct	user *finduser();
117 struct	user *wasuser();
118 
119 /*
120  *	Table elements are keyed by the name of the file exec'ed.
121  *	Because on large systems, many files can be exec'ed,
122  *	a static table size may grow to be too large.
123  *
124  *	Table elements are allocated in chunks dynamically, linked
125  *	together so that they may be retrieved sequentially.
126  *
127  *	An index into the table structure is provided by hashing through
128  *	a seperate hash table.
129  *	The hash table is segmented, and dynamically extendable.
130  *	Realize that the hash table and accounting information is kept
131  *	in different segments!
132  *
133  *	We have a linked list of hash table segments; within each
134  *	segment we use a quadratic rehash that touches no more than 1/2
135  *	of the buckets in the hash table when probing.
136  *	If the probe does not find the desired symbol, it moves to the
137  *	next segment, or allocates a new segment.
138  *
139  *	Hash table segments are kept on the linked list with the first
140  *	segment always first (that will probably contain the
141  *	most frequently executed commands) and
142  *	the last added segment immediately after the first segment,
143  *	to hopefully gain something by locality of reference.
144  *
145  *	We store the per user information in the same structure as
146  *	the per exec'ed file information.  This allows us to use the
147  *	same managers for both, as the number of user id's may be very
148  *	large.
149  *	User information is keyed by the first character in the name
150  *	being a '\001', followed by four bytes of (long extended)
151  *	user id number, followed by a null byte.
152  *	The actual user names are kept in a seperate field of the
153  *	user structure, and is filled in upon demand later.
154  *	Iteration through all users by low user id to high user id
155  *	is done by just probing the table, which is gross.
156  */
157 #define	USERKEY	'\001'
158 #define	ISPROCESS(tp)	(tp->p.name[0] && (tp->p.name[0] != USERKEY))
159 #define	ISUSER(tp)	(tp->p.name[0] && (tp->p.name[0] == USERKEY))
160 
161 #define	TABDALLOP	500
162 struct 	allocbox{
163 	struct	allocbox	*nextalloc;
164 	cell			tabslots[TABDALLOP];
165 };
166 
167 struct	allocbox	*allochead;	/*head of chunk list*/
168 struct	allocbox	*alloctail;	/*tail*/
169 struct	allocbox	*newbox;	/*for creating a new chunk*/
170 cell			*nexttab;	/*next table element that is free*/
171 int			tabsleft;	/*slots left in current chunk*/
172 int			ntabs;
173 /*
174  *	Iterate through all symbols in the symbol table in declaration
175  *	order.
176  *	struct	allocbox	*allocwalk;
177  *	cell			*sp, *ub;
178  *
179  *	sp points to the desired item, allocwalk and ub are there
180  *	to make the iteration go.
181  */
182 
183 #define DECLITERATE(allocwalk, walkpointer, ubpointer) \
184 	for(allocwalk = allochead; \
185 	    allocwalk != 0; \
186 	    allocwalk = allocwalk->nextalloc) \
187 		for (walkpointer = &allocwalk->tabslots[0],\
188 		        ubpointer = &allocwalk->tabslots[TABDALLOP], \
189 		        ubpointer = ubpointer > ( (cell *)alloctail) \
190 				 ? nexttab : ubpointer ;\
191 		     walkpointer < ubpointer; \
192 		     walkpointer++ )
193 
194 #define TABCHUNKS(allocwalk, tabptr, size) \
195 	for (allocwalk = allochead; \
196 	    allocwalk != 0; \
197 	    allocwalk = allocwalk->nextalloc) \
198 	    if ( \
199 		(tabptr = &allocwalk->tabslots[0]), \
200 		(size = \
201 		 (   (&allocwalk->tabslots[TABDALLOP]) \
202 		   > ((cell *)alloctail) \
203 		 ) \
204 		   ? (nexttab - tabptr) : TABDALLOP \
205 		), \
206 		1 \
207 	    )
208 #define	PROCESSITERATE(allocwalk, walkpointer, ubpointer) \
209 	DECLITERATE(allocwalk, walkpointer, ubpointer) \
210 	if (ISPROCESS(walkpointer))
211 
212 #define	USERITERATE(allocwalk, walkpointer, ubpointer) \
213 	DECLITERATE(allocwalk, walkpointer, ubpointer) \
214 	if (ISUSER(walkpointer))
215 /*
216  *	When we have to sort the segmented accounting table, we
217  *	create a vector of sorted queues that is merged
218  *	to sort the entire accounting table.
219  */
220 struct chunkdesc   {
221 	cell	*chunk_tp;
222 	int	chunk_n;
223 };
224 
225 /*
226  *	Hash table segments and manager
227  */
228 #define	NHASH	1103
229 struct hashdallop {
230 	int	h_nused;
231 	struct	hashdallop	*h_next;
232 	cell		*h_tab[NHASH];
233 };
234 struct	hashdallop	*htab;	/* head of the list */
235 int	htabinstall;		/* install the symbol */
236 
237 double	treal;
238 double	tcpu;
239 double	tsys;
240 double	tio;
241 double	timem;
242 cell	*junkp;
243 char	*sname;
244 double	ncom;
245 time_t	expand();
246 char	*getname();
247 
248 /*
249  *	usracct saves records of type Olduser.
250  *	There is one record for every possible uid less than
251  *	the largest uid seen in the previous usracct or in savacct.
252  *	uid's that had no activity correspond to zero filled slots;
253  *	thus one can index the file and get the user record out.
254  *	It would be better to save only user information for users
255  *	that the system knows about to save space, but that is not
256  *	upward compatabile with the old system.
257  *
258  *	In the old version of sa, uid's greater than 999 were not handled
259  *	properly; this system will do that.
260  */
261 
262 #ifdef	DEBUG
263 #define	USRACCT "./usracct"
264 #define	SAVACCT	"./savacct"
265 #define	ACCT	"./acct"
266 #else
267 #define	USRACCT _PATH_USRACCT
268 #define	SAVACCT	_PATH_SAVACCT
269 #define	ACCT	_PATH_ACCT
270 #endif	DEBUG
271 
272 
273 char *usracct = USRACCT;
274 char *savacct = SAVACCT;
275 
276 int	cellcmp();
277 cell	*junkp = 0;
278 /*
279  *	The threshold is built up from digits in the argv ;
280  *	eg, -v1s0u1
281  *	will build a value of thres of 101.
282  *
283  *	If the threshold is zero after processing argv, it is set to 1
284  */
285 int	thres = 0;
286 int	htabinstall = 1;
287 int	maxuser = -1;
288 int	(*cmp)();
289 
290 /* we assume pagesize is at least 1k */
291 int	pgdiv;
292 #define	pgtok(x)	((x) * pgdiv)
293 
294 extern	tcmp(), ncmp(), bflgcmp(), dcmp(), Dcmp(), kcmp(), Kcmp();
295 extern	double sum();
296 
297 main(argc, argv)
298 	char **argv;
299 {
300 	FILE *ff;
301 	double ft;
302 	register struct	allocbox *allocwalk;
303 	register cell *tp, *ub;
304 	int i, j, size, nchunks, smallest;
305 	struct chunkdesc *chunkvector;
306 
307 	pgdiv = getpagesize() / 1024;
308 	if (pgdiv == 0)
309 		pgdiv = 1;
310 	maxuser = USERSLOP + getmaxuid();
311 
312 	tabinit();
313 	cmp = tcmp;
314 	if (argc>1)
315 	if (argv[1][0]=='-') {
316 		argv++;
317 		argc--;
318 		for(i=1; argv[0][i]; i++)
319 		switch(argv[0][i]) {
320 
321 		case 'o':
322 			oflg++;
323 			break;
324 
325 		case 'i':
326 			iflg++;
327 			break;
328 
329 		case 'b':
330 			bflg++;
331 			cmp = bflgcmp;
332 			break;
333 
334 		case 'l':
335 			lflg++;
336 			break;
337 
338 		case 'c':
339 			cflg++;
340 			break;
341 
342 		case 'd':
343 			dflg++;
344 			cmp = dcmp;
345 			break;
346 
347 		case 'D':
348 			Dflg++;
349 			cmp = Dcmp;
350 			break;
351 
352 		case 'j':
353 			jflg++;
354 			break;
355 
356 		case 'k':
357 			kflg++;
358 			cmp = kcmp;
359 			break;
360 
361 		case 'K':
362 			Kflg++;
363 			cmp = Kcmp;
364 			break;
365 
366 		case 'n':
367 			nflg++;
368 			cmp = ncmp;
369 			break;
370 
371 		case 'a':
372 			aflg++;
373 			break;
374 
375 		case 'r':
376 			rflg++;
377 			break;
378 
379 		case 't':
380 			tflg++;
381 			break;
382 
383 		case 's':
384 			sflg++;
385 			aflg++;
386 			break;
387 
388 		case '0':
389 		case '1':
390 		case '2':
391 		case '3':
392 		case '4':
393 		case '5':
394 		case '6':
395 		case '7':
396 		case '8':
397 		case '9':
398 			thres = thres * 10 + (argv[0][i]-'0');
399 			break;
400 
401 		case 'v':
402 			vflg++;
403 			break;
404 
405 		case 'f':
406 			fflg++;	/* force v option; no tty interaction */
407 			break;
408 
409 		case 'u':
410 			uflg++;
411 			break;
412 
413 		case 'm':
414 			mflg++;
415 			break;
416 
417 		case 'U':
418 		case 'S':
419 			if (i != 1 || argv[0][2]) {	/* gross! */
420 				fprintf(stderr, "-U and -S options must be separate\n");
421 				exit(1);
422 			}
423 			argc++, argv--;			/* backup - yuk */
424 			goto doUS;
425 
426 		default:
427 		    	fprintf(stderr, "Invalid option %c\n", argv[0][1]);
428 			exit(1);
429 		}
430 	}
431 
432 #define optfile(f) {if (argc < 2) \
433 			{ fprintf(stderr, "Missing filename\n"); exit(1); } \
434 			argc--, argv++; f = argv[0]; }
435 
436 doUS:
437 	for (argc--, argv++; argc && argv[0][0] == '-'; argc--, argv++) {
438 		switch(argv[0][1]) {
439 		    case 'U':
440 		    	optfile(usracct);
441 			break;
442 
443 		    case 'S':
444 		    	optfile(savacct);
445 			break;
446 
447 		    default:
448 		    	fprintf(stderr, "Invalid option %c\n", argv[0][1]);
449 			exit(1);
450 		}
451 	}
452 
453 	if (thres == 0)
454 		thres = 1;
455 	if (iflg==0)
456 		init();
457 	if (argc<1)
458 		doacct(ACCT);
459 	else while (argc--)
460 		doacct(*argv++);
461 	if (uflg) {
462 		return;
463 	}
464 
465 /*
466  * cleanup pass
467  * put junk together
468  */
469 
470 	if (vflg)
471 		strip();
472 	if(!aflg)
473 	PROCESSITERATE(allocwalk, tp, ub){
474 		for(j=0; j<NC; j++)
475 			if(tp->p.name[j] == '?')
476 				goto yes;
477 		if(tp->p.count != 1)
478 			continue;
479 	yes:
480 		if(junkp == 0)
481 			junkp = enter("***other");
482 		junkp->p.count += tp->p.count;
483 		junkp->p.realt += tp->p.realt;
484 		junkp->p.cput += tp->p.cput;
485 		junkp->p.syst += tp->p.syst;
486 		junkp->p.imem += tp->p.imem;
487 		junkp->p.io += tp->p.io;
488 		tp->p.name[0] = 0;
489 	}
490 	if (sflg) {
491 		signal(SIGINT, SIG_IGN);
492 		if ((ff = fopen(usracct, "w")) != NULL) {
493 			static	struct	user ZeroUser = {0};
494 			struct 	user	*up;
495 			int	uid;
496 			/*
497 			 *	Write out just enough user slots,
498 			 *	filling with zero slots for users that
499 			 *	weren't found.
500 			 *	The file can be indexed directly by uid
501 			 *	to get the correct record.
502 			 */
503 			for (uid = 0; uid < maxuser; uid++){
504 				if ( (up = wasuser(uid)) != 0)
505 					fwrite((char *)&(up->oldu),
506 						sizeof(struct Olduser),1,ff);
507 				else
508 					fwrite((char *)&(ZeroUser.oldu),
509 						sizeof(struct Olduser),1,ff);
510 			}
511 		}
512 		if ((ff = fopen(savacct, "w")) == NULL) {
513 			printf("Can't save\n");
514 			exit(0);
515 		}
516 		PROCESSITERATE(allocwalk, tp, ub)
517 			fwrite((char *)&(tp->p), sizeof(struct process), 1, ff);
518 		fclose(ff);
519 		creat(sname, 0644);
520 		signal(SIGINT, SIG_DFL);
521 	}
522 /*
523  * sort and print
524  */
525 	if (mflg) {
526 		printmoney();
527 		exit(0);
528 	}
529 	column(ncom, treal, tcpu, tsys, timem, tio);
530 	printf("\n");
531 
532 	/*
533 	 *	the fragmented table is sorted by sorting each fragment
534 	 *	and then merging.
535 	 */
536 	nchunks = 0;
537 	TABCHUNKS(allocwalk, tp, size){
538 		qsort(tp, size, sizeof(cell), cellcmp);
539 		nchunks ++;
540 	}
541 	chunkvector = (struct chunkdesc *)calloc(nchunks,
542 		sizeof(struct chunkdesc));
543 	nchunks = 0;
544 	TABCHUNKS(allocwalk, tp, size){
545 		chunkvector[nchunks].chunk_tp = tp;
546 		chunkvector[nchunks].chunk_n = size;
547 		nchunks++;
548 	}
549 	for(; nchunks; ){
550 		/*
551 		 *	Find the smallest element at the head of the queues.
552 		 */
553 		smallest = 0;
554 		for (i = 1; i < nchunks; i++){
555 			if (cellcmp(chunkvector[i].chunk_tp,
556 				chunkvector[smallest].chunk_tp) < 0)
557 					smallest = i;
558 		}
559 		tp = chunkvector[smallest].chunk_tp++;
560 		/*
561 		 *	If this queue is drained, drop the chunk count,
562 		 *	and readjust the queues.
563 		 */
564 		if (--chunkvector[smallest].chunk_n == 0){
565 			nchunks--;
566 			for (i = smallest; i < nchunks; i++)
567 				chunkvector[i] = chunkvector[i+1];
568 		}
569 		if (ISPROCESS(tp)){
570 			ft = tp->p.count;
571 			column(ft, tp->p.realt, tp->p.cput,
572 				tp->p.syst, tp->p.imem, tp->p.io);
573 			printf("   %.14s\n", tp->p.name);
574 		}
575 	}	/* iterate to merge the lists */
576 }
577 
578 printmoney()
579 {
580 	register i;
581 	register char *cp;
582 	register	struct user	*up;
583 
584 	getnames();		/* fetches all of the names! */
585 	for (i = 0; i < maxuser; i++) {
586 		if ( (up = wasuser(i)) != 0){
587 			if (up->us_cnt) {
588 				if (up->us_name[0])
589 					printf("%-8s", up->us_name);
590 				else
591 					printf("%-8d", i);
592 				printf("%7u %9.2fcpu %10.0ftio %12.0fk*sec\n",
593 					up->us_cnt, up->us_ctime / 60,
594 					up->us_io,
595 					up->us_imem / AHZ);
596 			}
597 		}
598 	}
599 }
600 
601 column(n, a, b, c, d, e)
602 	double n, a, b, c, d, e;
603 {
604 
605 	printf("%8.0f", n);
606 	if(cflg) {
607 		if(n == ncom)
608 			printf("%9s", ""); else
609 			printf("%8.2f%%", 100.*n/ncom);
610 	}
611 	col(n, a, treal, "re");
612 	if (oflg)
613 		col(n, 60*AHZ*(b/(b+c)), tcpu+tsys, "u/s");
614 	else if(lflg) {
615 		col(n, b, tcpu, "u");
616 		col(n, c, tsys, "s");
617 	} else
618 		col(n, b+c, tcpu+tsys, "cp");
619 	if(tflg)
620 		printf("%8.1fre/cp", a/(b+c));
621 	if(dflg || !Dflg)
622 		printf("%10.0favio", e/(n?n:1));
623 	else
624 		printf("%10.0ftio", e);
625 	if (kflg || !Kflg)
626 		printf("%10.0fk", d/((b+c)!=0.0?(b+c):1.0));
627 	else
628 		printf("%10.0fk*sec", d/AHZ);
629 }
630 
631 col(n, a, m, cp)
632 	double n, a, m;
633 	char *cp;
634 {
635 
636 	if(jflg)
637 		printf("%11.2f%s", a/(n*(double)AHZ), cp); else
638 		printf("%11.2f%s", a/(60.*(double)AHZ), cp);
639 	if(cflg) {
640 		if(a == m)
641 			printf("%9s", ""); else
642 			printf("%8.2f%%", 100.*a/m);
643 	}
644 }
645 
646 doacct(f)
647 char *f;
648 {
649 	FILE *ff;
650 	long x, y, z;
651 	struct acct fbuf;
652 	register char *cp;
653 	register int c;
654 	register struct	user *up;
655 	register cell *tp;
656 #ifdef DEBUG
657 	int	nrecords = 0;
658 #endif DEBUG
659 
660 	if (sflg && sname) {
661 		printf("Only 1 file with -s\n");
662 		exit(0);
663 	}
664 	if (sflg)
665 		sname = f;
666 	if ((ff = fopen(f, "r"))==NULL) {
667 		printf("Can't open %s\n", f);
668 		return;
669 	}
670 	while (fread((char *)&fbuf, sizeof(fbuf), 1, ff) == 1) {
671 #ifdef DEBUG
672 		if (++nrecords % 1000 == 0)
673 			printf("Input record from %s number %d\n",
674 				f, nrecords);
675 #endif DEBUG
676 		for (cp = fbuf.ac_comm; *cp && cp < &fbuf.ac_comm[NC]; cp++)
677 			if (!isascii(*cp) || iscntrl(*cp))
678 				*cp = '?';
679 		if (cp == fbuf.ac_comm)
680 			*cp++ = '?';
681 		if (fbuf.ac_flag&AFORK) {
682 			if (cp >= &fbuf.ac_comm[NC])
683 				cp = &fbuf.ac_comm[NC-1];
684 			*cp++ = '*';
685 		}
686 		if (cp < &fbuf.ac_comm[NC])
687 			*cp = '\0';
688 		x = expand(fbuf.ac_utime) + expand(fbuf.ac_stime);
689 		y = pgtok((u_short)fbuf.ac_mem);
690 		z = expand(fbuf.ac_io) / AHZ;
691 		if (uflg) {
692 			printf("%3d %6.2f cpu %8luk mem %6ld io %.*s\n",
693 			    fbuf.ac_uid, x/(double)AHZ, y, z, NC, fbuf.ac_comm);
694 			continue;
695 		}
696 		up = finduser(fbuf.ac_uid);
697 		if (up == 0)
698 			continue;	/* preposterous user id */
699 		up->us_cnt++;
700 		up->us_ctime += x/(double)AHZ;
701 		up->us_imem += x * y;
702 		up->us_io += z;
703 		ncom += 1.0;
704 
705 		tp = enter(fbuf.ac_comm);
706 		tp->p.imem += x * y;
707 		timem += x * y;
708 		tp->p.count++;
709 		x = expand(fbuf.ac_etime);
710 		tp->p.realt += x;
711 		treal += x;
712 		x = expand(fbuf.ac_utime);
713 		tp->p.cput += x;
714 		tcpu += x;
715 		x = expand(fbuf.ac_stime);
716 		tp->p.syst += x;
717 		tsys += x;
718 		tp->p.io += z;
719 		tio += z;
720 	}
721 	fclose(ff);
722 }
723 
724 /*
725  *	Generalized cell compare routine, to cast out users
726  */
727 cellcmp(p1, p2)
728 	cell *p1, *p2;
729 {
730 	if (ISPROCESS(p1)){
731 		if (ISPROCESS(p2))
732 			return((*cmp)(p1, p2));
733 		return(-1);
734 	}
735 	if (ISPROCESS(p2))
736 		return(1);
737 	return(0);
738 }
739 
740 ncmp(p1, p2)
741 	cell *p1, *p2;
742 {
743 
744 	if(p1->p.count == p2->p.count)
745 		return(tcmp(p1, p2));
746 	if(rflg)
747 		return(p1->p.count - p2->p.count);
748 	return(p2->p.count - p1->p.count);
749 }
750 
751 bflgcmp(p1, p2)
752 	cell *p1, *p2;
753 {
754 	double f1, f2;
755 	double sum();
756 
757 	f1 = sum(p1)/p1->p.count;
758 	f2 = sum(p2)/p2->p.count;
759 	if(f1 < f2) {
760 		if(rflg)
761 			return(-1);
762 		return(1);
763 	}
764 	if(f1 > f2) {
765 		if(rflg)
766 			return(1);
767 		return(-1);
768 	}
769 	return(0);
770 }
771 
772 Kcmp(p1, p2)
773 	cell *p1, *p2;
774 {
775 
776 	if (p1->p.imem < p2->p.imem) {
777 		if(rflg)
778 			return(-1);
779 		return(1);
780 	}
781 	if (p1->p.imem > p2->p.imem) {
782 		if(rflg)
783 			return(1);
784 		return(-1);
785 	}
786 	return(0);
787 }
788 
789 kcmp(p1, p2)
790 	cell *p1, *p2;
791 {
792 	double a1, a2;
793 
794 	a1 = p1->p.imem / ((p1->p.cput+p1->p.syst)?(p1->p.cput+p1->p.syst):1);
795 	a2 = p2->p.imem / ((p2->p.cput+p2->p.syst)?(p2->p.cput+p2->p.syst):1);
796 	if (a1 < a2) {
797 		if(rflg)
798 			return(-1);
799 		return(1);
800 	}
801 	if (a1 > a2) {
802 		if(rflg)
803 			return(1);
804 		return(-1);
805 	}
806 	return(0);
807 }
808 
809 dcmp(p1, p2)
810 	cell *p1, *p2;
811 {
812 	double a1, a2;
813 
814 	a1 = p1->p.io / (p1->p.count?p1->p.count:1);
815 	a2 = p2->p.io / (p2->p.count?p2->p.count:1);
816 	if (a1 < a2) {
817 		if(rflg)
818 			return(-1);
819 		return(1);
820 	}
821 	if (a1 > a2) {
822 		if(rflg)
823 			return(1);
824 		return(-1);
825 	}
826 	return(0);
827 }
828 
829 Dcmp(p1, p2)
830 	cell *p1, *p2;
831 {
832 
833 	if (p1->p.io < p2->p.io) {
834 		if(rflg)
835 			return(-1);
836 		return(1);
837 	}
838 	if (p1->p.io > p2->p.io) {
839 		if(rflg)
840 			return(1);
841 		return(-1);
842 	}
843 	return(0);
844 }
845 
846 tcmp(p1, p2)
847 	cell *p1, *p2;
848 {
849 	extern double sum();
850 	double f1, f2;
851 
852 	f1 = sum(p1);
853 	f2 = sum(p2);
854 	if(f1 < f2) {
855 		if(rflg)
856 			return(-1);
857 		return(1);
858 	}
859 	if(f1 > f2) {
860 		if(rflg)
861 			return(1);
862 		return(-1);
863 	}
864 	return(0);
865 }
866 
867 double sum(p)
868 	cell *p;
869 {
870 
871 	if(p->p.name[0] == 0)
872 		return(0.0);
873 	return( p->p.cput + p->p.syst);
874 }
875 
876 init()
877 {
878 	struct user userbuf;
879 	struct process	tbuf;
880 	register cell *tp;
881 	register struct user *up;
882 	int uid;
883 	FILE *f;
884 
885 	if ((f = fopen(savacct, "r")) == NULL)
886 		goto gshm;
887 	while (fread((char *)&tbuf, sizeof(struct process), 1, f) == 1) {
888 		tp = enter(tbuf.name);
889 		ncom += tbuf.count;
890 		tp->p.count = tbuf.count;
891 		treal += tbuf.realt;
892 		tp->p.realt = tbuf.realt;
893 		tcpu += tbuf.cput;
894 		tp->p.cput = tbuf.cput;
895 		tsys += tbuf.syst;
896 		tp->p.syst = tbuf.syst;
897 		tio += tbuf.io;
898 		tp->p.io = tbuf.io;
899 		timem += tbuf.imem;
900 		tp->p.imem = tbuf.imem;
901 	}
902 	fclose(f);
903  gshm:
904 	if ((f = fopen(usracct, "r")) == NULL)
905 		return;
906 	for(uid = 0;
907 	    fread((char *)&(userbuf.oldu), sizeof(struct Olduser), 1, f) == 1;
908 	    uid++){
909 		if (userbuf.us_cnt){
910 			up = finduser(uid);
911 			if (up == 0)
912 				continue;	/* preposterous user id */
913 			up->oldu = userbuf.oldu;
914 		}
915 	}
916 	fclose(f);
917 }
918 
919 strip()
920 {
921 	int c;
922 	register struct allocbox *allocwalk;
923 	register cell *tp, *ub, *junkp;
924 
925 	if (fflg)
926 		printf("Categorizing commands used %d times or fewer as **junk**\n",
927 			thres);
928 	junkp = enter("**junk**");
929 	PROCESSITERATE(allocwalk, tp, ub){
930 		if (tp->p.name[0] && tp->p.count <= thres) {
931 			if (!fflg)
932 				printf("%.14s--", tp->p.name);
933 			if (fflg || ((c=getchar())=='y')) {
934 				tp->p.name[0] = '\0';
935 				junkp->p.count += tp->p.count;
936 				junkp->p.realt += tp->p.realt;
937 				junkp->p.cput += tp->p.cput;
938 				junkp->p.syst += tp->p.syst;
939 				junkp->p.imem += tp->p.imem;
940 				junkp->p.io += tp->p.io;
941 			}
942 			if (!fflg)
943 				while (c && c!='\n')
944 					c = getchar();
945 		}
946 	}
947 }
948 
949 time_t
950 expand(t)
951 	unsigned t;
952 {
953 	register time_t nt;
954 
955 	nt = t&017777;
956 	t >>= 13;
957 	while (t!=0) {
958 		t--;
959 		nt <<= 3;
960 	}
961 	return(nt);
962 }
963 
964 static	char UserKey[NAMELG + 2];
965 
966 char *
967 makekey(uid)
968 	int uid;
969 {
970 	(void)sprintf(UserKey+1, "%04x", uid);
971 	UserKey[0] = USERKEY;
972 	return(UserKey);
973 }
974 
975 struct user *
976 wasuser(uid)
977 	int uid;
978 {
979 	struct user *tp;
980 
981 	htabinstall = 0;
982 	tp = finduser(uid);
983 	htabinstall = 1;
984 	return(tp);
985 }
986 
987 /*
988  *	Only call this if you really want to insert it in the table!
989  */
990 struct user *
991 finduser(uid)
992 	int uid;
993 {
994 
995 	if (uid > maxuser){
996 		fprintf(stderr, "Preposterous user id, %d: ignored\n", uid);
997 		return(0);
998 	}
999 	return((struct user*)enter(makekey(uid)));
1000 }
1001 
1002 /*
1003  *	Set the names of all users in the password file.
1004  *	We will later not print those that didn't do anything.
1005  */
1006 getnames()
1007 {
1008 	register struct user *tp;
1009 	register struct passwd *pw;
1010 	struct passwd *getpwent();
1011 
1012 	setpwent();
1013 	while (pw = getpwent()){
1014 		/* use first name in passwd file for duplicate uid's */
1015 		if ((tp = wasuser(pw->pw_uid)) != 0 && !isalpha(tp->us_name[0]))
1016 			strncpy(tp->us_name, pw->pw_name, NAMELG);
1017 	}
1018 	endpwent();
1019 }
1020 
1021 int
1022 getmaxuid()
1023 {
1024 	register struct user *tp;
1025 	register struct passwd *pw;
1026 	struct passwd *getpwent();
1027 	int maxuid = -1;
1028 
1029 	setpwent();
1030 	while(pw = getpwent()){
1031 		if (pw->pw_uid > maxuid)
1032 			maxuid = pw->pw_uid;
1033 	}
1034 	endpwent();
1035 	return(maxuid);
1036 }
1037 
1038 tabinit()
1039 {
1040 	allochead = 0;
1041 	alloctail = 0;
1042 	nexttab = 0;
1043 	tabsleft = 0;
1044 	htab = 0;
1045 	ntabs = 0;
1046 	htaballoc();		/* get the first part of the hash table */
1047 }
1048 
1049 #define ALLOCQTY 	sizeof (struct allocbox)
1050 cell *
1051 taballoc()
1052 {
1053 
1054 	if (tabsleft == 0){
1055 		newbox = (struct allocbox *)calloc(1, ALLOCQTY);
1056 		tabsleft = TABDALLOP;
1057 		nexttab = &newbox->tabslots[0];
1058 		if (alloctail == 0){
1059 			allochead = alloctail = newbox;
1060 		} else {
1061 			alloctail->nextalloc = newbox;
1062 			alloctail = newbox;
1063 		}
1064 	}
1065 	--tabsleft;
1066 	++ntabs;
1067 #ifdef DEBUG
1068 	if (ntabs % 100 == 0)
1069 		printf("##Accounting table slot # %d\n", ntabs);
1070 #endif DEBUG
1071 	return(nexttab++);
1072 }
1073 
1074 htaballoc()
1075 {
1076 	register struct hashdallop *new;
1077 #ifdef DEBUG
1078 	static int ntables = 0;
1079 
1080 	printf("%%%New hash table chunk allocated, number %d\n", ++ntables);
1081 #endif DEBUG
1082 	new = (struct hashdallop *)calloc(1, sizeof (struct hashdallop));
1083 	if (htab == 0)
1084 		htab = new;
1085 	else {		/* add AFTER the 1st slot */
1086 		new->h_next = htab->h_next;
1087 		htab->h_next = new;
1088 	}
1089 }
1090 
1091 #define 	HASHCLOGGED	(NHASH / 2)
1092 /*
1093  *	Lookup a symbol passed in as the argument.
1094  *
1095  *	We take pains to avoid function calls; this function
1096  *	is called quite frequently, and the calling overhead
1097  *	contributes significantly to the overall execution speed of sa.
1098  */
1099 cell *
1100 enter(name)
1101 	char *name;
1102 {
1103 	static int initialprobe;
1104 	register cell **hp;
1105 	register char *from, *to;
1106 	register int len, nprobes;
1107 	static struct hashdallop *hdallop, *emptyhd;
1108 	static cell **emptyslot, **hp_ub;
1109 
1110 	emptyslot = 0;
1111 	for (nprobes = 0, from = name, len = 0;
1112 	     *from && len < NC;
1113 	     nprobes <<= 2, nprobes += *from++, len++)
1114 		continue;
1115 	nprobes += from[-1] << 5;
1116 	nprobes %= NHASH;
1117 	if (nprobes < 0)
1118 		nprobes += NHASH;
1119 
1120 	initialprobe = nprobes;
1121 	for (hdallop = htab; hdallop != 0; hdallop = hdallop->h_next){
1122 		for (hp = &(hdallop->h_tab[initialprobe]),
1123 				nprobes = 1,
1124 				hp_ub = &(hdallop->h_tab[NHASH]);
1125 		     (*hp) && (nprobes < NHASH);
1126 				hp += nprobes,
1127 				hp -= (hp >= hp_ub) ? NHASH:0,
1128 				nprobes += 2)
1129 		{
1130 			from = name;
1131 			to = (*hp)->p.name;
1132 
1133 			for (len = 0; (len<NC) && *from; len++)
1134 				if (*from++ != *to++)
1135 					goto nextprobe;
1136 			if (len >= NC)		/*both are maximal length*/
1137 				return(*hp);
1138 			if (*to == 0)		/*assert *from == 0*/
1139 				return(*hp);
1140 	nextprobe: ;
1141 		}
1142 		if (*hp == 0 && emptyslot == 0 &&
1143 		    hdallop->h_nused < HASHCLOGGED) {
1144 			emptyslot = hp;
1145 			emptyhd = hdallop;
1146 		}
1147 	}
1148 	if (emptyslot == 0) {
1149 		htaballoc();
1150 		hdallop = htab->h_next;		/* aren't we smart! */
1151 		hp = &hdallop->h_tab[initialprobe];
1152 	} else {
1153 		hdallop = emptyhd;
1154 		hp = emptyslot;
1155 	}
1156 	if (htabinstall){
1157 		*hp = taballoc();
1158 		hdallop->h_nused++;
1159 		for(len = 0, from = name, to = (*hp)->p.name; (len<NC); len++)
1160 			if ((*to++ = *from++) == '\0')
1161 				break;
1162 		return(*hp);
1163 	}
1164 	return(0);
1165 }
1166