/* * ttyload * * tty equivalent to xload * * Copyright 1996-2011 by David Lindes * All rights reserved. * * Version information... stored in git now. Hopefully you're looking * at this file from a git repo, and don't care. :) * */ #include #include #include #include #include #include #include #include #include #include #include "ttyload.h" #define HEIGHTPAD 7 /* 2 lines above; * 4 lines + cursor line below */ #define WIDTHPAD 14 #define CLOCKWIDTH 7 #define HOSTLENGTH 30 #define MINHOSTPAD 20 #define MINROWS (HEIGHTPAD + 6) #define MINCOLS (WIDTHPAD + 6) char strbuf[BUFSIZ], *optstring = "i:hvmr:c:", *usage = "Usage: %s []\n" "\n" " Available options:\n" " -h -- show this help, then exit\n" " -v -- show version info, then exit\n" " -m -- monochrome mode (no ANSI escapes)\n" " -c cols -- how wide is the screen?\n" " -r rows -- and how high?\n" " (these override the default auto-detect)\n" " -i secs\n" " Alter the number of seconds in " "the interval between refreshes.\n" " The default is 4, and the minimum " "is 1, which is silently clamped.\n\n" " (Note: use ctrl-C to quit)\n\n" " For more info, see http://" "www.daveltd.com/src/util/ttyload/\n"; int clockpad, clocks; clock_info *theclocks; /* version ID, passed in by the Makefile: */ char *version = VERSION; char *color_loadstrings[] = { " ", /* blank */ "\033[31m*\033[m", /* one minute average */ "\033[32m*\033[m", /* five minute average */ "\033[33m*\033[m", /* one & five, together */ "\033[34m*\033[m", /* fifteen minute average */ "\033[35m*\033[m", /* one & fifteen, together */ "\033[36m*\033[m", /* five & fifteen, together */ "\033[37m*\033[m" /* one, five & fifteen, together */ }; /* same stuff, same order: */ char *mono_loadstrings[] = { " ", "1", "2", "3", "4", "5", "6", "7" }; /* by default, use color: */ char **loadstrings = color_loadstrings; /* The following two variables should probably be assigned using some sort of real logic, rather than these hard-coded defaults, but the defaults work for now... */ int rows = 40, cols = 80, intsecs = 4, debug = 0, theclock = 0, height, width; /* other globals (ugh, I know, but it's a simple program, that needs re-writing) */ char hostname[HOSTLENGTH + 1]; load_list *loadavgs; int compute_height(load_t, load_t, int); void showloads(load_list *); void clear_screen(); void home_screen(); void cycle_load_list(load_list*, load_list, int); void initialize_load_list(load_list *list, int size); void print_header(int current) { printf("%s %.2f, %.2f, %.2f %s ttyload, v%s\n\n", hostname, (loadavgs[current].one_minute / 1024.), (loadavgs[current].five_minute / 1024.), (loadavgs[current].fifteen_minute / 1024.), strbuf, version); } void update_clocks(time_t thetime, struct tm *thetimetm, int position) { int lastclock = (theclock + clocks - 1) % clocks; /* if we don't have a previous clock, or "enough" time has past to have another */ if(theclocks[lastclock].pos < 0 /* getting-started special */ || (((thetime - theclocks[lastclock].when) >= CLOCKWIDTH * intsecs) && /* enough visual buffer space */ ((thetime / 60) > (theclocks[lastclock].when / 60)))) /* at least a minute (for different value) */ { if(!strftime(strbuf, 7, "^%H:%M", thetimetm)) { /* This should never happen, I hope... */ perror("strftime failed"); exit(1); } /* set up the current clock: */ theclocks[theclock].pos = position; strcpy(theclocks[theclock].clock, strbuf); theclocks[theclock].when = thetime; ++theclock; theclock %= clocks; /* as a temporary cleanup functionality after changing from * clear_screen on every iteration to home_screen on all but * the first, but since it's nice to occasionally actually * clear (at least until we're actually using curses or the * like, when we can put that activity on SIGWINCH and ctrl-L * command, or the like), I'm using the enclosing if() * condition as a "good" time to do that: */ clear_screen(); } } int main(argc, argv, envp) int argc; char *argv[], *envp[]; { load_list newload; int c, i, errflag=0, versflag=0; char *basename; time_t thetime; struct tm *thetimetm; /* set up the basename variable, used for Usage, etc. */ basename = (char *)strrchr(*argv, '/'); if(!basename) basename = *argv; else basename++; /* go past the '/' */ gettermsize(); /* sets our globals: rows, cols */ while((c = getopt(argc, argv, optstring)) != EOF) { switch(c) { /* timing interval: */ case 'i': intsecs = atoi(optarg); break; /* display mode selection: */ case 'm': loadstrings = mono_loadstrings; break; /* height/width stuff... error checking done later. */ case 'r': rows = atoi(optarg); break; case 'c': cols = atoi(optarg); break; /* help and such, plus default: */ case 'v': versflag++; break; case 'h': default: errflag++; break; /* redundant */ } } /* version info requested, show it: */ if(versflag) fprintf(stderr, "%s version %s\n", basename, version); /* error, show usage: */ if(errflag) fprintf(stderr, usage, basename); /* for either, we exit: */ if(errflag || versflag) exit(1); if(gethostname(hostname, HOSTLENGTH)) { perror("NOTICE: couldn't determine hostname"); strcpy(hostname, "localhost"); sleep(2); } /* do space-padding of hostname out to MINHOSTPAD chars: */ for(i = 0; i < (MINHOSTPAD); i++) { if(hostname[i] == '\0') { hostname[i] = ' '; hostname[i + 1] = '\0'; } } if(rows < MINROWS) { fprintf(stderr, "Sorry, %s requires at least %d rows to run.\n", basename, MINROWS); exit(1); } if(cols < MINCOLS) { fprintf(stderr, "Sorry, %s requires at least %d cols to run.\n", basename, MINCOLS); exit(1); } intsecs = MAX(1, intsecs); /* must be positive */ height = rows - HEIGHTPAD - 1; width = cols - WIDTHPAD; clocks = MAX(width/intsecs, width/CLOCKWIDTH); clockpad = (width / clocks) - CLOCKWIDTH; loadavgs = (load_list *)calloc(width, sizeof(load_list)); theclocks = (clock_info *)calloc(clocks, sizeof(clock_info)); initialize_load_list(loadavgs, width); if(!loadavgs) { perror("calloc for loadavgs failed"); exit(1); } if(!theclocks) { perror("calloc for clocks failed"); exit(1); } /* run getload one time before clear_screen, in case it * errors out or something... not that I've seen it do that, * but there's code there for checking stuff. */ getload(&loadavgs[0]); for(i=0;i 3) printf("Load averages: %f, %f, %f\n", loadavgs[i].one_minute / 1024., loadavgs[i].five_minute / 1024., loadavgs[i].fifteen_minute / 1024.); showloads(loadavgs); if(i == (width - 1)) { if(debug > 4) { printf("CYCLING LOAD LIST...\n"); sleep(3); } getload(&newload); cycle_load_list(loadavgs, newload, width); i--; } } return(0); /* not that we get here... right? */ } void showloads(loadavgs) load_list *loadavgs; { load_list min = {LONG_MAX-1, LONG_MAX-1, LONG_MAX-1, 0, 0, 0}, max = {0, 0, 0, 0, 0, 0}; load_t lmin, lmax; float omin, omax; int i, j, k; if(debug>3) { printf("Starting with min set: %ld, %ld, %ld\n", min.one_minute, min.five_minute, min.fifteen_minute); printf("Starting with first set: %ld, %ld, %ld\n", loadavgs[0].one_minute, loadavgs[0].five_minute, loadavgs[0].fifteen_minute); sleep(1); } for(i=0;i9) { printf("Checking for min/max at %d...\n", i); printf("Comparing, for example, %ld <=> %ld\n", min.one_minute, loadavgs[i].one_minute); } min.one_minute = MIN(min.one_minute, loadavgs[i].one_minute); min.five_minute = MIN(min.five_minute, loadavgs[i].five_minute); min.fifteen_minute = MIN(min.fifteen_minute, loadavgs[i].fifteen_minute); max.one_minute = MAX(max.one_minute, loadavgs[i].one_minute); max.five_minute = MAX(max.five_minute, loadavgs[i].five_minute); max.fifteen_minute = MAX(max.fifteen_minute, loadavgs[i].fifteen_minute); } if(debug>3) { printf("Continuing with min set: %ld,%ld,%ld\n", min.one_minute, min.five_minute, min.fifteen_minute ); printf("Continuing with first set: %ld,%ld,%ld\n", loadavgs[0].one_minute, loadavgs[0].five_minute, loadavgs[0].fifteen_minute); sleep(1); printf("MIN Load averages: %f, %f, %f\n", min.one_minute / 1024., min.five_minute / 1024., min.fifteen_minute / 1024.); printf("MAX Load averages: %f, %f, %f\n", max.one_minute / 1024., max.five_minute / 1024., max.fifteen_minute / 1024.); } lmin=MIN(min.one_minute, MIN(min.five_minute, min.fifteen_minute)); lmax=MAX(max.one_minute, MAX(max.five_minute, max.fifteen_minute)); if(debug > 3) printf("Overall MIN, MAX: %f, %f\n", lmin/1024., lmax/1024.); omin = (int)(lmin / 1024); lmin = 1024 * omin; if((lmax / 1024.) < .25) { omax = .25; } else if((lmax / 1024.) < .5) { omax = .5; } else { omax = (int)(lmax / 1024) + 1; } lmax = 1024 * omax; if(debug > 3) { printf("Boundaries: %g, %g... ", omin, omax); printf("Long Boundaries: %ld, %ld\n", lmin, lmax); } for(i=0;i 3) { printf("Load averages: %f, %f, %f -- ", loadavgs[i].one_minute / 1024., loadavgs[i].five_minute / 1024., loadavgs[i].fifteen_minute / 1024.); printf("Heights: %d, %d, %d\n", loadavgs[i].height1, loadavgs[i].height5, loadavgs[i].height15); } } for(j = 0; j <= height; j++) { printf("%6.2f ", (((omax)*(height-j)) / (height)) ); for(i=0;i= 0) { strncpy( &strbuf[9+theclocks[i].pos], theclocks[i].clock, 6); } } /* make sure the string still terminates in the same place: */ strbuf[cols -1] = '\0'; printf("%s\n Legend:\n" " 1 min: %s, 5 min: %s, 15 min: %s\n" " 1&5 same: %s, 1&15: %s, 5&15: %s, all: %s\n", strbuf, loadstrings[1], loadstrings[2], loadstrings[4], loadstrings[3], loadstrings[5], loadstrings[6], loadstrings[7]); } int compute_height(thisload, maxload, height) load_t thisload, maxload; int height; { /* if thisload is negative (done by initialization), return * something impossible, so we don't draw bogus data: */ if(thisload < 0) return(height + 1); /* this is a reversal of what you might think... the X axis * is really on the top of the graph, so larger heights sit * lower. the 'height -' part is implicitly done elsewhere. * (but not really ever actually done) */ return(/* height- */ ( height * ( (maxload - thisload) / (float)maxload ) ) ); } void clear_screen() { printf("\033[H\033[2J"); } void home_screen() { printf("\033[H"); } void cycle_load_list(loadavgs, newload, width) load_list *loadavgs, newload; int width; { /* This function will eventually have code to clear locations on the screen that change. */ int i; for(i=0;i<(width-1);i++) { loadavgs[i] = loadavgs[i+1]; } loadavgs[i] = newload; for(i=0;i