xref: /dragonfly/usr.bin/dsynth/ncurses.c (revision 2295606e)
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