xref: /minix/minix/usr.bin/mtop/mtop.c (revision fb4fbf7a)
1 
2 /* Author: Ben Gras <beng@few.vu.nl>  17 march 2006 */
3 
4 #define _MINIX_SYSTEM 1
5 
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <pwd.h>
9 #include <curses.h>
10 #include <minix/timers.h>
11 #include <stdlib.h>
12 #include <limits.h>
13 #include <termcap.h>
14 #include <termios.h>
15 #include <time.h>
16 #include <string.h>
17 #include <signal.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #include <dirent.h>
21 #include <assert.h>
22 
23 #include <sys/ttycom.h>
24 #include <sys/ioctl.h>
25 #include <sys/times.h>
26 #include <sys/types.h>
27 #include <sys/time.h>
28 #include <sys/select.h>
29 
30 #include <minix/com.h>
31 #include <minix/config.h>
32 #include <minix/type.h>
33 #include <minix/endpoint.h>
34 #include <minix/const.h>
35 #include <minix/u64.h>
36 #include <minix/paths.h>
37 #include <minix/procfs.h>
38 
39 #define TIMECYCLEKEY 't'
40 #define ORDERKEY 'o'
41 
42 #define ORDER_CPU	0
43 #define ORDER_MEMORY	1
44 #define ORDER_HIGHEST	ORDER_MEMORY
45 int order = ORDER_CPU;
46 
47 u32_t system_hz;
48 
49 /* name of cpu cycle types, in the order they appear in /psinfo. */
50 const char *cputimenames[] = { "user", "ipc", "kernelcall" };
51 
52 #define CPUTIMENAMES ((int) (sizeof(cputimenames)/sizeof(cputimenames[0])))
53 
54 #define CPUTIME(m, i) (m & (1L << (i)))
55 
56 unsigned int nr_procs, nr_tasks;
57 int nr_total;
58 
59 #define  SLOT_NR(e) (_ENDPOINT_P(e) + nr_tasks)
60 
61 #define  TC_BUFFER  1024        /* Size of termcap(3) buffer    */
62 #define  TC_STRINGS  200        /* Enough room for cm,cl,so,se  */
63 
64 const char *Tclr_all;
65 
66 int blockedverbose = 0;
67 
68 #define  USED		0x1
69 #define  IS_TASK	0x2
70 #define  IS_SYSTEM	0x4
71 #define  BLOCKED	0x8
72 
73 struct proc {
74 	int p_flags;
75 	endpoint_t p_endpoint;
76 	pid_t p_pid;
77 	u64_t p_cpucycles[CPUTIMENAMES];
78 	int p_priority;
79 	endpoint_t p_blocked;
80 	clock_t p_user_time;
81 	vir_bytes p_memory;
82 	uid_t p_effuid;
83 	int p_nice;
84 	char p_name[PROC_NAME_LEN+1];
85 };
86 
87 struct proc *proc = NULL, *prev_proc = NULL;
88 
89 static void
90 parse_file(pid_t pid)
91 {
92 	char path[PATH_MAX], name[256], type, state;
93 	int version, endpt;
94 	FILE *fp;
95 	struct proc *p;
96 	int slot;
97 
98 	sprintf(path, "%d/psinfo", pid);
99 
100 	if ((fp = fopen(path, "r")) == NULL)
101 		return;
102 
103 	if (fscanf(fp, "%d", &version) != 1) {
104 		fclose(fp);
105 		return;
106 	}
107 
108 	if (version != PSINFO_VERSION) {
109 		fputs("procfs version mismatch!\n", stderr);
110 		exit(1);
111 	}
112 
113 	if (fscanf(fp, " %c %d", &type, &endpt) != 2) {
114 		fclose(fp);
115 		return;
116 	}
117 
118 	slot = SLOT_NR(endpt);
119 
120 	if (slot < 0 || slot >= nr_total) {
121 		fprintf(stderr, "top: unreasonable endpoint number %d\n",
122 		    endpt);
123 		fclose(fp);
124 		return;
125 	}
126 
127 	p = &proc[slot];
128 
129 	if (type == TYPE_TASK)
130 		p->p_flags |= IS_TASK;
131 	else if (type == TYPE_SYSTEM)
132 		p->p_flags |= IS_SYSTEM;
133 
134 	p->p_endpoint = endpt;
135 	p->p_pid = pid;
136 
137 	if (fscanf(fp, " %255s %c %d %d %u %*u %"PRIu64" %"PRIu64" %"PRIu64
138 	    " %lu %d %u",
139 	    name, &state, &p->p_blocked, &p->p_priority, &p->p_user_time,
140 	    &p->p_cpucycles[0], &p->p_cpucycles[1], &p->p_cpucycles[2],
141 	    &p->p_memory, &p->p_nice, &p->p_effuid) != 11) {
142 		fclose(fp);
143 		return;
144 	}
145 
146 	strlcpy(p->p_name, name, sizeof(p->p_name));
147 
148 	if (state != STATE_RUN)
149 		p->p_flags |= BLOCKED;
150 
151 	p->p_flags |= USED;
152 
153 	fclose(fp);
154 }
155 
156 static void parse_dir(void)
157 {
158 	DIR *p_dir;
159 	struct dirent *p_ent;
160 	pid_t pid;
161 	char *end;
162 
163 	if ((p_dir = opendir(".")) == NULL) {
164 		perror("opendir on " _PATH_PROC);
165 		exit(1);
166 	}
167 
168 	for (p_ent = readdir(p_dir); p_ent != NULL; p_ent = readdir(p_dir)) {
169 		pid = strtol(p_ent->d_name, &end, 10);
170 
171 		if (!end[0] && pid != 0)
172 			parse_file(pid);
173 	}
174 
175 	closedir(p_dir);
176 }
177 
178 static void get_procs(void)
179 {
180 	struct proc *p;
181 	int i;
182 
183 	p = prev_proc;
184 	prev_proc = proc;
185 	proc = p;
186 
187 	if (proc == NULL) {
188 		proc = malloc(nr_total * sizeof(proc[0]));
189 
190 		if (proc == NULL) {
191 			fprintf(stderr, "Out of memory!\n");
192 			exit(1);
193 		}
194 	}
195 
196 	for (i = 0; i < nr_total; i++)
197 		proc[i].p_flags = 0;
198 
199 	parse_dir();
200 }
201 
202 static int print_memory(void)
203 {
204 	FILE *fp;
205 	unsigned int pagesize;
206 	unsigned long total, freemem, largest, cached;
207 
208 	if ((fp = fopen("meminfo", "r")) == NULL)
209 		return 0;
210 
211 	if (fscanf(fp, "%u %lu %lu %lu %lu", &pagesize, &total, &freemem,
212 			&largest, &cached) != 5) {
213 		fclose(fp);
214 		return 0;
215 	}
216 
217 	fclose(fp);
218 
219 	printf("main memory: %ldK total, %ldK free, %ldK contig free, "
220 		"%ldK cached\n",
221 		(pagesize * total)/1024, (pagesize * freemem)/1024,
222 		(pagesize * largest)/1024, (pagesize * cached)/1024);
223 
224 	return 1;
225 }
226 
227 static int print_load(double *loads, int nloads)
228 {
229 	int i;
230 	printf("load averages: ");
231 	for(i = 0; i < nloads; i++)
232 		printf("%s %.2f", (i > 0) ? "," : "", loads[i]);
233 	printf("\n");
234 	return 1;
235 }
236 
237 static int print_proc_summary(struct proc *pproc)
238 {
239 	int p, alive, running, sleeping;
240 
241 	alive = running = sleeping = 0;
242 
243 	for(p = 0; p < nr_total; p++) {
244 		if (pproc[p].p_endpoint == IDLE)
245 			continue;
246 		if(!(pproc[p].p_flags & USED))
247 			continue;
248 		alive++;
249 		if(pproc[p].p_flags & BLOCKED)
250 			sleeping++;
251 		else
252 			running++;
253 	}
254 	printf("%d processes: %d running, %d sleeping\n",
255 		alive, running, sleeping);
256 	return 1;
257 }
258 
259 struct tp {
260 	struct proc *p;
261 	u64_t ticks;
262 };
263 
264 static int cmp_procs(const void *v1, const void *v2)
265 {
266 	const struct tp *p1 = (const struct tp *) v1,
267 		*p2 = (const struct tp *) v2;
268 	int p1blocked, p2blocked;
269 
270 	if(order == ORDER_MEMORY) {
271 		if(p1->p->p_memory < p2->p->p_memory) return 1;
272 		if(p1->p->p_memory > p2->p->p_memory) return -1;
273 		return 0;
274 	}
275 
276 	p1blocked = !!(p1->p->p_flags & BLOCKED);
277 	p2blocked = !!(p2->p->p_flags & BLOCKED);
278 
279 	/* Primarily order by used number of cpu cycles.
280 	 *
281 	 * Exception: if in blockedverbose mode, a blocked
282 	 * process is always printed after an unblocked
283 	 * process, and used cpu cycles don't matter.
284 	 *
285 	 * In both cases, process slot number is a tie breaker.
286 	 */
287 
288 	if(blockedverbose && (p1blocked || p2blocked)) {
289 		if(!p1blocked && p2blocked)
290 			return -1;
291 		if(!p2blocked && p1blocked)
292 			return 1;
293 	} else if(p1->ticks != p2->ticks) {
294 		if(p1->ticks > p2->ticks) return -1;
295 		assert(p1->ticks < p2->ticks);
296 		return 1;
297 	}
298 
299 	/* Process slot number is a tie breaker. */
300 	return (int) (p1->p - p2->p);
301 }
302 
303 static struct tp *lookup(endpoint_t who, struct tp *tptab, int np)
304 {
305 	int t;
306 
307 	for(t = 0; t < np; t++)
308 		if(who == tptab[t].p->p_endpoint)
309 			return &tptab[t];
310 
311 	fprintf(stderr, "lookup: tp %d (0x%x) not found.\n", who, who);
312 	abort();
313 
314 	return NULL;
315 }
316 
317 double ktotal = 0;
318 
319 static void print_proc(struct tp *tp, u64_t total_ticks)
320 {
321 	int euid = 0;
322 	static struct passwd *who = NULL;
323 	static int last_who = -1;
324 	const char *name = "";
325 	int ticks;
326 	struct proc *pr = tp->p;
327 
328 	printf("%5d ", pr->p_pid);
329 	euid = pr->p_effuid;
330 	name = pr->p_name;
331 
332 	if(last_who != euid || !who) {
333 		who = getpwuid(euid);
334 		last_who = euid;
335 	}
336 
337 	if(who && who->pw_name) printf("%-8s ", who->pw_name);
338 	else if(!(pr->p_flags & IS_TASK)) printf("%8d ", pr->p_effuid);
339 	else printf("         ");
340 
341 	printf(" %2d ", pr->p_priority);
342 	if(!(pr->p_flags & IS_TASK)) {
343 		printf(" %3d ", pr->p_nice);
344 	} else printf("     ");
345 	printf("%6ldK", (pr->p_memory + 512) / 1024);
346 	printf("%6s", (pr->p_flags & BLOCKED) ? "" : "RUN");
347 	ticks = pr->p_user_time;
348 	printf(" %3u:%02u ", (ticks/system_hz/60), (ticks/system_hz)%60);
349 
350 	printf("%6.2f%% %s", 100.0 * tp->ticks / total_ticks, name);
351 }
352 
353 static char *cputimemodename(int cputimemode)
354 {
355 	static char name[100];
356 	int i;
357 
358 	name[0] = '\0';
359 
360 	for(i = 0; i < CPUTIMENAMES; i++) {
361 		if(CPUTIME(cputimemode, i)) {
362 			assert(strlen(name) +
363 				strlen(cputimenames[i]) < sizeof(name));
364 			strcat(name, cputimenames[i]);
365 			strcat(name, " ");
366 		}
367 	}
368 
369 	return name;
370 }
371 
372 static u64_t cputicks(struct proc *p1, struct proc *p2, int timemode)
373 {
374 	int i;
375 	u64_t t = 0;
376 	for(i = 0; i < CPUTIMENAMES; i++) {
377 		if(!CPUTIME(timemode, i))
378 			continue;
379 		if(p1->p_endpoint == p2->p_endpoint) {
380 			t = t + p2->p_cpucycles[i] - p1->p_cpucycles[i];
381 		} else {
382 			t = t + p2->p_cpucycles[i];
383 		}
384 	}
385 
386 	return t;
387 }
388 
389 static const char *ordername(int orderno)
390 {
391 	switch(orderno) {
392 		case ORDER_CPU: return "cpu";
393 		case ORDER_MEMORY: return "memory";
394 	}
395 	return "invalid order";
396 }
397 
398 static void print_procs(int maxlines,
399 	struct proc *proc1, struct proc *proc2, int cputimemode)
400 {
401 	int p, nprocs;
402 	u64_t idleticks = 0;
403 	u64_t kernelticks = 0;
404 	u64_t systemticks = 0;
405 	u64_t userticks = 0;
406 	u64_t total_ticks = 0;
407 	int blockedseen = 0;
408 	static struct tp *tick_procs = NULL;
409 
410 	if (tick_procs == NULL) {
411 		tick_procs = malloc(nr_total * sizeof(tick_procs[0]));
412 
413 		if (tick_procs == NULL) {
414 			fprintf(stderr, "Out of memory!\n");
415 			exit(1);
416 		}
417 	}
418 
419 	for(p = nprocs = 0; p < nr_total; p++) {
420 		u64_t uticks;
421 		if(!(proc2[p].p_flags & USED))
422 			continue;
423 		tick_procs[nprocs].p = proc2 + p;
424 		tick_procs[nprocs].ticks = cputicks(&proc1[p], &proc2[p], cputimemode);
425 		uticks = cputicks(&proc1[p], &proc2[p], 1);
426 		total_ticks = total_ticks + uticks;
427 		if(p-NR_TASKS == IDLE) {
428 			idleticks = uticks;
429 			continue;
430 		}
431 		if(p-NR_TASKS == KERNEL) {
432 			kernelticks = uticks;
433 		}
434 		if(!(proc2[p].p_flags & IS_TASK)) {
435 			if(proc2[p].p_flags & IS_SYSTEM)
436 				systemticks = systemticks + tick_procs[nprocs].ticks;
437 			else
438 				userticks = userticks + tick_procs[nprocs].ticks;
439 		}
440 
441 		nprocs++;
442 	}
443 
444 	if (total_ticks == 0)
445 		return;
446 
447 	qsort(tick_procs, nprocs, sizeof(tick_procs[0]), cmp_procs);
448 
449 	printf("CPU states: %6.2f%% user, ", 100.0 * userticks / total_ticks);
450 	printf("%6.2f%% system, ", 100.0 * systemticks / total_ticks);
451 	printf("%6.2f%% kernel, ", 100.0 * kernelticks/ total_ticks);
452 	printf("%6.2f%% idle", 100.0 * idleticks / total_ticks);
453 
454 #define NEWLINE do { printf("\n"); if(--maxlines <= 0) { return; } } while(0)
455 	NEWLINE;
456 
457 	printf("CPU time displayed ('%c' to cycle): %s; ",
458 		TIMECYCLEKEY, cputimemodename(cputimemode));
459 	printf(" sort order ('%c' to cycle): %s", ORDERKEY, ordername(order));
460 	NEWLINE;
461 
462 	NEWLINE;
463 
464 	printf("  PID USERNAME PRI NICE    SIZE STATE   TIME     CPU COMMAND");
465 	NEWLINE;
466 	for(p = 0; p < nprocs; p++) {
467 		struct proc *pr;
468 		int level = 0;
469 
470 		pr = tick_procs[p].p;
471 
472 		if((pr->p_flags & IS_TASK) && pr->p_pid != KERNEL) {
473 			/* skip old kernel tasks as they don't run anymore */
474 			continue;
475 		}
476 
477 		/* If we're in blocked verbose mode, indicate start of
478 		 * blocked processes.
479 		 */
480 		if(blockedverbose && (pr->p_flags & BLOCKED) && !blockedseen) {
481 			NEWLINE;
482 			printf("Blocked processes:");
483 			NEWLINE;
484 			blockedseen = 1;
485 		}
486 
487 		print_proc(&tick_procs[p], total_ticks);
488 		NEWLINE;
489 
490 		if(!blockedverbose)
491 			continue;
492 
493 		/* Traverse dependency chain if blocked. */
494 		while(pr->p_flags & BLOCKED) {
495 			endpoint_t dep = NONE;
496 			struct tp *tpdep;
497 			level += 5;
498 
499 			if((dep = pr->p_blocked) == NONE) {
500 				printf("not blocked on a process");
501 				NEWLINE;
502 				break;
503 			}
504 
505 			if(dep == ANY)
506 				break;
507 
508 			tpdep = lookup(dep, tick_procs, nprocs);
509 			pr = tpdep->p;
510 			printf("%*s> ", level, "");
511 			print_proc(tpdep, total_ticks);
512 			NEWLINE;
513 		}
514 	}
515 }
516 
517 static void showtop(int cputimemode, int r)
518 {
519 #define NLOADS 3
520 	double loads[NLOADS];
521 	int nloads, lines = 0;
522 	struct winsize winsize;
523 
524 	if(ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize) != 0) {
525 		perror("TIOCGWINSZ");
526 		fprintf(stderr, "TIOCGWINSZ failed\n");
527 		exit(1);
528 	}
529 
530 	get_procs();
531 	if (prev_proc == NULL) {
532 		/*
533 		 * A delay short enough to be unnoticable but long enough to
534 		 * allow for accumulation of sufficient data for the initial
535 		 * display not to show wildly inaccurate numbers.
536 		 */
537 		usleep(100000);
538 		get_procs();
539 	}
540 
541 	if((nloads = getloadavg(loads, NLOADS)) != NLOADS) {
542 		fprintf(stderr, "getloadavg() failed - %d loads\n", nloads);
543 		exit(1);
544 	}
545 
546 
547 	printf("%s", Tclr_all);
548 
549 	lines += print_load(loads, NLOADS);
550 	lines += print_proc_summary(proc);
551 	lines += print_memory();
552 
553 	if(winsize.ws_row > 0) r = winsize.ws_row;
554 
555 	print_procs(r - lines - 2, prev_proc, proc, cputimemode);
556 	fflush(NULL);
557 }
558 
559 static void init(int *rows)
560 {
561 	char  *term;
562 	static char   buffer[TC_BUFFER], strings[TC_STRINGS];
563 	char *s = strings, *v;
564 
565 	*rows = 0;
566 
567 	if(!(term = getenv("TERM"))) {
568 		fprintf(stderr, "No TERM set\n");
569 		exit(1);
570 	}
571 
572 	if ( tgetent( buffer, term ) != 1 ) {
573 		fprintf(stderr, "tgetent failed for term %s\n", term);
574 		exit(1);
575 	}
576 
577 	initscr();
578 	cbreak();
579 
580 	if ( (Tclr_all = tgetstr( "cl", &s )) == NULL )
581 		Tclr_all = "\f";
582 
583 	if((v = tgetstr ("li", &s)) != NULL)
584 		sscanf(v, "%d", rows);
585 	if(*rows < 1) *rows = 24;
586 }
587 
588 static void sigwinch(int sig) { }
589 
590 static void getkinfo(void)
591 {
592 	FILE *fp;
593 
594 	if ((fp = fopen("kinfo", "r")) == NULL) {
595 		fprintf(stderr, "opening " _PATH_PROC "kinfo failed\n");
596 		exit(1);
597 	}
598 
599 	if (fscanf(fp, "%u %u", &nr_procs, &nr_tasks) != 2) {
600 		fprintf(stderr, "reading from " _PATH_PROC "kinfo failed\n");
601 		exit(1);
602 	}
603 
604 	fclose(fp);
605 
606 	nr_total = (int) (nr_procs + nr_tasks);
607 }
608 
609 int main(int argc, char *argv[])
610 {
611 	int r, optc, s = 0;
612 	int cputimemode = 1;	/* bitmap. */
613 
614 	if (chdir(_PATH_PROC) != 0) {
615 		perror("chdir to " _PATH_PROC);
616 		return 1;
617 	}
618 
619 	system_hz = (u32_t) sysconf(_SC_CLK_TCK);
620 
621 	getkinfo();
622 
623 	init(&r);
624 
625 	while((optc=getopt(argc, argv, "s:B")) != EOF) {
626 		switch(optc) {
627 			case 's':
628 				s = atoi(optarg);
629 				break;
630 			case 'B':
631 				blockedverbose = 1;
632 				break;
633 			default:
634 				fprintf(stderr,
635 					"Usage: %s [-s<secdelay>] [-B]\n",
636 						argv[0]);
637 				return 1;
638 		}
639 	}
640 
641 	if(s < 1)
642 		s = 2;
643 
644 	/* Catch window size changes so display is updated properly
645 	 * right away.
646 	 */
647 	signal(SIGWINCH, sigwinch);
648 
649 	while(1) {
650 		fd_set fds;
651 		int ns;
652 		struct timeval tv;
653 		showtop(cputimemode, r);
654 		tv.tv_sec = s;
655 		tv.tv_usec = 0;
656 
657 		FD_ZERO(&fds);
658 		FD_SET(STDIN_FILENO, &fds);
659 
660 		if((ns=select(STDIN_FILENO+1, &fds, NULL, NULL, &tv)) < 0
661 			&& errno != EINTR) {
662 			perror("select");
663 			sleep(1);
664 		}
665 
666 		if(ns > 0 && FD_ISSET(STDIN_FILENO, &fds)) {
667 			char inc;
668 			if(read(STDIN_FILENO, &inc, 1) == 1) {
669 				switch(inc) {
670 					case 'q':
671 						putchar('\r');
672 						return 0;
673 						break;
674 					case ORDERKEY:
675 						order++;
676 						if(order > ORDER_HIGHEST)
677 							order = 0;
678 						break;
679 					case TIMECYCLEKEY:
680 						cputimemode++;
681 						if(cputimemode >= (1L << CPUTIMENAMES))
682 						cputimemode = 1;
683 						break;
684 				}
685 			}
686 		}
687 	}
688 
689 	return 0;
690 }
691 
692