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