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 monitorlog_t nclog; 57 58 #define TOTAL_COL 7 59 #define BUILT_COL 21 60 #define IGNORED_COL 36 61 #define LOAD_COL 48 62 #define GPKGRATE_COL 64 63 #define REDUCE_COL 71 64 65 #define LEFT_COL 7 66 #define FAILED_COL 21 67 #define SKIPPED_COL 36 68 #define SWAP_COL 48 69 #define IMPULSE_COL 64 70 #define TIME_COL 71 71 72 #define ID_COL 1 73 #define DURATION_COL 5 74 #define BUILD_PHASE_COL 15 75 #define ORIGIN_COL 32 76 #define LINES_COL 73 77 78 /* 79 * The row that the worker list starts on, and the row that the log starts 80 * on. 81 */ 82 #define WORKER_START 5 83 #define LOG_START (WORKER_START + MaxWorkers + 1) 84 85 static void NCursesReset(void); 86 87 static void 88 NCursesInit(void) 89 { 90 if (UseNCurses == 0) 91 return; 92 93 CWin = initscr(); 94 NCursesReset(); 95 96 intrflush(stdscr, FALSE); 97 nonl(); 98 noecho(); 99 cbreak(); 100 101 start_color(); 102 use_default_colors(); 103 init_pair(1, COLOR_RED, -1); 104 init_pair(2, COLOR_GREEN, -1); 105 init_pair(3, -1, -1); 106 } 107 108 static void 109 NCursesReset(void) 110 { 111 int i; 112 113 if (UseNCurses == 0) 114 return; 115 116 if (CMon) { 117 delwin(CMon); 118 CMon = NULL; 119 } 120 121 werase(CWin); 122 curs_set(0); 123 redrawwin(CWin); 124 wrefresh(CWin); 125 mvwprintw(CWin, 0, 0, "%s", Line0); 126 mvwprintw(CWin, 1, 0, "%s", Line1); 127 mvwprintw(CWin, 2, 0, "%s", LineB); 128 mvwprintw(CWin, 3, 0, "%s", LineI); 129 mvwprintw(CWin, 4, 0, "%s", LineB); 130 131 for (i = 0; i < MaxWorkers; ++i) { 132 mvwprintw(CWin, WORKER_START + i, ID_COL, "%02d", i); 133 mvwprintw(CWin, WORKER_START + i, DURATION_COL, "--:--:--"); 134 mvwprintw(CWin, WORKER_START + i, BUILD_PHASE_COL, "Idle"); 135 mvwprintw(CWin, WORKER_START + i, ORIGIN_COL, "%39.39s", ""); 136 mvwprintw(CWin, WORKER_START + i, LINES_COL, "%6.6s", ""); 137 } 138 mvwprintw(CWin, WORKER_START + MaxWorkers, 0, "%s", LineB); 139 wrefresh(CWin); 140 141 CMon = subwin(CWin, 0, 0, LOG_START, 0); 142 scrollok(CMon, 1); 143 144 bzero(&nclog, sizeof(nclog)); 145 nclog.fd = dlog00_fd(); 146 nodelay(CMon, 1); 147 148 LastReduce = -1; 149 } 150 151 static void 152 NCursesUpdateTop(topinfo_t *info) 153 { 154 if (UseNCurses == 0) 155 return; 156 157 mvwprintw(CWin, 0, TOTAL_COL, "%-5d", info->total); 158 mvwprintw(CWin, 0, BUILT_COL, "%-5d", info->successful); 159 mvwprintw(CWin, 0, IGNORED_COL, "%-5d", info->ignored); 160 if (info->dload[0] > 999.9) 161 mvwprintw(CWin, 0, LOAD_COL, "%5.0f", info->dload[0]); 162 else 163 mvwprintw(CWin, 0, LOAD_COL, "%5.1f", info->dload[0]); 164 mvwprintw(CWin, 0, GPKGRATE_COL, "%-5d", info->pkgrate); 165 166 /* 167 * If dynamic worker reduction is active include a field, 168 * Otherwise blank the field. 169 */ 170 if (LastReduce != info->dynmaxworkers) { 171 LastReduce = info->dynmaxworkers; 172 if (MaxWorkers == LastReduce) 173 mvwprintw(CWin, 0, REDUCE_COL, " "); 174 else 175 mvwprintw(CWin, 0, REDUCE_COL, "Limit %-2d", 176 LastReduce); 177 } 178 179 mvwprintw(CWin, 1, LEFT_COL, "%-5d", info->remaining); 180 mvwprintw(CWin, 1, FAILED_COL, "%-5d", info->failed); 181 mvwprintw(CWin, 1, SKIPPED_COL, "%-5d", info->skipped); 182 if (info->noswap) 183 mvwprintw(CWin, 1, SWAP_COL, "- "); 184 else 185 mvwprintw(CWin, 1, SWAP_COL, "%5.1f", info->dswap); 186 mvwprintw(CWin, 1, IMPULSE_COL, "%-5d", info->pkgimpulse); 187 if (info->h > 99) 188 mvwprintw(CWin, 1, TIME_COL-1, "%3d:%02d:%02d", 189 info->h, info->m, info->s); 190 else 191 mvwprintw(CWin, 1, TIME_COL, "%02d:%02d:%02d", 192 info->h, info->m, info->s); 193 } 194 195 static void 196 NCursesUpdateLogs(void) 197 { 198 char *ptr; 199 char c; 200 ssize_t n; 201 int w; 202 203 if (UseNCurses == 0) 204 return; 205 206 for (;;) { 207 n = readlogline(&nclog, &ptr); 208 if (n < 0) 209 break; 210 if (n == 0) 211 continue; 212 213 /* 214 * Scroll down 215 */ 216 if (n > COLS) 217 w = COLS; 218 else 219 w = n; 220 c = ptr[w]; 221 ptr[w] = 0; 222 223 /* 224 * Filter out these logs from the display (they remain in 225 * the 00*.log file) to reduce clutter. 226 */ 227 if (strncmp(ptr, "[XXX] Load=", 11) != 0) { 228 /* 229 * Output possibly colored log line 230 */ 231 wscrl(CMon, -1); 232 if (strstr(ptr, "] SUCCESS ")) { 233 wattrset(CMon, COLOR_PAIR(2)); 234 } else if (strstr(ptr, "] FAILURE ")) { 235 wattrset(CMon, COLOR_PAIR(1)); 236 } 237 mvwprintw(CMon, 0, 0, "%s", ptr); 238 wattrset(CMon, COLOR_PAIR(3)); 239 } 240 ptr[w] = c; 241 } 242 } 243 244 static void 245 NCursesUpdate(worker_t *work, const char *portdir) 246 { 247 const char *phase; 248 const char *origin; 249 time_t t; 250 int i = work->index; 251 int h; 252 int m; 253 int s; 254 255 if (UseNCurses == 0) 256 return; 257 258 phase = "Unknown"; 259 origin = ""; 260 261 switch(work->state) { 262 case WORKER_NONE: 263 phase = "None"; 264 /* fall through */ 265 case WORKER_IDLE: 266 if (work->state == WORKER_IDLE) 267 phase = "Idle"; 268 /* fall through */ 269 case WORKER_FAILED: 270 if (work->state == WORKER_FAILED) 271 phase = "Failed"; 272 /* fall through */ 273 case WORKER_EXITING: 274 if (work->state == WORKER_EXITING) 275 phase = "Exiting"; 276 mvwprintw(CWin, WORKER_START + i, DURATION_COL, 277 "--:--:--"); 278 mvwprintw(CWin, WORKER_START + i, BUILD_PHASE_COL, 279 "%-16.16s", phase); 280 mvwprintw(CWin, WORKER_START + i, ORIGIN_COL, 281 "%-39.39s", ""); 282 mvwprintw(CWin, WORKER_START + i, LINES_COL, 283 "%-6.6s", ""); 284 return; 285 case WORKER_PENDING: 286 phase = "Pending"; 287 break; 288 case WORKER_RUNNING: 289 phase = "Running"; 290 break; 291 case WORKER_DONE: 292 phase = "Done"; 293 break; 294 case WORKER_FROZEN: 295 phase = "FROZEN"; 296 break; 297 default: 298 break; 299 } 300 301 t = time(NULL) - work->start_time; 302 s = t % 60; 303 m = t / 60 % 60; 304 h = t / 60 / 60; 305 306 if (work->state == WORKER_RUNNING) 307 phase = getphasestr(work->phase); 308 309 /* 310 * When called from the monitor frontend portdir has to be passed 311 * in directly because work->pkg is not mapped. 312 */ 313 if (portdir) 314 origin = portdir; 315 else if (work->pkg) 316 origin = work->pkg->portdir; 317 else 318 origin = ""; 319 320 mvwprintw(CWin, WORKER_START + i, DURATION_COL, 321 "%02d:%02d:%02d", h, m, s); 322 mvwprintw(CWin, WORKER_START + i, BUILD_PHASE_COL, 323 "%-16.16s", phase); 324 mvwprintw(CWin, WORKER_START + i, ORIGIN_COL, 325 "%-39.39s", origin); 326 mvwprintw(CWin, WORKER_START + i, LINES_COL, 327 "%6d", work->lines); 328 } 329 330 static void 331 NCursesSync(void) 332 { 333 int c; 334 335 if (UseNCurses == 0) 336 return; 337 338 while ((c = wgetch(CMon)) != ERR) { 339 if (c == KEY_RESIZE) 340 NCursesReset(); 341 } 342 wrefresh(CWin); 343 wrefresh(CMon); 344 } 345 346 static void 347 NCursesDone(void) 348 { 349 if (UseNCurses == 0) 350 return; 351 352 endwin(); 353 } 354 355 runstats_t NCursesRunStats = { 356 .init = NCursesInit, 357 .done = NCursesDone, 358 .reset = NCursesReset, 359 .update = NCursesUpdate, 360 .updateTop = NCursesUpdateTop, 361 .updateLogs = NCursesUpdateLogs, 362 .sync = NCursesSync 363 }; 364