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 72
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
NCursesInit(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
NCursesReset(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, "%38.38s", "");
136 mvwprintw(CWin, WORKER_START + i, LINES_COL, "%7.7s", "");
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
NCursesUpdateTop(topinfo_t * info)152 NCursesUpdateTop(topinfo_t *info)
153 {
154 if (UseNCurses == 0)
155 return;
156
157 mvwprintw(CWin, 0, TOTAL_COL, "%-6d", info->total);
158 mvwprintw(CWin, 0, BUILT_COL, "%-6d", info->successful);
159 mvwprintw(CWin, 0, IGNORED_COL, "%-6d", 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, "%-6d", 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, "Lim %-3d",
176 LastReduce);
177 }
178
179 mvwprintw(CWin, 1, LEFT_COL, "%-6d", info->remaining);
180 mvwprintw(CWin, 1, FAILED_COL, "%-6d", info->failed);
181 mvwprintw(CWin, 1, SKIPPED_COL, "%-6d", 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, "%-6d", 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
NCursesUpdateLogs(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
NCursesUpdate(worker_t * work,const char * portdir)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 "%-38.38s", "");
282 mvwprintw(CWin, WORKER_START + i, LINES_COL,
283 "%-7.7s", "");
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 "%-38.38s", origin);
326 if (work->lines > 9999999) {
327 mvwprintw(CWin, WORKER_START + i, LINES_COL,
328 "%7s", "*MANY*%d", work->lines % 10);
329 } else {
330 mvwprintw(CWin, WORKER_START + i, LINES_COL,
331 "%7d", work->lines);
332 }
333 }
334
335 static void
NCursesSync(void)336 NCursesSync(void)
337 {
338 int c;
339
340 if (UseNCurses == 0)
341 return;
342
343 while ((c = wgetch(CMon)) != ERR) {
344 if (c == KEY_RESIZE)
345 NCursesReset();
346 }
347 wrefresh(CWin);
348 wrefresh(CMon);
349 }
350
351 static void
NCursesDone(void)352 NCursesDone(void)
353 {
354 if (UseNCurses == 0)
355 return;
356
357 endwin();
358 }
359
360 runstats_t NCursesRunStats = {
361 .init = NCursesInit,
362 .done = NCursesDone,
363 .reset = NCursesReset,
364 .update = NCursesUpdate,
365 .updateTop = NCursesUpdateTop,
366 .updateLogs = NCursesUpdateLogs,
367 .sync = NCursesSync
368 };
369