sys/vfs/hammer2: Remove obsolete comments on hidden inode
[dragonfly.git] / usr.bin / dsynth / html.c
blob416cc86f94b4b2e8158631155b262736cb80b147
1 /*
2 * Copyright (c) 2019-2020 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * This code uses concepts and configuration based on 'synth', by
8 * John R. Marino <draco@marino.st>, which was written in ada.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
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.
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.
37 #include "dsynth.h"
39 #define SNPRINTF(buf, ctl, ...) \
40 snprintf((buf), sizeof(buf), ctl, ## __VA_ARGS__)
42 static char *ReportPath;
43 static int HistNum;
44 static int EntryNum;
45 static char KickOff_Buf[64];
47 static const char *CopyFilesAry[] = {
48 "favicon.png",
49 "progress.html",
50 "progress.css",
51 "progress.js",
52 "dsynth.png",
53 NULL
56 static char **HtmlSlots;
57 static time_t HtmlStart;
58 static time_t HtmlLast;
61 * Get rid of stuff that might blow up the json output.
63 static const char *
64 dequote(const char *reason)
66 static char *buf;
67 int i;
69 for (i = 0; reason[i]; ++i) {
70 if (reason[i] == '\"' || reason[i] == '\n' ||
71 reason[i] == '\\') {
72 if (reason != buf) {
73 if (buf)
74 free(buf);
75 buf = strdup(reason);
76 reason = buf;
78 buf[i] = ' ';
81 return reason;
84 static void
85 HtmlInit(void)
87 struct dirent *den;
88 DIR *dir;
89 struct stat st;
90 struct tm tmm;
91 size_t len;
92 char *src;
93 char *dst;
94 time_t t;
95 int i;
97 HtmlSlots = calloc(sizeof(char *), MaxWorkers);
98 HtmlLast = 0;
99 HtmlStart = time(NULL);
101 asprintf(&ReportPath, "%s/Report", LogsPath);
102 if (stat(ReportPath, &st) < 0 && mkdir(ReportPath, 0755) < 0)
103 dfatal("Unable to create %s", ReportPath);
104 for (i = 0; CopyFilesAry[i]; ++i) {
105 asprintf(&src, "%s/%s", SCRIPTPATH(SCRIPTDIR), CopyFilesAry[i]);
106 if (strcmp(CopyFilesAry[i], "progress.html") == 0) {
107 asprintf(&dst, "%s/index.html", ReportPath);
108 } else {
109 asprintf(&dst, "%s/%s", ReportPath, CopyFilesAry[i]);
111 copyfile(src, dst);
112 free(src);
113 free(dst);
116 asprintf(&src, "%s/summary.json", ReportPath);
117 remove(src);
118 free(src);
120 t = time(NULL);
121 gmtime_r(&t, &tmm);
122 strftime(KickOff_Buf, sizeof(KickOff_Buf),
123 " %d-%b-%Y %H:%M:%S %Z", &tmm);
125 dir = opendir(ReportPath);
126 if (dir == NULL)
127 dfatal("Unable to scan %s", ReportPath);
128 while ((den = readdir(dir)) != NULL) {
129 len = strlen(den->d_name);
130 if (len > 13 &&
131 strcmp(den->d_name + len - 13, "_history.json") == 0) {
132 asprintf(&src, "%s/%s", ReportPath, den->d_name);
133 remove(src);
134 free(src);
137 closedir(dir);
140 * First history file
142 HistNum = 0;
143 EntryNum = 1;
146 static void
147 HtmlDone(void)
149 int i;
151 for (i = 0; i < MaxWorkers; ++i) {
152 if (HtmlSlots[i])
153 free(HtmlSlots[i]);
155 free(HtmlSlots);
156 HtmlSlots = NULL;
159 static void
160 HtmlReset(void)
164 static void
165 HtmlUpdate(worker_t *work, const char *portdir)
167 const char *phase;
168 const char *origin;
169 time_t t;
170 int i = work->index;
171 int h;
172 int m;
173 int s;
174 int clear;
175 char elapsed_buf[32];
176 char lines_buf[32];
178 phase = "Unknown";
179 origin = "";
180 clear = 0;
182 switch(work->state) {
183 case WORKER_NONE:
184 phase = "None";
185 /* fall through */
186 case WORKER_IDLE:
187 if (work->state == WORKER_IDLE)
188 phase = "Idle";
189 clear = 1;
190 break;
191 case WORKER_FAILED:
192 if (work->state == WORKER_FAILED)
193 phase = "Failed";
194 /* fall through */
195 case WORKER_EXITING:
196 if (work->state == WORKER_EXITING)
197 phase = "Exiting";
198 return;
199 /* NOT REACHED */
200 case WORKER_PENDING:
201 phase = "Pending";
202 break;
203 case WORKER_RUNNING:
204 phase = "Running";
205 break;
206 case WORKER_DONE:
207 phase = "Done";
208 break;
209 case WORKER_FROZEN:
210 phase = "FROZEN";
211 break;
212 default:
213 break;
216 if (clear) {
217 SNPRINTF(elapsed_buf, "%s", " --:--:--");
218 SNPRINTF(lines_buf, "%s", "");
219 origin = "";
220 } else {
221 t = time(NULL) - work->start_time;
222 s = t % 60;
223 m = t / 60 % 60;
224 h = t / 60 / 60;
225 if (h > 99)
226 SNPRINTF(elapsed_buf, "%3d:%02d:%02d", h, m, s);
227 else
228 SNPRINTF(elapsed_buf, " %02d:%02d:%02d", h, m, s);
230 if (work->state == WORKER_RUNNING)
231 phase = getphasestr(work->phase);
234 * When called from the monitor frontend portdir has to be
235 * passed in directly because work->pkg is not mapped.
237 if (portdir)
238 origin = portdir;
239 else if (work->pkg)
240 origin = work->pkg->portdir;
241 else
242 origin = "";
244 SNPRINTF(lines_buf, "%ld", work->lines);
248 * Update the summary information
250 if (HtmlSlots[i])
251 free(HtmlSlots[i]);
252 asprintf(&HtmlSlots[i],
253 " {\n"
254 " \"ID\":\"%02d\"\n"
255 " ,\"elapsed\":\"%s\"\n"
256 " ,\"phase\":\"%s\"\n"
257 " ,\"origin\":\"%s\"\n"
258 " ,\"lines\":\"%s\"\n"
259 " }\n",
261 elapsed_buf,
262 phase,
263 origin,
264 lines_buf
268 static void
269 HtmlUpdateTop(topinfo_t *info)
271 char *path;
272 char *dst;
273 FILE *fp;
274 int i;
275 char elapsed_buf[32];
276 char swap_buf[32];
277 char load_buf[32];
280 * Be sure to do the first update and final update, but otherwise
281 * only update every 10 seconds or so.
283 if (HtmlLast && (int)(time(NULL) - HtmlLast) < 10 && info->active)
284 return;
285 HtmlLast = time(NULL);
287 if (info->h > 99) {
288 SNPRINTF(elapsed_buf, "%3d:%02d:%02d",
289 info->h, info->m, info->s);
290 } else {
291 SNPRINTF(elapsed_buf, " %02d:%02d:%02d",
292 info->h, info->m, info->s);
295 if (info->noswap)
296 SNPRINTF(swap_buf, "- ");
297 else
298 SNPRINTF(swap_buf, "%5.1f", info->dswap);
300 if (info->dload[0] > 999.9)
301 SNPRINTF(load_buf, "%5.0f", info->dload[0]);
302 else
303 SNPRINTF(load_buf, "%5.1f", info->dload[0]);
305 asprintf(&path, "%s/summary.json.new", ReportPath);
306 asprintf(&dst, "%s/summary.json", ReportPath);
307 fp = fopen(path, "we");
308 if (!fp)
309 ddassert(0);
310 if (fp) {
311 fprintf(fp,
312 "{\n"
313 " \"profile\":\"%s\"\n"
314 " ,\"kickoff\":\"%s\"\n"
315 " ,\"kfiles\":%d\n"
316 " ,\"active\":%d\n"
317 " ,\"stats\":{\n"
318 " \"queued\":%d\n"
319 " ,\"built\":%d\n"
320 " ,\"failed\":%d\n"
321 " ,\"ignored\":%d\n"
322 " ,\"skipped\":%d\n"
323 " ,\"remains\":%d\n"
324 " ,\"meta\":%d\n"
325 " ,\"elapsed\":\"%s\"\n"
326 " ,\"pkghour\":%d\n"
327 " ,\"impulse\":%d\n"
328 " ,\"swapinfo\":\"%s\"\n"
329 " ,\"load\":\"%s\"\n"
330 " }\n",
331 Profile,
332 KickOff_Buf,
333 HistNum, /* kfiles */
334 info->active, /* active */
336 info->total, /* queued */
337 info->successful, /* built */
338 info->failed, /* failed */
339 info->ignored, /* ignored */
340 info->skipped, /* skipped */
341 info->remaining, /* remaining */
342 info->meta, /* meta-nodes */
343 elapsed_buf, /* elapsed */
344 info->pkgrate, /* pkghour */
345 info->pkgimpulse, /* impulse */
346 swap_buf, /* swapinfo */
347 load_buf /* load */
349 fprintf(fp,
350 " ,\"builders\":[\n"
352 for (i = 0; i < MaxWorkers; ++i) {
353 if (HtmlSlots[i]) {
354 if (i)
355 fprintf(fp, ",");
356 fwrite(HtmlSlots[i], 1,
357 strlen(HtmlSlots[i]), fp);
358 } else {
359 fprintf(fp,
360 " %s{\n"
361 " \"ID\":\"%02d\"\n"
362 " ,\"elapsed\":\"Shutdown\"\n"
363 " ,\"phase\":\"\"\n"
364 " ,\"origin\":\"\"\n"
365 " ,\"lines\":\"\"\n"
366 " }\n",
367 (i ? "," : ""),
372 fprintf(fp,
373 " ]\n"
374 "}\n");
375 fflush(fp);
376 fclose(fp);
378 rename(path, dst);
379 free(path);
380 free(dst);
383 static void
384 HtmlUpdateLogs(void)
388 static void
389 HtmlUpdateCompletion(worker_t *work, int dlogid, pkg_t *pkg,
390 const char *reason, const char *skipbuf)
392 FILE *fp;
393 char *path;
394 char elapsed_buf[64];
395 struct stat st;
396 time_t t;
397 int s, m, h;
398 int slot;
399 const char *result;
400 char *mreason;
402 if (skipbuf[0] == 0)
403 skipbuf = "0";
404 else if (skipbuf[0] == ' ')
405 ++skipbuf;
407 mreason = NULL;
408 if (work) {
409 t = time(NULL) - work->start_time;
410 s = t % 60;
411 m = t / 60 % 60;
412 h = t / 60 / 60;
413 SNPRINTF(elapsed_buf, "%02d:%02d:%02d", h, m, s);
414 slot = work->index;
415 } else {
416 slot = -1;
417 elapsed_buf[0] = 0;
420 switch(dlogid) {
421 case DLOG_SUCC:
422 if (pkg->flags & PKGF_DUMMY)
423 result = "meta";
424 else
425 result = "built";
426 break;
427 case DLOG_FAIL:
428 result = "failed";
429 if (work) {
430 asprintf(&mreason, "%s:%s",
431 getphasestr(work->phase),
432 reason);
433 } else {
434 asprintf(&mreason, "unknown:%s", reason);
436 reason = mreason;
437 break;
438 case DLOG_IGN:
439 result = "ignored";
440 asprintf(&mreason, "%s:|:%s", reason, skipbuf);
441 reason = mreason;
442 break;
443 case DLOG_SKIP:
444 result = "skipped";
445 break;
446 default:
447 result = "Unknown";
448 break;
451 t = time(NULL) - HtmlStart;
452 s = t % 60;
453 m = t / 60 % 60;
454 h = t / 60 / 60;
457 * Cycle history file as appropriate, includes initial file handling.
459 if (HistNum == 0)
460 HistNum = 1;
461 asprintf(&path, "%s/%02d_history.json", ReportPath, HistNum);
462 if (stat(path, &st) < 0) {
463 fp = fopen(path, "we");
464 } else if (st.st_size > 50000) {
465 ++HistNum;
466 free(path);
467 asprintf(&path, "%s/%02d_history.json", ReportPath, HistNum);
468 fp = fopen(path, "we");
469 } else {
470 fp = fopen(path, "r+e");
471 fseek(fp, 0, SEEK_END);
474 if (fp) {
475 if (ftell(fp) == 0) {
476 fprintf(fp, "[\n");
477 } else {
478 fseek(fp, -2, SEEK_END);
480 fprintf(fp,
481 " %s{\n"
482 " \"entry\":%d\n"
483 " ,\"elapsed\":\"%02d:%02d:%02d\"\n"
484 " ,\"ID\":\"%02d\"\n"
485 " ,\"result\":\"%s\"\n"
486 " ,\"origin\":\"%s\"\n"
487 " ,\"info\":\"%s\"\n"
488 " ,\"duration\":\"%s\"\n"
489 " }\n"
490 "]\n",
491 ((ftell(fp) > 10) ? "," : ""),
492 EntryNum,
493 h, m, s,
494 slot,
495 result,
496 pkg->portdir,
497 dequote(reason),
498 elapsed_buf
500 ++EntryNum;
501 fclose(fp);
504 free(path);
505 if (mreason)
506 free(mreason);
509 static void
510 HtmlSync(void)
514 runstats_t HtmlRunStats = {
515 .init = HtmlInit,
516 .done = HtmlDone,
517 .reset = HtmlReset,
518 .update = HtmlUpdate,
519 .updateTop = HtmlUpdateTop,
520 .updateLogs = HtmlUpdateLogs,
521 .updateCompletion = HtmlUpdateCompletion,
522 .sync = HtmlSync