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