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
25static const char IDLE_NAME[] = "idle thread ";
26static bigtime_t lastMeasure = 0;
27
28/*
29 * Keeps track of a single thread's times
30 */
31struct 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 */
48typedef std::list<ThreadTime> ThreadTimeList;
49
50
51static char *clear_string; /*output string for clearing the screen */
52static char *enter_ca_mode; /* output string for switching screen buffer */
53static char *exit_ca_mode; /* output string for releasing screen buffer */
54static char *cursor_home;	/* Places cursor back to (1,1) */
55static char *restore_cursor;
56static char *save_cursor;
57static int columns;	/* Columns on screen */
58static int rows;	/* how many rows on the screen */
59static int screen_size_changed = 0;	/* tells to refresh the screen size */
60static int cpus;	/* how many cpus we are runing on */
61
62/* SIGWINCH handler */
63static void
64winch_handler(int notused)
65{
66	(void) notused;
67	screen_size_changed = 1;
68}
69
70
71/* SIGINT handler */
72static void
73sigint_handler(int)
74{
75	tputs(exit_ca_mode, 1, putchar);
76	tputs(restore_cursor, 1, putchar);
77	exit(1);
78}
79
80
81static void
82init_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 */
106static float
107cpu_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 */
116static void
117compare(
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
258static int
259adjust_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 */
281static ThreadTimeList
282gather(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 */
320static void
321usage(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
335int
336main(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