1 /* 2 * Copyright (c) 2019 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@backplane.com> 6 * 7 * This code uses concepts and configuration based on 'synth', by 8 * John R. Marino <draco@marino.st>, which was written in ada. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in 18 * the documentation and/or other materials provided with the 19 * distribution. 20 * 3. Neither the name of The DragonFly Project nor the names of its 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific, prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 32 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 34 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 #include "dsynth.h" 38 39 #include <curses.h> 40 41 /* 42 * ncurses - LINES, COLS are the main things we care about 43 */ 44 static WINDOW *CWin; 45 static WINDOW *CMon; 46 static const char *Line0 = " Total - Built - Ignored - " 47 "Load - Pkg/hour - "; 48 static const char *Line1 = " Left - Failed - Skipped - " 49 "Swap - Impulse - --:--:-- "; 50 static const char *LineB = "===========================================" 51 "===================================="; 52 static const char *LineI = " ID Duration Build Phase Origin " 53 " Lines"; 54 55 static int LastReduce; 56 static off_t MonitorLogOff; 57 static int MonitorLogLines; 58 static int MonitorBufBeg; 59 static int MonitorBufEnd; 60 static int MonitorBufScan; 61 static int MonitorBufDiscardMode; 62 static char MonitorBuf[1024]; 63 64 static int guireadline(int fd, char **bufp); 65 66 #define TOTAL_COL 7 67 #define BUILT_COL 21 68 #define IGNORED_COL 36 69 #define LOAD_COL 48 70 #define GPKGRATE_COL 64 71 #define REDUCE_COL 71 72 73 #define LEFT_COL 7 74 #define FAILED_COL 21 75 #define SKIPPED_COL 36 76 #define SWAP_COL 48 77 #define IMPULSE_COL 64 78 #define TIME_COL 71 79 80 #define ID_COL 1 81 #define DURATION_COL 5 82 #define BUILD_PHASE_COL 15 83 #define ORIGIN_COL 32 84 #define LINES_COL 73 85 86 /* 87 * The row that the worker list starts on, and the row that the log starts 88 * on. 89 */ 90 #define WORKER_START 5 91 #define LOG_START (WORKER_START + MaxWorkers + 1) 92 93 static void NCursesReset(void); 94 95 static void 96 NCursesInit(void) 97 { 98 if (UseNCurses == 0) 99 return; 100 101 CWin = initscr(); 102 NCursesReset(); 103 104 intrflush(stdscr, FALSE); 105 nonl(); 106 noecho(); 107 cbreak(); 108 109 start_color(); 110 use_default_colors(); 111 init_pair(1, COLOR_RED, -1); 112 init_pair(2, COLOR_GREEN, -1); 113 init_pair(3, -1, -1); 114 } 115 116 static void 117 NCursesReset(void) 118 { 119 int i; 120 121 if (UseNCurses == 0) 122 return; 123 124 if (CMon) { 125 delwin(CMon); 126 CMon = NULL; 127 } 128 129 werase(CWin); 130 curs_set(0); 131 redrawwin(CWin); 132 wrefresh(CWin); 133 mvwprintw(CWin, 0, 0, "%s", Line0); 134 mvwprintw(CWin, 1, 0, "%s", Line1); 135 mvwprintw(CWin, 2, 0, "%s", LineB); 136 mvwprintw(CWin, 3, 0, "%s", LineI); 137 mvwprintw(CWin, 4, 0, "%s", LineB); 138 139 for (i = 0; i < MaxWorkers; ++i) { 140 mvwprintw(CWin, WORKER_START + i, ID_COL, "%02d", i); 141 mvwprintw(CWin, WORKER_START + i, DURATION_COL, "--:--:--"); 142 mvwprintw(CWin, WORKER_START + i, BUILD_PHASE_COL, "Idle"); 143 mvwprintw(CWin, WORKER_START + i, ORIGIN_COL, "%39.39s", ""); 144 mvwprintw(CWin, WORKER_START + i, LINES_COL, "%6.6s", ""); 145 } 146 mvwprintw(CWin, WORKER_START + MaxWorkers, 0, "%s", LineB); 147 wrefresh(CWin); 148 149 CMon = subwin(CWin, 0, 0, LOG_START, 0); 150 scrollok(CMon, 1); 151 MonitorLogLines = LINES - LOG_START; 152 MonitorLogOff = 0; 153 MonitorBufBeg = 0; 154 MonitorBufEnd = 0; 155 MonitorBufScan = 0; 156 MonitorBufDiscardMode = 0; 157 nodelay(CMon, 1); 158 159 LastReduce = -1; 160 } 161 162 static void 163 NCursesUpdateTop(topinfo_t *info) 164 { 165 if (UseNCurses == 0) 166 return; 167 168 mvwprintw(CWin, 0, TOTAL_COL, "%-5d", info->total); 169 mvwprintw(CWin, 0, BUILT_COL, "%-5d", info->successful); 170 mvwprintw(CWin, 0, IGNORED_COL, "%-5d", info->ignored); 171 if (info->dload[0] > 999.9) 172 mvwprintw(CWin, 0, LOAD_COL, "%5.0f", info->dload[0]); 173 else 174 mvwprintw(CWin, 0, LOAD_COL, "%5.1f", info->dload[0]); 175 mvwprintw(CWin, 0, GPKGRATE_COL, "%-5d", info->pkgrate); 176 177 /* 178 * If dynamic worker reduction is active include a field, 179 * Otherwise blank the field. 180 */ 181 if (LastReduce != DynamicMaxWorkers) { 182 LastReduce = DynamicMaxWorkers; 183 if (MaxWorkers == LastReduce) 184 mvwprintw(CWin, 0, REDUCE_COL, " "); 185 else 186 mvwprintw(CWin, 0, REDUCE_COL, "Limit %-2d", 187 LastReduce); 188 } 189 190 mvwprintw(CWin, 1, LEFT_COL, "%-5d", info->remaining); 191 mvwprintw(CWin, 1, FAILED_COL, "%-5d", info->failed); 192 mvwprintw(CWin, 1, SKIPPED_COL, "%-5d", info->skipped); 193 if (info->noswap) 194 mvwprintw(CWin, 1, SWAP_COL, "- "); 195 else 196 mvwprintw(CWin, 1, SWAP_COL, "%5.1f", info->dswap); 197 mvwprintw(CWin, 1, IMPULSE_COL, "%-5d", info->pkgimpulse); 198 if (info->h > 99) 199 mvwprintw(CWin, 1, TIME_COL-1, "%3d:%02d:%02d", 200 info->h, info->m, info->s); 201 else 202 mvwprintw(CWin, 1, TIME_COL, "%02d:%02d:%02d", 203 info->h, info->m, info->s); 204 } 205 206 static void 207 NCursesUpdateLogs(void) 208 { 209 int fd = dlog00_fd(); 210 char *ptr; 211 char c; 212 ssize_t n; 213 int w; 214 215 if (UseNCurses == 0) 216 return; 217 218 for (;;) { 219 n = guireadline(fd, &ptr); 220 if (n < 0) 221 break; 222 if (n == 0) 223 continue; 224 225 /* 226 * Scroll down 227 */ 228 if (n > COLS) 229 w = COLS; 230 else 231 w = n; 232 c = ptr[w]; 233 ptr[w] = 0; 234 235 /* 236 * Filter out these logs from the display (they remain in 237 * the 00*.log file) to reduce clutter. 238 */ 239 if (strncmp(ptr, "[XXX] Load=", 11) == 0) 240 continue; 241 242 /* 243 * Output possibly colored log line 244 */ 245 wscrl(CMon, -1); 246 if (strstr(ptr, "] SUCCESS ")) { 247 wattrset(CMon, COLOR_PAIR(2)); 248 } else if (strstr(ptr, "] FAILURE ")) { 249 wattrset(CMon, COLOR_PAIR(1)); 250 } 251 mvwprintw(CMon, 0, 0, "%s", ptr); 252 ptr[w] = c; 253 wattrset(CMon, COLOR_PAIR(3)); 254 } 255 } 256 257 static void 258 NCursesUpdate(worker_t *work) 259 { 260 const char *phase; 261 const char *origin; 262 time_t t; 263 int i = work->index; 264 int h; 265 int m; 266 int s; 267 268 if (UseNCurses == 0) 269 return; 270 271 phase = "Unknown"; 272 origin = ""; 273 274 switch(work->state) { 275 case WORKER_NONE: 276 phase = "None"; 277 /* fall through */ 278 case WORKER_IDLE: 279 if (work->state == WORKER_IDLE) 280 phase = "Idle"; 281 /* fall through */ 282 case WORKER_FAILED: 283 if (work->state == WORKER_FAILED) 284 phase = "Failed"; 285 /* fall through */ 286 case WORKER_EXITING: 287 if (work->state == WORKER_EXITING) 288 phase = "Exiting"; 289 mvwprintw(CWin, WORKER_START + i, DURATION_COL, 290 "--:--:--"); 291 mvwprintw(CWin, WORKER_START + i, BUILD_PHASE_COL, 292 "%-16.16s", phase); 293 mvwprintw(CWin, WORKER_START + i, ORIGIN_COL, 294 "%-39.39s", ""); 295 mvwprintw(CWin, WORKER_START + i, LINES_COL, 296 "%-6.6s", ""); 297 return; 298 case WORKER_PENDING: 299 phase = "Pending"; 300 break; 301 case WORKER_RUNNING: 302 phase = "Running"; 303 break; 304 case WORKER_DONE: 305 phase = "Done"; 306 break; 307 case WORKER_FROZEN: 308 phase = "FROZEN"; 309 break; 310 default: 311 break; 312 } 313 314 t = time(NULL) - work->start_time; 315 s = t % 60; 316 m = t / 60 % 60; 317 h = t / 60 / 60; 318 319 if (work->state == WORKER_RUNNING) 320 phase = getphasestr(work->phase); 321 322 if (work->pkg) 323 origin = work->pkg->portdir; 324 else 325 origin = ""; 326 327 mvwprintw(CWin, WORKER_START + i, DURATION_COL, 328 "%02d:%02d:%02d", h, m, s); 329 mvwprintw(CWin, WORKER_START + i, BUILD_PHASE_COL, 330 "%-16.16s", phase); 331 mvwprintw(CWin, WORKER_START + i, ORIGIN_COL, 332 "%-39.39s", origin); 333 mvwprintw(CWin, WORKER_START + i, LINES_COL, 334 "%6d", work->lines); 335 } 336 337 static void 338 NCursesSync(void) 339 { 340 int c; 341 342 if (UseNCurses == 0) 343 return; 344 345 while ((c = wgetch(CMon)) != ERR) { 346 if (c == KEY_RESIZE) 347 NCursesReset(); 348 } 349 wrefresh(CWin); 350 wrefresh(CMon); 351 } 352 353 static void 354 NCursesDone(void) 355 { 356 if (UseNCurses == 0) 357 return; 358 359 endwin(); 360 } 361 362 static int 363 guireadline(int fd, char **bufp) 364 { 365 int r; 366 int n; 367 368 /* 369 * Reset buffer as an optimization to avoid unnecessary 370 * shifts. 371 */ 372 *bufp = NULL; 373 if (MonitorBufBeg == MonitorBufEnd) { 374 MonitorBufBeg = 0; 375 MonitorBufEnd = 0; 376 MonitorBufScan = 0; 377 } 378 379 /* 380 * Look for newline, handle discard mode 381 */ 382 again: 383 for (n = MonitorBufScan; n < MonitorBufEnd; ++n) { 384 if (MonitorBuf[n] == '\n') { 385 *bufp = MonitorBuf + MonitorBufBeg; 386 r = n - MonitorBufBeg; 387 MonitorBufBeg = n + 1; 388 MonitorBufScan = n + 1; 389 390 if (MonitorBufDiscardMode == 0) 391 return r; 392 MonitorBufDiscardMode = 0; 393 goto again; 394 } 395 } 396 397 /* 398 * Handle overflow 399 */ 400 if (n == sizeof(MonitorBuf)) { 401 if (MonitorBufBeg) { 402 /* 403 * Shift the buffer to make room and read more data. 404 */ 405 bcopy(MonitorBuf + MonitorBufBeg, 406 MonitorBuf, 407 n - MonitorBufBeg); 408 MonitorBufEnd -= MonitorBufBeg; 409 MonitorBufScan -= MonitorBufBeg; 410 n -= MonitorBufBeg; 411 MonitorBufBeg = 0; 412 } else if (MonitorBufDiscardMode) { 413 /* 414 * Overflow. If in discard mode just throw it all 415 * away. Stay in discard mode. 416 */ 417 MonitorBufBeg = 0; 418 MonitorBufEnd = 0; 419 MonitorBufScan = 0; 420 } else { 421 /* 422 * Overflow. If not in discard mode return a truncated 423 * line and enter discard mode. 424 * 425 * The caller will temporarily set ptr[r] = 0 so make 426 * sure that does not overflow our buffer as we are not 427 * at a newline. 428 * 429 * (MonitorBufBeg is 0); 430 */ 431 *bufp = MonitorBuf + MonitorBufBeg; 432 r = n - 1; 433 MonitorBufBeg = n; 434 MonitorBufScan = n; 435 MonitorBufDiscardMode = 1; 436 437 return r; 438 } 439 } 440 441 /* 442 * Read more data. If there is no data pending then return -1, 443 * otherwise loop up to see if more complete line(s) are available. 444 */ 445 r = pread(fd, 446 MonitorBuf + MonitorBufEnd, 447 sizeof(MonitorBuf) - MonitorBufEnd, 448 MonitorLogOff); 449 if (r <= 0) 450 return -1; 451 MonitorLogOff += r; 452 MonitorBufEnd += r; 453 goto again; 454 } 455 456 runstats_t NCursesRunStats = { 457 .init = NCursesInit, 458 .done = NCursesDone, 459 .reset = NCursesReset, 460 .update = NCursesUpdate, 461 .updateTop = NCursesUpdateTop, 462 .updateLogs = NCursesUpdateLogs, 463 .sync = NCursesSync 464 }; 465