ttyplot.c (8593B)
1 // 2 // ttyplot: a realtime plotting utility for terminal with data input from stdin 3 // Copyright (c) 2018 by Antoni Sawicki 4 // Copyright (c) 2019 by Google LLC 5 // Apache License 2.0 6 // 7 8 #include <stdio.h> 9 #include <string.h> 10 #include <stdlib.h> 11 #include <unistd.h> 12 #include <float.h> 13 #include <time.h> 14 #include <curses.h> 15 #include <signal.h> 16 17 #ifdef __OpenBSD__ 18 #include <err.h> 19 #endif 20 21 #define verstring "github.com/tenox7/ttyplot 1.4" 22 23 #ifdef NOACS 24 #define T_HLINE '-' 25 #define T_VLINE '|' 26 #define T_RARR '>' 27 #define T_UARR '^' 28 #define T_LLCR 'L' 29 #else 30 #define T_HLINE ACS_HLINE 31 #define T_VLINE ACS_VLINE 32 #define T_RARR ACS_RARROW 33 #define T_UARR ACS_UARROW 34 #define T_LLCR ACS_LLCORNER 35 #endif 36 37 void usage() { 38 printf("Usage:\n ttyplot [-2] [-r] [-c plotchar] [-s scale] [-m max] [-t title] [-u unit]\n\n" 39 " -2 read two values and draw two plots, the second one is in reverse video\n" 40 " -r rate of a counter (divide value by measured sample interval)\n" 41 " -c character to use for plot line, eg @ # %% . etc\n" 42 " -e character to use for plot error line when value exceeds hardmax (default: e)\n" 43 " -s minimum/initial scale of the plot (can go above if data input has larger value)\n" 44 " -m maximum value, if exceeded draws error line (see -e), plot scale is fixed\n" 45 " -t title of the plot\n" 46 " -u unit displayed beside vertical bar\n\n"); 47 exit(0); 48 } 49 50 void getminmax(int pw, int n, double *values, double *min, double *max, double *avg) { 51 double tot=0; 52 int i=0; 53 54 *min=FLT_MAX; 55 *max=FLT_MIN; 56 tot=FLT_MIN; 57 58 for(i=0; i<pw; i++) { 59 if(values[i]>*max) 60 *max=values[i]; 61 62 if(values[i]<*min) 63 *min=values[i]; 64 65 tot=tot+values[i]; 66 } 67 68 *avg=tot/pw; 69 } 70 71 void draw_axes(int h, int w, int ph, int pw, double max, char *unit) { 72 mvhline(h-3, 2, T_HLINE, pw); 73 mvvline(2, 2, T_VLINE, ph); 74 mvprintw(1, 4, "%.1f %s", max, unit); 75 mvprintw((ph/4)+1, 4, "%.1f %s", max*3/4, unit); 76 mvprintw((ph/2)+1, 4, "%.1f %s", max/2, unit); 77 mvprintw((ph*3/4)+1, 4, "%.1f %s", max/4, unit); 78 mvaddch(h-3, 2+pw, T_RARR); 79 mvaddch(1, 2, T_UARR); 80 mvaddch(h-3, 2, T_LLCR); 81 } 82 83 void draw_line(int x, int ph, int l1, int l2, chtype c1, chtype c2, chtype ce) { 84 if(l1 > l2) { 85 mvvline(ph+1-l1, x, c1, l1-l2 ); 86 mvvline(ph+1-l2, x, c2|A_REVERSE, l2 ); 87 } else if(l1 < l2) { 88 mvvline(ph+1-l2, x, (c2==ce) ? c2|A_REVERSE : ' '|A_REVERSE, l2-l1 ); 89 mvvline(ph+1-l1, x, c1|A_REVERSE, l1 ); 90 } else { 91 mvvline(ph+1-l2, x, c2|A_REVERSE, l2 ); 92 } 93 } 94 95 void plot_values(int h, int w, int ph, int pw, double *v1, double *v2, double max, int n, chtype pc, chtype ce, double hm) { 96 int i; 97 int x=3; 98 99 for(i=n+1; i<pw; i++) 100 draw_line(x++, ph, 101 (v1[i]>hm) ? ph : (int)((v1[i]/max)*(double)ph), 102 (v2[i]>hm) ? ph : (int)((v2[i]/max)*(double)ph), 103 (v1[i]>hm) ? ce : pc, 104 (v2[i]>hm) ? ce : pc, 105 ce); 106 107 for(i=0; i<=n; i++) 108 draw_line(x++, ph, 109 (v1[i]>hm) ? ph : (int)((v1[i]/max)*(double)ph), 110 (v2[i]>hm) ? ph : (int)((v2[i]/max)*(double)ph), 111 (v1[i]>hm) ? ce : pc, 112 (v2[i]>hm) ? ce : pc, 113 ce); 114 } 115 116 void resize(int sig) { 117 endwin(); 118 refresh(); 119 } 120 121 void finish(int sig) { 122 curs_set(FALSE); 123 echo(); 124 refresh(); 125 endwin(); 126 exit(0); 127 } 128 129 int main(int argc, char *argv[]) { 130 double values1[1024]={0}; 131 double values2[1024]={0}; 132 double cval1=FLT_MAX, pval1=FLT_MAX; 133 double cval2=FLT_MAX, pval2=FLT_MAX; 134 double min1=FLT_MAX, max1=FLT_MIN, avg1=0; 135 double min2=FLT_MAX, max2=FLT_MIN, avg2=0; 136 int n=0; 137 int r=0; 138 int width=0, height=0; 139 int plotwidth=0, plotheight=0; 140 time_t t1,t2,td; 141 struct tm *lt; 142 int c; 143 chtype plotchar=T_VLINE, errchar='e'; 144 double max=FLT_MIN; 145 double softmax=FLT_MIN; 146 double hardmax=FLT_MAX; 147 char title[256]=".: ttyplot :."; 148 char unit[64]={0}; 149 char ls[256]={0}; 150 int rate=0; 151 int two=0; 152 153 opterr=0; 154 while((c=getopt(argc, argv, "2rc:c:e:s:m:t:u:")) != -1) 155 switch(c) { 156 case 'r': 157 rate=1; 158 break; 159 case '2': 160 two=1; 161 plotchar='|'; 162 break; 163 case 'c': 164 plotchar=optarg[0]; 165 break; 166 case 'e': 167 errchar=optarg[0]; 168 break; 169 case 's': 170 softmax=atof(optarg); 171 break; 172 case 'm': 173 hardmax=atof(optarg); 174 break; 175 case 't': 176 snprintf(title, sizeof(title), "%s", optarg); 177 break; 178 case 'u': 179 snprintf(unit, sizeof(unit), "%s", optarg); 180 break; 181 case '?': 182 usage(); 183 break; 184 } 185 186 initscr(); /* uses filesystem, so before pledge */ 187 188 #ifdef __OpenBSD__ 189 if (pledge("stdio tty", NULL) == -1) 190 err(1, "pledge"); 191 #endif 192 193 time(&t1); 194 noecho(); 195 curs_set(FALSE); 196 signal(SIGWINCH, (void*)resize); 197 signal(SIGINT, (void*)finish); 198 199 erase(); 200 refresh(); 201 #ifdef NOGETMAXYX 202 height=LINES; 203 width=COLS; 204 #else 205 getmaxyx(stdscr, height, width); 206 #endif 207 mvprintw(height/2, (width/2)-14, "waiting for data from stdin"); 208 refresh(); 209 210 while(1) { 211 if(two) 212 r=scanf("%lf %lf", &values1[n], &values2[n]); 213 else 214 r=scanf("%lf", &values1[n]); 215 if(r==0) { 216 while(getchar()!='\n'); 217 continue; 218 } 219 else if(r<0) { 220 break; 221 } 222 223 if(values1[n] < 0) 224 values1[n] = 0; 225 if(values2[n] < 0) 226 values2[n] = 0; 227 228 if(rate) { 229 t2=t1; 230 time(&t1); 231 td=t1-t2; 232 if(td==0) 233 td=1; 234 235 if(cval1==FLT_MAX) 236 pval1=values1[n]; 237 else 238 pval1=cval1; 239 cval1=values1[n]; 240 241 values1[n]=(cval1-pval1)/td; 242 243 if(values1[n] < 0) // counter rewind 244 values1[n]=0; 245 246 if(two) { 247 if(cval2==FLT_MAX) 248 pval2=values2[n]; 249 else 250 pval2=cval2; 251 cval2=values2[n]; 252 253 values2[n]=(cval2-pval2)/td; 254 255 if(values2[n] < 0) // counter rewind 256 values2[n]=0; 257 } 258 } else { 259 time(&t1); 260 } 261 262 erase(); 263 #ifdef _AIX 264 refresh(); 265 #endif 266 #ifdef NOGETMAXYX 267 height=LINES; 268 width=COLS; 269 #else 270 getmaxyx(stdscr, height, width); 271 #endif 272 plotheight=height-4; 273 plotwidth=width-4; 274 if(plotwidth>=(sizeof(values1)/sizeof(double))-1) 275 return 0; 276 277 getminmax(plotwidth, n, values1, &min1, &max1, &avg1); 278 getminmax(plotwidth, n, values2, &min2, &max2, &avg2); 279 280 if(max1>max2) 281 max=max1; 282 else 283 max=max2; 284 285 if(max<softmax) 286 max=softmax; 287 if(hardmax!=FLT_MAX) 288 max=hardmax; 289 290 mvprintw(height-1, width-sizeof(verstring)/sizeof(char), verstring); 291 292 lt=localtime(&t1); 293 #ifdef __sun 294 asctime_r(lt, ls, sizeof(ls)); 295 #else 296 asctime_r(lt, ls); 297 #endif 298 mvprintw(height-2, width-strlen(ls), "%s", ls); 299 300 #ifdef _AIX 301 mvaddch(height-2, 5, plotchar); 302 #else 303 mvvline(height-2, 5, plotchar|A_NORMAL, 1); 304 #endif 305 mvprintw(height-2, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ", values1[n], min1, max1, avg1, unit); 306 if(rate) 307 printw(" interval=%ds", td); 308 309 if(two) { 310 mvaddch(height-1, 5, ' '|A_REVERSE); 311 mvprintw(height-1, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ", values2[n], min2, max2, avg2, unit); 312 } 313 314 plot_values(height, width, plotheight, plotwidth, values1, values2, max, n, plotchar, errchar, hardmax); 315 316 draw_axes(height, width, plotheight, plotwidth, max, unit); 317 318 mvprintw(0, (width/2)-(strlen(title)/2), "%s", title); 319 320 if(n<(plotwidth)-1) 321 n++; 322 else 323 n=0; 324 325 move(0,0); 326 refresh(); 327 } 328 329 endwin(); 330 return 0; 331 }