1 /*
2  * ttyload
3  *
4  * tty equivalent to xload
5  *
6  * Copyright 1996-2011 by David Lindes
7  * All rights reserved.
8  *
9  * Version information... stored in git now.  Hopefully you're looking
10  * at this file from a git repo, and don't care.  :)
11  *
12  */
13 
14 #include <limits.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/fcntl.h>
19 #include <sys/stat.h>
20 #include <time.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 #include <time.h>
24 
25 #include "ttyload.h"
26 
27 #define	HEIGHTPAD	7	/* 2 lines above;
28 				 * 4 lines + cursor line below */
29 #define	WIDTHPAD	14
30 #define	CLOCKWIDTH	7
31 #define	HOSTLENGTH	30
32 #define MINHOSTPAD	20
33 
34 #define	MINROWS		(HEIGHTPAD + 6)
35 #define	MINCOLS		(WIDTHPAD + 6)
36 
37 char		strbuf[BUFSIZ],
38 		*optstring	= "i:hvmr:c:",
39 		*usage	=
40 		    "Usage: %s [<options>]\n"
41 		    "\n"
42 		    " Available options:\n"
43 		    "  -h -- show this help, then exit\n"
44 		    "  -v -- show version info, then exit\n"
45 		    "  -m -- monochrome mode (no ANSI escapes)\n"
46 		    "  -c cols -- how wide is the screen?\n"
47 		    "  -r rows -- and how high?\n"
48 		    "     (these override the default auto-detect)\n"
49 		    "  -i secs\n"
50 		    "     Alter the number of seconds in "
51 			"the interval between refreshes.\n"
52 		    "     The default is 4, and the minimum "
53 			"is 1, which is silently clamped.\n\n"
54 		    "  (Note: use ctrl-C to quit)\n\n"
55 		    "  For more info, see http://"
56 		    	"www.daveltd.com/src/util/ttyload/\n";
57 int		clockpad, clocks;
58 clock_info	*theclocks;
59 
60 /* version ID, passed in by the Makefile: */
61 char		*version	= VERSION;
62 
63 char *color_loadstrings[] = {
64 	" ",	/* blank */
65 	"\033[31m*\033[m",	/* one minute average */
66 	"\033[32m*\033[m",	/* five minute average */
67 	"\033[33m*\033[m",	/* one & five, together */
68 	"\033[34m*\033[m",	/* fifteen minute average */
69 	"\033[35m*\033[m",	/* one & fifteen, together */
70 	"\033[36m*\033[m",	/* five & fifteen, together */
71 	"\033[37m*\033[m"	/* one, five & fifteen, together */
72     };
73 
74 /* same stuff, same order: */
75 char *mono_loadstrings[] = { " ", "1", "2", "3", "4", "5", "6", "7" };
76 
77 /* by default, use color: */
78 char **loadstrings = color_loadstrings;
79 
80 /* The following two variables should probably be assigned
81    using some sort of real logic, rather than these hard-coded
82    defaults, but the defaults work for now... */
83 int	rows		= 40,
84 	cols		= 80,
85 
86 	intsecs		= 4,
87 	debug		= 0,
88 	theclock	= 0,
89 
90 	height, width;
91 
92 /* other globals (ugh, I know, but it's a simple program, that needs re-writing) */
93 char	hostname[HOSTLENGTH + 1];
94 load_list	*loadavgs;
95 
96 int	compute_height(load_t, load_t, int);
97 void	showloads(load_list *);
98 void	clear_screen();
99 void	home_screen();
100 void	cycle_load_list(load_list*, load_list, int);
101 void	initialize_load_list(load_list *list, int size);
102 
print_header(int current)103 void print_header(int current)
104 {
105 	printf("%s   %.2f, %.2f, %.2f   %s       ttyload, v%s\n\n",
106 		hostname,
107 		(loadavgs[current].one_minute / 1024.),
108 		(loadavgs[current].five_minute / 1024.),
109 		(loadavgs[current].fifteen_minute / 1024.),
110 		strbuf,
111 		version);
112 }
113 
update_clocks(time_t thetime,struct tm * thetimetm,int position)114 void update_clocks(time_t thetime, struct tm *thetimetm, int position)
115 {
116     int lastclock = (theclock + clocks - 1) % clocks;
117 
118     /* if we don't have a previous clock, or "enough" time has past to have another */
119     if(theclocks[lastclock].pos < 0 /* getting-started special */ ||
120        (((thetime - theclocks[lastclock].when) >= CLOCKWIDTH * intsecs) && /* enough visual buffer space */
121         ((thetime / 60) > (theclocks[lastclock].when / 60)))) /* at least a minute (for different value) */
122     {
123         if(!strftime(strbuf, 7, "^%H:%M", thetimetm))
124         {
125             /* This should never happen, I hope... */
126             perror("strftime failed");
127             exit(1);
128         }
129 
130         /* set up the current clock: */
131         theclocks[theclock].pos	= position;
132         strcpy(theclocks[theclock].clock, strbuf);
133         theclocks[theclock].when = thetime;
134 
135         ++theclock;
136         theclock	%= clocks;
137 
138         /* as a temporary cleanup functionality after changing from
139          * clear_screen on every iteration to home_screen on all but
140          * the first, but since it's nice to occasionally actually
141          * clear (at least until we're actually using curses or the
142          * like, when we can put that activity on SIGWINCH and ctrl-L
143          * command, or the like), I'm using the enclosing if()
144          * condition as a "good" time to do that: */
145         clear_screen();
146     }
147 }
148 
main(argc,argv,envp)149 int main(argc, argv, envp)
150     int		argc;
151     char	*argv[],
152 		*envp[];
153 {
154     load_list	newload;
155     int		c, i, errflag=0, versflag=0;
156     char	*basename;
157     time_t	thetime;
158     struct tm	*thetimetm;
159 
160     /* set up the basename variable, used for Usage, etc. */
161     basename		= (char *)strrchr(*argv, '/');
162     if(!basename)
163 	basename	= *argv;
164     else
165 	basename++;	/* go past the '/' */
166 
167     gettermsize();	/* sets our globals: rows, cols */
168 
169     while((c = getopt(argc, argv, optstring)) != EOF)
170     {
171 	switch(c)
172 	{
173 	    /* timing interval: */
174 	    case 'i':
175 		intsecs	= atoi(optarg);
176 		break;
177 
178 	    /* display mode selection: */
179 	    case 'm':
180 	    	loadstrings	= mono_loadstrings;
181 		break;
182 
183 	    /* height/width stuff...  error checking done later.  */
184 	    case 'r':
185 	    	rows	= atoi(optarg);
186 		break;
187 	    case 'c':
188 	    	cols	= atoi(optarg);
189 		break;
190 
191 	    /* help and such, plus default: */
192 	    case 'v':
193 	    	versflag++;
194 	    	break;
195 	    case 'h':
196 	    default:
197 		errflag++;
198 		break;	/* redundant */
199 	}
200     }
201 
202     /* version info requested, show it: */
203     if(versflag)
204 	fprintf(stderr, "%s version %s\n", basename, version);
205     /* error, show usage: */
206     if(errflag)
207 	fprintf(stderr, usage, basename);
208     /* for either, we exit: */
209     if(errflag || versflag)
210 	exit(1);
211 
212     if(gethostname(hostname, HOSTLENGTH))
213     {
214 	perror("NOTICE: couldn't determine hostname");
215 	strcpy(hostname, "localhost");
216 	sleep(2);
217     }
218 
219     /* do space-padding of hostname out to MINHOSTPAD chars: */
220     for(i = 0; i < (MINHOSTPAD); i++)
221     {
222     	if(hostname[i] == '\0')
223 	{
224 	    hostname[i]		= ' ';
225 	    hostname[i + 1]	= '\0';
226 	}
227     }
228 
229     if(rows < MINROWS)
230     {
231 	fprintf(stderr, "Sorry, %s requires at least %d rows to run.\n",
232 		basename, MINROWS);
233 	exit(1);
234     }
235     if(cols < MINCOLS)
236     {
237 	fprintf(stderr, "Sorry, %s requires at least %d cols to run.\n",
238 		basename, MINCOLS);
239 	exit(1);
240     }
241 
242     intsecs	= MAX(1, intsecs);	/* must be positive */
243     height	= rows - HEIGHTPAD - 1;
244     width	= cols - WIDTHPAD;
245     clocks	= MAX(width/intsecs, width/CLOCKWIDTH);
246     clockpad	= (width / clocks) - CLOCKWIDTH;
247 
248     loadavgs	= (load_list *)calloc(width, sizeof(load_list));
249     theclocks	= (clock_info *)calloc(clocks, sizeof(clock_info));
250 
251     initialize_load_list(loadavgs, width);
252 
253     if(!loadavgs)
254     {
255 	perror("calloc for loadavgs failed");
256 	exit(1);
257     }
258 
259     if(!theclocks)
260     {
261 	perror("calloc for clocks failed");
262 	exit(1);
263     }
264 
265     /* run getload one time before clear_screen, in case it
266      * errors out or something... not that I've seen it do that,
267      * but there's code there for checking stuff. */
268     getload(&loadavgs[0]);
269 
270     for(i=0;i<clocks;i++)
271     {
272 	theclocks[i].pos	= -1;
273     }
274 
275     clear_screen();
276 
277     for(i=0; i<width; ++i /* note: gets decremented, too; this is forever! */)
278     {
279 	if(i != 0)
280 	{
281 	    sleep(intsecs);
282 	}
283 
284 	time(&thetime);
285 
286 	thetimetm	= localtime(&thetime);
287 
288 	getload(&loadavgs[i]);
289 
290         update_clocks(thetime, thetimetm, i);
291 
292 	if(!strftime(strbuf, 9, "%H:%M:%S", thetimetm))
293 	{
294 	    /* This should never happen, I hope... */
295 	    perror("strftime failed");
296 	    exit(1);
297 	}
298 
299 	home_screen();
300         print_header(i);
301 
302 	if(debug > 3)
303 	    printf("Load averages: %f, %f, %f\n",
304 		    loadavgs[i].one_minute / 1024.,
305 		    loadavgs[i].five_minute / 1024.,
306 		    loadavgs[i].fifteen_minute / 1024.);
307 
308 	showloads(loadavgs);
309 
310 	if(i == (width - 1))
311 	{
312 	    if(debug > 4)
313 	    {
314 		printf("CYCLING LOAD LIST...\n");
315 		sleep(3);
316 	    }
317 	    getload(&newload);
318 	    cycle_load_list(loadavgs, newload, width);
319 	    i--;
320 	}
321     }
322 
323     return(0);	/* not that we get here...  right? */
324 }
325 
showloads(loadavgs)326 void	showloads(loadavgs)
327     load_list	*loadavgs;
328 {
329     load_list	min	= {LONG_MAX-1, LONG_MAX-1, LONG_MAX-1, 0, 0, 0},
330 		max	= {0, 0, 0, 0, 0, 0};
331     load_t	lmin, lmax;
332     float	omin, omax;
333     int		i, j, k;
334 
335     if(debug>3)
336     {
337 	printf("Starting with min set: %ld, %ld, %ld\n",
338 		min.one_minute,
339 		min.five_minute,
340 		min.fifteen_minute);
341 	printf("Starting with first set: %ld, %ld, %ld\n",
342 		loadavgs[0].one_minute,
343 		loadavgs[0].five_minute,
344 		loadavgs[0].fifteen_minute);
345 	sleep(1);
346     }
347     for(i=0;i<width;i++)
348     {
349 	if(debug>9)
350 	{
351 	    printf("Checking for min/max at %d...\n", i);
352 	    printf("Comparing, for example, %ld <=> %ld\n",
353 		    min.one_minute, loadavgs[i].one_minute);
354 	}
355 	min.one_minute	= MIN(min.one_minute, loadavgs[i].one_minute);
356 	min.five_minute	= MIN(min.five_minute, loadavgs[i].five_minute);
357 	min.fifteen_minute
358 		= MIN(min.fifteen_minute, loadavgs[i].fifteen_minute);
359 	max.one_minute	= MAX(max.one_minute, loadavgs[i].one_minute);
360 	max.five_minute	= MAX(max.five_minute, loadavgs[i].five_minute);
361 	max.fifteen_minute
362 		= MAX(max.fifteen_minute, loadavgs[i].fifteen_minute);
363     }
364     if(debug>3)
365     {
366 	printf("Continuing with min set: %ld,%ld,%ld\n",
367 		min.one_minute,
368 		min.five_minute,
369 		min.fifteen_minute
370 	    );
371 	printf("Continuing with first set: %ld,%ld,%ld\n",
372 		loadavgs[0].one_minute,
373 		loadavgs[0].five_minute,
374 		loadavgs[0].fifteen_minute);
375 	sleep(1);
376 	printf("MIN Load averages: %f, %f, %f\n",
377 		min.one_minute / 1024.,
378 		min.five_minute / 1024.,
379 		min.fifteen_minute / 1024.);
380 	printf("MAX Load averages: %f, %f, %f\n",
381 		max.one_minute / 1024.,
382 		max.five_minute / 1024.,
383 		max.fifteen_minute / 1024.);
384     }
385     lmin=MIN(min.one_minute, MIN(min.five_minute, min.fifteen_minute));
386     lmax=MAX(max.one_minute, MAX(max.five_minute, max.fifteen_minute));
387 
388     if(debug > 3)
389 	printf("Overall MIN, MAX: %f, %f\n", lmin/1024., lmax/1024.);
390 
391     omin	= (int)(lmin / 1024);
392     lmin	= 1024 * omin;
393 
394     if((lmax / 1024.) < .25)
395     {
396 	omax	= .25;
397     }
398     else if((lmax / 1024.) < .5)
399     {
400 	omax	= .5;
401     }
402     else
403     {
404 	omax	= (int)(lmax / 1024) + 1;
405     }
406     lmax	= 1024 * omax;
407 
408     if(debug > 3)
409     {
410 	printf("Boundaries: %g, %g...  ", omin, omax);
411 	printf("Long Boundaries: %ld, %ld\n", lmin, lmax);
412     }
413 
414 
415     for(i=0;i<width;i++)
416     {
417 	loadavgs[i].height1 =
418 	compute_height(loadavgs[i].one_minute, lmax, height);
419 	loadavgs[i].height5 =
420 	compute_height(loadavgs[i].five_minute, lmax, height);
421 	loadavgs[i].height15 =
422 	compute_height(loadavgs[i].fifteen_minute, lmax, height);
423 
424 	if(debug > 3)
425 	{
426 	    printf("Load averages: %f, %f, %f  -- ",
427 		    loadavgs[i].one_minute / 1024.,
428 		    loadavgs[i].five_minute / 1024.,
429 		    loadavgs[i].fifteen_minute / 1024.);
430 	    printf("Heights: %d, %d, %d\n",
431 		    loadavgs[i].height1,
432 		    loadavgs[i].height5,
433 		    loadavgs[i].height15);
434 	}
435     }
436 
437     for(j = 0; j <= height; j++)
438     {
439 	printf("%6.2f   ",
440 		(((omax)*(height-j)) / (height))
441 	    );
442 	for(i=0;i<width;i++)
443 	{
444 	    k=0;
445 	    if(loadavgs[i].height1	== j)
446 		k+=ONE;
447 	    if(loadavgs[i].height5	== j)
448 		k+=FIVE;
449 	    if(loadavgs[i].height15	== j)
450 		k+=FIFTEEN;
451 	    printf("%s", loadstrings[k]);
452 	}
453 	printf("\n");
454     }
455 
456     memset(strbuf, '\0', BUFSIZ);
457     memset(strbuf, ' ', cols - 1);
458 
459     for(i=0;i<clocks;i++)
460     {
461 	if(theclocks[i].pos >= 0)
462 	{
463 	    strncpy(
464 		    &strbuf[9+theclocks[i].pos],
465 		    theclocks[i].clock,
466 		    6);
467 	}
468     }
469 
470     /* make sure the string still terminates in the same place: */
471     strbuf[cols -1]	= '\0';
472 
473     printf("%s\n  Legend:\n"
474 	   "     1 min: %s, 5 min: %s, 15 min: %s\n"
475 	   "     1&5 same: %s, 1&15: %s, 5&15: %s, all: %s\n",
476 	    strbuf,
477 	    loadstrings[1],
478 	    loadstrings[2],
479 	    loadstrings[4],
480 	    loadstrings[3],
481 	    loadstrings[5],
482 	    loadstrings[6],
483 	    loadstrings[7]);
484 }
485 
compute_height(thisload,maxload,height)486 int	compute_height(thisload, maxload, height)
487     load_t	thisload,
488 		maxload;
489     int		height;
490 {
491     /* if thisload is negative (done by initialization), return
492      * something impossible, so we don't draw bogus data: */
493     if(thisload < 0)
494     	return(height + 1);
495 
496     /* this is a reversal of what you might think... the X axis
497      * is really on the top of the graph, so larger heights sit
498      * lower.  the 'height -' part is implicitly done elsewhere.
499      * (but not really ever actually done) */
500     return(/* height- */ ( height * ( (maxload - thisload) / (float)maxload ) ) );
501 }
502 
clear_screen()503 void	clear_screen()
504 {
505     printf("\033[H\033[2J");
506 }
507 
home_screen()508 void	home_screen()
509 {
510     printf("\033[H");
511 }
512 
cycle_load_list(loadavgs,newload,width)513 void	cycle_load_list(loadavgs, newload, width)
514     load_list	*loadavgs,
515 		newload;
516     int		width;
517 {
518     /* This function will eventually have code to
519        clear locations on the screen that change. */
520 
521     int i;
522 
523     for(i=0;i<(width-1);i++)
524     {
525 	loadavgs[i]	= loadavgs[i+1];
526     }
527     loadavgs[i]	= newload;
528 
529     for(i=0;i<clocks;i++)
530     {
531 	theclocks[i].pos--;
532     }
533 }
534 
initialize_load_list(load_list * list,int size)535 void	initialize_load_list(load_list *list, int size)
536 {
537     int		i;
538 
539     for(i = width; i-- ;)
540     {
541     	list[i].one_minute	= -1;
542     	list[i].five_minute	= -1;
543     	list[i].fifteen_minute	= -1;
544     }
545 }
546