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