commit bfa11aee1d80d79bd79d1834c43c55f17e4b7394
Author: Antoni Sawicki <as@tenoware.com>
Date: Thu, 11 Oct 2018 02:38:01 -0700
initial commit
Diffstat:
A | README.md | | | 113 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ttyplot.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();
+
+}