xref: /haiku/src/bin/top.cpp (revision 830f67ef991407f287dbc1238aa5f5906d90c991)
1 /*
2  * Copyright (C) 1996 Be, Inc.
3  * All Rights Reserved
4  *
5  * This source code was published by Be, Inc. in the file gnu_x86.tar.gz for R3,
6  * a mirror is at http://linux.inf.elte.hu/ftp/beos/development/gnu/r3.0/
7  * needs to link to termcap.
8  * The GPL should apply here, since AFAIK termcap is GPLed.
9  */
10 
11 /*
12  * Top -- a program for finding the top cpu-eating threads
13  */
14 #include <signal.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <OS.h>
19 #include <termios.h>
20 
21 #include <list>
22 
23 #include "termcap.h"
24 
25 static const char IDLE_NAME[] = "idle thread ";
26 static bigtime_t lastMeasure = 0;
27 
28 /*
29  * Keeps track of a single thread's times
30  */
31 struct ThreadTime {
32 	thread_id thid;
33 	bigtime_t user_time;
34 	bigtime_t kernel_time;
35 
36 	bigtime_t total_time() const {
37 		return user_time + kernel_time;
38 	}
39 
40 	bool operator< (const ThreadTime& other) const {
41 		return total_time() > other.total_time();
42 	}
43 };
44 
45 /*
46  * Keeps track of all the threads' times
47  */
48 typedef std::list<ThreadTime> ThreadTimeList;
49 
50 
51 static char *clear_string; /*output string for clearing the screen */
52 static char *enter_ca_mode; /* output string for switching screen buffer */
53 static char *exit_ca_mode; /* output string for releasing screen buffer */
54 static char *cursor_home;	/* Places cursor back to (1,1) */
55 static char *restore_cursor;
56 static char *save_cursor;
57 static int columns;	/* Columns on screen */
58 static int rows;	/* how many rows on the screen */
59 static int screen_size_changed = 0;	/* tells to refresh the screen size */
60 static int cpus;	/* how many cpus we are runing on */
61 
62 /* SIGWINCH handler */
63 static void
64 winch_handler(int notused)
65 {
66 	(void) notused;
67 	screen_size_changed = 1;
68 }
69 
70 
71 /* SIGINT handler */
72 static void
73 sigint_handler(int)
74 {
75 	tputs(exit_ca_mode, 1, putchar);
76 	tputs(restore_cursor, 1, putchar);
77 	exit(1);
78 }
79 
80 
81 static void
82 init_term()
83 {
84 	static char buf[2048];
85 	char *entries = &buf[0];
86 
87 	tgetent(buf, getenv("TERM"));
88 	exit_ca_mode = tgetstr("te", &entries);
89 	enter_ca_mode = tgetstr("ti", &entries);
90 	cursor_home = tgetstr("ho", &entries);
91 	clear_string = tgetstr("cl", &entries);
92 	restore_cursor = tgetstr("rc", &entries);
93 	save_cursor = tgetstr("sc", &entries);
94 
95 	tputs(save_cursor, 1, putchar);
96 	tputs(enter_ca_mode, 1, putchar);
97 	tputs(clear_string, 1, putchar);
98 }
99 
100 
101 /*
102  * Calculate the cpu percentage used by a given thread
103  * Remember: for multiple CPUs, multiply the interval by # cpus
104  * when calculating
105  */
106 static float
107 cpu_perc(bigtime_t spent, bigtime_t interval)
108 {
109 	return ((100.0 * spent) / (cpus * interval));
110 }
111 
112 
113 /*
114  * Compare an old snapshot with the new one
115  */
116 static void
117 compare(
118 		ThreadTimeList *old,
119 		ThreadTimeList *newList,
120 		bigtime_t uinterval,
121 		int refresh
122 		)
123 {
124 	bigtime_t oldtime;
125 	bigtime_t newtime;
126 	ThreadTimeList times;
127 	thread_info t;
128 	team_info tm;
129 	bigtime_t total;
130 	bigtime_t utotal;
131 	bigtime_t ktotal;
132 	bigtime_t gtotal;
133 	bigtime_t idletime;
134 	int newthread;
135 	int ignore;
136 	int linecount;
137 	//system_info info;
138 	char *p;
139 
140 	/*
141 	 * First, merge both old and new lists, computing the differences in time
142 	 * Threads in only one list are dropped.
143 	 * Threads with no elapsed time are dropped too.
144 	 */
145 	gtotal = 0;
146 	utotal = 0;
147 	ktotal = 0;
148 	ThreadTimeList::iterator it;
149 	ThreadTimeList::iterator itOld;
150 	ThreadTime entry;
151 
152 	for (it = newList->begin(); it != newList->end(); it++) {
153 		newthread = 1;
154 		ignore = 0;
155 		for (itOld = old->begin(); itOld != old->end(); itOld++) {
156 			if (itOld->thid != it->thid) {
157 				continue;
158 			}
159 			newthread = 0;
160 			oldtime = itOld->total_time();
161 			newtime = it->total_time();
162 			if (oldtime == newtime) {
163 				ignore = 1;
164 				break;
165 			}
166 			entry.thid = it->thid;
167 			entry.user_time = (it->user_time - itOld->user_time);
168 			entry.kernel_time = (it->kernel_time - itOld->kernel_time);
169 		}
170 		if (newthread) {
171 			entry.thid = it->thid;
172 			entry.user_time = it->user_time;
173 			entry.kernel_time = it->kernel_time;
174 		}
175 		if (!ignore) {
176 			times.push_back(entry);
177 
178 			total = entry.total_time();
179 			gtotal += total;
180 			utotal += entry.user_time;
181 			ktotal += entry.kernel_time;
182 		}
183 	}
184 
185 	/*
186 	 * Sort what we found and print
187 	 */
188 	times.sort();
189 
190 	printf("%6s %7s %7s %7s %4s %16s %-16s \n", "THID", "TOTAL", "USER",
191 		"KERNEL", "%CPU", "TEAM NAME", "THREAD NAME");
192 	linecount = 1;
193 	idletime = 0;
194 	gtotal = 0;
195 	ktotal = 0;
196 	utotal = 0;
197 	for (it = times.begin(); it != times.end(); it++) {
198 		ignore = 0;
199 		if (get_thread_info(it->thid, &t) < B_NO_ERROR) {
200 			strcpy(t.name, "(unknown)");
201 			strcpy(tm.args, "(unknown)");
202 		} else {
203 			if (strncmp(t.name, IDLE_NAME, strlen(IDLE_NAME)) == 0) {
204 				ignore++;
205 			}
206 			if (get_team_info(t.team, &tm) < B_NO_ERROR) {
207 				strcpy(tm.args, "(unknown)");
208 			} else {
209 				if ((p = strrchr(tm.args, '/'))) {
210 					strlcpy(tm.args, p + 1, sizeof(tm.args));
211 				}
212 			}
213 		}
214 
215 		tm.args[16] = 0;
216 
217 		if (columns <= 80)
218 			t.name[16] = 0;
219 		else if (columns - 64 < sizeof(t.name))
220 			t.name[columns - 64] = 0;
221 
222 		total = it->total_time();
223 		if (ignore) {
224 			idletime += total;
225 		} else {
226 			gtotal += total;
227 			ktotal += it->kernel_time;
228 			utotal += it->user_time;
229 		}
230 		if (!ignore && (!refresh || (linecount < (rows - 1)))) {
231 
232 			printf("%6" B_PRId32 " %7.2f %7.2f %7.2f %4.1f %16s %s \n",
233 				it->thid,
234 				total / 1000.0,
235 				(double)(it->user_time / 1000),
236 				(double)(it->kernel_time / 1000),
237 				cpu_perc(total, uinterval),
238 				tm.args,
239 				t.name);
240 			linecount++;
241 		}
242 	}
243 
244 	printf("------ %7.2f %7.2f %7.2f %4.1f%% "
245 		"TOTAL (%4.1f%% idle time, %4.1f%% unknown)",
246 		(double) (gtotal / 1000),
247 		(double) (utotal / 1000),
248 		(double) (ktotal / 1000),
249 		cpu_perc(gtotal, uinterval),
250 		cpu_perc(idletime, uinterval),
251 		cpu_perc(cpus * uinterval - (gtotal + idletime), uinterval));
252 	fflush(stdout);
253 	tputs(clear_string, 1, putchar);
254 	tputs(cursor_home, 1, putchar);
255 }
256 
257 
258 static int
259 adjust_term(bool onlyRows)
260 {
261 	struct winsize ws;
262 
263 	if (ioctl(1, TIOCGWINSZ, &ws) < 0) {
264 		return (0);
265 	}
266 	if (ws.ws_row <= 0) {
267 		return (0);
268 	}
269 	columns = ws.ws_col;
270 	rows = ws.ws_row;
271 	if (onlyRows)
272 		return 1;
273 
274 	return (1);
275 }
276 
277 
278 /*
279  * Gather up thread data since previous run
280  */
281 static ThreadTimeList
282 gather(ThreadTimeList *old, int refresh)
283 {
284 	int32		tmcookie;
285 	int32		thcookie;
286 	thread_info	t;
287 	team_info	tm;
288 	ThreadTimeList times;
289 	bigtime_t	oldLastMeasure;
290 
291 	tmcookie = 0;
292 	oldLastMeasure = lastMeasure;
293 	lastMeasure = system_time();
294 
295 	while (get_next_team_info(&tmcookie, &tm) == B_NO_ERROR) {
296 		thcookie = 0;
297 		while (get_next_thread_info(tm.team, &thcookie, &t) == B_NO_ERROR) {
298 			ThreadTime entry;
299 			entry.thid = t.thread;
300 			entry.user_time = t.user_time;
301 			entry.kernel_time = t.kernel_time;
302 			times.push_back(entry);
303 		}
304 	}
305 	if (old != NULL) {
306 		if (screen_size_changed) {
307 			adjust_term(true);
308 			screen_size_changed = 0;
309 		}
310 		compare(old, &times, system_time() - oldLastMeasure, refresh);
311 		old->clear();
312 	}
313 	return (times);
314 }
315 
316 
317 /*
318  * print usage message and exit
319  */
320 static void
321 usage(const char *myname)
322 {
323 	fprintf(stderr, "usage: %s [-d] [-i interval] [-n ntimes]\n", myname);
324 	fprintf(stderr,
325 			" -d,          do not clear the screen between displays\n");
326 	fprintf(stderr,
327 			" -i interval, wait `interval' seconds before displaying\n");
328 	fprintf(stderr,
329 			" -n ntimes,   display `ntimes' times before exiting\n");
330 	fprintf(stderr, "Default is clear screen, interval=5, ntimes=infinite\n");
331 	exit(1);
332 }
333 
334 
335 int
336 main(int argc, char **argv)
337 {
338 	ThreadTimeList baseline;
339 	int i;
340 	int iters = -1;
341 	int interval = 5;
342 	int refresh = 1;
343 	system_info sysinfo;
344 	bigtime_t uinterval;
345 	bigtime_t elapsed;
346 	char *myname;
347 
348 	get_system_info (&sysinfo);
349 	cpus = sysinfo.cpu_count;
350 
351 	myname = argv[0];
352 	for (argc--, argv++; argc > 0 && **argv == '-'; argc--, argv++) {
353 		if (strcmp(argv[0], "-i") == 0) {
354 			argc--, argv++;
355 			if (argc == 0) {
356 				usage(myname);
357 			}
358 			interval = atoi(argv[0]);
359 		} else if (strcmp(argv[0], "-n") == 0) {
360 			argc--, argv++;
361 			if (argc == 0) {
362 				usage(myname);
363 			}
364 			iters = atoi(argv[0]);
365 		} else if (strcmp(argv[0], "-d") == 0) {
366 			refresh = 0;
367 		} else {
368 			usage(myname);
369 		}
370 	}
371 	if (argc) {
372 		usage(myname);
373 	}
374 
375 	init_term();
376 
377 	if (refresh) {
378 		if (!adjust_term(false)) {
379 			refresh = 0;
380 		}
381 	}
382 	if (iters >= 0) {
383 		printf("Starting: %d interval%s of %d second%s each\n", iters,
384 			(iters == 1) ? "" : "s", interval,
385 			(interval == 1) ? "" : "s");
386 	}
387 
388 	signal(SIGWINCH, winch_handler);
389 	signal(SIGINT, sigint_handler);
390 
391 	lastMeasure = system_time();
392 	if (iters < 0) {
393 		// You will only have to wait half a second for the first iteration.
394 		uinterval = 1 * 1000000 / 2;
395 		baseline = gather(NULL, refresh);
396 		elapsed = system_time() - lastMeasure;
397 		if (elapsed < uinterval)
398 			snooze(uinterval - elapsed);
399 		// then = system_time();
400 		baseline = gather(&baseline, refresh);
401 
402 	} else
403 		baseline = gather(NULL, refresh);
404 
405 	uinterval = interval * 1000000;
406 	for (i = 0; iters < 0 || i < iters; i++) {
407 		elapsed = system_time() - lastMeasure;
408 		if (elapsed < uinterval)
409 			snooze(uinterval - elapsed);
410 		baseline = gather(&baseline, refresh);
411 	}
412 
413 	tputs(exit_ca_mode, 1, putchar);
414 	tputs(restore_cursor, 1, putchar);
415 
416 	exit(0);
417 }
418