ttyplot

Realtime terminal plotter
git clone git://git.sgregoratto.me/ttyplot
Log | Files | Refs | README | LICENSE

commit bfa11aee1d80d79bd79d1834c43c55f17e4b7394
Author: Antoni Sawicki <as@tenoware.com>
Date:   Thu, 11 Oct 2018 02:38:01 -0700

initial commit

Diffstat:
AREADME.md | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Attyplot.c | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 362 insertions(+), 0 deletions(-)

diff --git a/README.md b/README.md @@ -0,0 +1,113 @@ +ttyplot +======= +a simple general purpose plotting utility for tty with data input from stdin + +takes data from stdin, most commonly unix pipeline and plots in text mode on a terminal or console, +supports rate calculation for counters and up to two plos on a single display using reverse video for second line + +``` + ping to 8.8.8.8 + ^ 23 ms + | # + | # + | 17 ms # + | # + | # # # # + | 12 ms # # # # + | # # # # + | ## ## ## # ## ## # # # # # # # # # ## # # # # # # # + |#6 ms################################################################################### + |######################################################################################## + |######################################################################################## + +---------------------------------------------------------------------------------------> + last=9 min=8 max=23 avg=9 ms +``` + +usage examples +============== + +### cpu usage from vmstat +``` +vmstat -n 1 | gawk '{ print 100-int($(NF-2)); fflush(); }' | ttyplot +``` + +### cpu usage from sar with title and fixed scale to 100% +``` +sar 1 | gawk '{ print 100-int($NF); fflush(); }' | ttyplot -s 100 -t "cpu usage" -u "%" +``` + +### ping plot +``` +ping 8.8.8.8 | gawk '{ gsub(/time=/,"",$(NF-1)); print $(NF-1); fflush(); }' | ttyplot -t "ping to 8.8.8.8" -u ms +``` + +### local network throughput for all interfaces (in/out combined) +``` +sar -n DEV 1 | gawk '{ if($6 ~ /rxkB/) { print tot; tot=0; fflush(); } tot=tot+$6+$7 }' | ttyplot -u "kB/s" +``` + +### snmp network throughput for an interface using [ttg](https://github.com/tenox7/ttg) and two line plot +``` +ttg -i 10 -u Mb 10.23.73.254 public 9 | gawk '{ print $5,$8; fflush(); } | ttyplot -2 -u Mb/s +``` + +### snmp network throughput for an interface using snmpdelta with two line plot +``` +snmpdelta -v 2c -c public -Cp 10 10.23.73.254 1.3.6.1.2.1.2.2.1.10.9 1.3.6.1.2.1.2.2.1.16.9 | gawk '{ print $NF/1000/1000/10; fflush(); }' | ttyplot -t "ifindex 9 throughput" -u Mb/s +``` + +### remote node load via prometheus node exporter +``` +{ while true; do curl -s http://10.4.7.180:9100/metrics | gawk '/^node_load1 / { print $2; fflush(); }'; sleep 1; done } | ttyplot +``` + +### bitcoin price chart +``` +{ while true; do curl -sL https://coinbase.com/api/v1/prices/historical | head -1 | cut -d, -f2 ; sleep 600; done } | ttyplot -t "bitcoin price" -u usd +``` + +### CPU temperature +``` +{ while true; do gawk '{ printf("%.1f\n", $1/1000); fflush(); }' /sys/class/thermal/thermal_zone0/temp; sleep 1; done } | ttyplot -t "cpu temp" -u C +``` + +rate calculator for counters +============================ + +### snmpget counter rate for interface in MB/s using two plot lines +``` +{ while true; do snmpget -v 2c -c public 10.23.73.254 1.3.6.1.2.1.2.2.1.10.9 1.3.6.1.2.1.2.2.1.16.9 | gawk '{ print $NF/1000/1000; fflush(); }'; sleep 10; done } | ttyplot -2 -r -u "MB/s" +``` + +### prometheus node exporter disk write MB/s for sda device +``` +{ while true; do curl -s http://10.11.0.173:9100/metrics | gawk '/^node_disk_written_bytes_total{device="sda"}/ { printf("%f\n", $2/1024/1024); fflush(); }'; sleep 1; done } | ttyplot -r -u MB/s -t "10.11.0.173 sda writes" +``` + + +options +======= + +``` +ttyplot [-r] [-c plotchar] [-s softmax] [-m hardmax] [-t title] [-u unit] + +-2 read two values and draw two plots, the second one is in reveverse video + +-r calculate counter rate and divide by measured sample interval + +-c character to use for plot line, eg @ # % . etc + +-s softmax is an initial maximum value that can grow if data input has larger value + +-m hardmax is a hard maximum value that can never grow, + if data input has larger value the plot line will not be drawn + +-t title of the plot + +-u unit displayed beside vertical bar +``` + + +stdio buffering +=============== +by default stdio is buffered, you can work around it in [various ways](http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html) diff --git a/ttyplot.c b/ttyplot.c @@ -0,0 +1,249 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <float.h> +#include <time.h> +#include <ncurses.h> +#include <signal.h> + +#define verstring "github.com/tenox7/ttyplot 1.0" + +int usage() { + printf("Usage:\n asplot [-2] [-r] [-c plotchar] [-s softmax] [-m hardmax] [-t title] [-u unit]\n\n" + "-2 read two values and draw two plots, the second one is in reveverse video\n\n" + "-r calculate counter rate and divide by measured sample interval\n\n" + "-c character to use for plot line, eg @ # %% . etc\n\n" + "-s softmax is an initial maximum value that can grow if data input has larger value\n\n" + "-m hardmax is a hard maximum value that can never grow, \n" + " if data input has larger value the plot line will not be drawn\n\n" + "-t title of the plot\n\n" + "-u unit displayed beside vertical bar\n\n"); + exit(0); +} + +int getminmax(int pw, int n, double *values, double *min, double *max, double *avg) { + double tot=0; + int i=0; + + *min=FLT_MAX; + *max=FLT_MIN; + tot=FLT_MIN; + + for(i=0; i<pw; i++) + if(values[i]>*max) + *max=values[i]; + + for(i=0; i<pw; i++) + if(values[i]<*min) + *min=values[i]; + + for(i=0; i<pw; i++) + tot=tot+values[i]; + + *avg=tot/pw; +} + +int draw_axes(int h, int w, int ph, int pw, double max, char *unit) { + mvhline(h-3, 2, ACS_HLINE, pw); + mvaddch(h-3, 2+pw, ACS_RARROW); + + mvvline(2, 2, ACS_VLINE, ph); + mvaddch(1, 2, ACS_UARROW); + + mvaddch(h-3, 2, ACS_LLCORNER); + + mvprintw(1, 4, "%.1f %s", max, unit); + mvprintw((ph/4)+1, 4, "%.1f %s", max*3/4, unit); + mvprintw((ph/2)+1, 4, "%.1f %s", max/2, unit); + mvprintw((ph*3/4)+1, 4, "%.1f %s", max/4, unit); +} + +int draw_line(int ph, int l1, int l2, int x, chtype plotchar) { + if(l1 > l2) { + mvvline(ph+1-l1, x, plotchar, l1-l2 ); + mvvline(ph+1-l2, x, plotchar|A_REVERSE, l2 ); + } else if(l1 < l2) { + mvvline(ph+1-l2, x, ' '|A_REVERSE, l2-l1 ); + mvvline(ph+1-l1, x, plotchar|A_REVERSE, l1 ); + } else { + mvvline(ph+1-l2, x, plotchar|A_REVERSE, l2 ); + } + +} + + +int draw_values(int h, int w, int ph, int pw, double *v1, double *v2, double max, int n, chtype plotchar) { + int i; + int x=3; + int l1=0, l2=0; + + for(i=n+1; i<pw; i++) { + l1=(((int)v1[i]/max)*ph); + l2=(((int)v2[i]/max)*ph); + draw_line(ph, l1, l2, x++, plotchar); + } + + for(i=0; i<=n; i++) { + l1=(((int)v1[i]/max)*ph); + l2=(((int)v2[i]/max)*ph); + draw_line(ph, l1, l2, x++, plotchar); + } + +} + +int resize(int sig) { + endwin(); + refresh(); +} + +int main(int argc, char *argv[]) { + double values1[1024]={0}; + double values2[1024]={0}; + double cval1=FLT_MAX, pval1=FLT_MAX; + double cval2=FLT_MAX, pval2=FLT_MAX; + double min1=FLT_MAX, max1=FLT_MIN, avg1=0; + double min2=FLT_MAX, max2=FLT_MIN, avg2=0; + int n=0; + int r=0; + int width=0, height=0; + int plotwidth=0, plotheight=0; + time_t t1,t2,td; + int c; + chtype plotchar=ACS_VLINE; + double max=FLT_MIN; + double softmax=FLT_MIN; + double hardmax=FLT_MIN; + char title[256]={0}; + char unit[64]={0}; + int rate=0; + int two=0; + + + opterr=0; + while((c=getopt(argc, argv, "2rc:s:m:t:u:")) != -1) + switch(c) { + case 'r': + rate=1; + break; + case '2': + two=1; + plotchar='|'; + break; + case 'c': + plotchar=optarg[0]; + break; + case 's': + softmax=atof(optarg); + break; + case 'm': + hardmax=atof(optarg); + break; + case 't': + snprintf(title, sizeof(title), "%s", optarg); + break; + case 'u': + snprintf(unit, sizeof(unit), "%s", optarg); + break; + case '?': + usage(); + break; + } + + time(&t1); + initscr(); + noecho(); + curs_set(FALSE); + signal(SIGWINCH, (void*)resize); + + while(1) { + if(two) + r=scanf("%lf %lf", &values1[n], &values2[n]); + else + r=scanf("%lf", &values1[n]); + if(r==0) { + while(getchar()!='\n'); + continue; + } + else if(r<0) { + break; + } + + if(rate) { + t2=t1; + time(&t1); + td=t1-t2; + if(td==0) + td=1; + + if(cval1==FLT_MAX) + pval1=values1[n]; + else + pval1=cval1; + cval1=values1[n]; + + values1[n]=(cval1-pval1)/td; + + if(two) { + if(cval2==FLT_MAX) + pval2=values2[n]; + else + pval2=cval2; + cval2=values2[n]; + + values2[n]=(cval2-pval2)/td; + } + } + + erase(); + getmaxyx(stdscr, height, width); + plotheight=height-4; + plotwidth=width-4; + if(plotwidth>=(sizeof(values1)/sizeof(double))-1) + return 0; + + + getminmax(plotwidth, n, values1, &min1, &max1, &avg1); + getminmax(plotwidth, n, values2, &min2, &max2, &avg2); + + if(max1>max2) + max=max1; + else + max=max2; + + if(max<softmax) + max=softmax; + if(hardmax!=FLT_MIN) + max=hardmax; + + mvprintw(height-1, width-sizeof(verstring)/sizeof(char), verstring); + + mvvline(height-2, 5, plotchar|A_NORMAL, 1); + mvprintw(height-2, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ", values1[n], min1, max1, avg1, unit); + if(rate) + printw(" interval=%ds", td); + + if(two) { + mvchgat(height-1, 5, 1, A_REVERSE, 0, NULL); + mvprintw(height-1, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ", values2[n], min2, max2, avg2, unit); + } + + draw_values(height, width, plotheight, plotwidth, values1, values2, max, n, plotchar); + + draw_axes(height, width, plotheight, plotwidth, max, unit); + + mvprintw(0, (width/2)-(strlen(title)/2), "%s", title); + + + if(n<(plotwidth)-1) + n++; + else + n=0; + + move(0,0); + refresh(); + } + + endwin(); + +}