Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / assets / arch-x86_64-intel-i3.svg
Author[]Andy Green <andy@warmcat.com> 2025-09-24 03:28 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-09-24 03:41 UTC
Treed5d5ed7577cda09f35eb99c7fc84af12b4325a5f   Raw Patch
 
loadreport debug
loadreport debug

Co-developed-by: Gemini 2.5 Pro
diff --git a/AGENTS.md b/AGENTS.md index 2251e1a..ebf8fd7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,8 @@ ## Overview -Please make high quality, not lazy implementation decisions. +Please make high quality, not lazy, implementation decisions, bearing in mind +maintainability. Our work should follow the existing usage of apis in the project as much as possible. @@ -16,6 +17,15 @@ We avoid casual linked-lists and use `lws_dll2_t`. We consider using lwsac instead of discrete allocations, if the pattern of allocations will benefit from it. +We consider using lws_struct to convert between sqlite storage <-> structs <-> JSON + +## Security + +Please bear in mind what parts of the system are secrets and look after the security of them. + +In particular, all web pieces are made available on the internet with a strict CSP. That means +no inline styles or scripts. You can find the web pieces (JS, HTML, css) in ./assets/ + ## Build testing Please don't worry about build-testing, just push patches when you are confident they are complete and have considered all affected code (ie, not half-assed) and ready and I will try them and report back with grounded information. @@ -27,9 +37,9 @@ Sai dependencies: - libwebsockets from main branch - libssl-dev - libsqlite3 - - Build lws: git init && git fetch https://github.com/warmcat/libwebsockets +main:main && cd libwebsockets && mkdir -p build && cd build && cmake .. -DLWS_UNIX_SOCK=1 -DLWS_WITH_STRUCT_JSON=1 -DLWS_WITH_JOSE=1 -DLWS_WITH_STRUCT_SQLITE3=1 -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_SPAWN=1 -DLWS_WITH_SECURE_STREAMS=1 && make && sudo make install && sudo ldconfig + - Build lws: `git init && git fetch https://github.com/warmcat/libwebsockets +main:main && cd libwebsockets && mkdir -p build && cd build && cmake .. -DLWS_UNIX_SOCK=1 -DLWS_WITH_STRUCT_JSON=1 -DLWS_WITH_JOSE=1 -DLWS_WITH_STRUCT_SQLITE3=1 -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_SPAWN=1 -DLWS_WITH_SECURE_STREAMS=1 && make && sudo make install && sudo ldconfig` - sai (main branch shown) - - Build sai: git init && git fetch https://github.com/warmcat/sai +main:m && cd libwebsockets && mkdir -p build && cd build && cmake .. && make && sudo make install && sudo ldconfig + - Build sai: `git init && git fetch https://github.com/warmcat/sai +main:m && cd libwebsockets && mkdir -p build && cd build && cmake .. && make && sudo make install && sudo ldconfig` diff --git a/assets/sai.css b/assets/sai.css index 14bc7cf..b846bb3 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -586,6 +586,7 @@ td.bn { font-weight: normal; font-size: 7pt; padding: 1px; + position: relative; } td.bins { @@ -604,7 +605,7 @@ table.nomar { padding: 0px; margin: -3px; width: 100%; - text-align:left; + text-align:center; } td.nomar { @@ -641,6 +642,7 @@ div.ibuil { user-select: none; -webkit-user-select: none; cursor: pointer; + width: 80px; } div.ibuilctr { @@ -761,39 +763,6 @@ img.branch { color: rgba(0, 0, 0, 0); } -.inst_box { - display: inline-block; - width: 12px; - height: 12px; - border: 1px solid #777; - margin-left: 2px; - vertical-align: middle; -} -.inst_idle { background-color: #d0d0d0; } -.inst_run { background-color: #78c678; } -.load_text { font-size: 9px; vertical-align: middle; } - -/* Container for all instance boxes for a given platform */ -.instload { - display: flex; - flex-wrap: wrap; - gap: 2px; - padding-top: 4px; - padding-bottom: 4px; - min-height: 22px; /* Ensure container doesn't collapse if empty */ -} - -/* The outer box for a single instance */ -.inst_box { - width: 16px; - height: 16px; - border: 1px solid #555; - box-sizing: border-box; - background-color: #ddd; /* Dark background for the meter */ - position: relative; /* Needed for positioning the child bar */ - overflow: hidden; /* Hide any part of the bar that goes over 100% */ -} - /* The actual colored bar inside the box */ .inst_bar { background-color: #00afff; /* A nice, high-contrast green */ @@ -804,18 +773,6 @@ img.branch { transition: height 0.2s ease-out; /* Smoothly animate height changes */ } -.inst_idle { - border-color: #555; - opacity: 0.3; /* Make idle instances semi-transparent */ - transition: opacity 0.3s; /* Smoothly fade in/out */ -} - -.inst_busy { - border-color: #999; - opacity: 1.0; /* Ensure busy instances are fully opaque */ - transition: opacity 0.3s; -} - @keyframes flash-slow { 50% { opacity: 0.8; } } @@ -1025,3 +982,70 @@ body.overlay-active { background-color: #ccc; flex-shrink: 0; } + +.step-progress { + display: flex; + align-items: center; +} + +.step-block { + width: 8px; + height: 8px; + border: 1px solid #aaa; + margin-right: 2px; +} + +.step-done { + background-color: #555; +} + +.step-current { + background-color: #aaa; +} + +.step-pending { + background-color: #fff; +} + +.step-label { + font-size: 7pt; + margin-left: 4px; +} + +.resource-bars { + display: flex; + flex-direction: column; + gap: 1px; + margin-top: 2px; +} + +.res-bar { + height: 2px; + background-color: rgba(0, 0, 0, 0.2); + border: 1px solid #fff; + width: 100%; +} + +.res-bar-inner { + height: 100%; + transition: width 0.2s ease-out; +} + +.res-bar-cpu { + background-color: #ff4136; /* Red */ +} + +.res-bar-ram { + background-color: #0074d9; /* Blue */ +} + +.res-bar-disk { + background-color: #2ecc40; /* Green */ +} + +.res-bar.empty { + border: 1px solid #777; + height: 5px; + width: 24px; + margin-bottom: 1px; +} diff --git a/assets/sai.js b/assets/sai.js index 777c73a..3d98622 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -295,11 +295,11 @@ var lang_ja = "{" + "\"Committer\": \"コミッター\"," + "\"Raw Patch\": \"生パッチ\"," + "\"Page fetched %{pf} ago, creation time: %{ct}ms " + - "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + - "\"%{pf}間前に取得されたページ, 作成にかかった時間: %{ct}ms " + - "(vhost etag キャッシュヒット: %{ve}%, キャッシュヒット: %{ch}%)\"," + - "\"Created %{pf} ago, creation time: %{ct}ms \":" + - "\"%{pf}間前に作成されました, 作成にかかった時間: %{ct}ms\"" + + "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + + "\"%{pf}間前に取得されたページ, 作成にかかった時間: %{ct}ms " + + "(vhost etag キャッシュヒット: %{ve}%, キャッシュヒット: %{ch}%)\"," + + "\"Created %{pf} ago, creation time: %{ct}ms \":\"" + + "%{pf}間前に作成されました, 作成にかかった時間: %{ct}ms\"" + "}}"; var lang_zht = "{" + @@ -341,11 +341,11 @@ var lang_zht = "{" + "\"Committer\": \"提交者\"," + "\"Raw Patch\": \"原始補丁\"," + "\"Page fetched %{pf} ago, creation time: %{ct}ms " + - "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + - "\"頁面%{pf}前獲取, 創作時間: %{ct}ms " + - "(vhost etag 緩存命中: %{ve}%, 緩存命中: %{ch}%)\"," + - "\"Created %{pf} ago, creation time: %{ct}ms \":" + - "\"%{pf}前創建, 創作時間: %{ct}ms \"" + + "(vhost etag hits: %{ve}%, cache hits: %{ch}%)\": " + + "\"頁面%{pf}前獲取, 創作時間: %{ct}ms " + + "(vhost etag 緩存命中: %{ve}%, 緩存命中: %{ch}%)\"," + + "\"Created %{pf} ago, creation time: %{ct}ms \":\"" + + "%{pf}前創建, 創作時間: %{ct}ms \"" + "}}"; var lang_zhs = "{" + @@ -396,7 +396,7 @@ var lang_zhs = "{" + var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = "", logAnsiState = {}, - ongoing_task_activities = {}, last_log_timestamp = 0; + ongoing_task_activities = {}, last_log_timestamp = 0, spreadsheet_data_cache = {}, loadreport_data_cache = {}; function update_task_activities() { for (const uuid in ongoing_task_activities) { @@ -508,6 +508,33 @@ function hsanitize(s) }).replace(/\n/g, '\n'); } +function renderSpreadsheet(tasks) { + if (!tasks || tasks.length === 0) { + return ''; + } + + tasks.sort((a, b) => b.started - a.started || a.task_name.localeCompare(b.task_name)); + + var now_ut = Math.round((new Date().getTime() / 1000)); + let html = '<table class="spreadsheet">' + + '<thead><tr>' + + '<th>Task</th>' + + '<th>Build Step</th>' + + '<th>Started</th>' + + '</tr></thead><tbody>'; + + for (const task of tasks) { + html += '<tr>' + + `<td><a href="?task=${hsanitize(task.task_uuid)}">${hsanitize(task.task_name)}</a></td>` + + `<td>${hsanitize(task.build_step)} / ${hsanitize(task.total_steps)}</td>` + + `<td>${agify(now_ut, task.started)} ago</td>` + + '</tr>'; + } + + html += '</tbody></table>'; + return html; +} + var pos = 0, lli = 1, lines = "", times = "", locked = 1, tfirst = 0, cont = [ 0, 0, 0, 0, 0]; @@ -988,7 +1015,6 @@ function createBuilderDiv(plat) { platDiv.className = "ibuil bdr"; if (!plat.online) platDiv.className += " offline"; - // Add a new class if the builder is powering up if (plat.powering_up) platDiv.className += " powering-up"; if (plat.powering_down) @@ -1003,19 +1029,27 @@ function createBuilderDiv(plat) { let plat_tc = plat_parts[2] || 'generic'; let innerHTML = `<table class="nomar"><tbody><tr><td class="bn">`; - innerHTML += `<img class="ip1 zup" src="/sai/${plat_os}.svg" onerror="this.src='/sai/generic.svg';this.onerror=null;">`; - innerHTML += `<img class="ip1 tread1" src="/sai/arch-${plat_arch}.svg" onerror="this.src='/sai/generic.svg';this.onerror=null;">`; - innerHTML += `<img class="ip1 tread2" src="/sai/tc-${plat_tc}.svg" onerror="this.src='/sai/generic.svg';this.onerror=null;">`; - innerHTML += `<br>${plat.peer_ip}`; - innerHTML += `<div class="instload" id="instload-${plat.name}">` + - `<div class="inst_box inst_idle" title="active steps: 0">` + - `<div class="inst_text">0</div>` + - `<div class="inst_bar"></div>` + - `</div>` + - `</div></td></tr></tbody></table>`; + innerHTML += `<img class="ip1 zup" data-sai-src="/sai/${plat_os}.svg">`; + innerHTML += `<img class="ip1 tread1" data-sai-src="/sai/arch-${plat_arch}.svg">`; + innerHTML += `<img class="ip1 tread2" data-sai-src="/sai/tc-${plat_tc}.svg">`; + innerHTML += `<div class="resource-bars">` + + `<div class="res-bar"><div class="res-bar-inner res-bar-cpu w-0"></div></div>` + + `<div class="res-bar"><div class="res-bar-inner res-bar-ram w-0"></div></div>` + + `<div class="res-bar"><div class="res-bar-inner res-bar-disk w-0"></div></div>` + + `</div>`; + innerHTML += `<br>${plat.peer_ip}</td></tr></tbody></table>`; platDiv.innerHTML = innerHTML; + const images = platDiv.querySelectorAll('img[data-sai-src]'); + images.forEach(img => { + img.onerror = () => { + img.src = '/sai/generic.svg'; + img.onerror = null; // prevent infinite loops + }; + img.src = img.getAttribute('data-sai-src'); + }); + const menuItems = [ { label: `<b>SAI Hash:</b> ${plat.sai_hash}` }, { label: `<b>LWS Hash:</b> ${plat.lws_hash}` }, @@ -1145,11 +1179,11 @@ function ws_open_sai() console.log("tid " + tid); sai.send("{\"schema\":" + - "\"com.warmcat.sai.taskinfo\"," + + "\"com.warmcat.sai.taskinfo\"," + "\"js_api_version\": " + SAI_JS_API_VERSION + "," + - "\"logs\": 1," + + "\"logs\": 1," + "\"last_log_ts\":" + last_log_timestamp + "," + - "\"task_hash\":" + + "\"task_hash\":" + JSON.stringify(tid) + "}"); return; @@ -1165,7 +1199,7 @@ function ws_open_sai() console.log("eid " + eid); sai.send("{\"schema\":" + - "\"com.warmcat.sai.eventinfo\"," + + "\"com.warmcat.sai.eventinfo\"," + "\"js_api_version\": " + SAI_JS_API_VERSION + "," + "\"event_hash\":" + JSON.stringify(eid) + "}"); @@ -1178,7 +1212,7 @@ function ws_open_sai() */ sai.send("{\"schema\":" + - "\"com.warmcat.sai.taskinfo\", \"js_api_version\": " + SAI_JS_API_VERSION + "}"); + "\"com.warmcat.sai.taskinfo\", \"js_api_version\": " + SAI_JS_API_VERSION + "}"); }; sai.onmessage = function got_packet(msg) { @@ -1252,9 +1286,10 @@ function ws_open_sai() break; } + // --- Reconciliation Logic --- - buildersContainer.innerHTML = ""; // Clear for safety + buildersContainer.innerHTML = "&nbsp;"; // Clear for safety const table = document.createElement("table"); table.className = "builders"; const tbody = document.createElement("tbody"); @@ -1279,6 +1314,21 @@ function ws_open_sai() const tdSpreadsheet = document.createElement("td"); tdSpreadsheet.className = "spreadsheet-container"; tdSpreadsheet.id = "spreadsheet-" + plat.name; + + let best_match_key = null; + for (const short_name in spreadsheet_data_cache) { + if (plat.name.startsWith(short_name)) { + if (!best_match_key || short_name.length > best_match_key.length) { + best_match_key = short_name; + } + } + } + + if (best_match_key) { + tdSpreadsheet.innerHTML = renderSpreadsheet(spreadsheet_data_cache[best_match_key]); + aging(); + } + tr.appendChild(tdSpreadsheet); tbody.appendChild(tr); @@ -1413,8 +1463,8 @@ function ws_open_sai() addEventListener("click", function(e) { console.log(e); var rs= "{\"schema\":" + - "\"com.warmcat.sai.eventreset\"," + - "\"uuid\": " + + "\"com.warmcat.sai.eventreset\"," + + "\"uuid\": " + JSON.stringify(san(e.srcElement.id.substring(11))) + "}"; console.log(rs); @@ -1425,8 +1475,8 @@ function ws_open_sai() addEventListener("click", function(e) { console.log(e); var rs= "{\"schema\":" + - "\"com.warmcat.sai.eventdelete\"," + - "\"uuid\": " + + "\"com.warmcat.sai.eventdelete\"," + + "\"uuid\": " + JSON.stringify(san(e.srcElement.id.substring(10))) + "}"; console.log(rs); @@ -1543,8 +1593,8 @@ function ws_open_sai() document.getElementById("rebuild-" + san(jso.t.uuid)). addEventListener("click", function(e) { var rs= "{\"schema\":" + - "\"com.warmcat.sai.taskreset\"," + - "\"uuid\": " + + "\"com.warmcat.sai.taskreset\"," + + "\"uuid\": " + JSON.stringify(san(e.srcElement.id.substring(8))) + "}"; console.log(rs); @@ -1581,8 +1631,8 @@ function ws_open_sai() document.getElementById("stop-" + san(jso.t.uuid)). addEventListener("click", function(e) { var rs= "{\"schema\":" + - "\"com.warmcat.sai.taskcan\"," + - "\"task_uuid\": " + + "\"com.warmcat.sai.taskcan\"," + + "\"task_uuid\": " + JSON.stringify(san(e.srcElement.id.substring(5))) + "}"; console.log(rs); sai.send(rs); @@ -1593,68 +1643,64 @@ function ws_open_sai() } break; - case "com.warmcat.sai.loadreport": - const loadContainer = document.getElementById("instload-" + jso.builder_name); - if (!loadContainer) { - break; - } + case "com.warmcat.sai.loadreport": + // Cache the whole report for subsequent builder redraws + loadreport_data_cache[jso.builder_name] = jso; - const instanceDiv = loadContainer.querySelector(".inst_box"); - if (!instanceDiv) { - break; - } + const builderDiv = document.getElementById('binfo-' + jso.builder_name); + if (builderDiv) { + const cpuBar = builderDiv.querySelector(".res-bar-cpu"); + const ramBar = builderDiv.querySelector(".res-bar-ram"); + const diskBar = builderDiv.querySelector(".res-bar-disk"); - const textDiv = instanceDiv.querySelector(".inst_text"); - const barDiv = instanceDiv.querySelector(".inst_bar"); + if (cpuBar) { + let cpu_percentage = (jso.cpu_percent / (jso.core_count * 1000)) * 100; + if (cpu_percentage > 100) cpu_percentage = 100; + if (cpu_percentage < 0) cpu_percentage = 0; + let width_class = `w-${Math.round(cpu_percentage / 5) * 5}`; - if (textDiv) { - textDiv.textContent = jso.active_steps; - } + cpuBar.classList.forEach(c => { if (c.startsWith('w-')) cpuBar.classList.remove(c); }); + cpuBar.classList.add(width_class); + } + if (ramBar) { + let ram_percentage = 0; + if (jso.initial_free_ram_kib > 0) { + ram_percentage = (jso.reserved_ram_kib / jso.initial_free_ram_kib) * 100; + } + if (ram_percentage > 100) ram_percentage = 100; + if (ram_percentage < 0) ram_percentage = 0; + let width_class = `w-${Math.round(ram_percentage / 5) * 5}`; - instanceDiv.title = `Active steps: ${jso.active_steps}\n` + - `CPU: ${(jso.cpu_percent / 10).toFixed(1)}%\n` + - `Free RAM: ${humanize(jso.free_ram_kib * 1024)}B\n` + - `Free Disk: ${humanize(jso.free_disk_kib * 1024)}B`; - - if (jso.active_steps > 0) { - instanceDiv.classList.add("inst_busy"); - instanceDiv.classList.remove("inst_idle"); - } else { - instanceDiv.classList.add("inst_idle"); - instanceDiv.classList.remove("inst_busy"); - } + ramBar.classList.forEach(c => { if (c.startsWith('w-')) ramBar.classList.remove(c); }); + ramBar.classList.add(width_class); + } + if (diskBar) { + let disk_percentage = 0; + if (jso.initial_free_disk_kib > 0) { + disk_percentage = (jso.reserved_disk_kib / jso.initial_free_disk_kib) * 100; + } + if (disk_percentage > 100) disk_percentage = 100; + if (disk_percentage < 0) disk_percentage = 0; + let width_class = `w-${Math.round(disk_percentage / 5) * 5}`; - if (barDiv) { - let cpu_percentage = (jso.cpu_percent / (jso.core_count * 1000)) * 100; - if (cpu_percentage > 100) cpu_percentage = 100; - if (cpu_percentage < 0) cpu_percentage = 0; - barDiv.style.height = `${cpu_percentage}%`; - } + diskBar.classList.forEach(c => { if (c.startsWith('w-')) diskBar.classList.remove(c); }); + diskBar.classList.add(width_class); + } + } - const spreadsheetContainer = document.getElementById("spreadsheet-" + jso.builder_name); - if (spreadsheetContainer) { - let table = '<table><thead><tr>' + - '<th>Task</th><th>Step</th><th>Started</th>' + - '<th>Mem (KiB)</th><th>CPU (%)</th><th>Disk (KiB)</th>' + - '</tr></thead><tbody>'; - - if (jso.active_tasks) { - jso.active_tasks.forEach(task => { - table += `<tr>` + - `<td>${hsanitize(task.task_name)}</td>` + - `<td>${hsanitize(task.build_step)}</td>` + - `<td>${agify(now_ut, task.started)}</td>` + - `<td>${humanize(task.est_peak_mem_kib * 1024)}</td>` + - `<td>${task.est_cpu_load_pct}</td>` + - `<td>${humanize(task.est_disk_kib * 1024)}</td>` + - `</tr>`; - }); - } + // Part 2: Update the spreadsheet of active tasks for the builder + if (jso.active_tasks && jso.active_tasks.length > 0) + spreadsheet_data_cache[jso.builder_name] = jso.active_tasks; + else + delete spreadsheet_data_cache[jso.builder_name]; - table += '</tbody></table>'; - spreadsheetContainer.innerHTML = table; - } - break; + const spreadsheetContainer = document.getElementById('spreadsheet-' + jso.builder_name); + if (spreadsheetContainer) { + spreadsheetContainer.innerHTML = renderSpreadsheet(spreadsheet_data_cache[jso.builder_name]); + if (spreadsheet_data_cache[jso.builder_name]) + aging(); + } + break; case "com-warmcat-sai-artifact": console.log(jso); @@ -1678,7 +1724,8 @@ function ws_open_sai() var act = jso.activity[i]; ongoing_task_activities[act.uuid] = act.cat; } - } + } else + console.log("no spreadsheetContainer"); break; case "com.warmcat.sai.unauthorized": diff --git a/src/builder/b-comms.c b/src/builder/b-comms.c index e8f8229..cd3542a 100644 --- a/src/builder/b-comms.c +++ b/src/builder/b-comms.c @@ -506,60 +506,122 @@ cleanup_on_ss_disconnect(struct lws_dll2 *d, void *user) void saib_sul_load_report_cb(struct lws_sorted_usec_list *sul) { - struct sai_plat_server *spm = lws_container_of(sul, - struct sai_plat_server, sul_load_report); - sai_load_report_t *lr = calloc(1, sizeof(*lr)); - struct sai_plat *sp = NULL; - char somebody_not_idle = 0; - - if (!lr) - return; - - /* - * This builder process has one name, but may have multiple platforms, - * each with multiple instances. For now, we report on the whole builder - * under one name. - */ - lws_strncpy(lr->builder_name, builder.host, sizeof(lr->builder_name)); - lr->core_count = saib_get_cpu_count(); - lr->free_ram_kib = saib_get_free_ram_kib(); - lr->free_disk_kib = saib_get_free_disk_kib(builder.home); - lr->cpu_percent = (unsigned int)saib_get_system_cpu(&builder); - lr->active_steps = 0; - lws_dll2_owner_clear(&lr->active_tasks); - - lws_start_foreach_dll(struct lws_dll2 *, p, builder.sai_plat_owner.head) { - sp = lws_container_of(p, sai_plat_t, sai_plat_list); - lws_start_foreach_dll(struct lws_dll2 *, d, sp->nspawn_owner.head) { - struct sai_nspawn *ns = lws_container_of(d, - struct sai_nspawn, list); - if (ns->state == NSSTATE_EXECUTING_STEPS && ns->task) { - sai_active_task_info_t *ati = malloc(sizeof(*ati)); - if (ati) { - memset(ati, 0, sizeof(*ati)); - lws_strncpy(ati->task_uuid, ns->task->uuid, sizeof(ati->task_uuid)); - lws_strncpy(ati->task_name, ns->task->taskname, sizeof(ati->task_name)); - ati->build_step = ns->current_step; - ati->est_peak_mem_kib = ns->task->est_peak_mem_kib; - ati->est_cpu_load_pct = ns->task->est_cpu_load_pct; - ati->est_disk_kib = ns->task->est_disk_kib; - ati->started = ns->task->started; - lws_dll2_add_tail(&ati->list, &lr->active_tasks); - lr->active_steps++; - } - somebody_not_idle = 1; - } - } lws_end_foreach_dll(d); - } lws_end_foreach_dll(p); - - lws_dll2_add_tail(&lr->list, &spm->load_report_owner); - if (lws_ss_request_tx(spm->ss)) - lwsl_debug("%s: request tx failed\n", __func__); - - if (somebody_not_idle) + struct sai_plat_server *spm = lws_container_of(sul, + struct sai_plat_server, sul_load_report); + char any_platform_on_this_spm_active = 0; + + /* + * This builder process may have multiple platforms, each with + * multiple instances. We report on each platform separately so the + * UI can distinguish them. + * + * This SUL is per-server-connection. We iterate all platforms and + * for each, see if it's supposed to connect to this server. + * + * To avoid spamming idle reports, we only report if the platform + * is active for this server, OR if it was active the last time we + * checked (ie, it has just become idle, so we need to send one last + * report with no active tasks to clear the UI). + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, builder.sai_plat_owner.head) { + struct sai_plat *sp = lws_container_of(p, sai_plat_t, sai_plat_list); + sai_plat_server_ref_t *ref = NULL; + sai_load_report_t *lr; + char is_active = 0; + + /* + * Find the specific ref for this platform and this server + * connection (spm) + */ + lws_start_foreach_dll(struct lws_dll2 *, s, sp->servers.head) { + sai_plat_server_ref_t *r = lws_container_of(s, + sai_plat_server_ref_t, list); + if (r->spm == spm) { + ref = r; + break; + } + } lws_end_foreach_dll(s); + + if (!ref) /* This platform doesn't use this server connection */ + continue; + + /* + * Check for active tasks on this platform for this server conn + */ + lws_start_foreach_dll(struct lws_dll2 *, d, sp->nspawn_owner.head) { + struct sai_nspawn *ns = lws_container_of(d, + struct sai_nspawn, list); + if (ns->spm == spm && + ns->state == NSSTATE_EXECUTING_STEPS && ns->task) { + is_active = 1; + break; + } + } lws_end_foreach_dll(d); + + if (is_active) + any_platform_on_this_spm_active = 1; + + if (!is_active && !ref->was_active) + goto around; + + /* This platform is active for this spm, or just became idle */ + + lr = calloc(1, sizeof(*lr)); + if (!lr) + goto around; + + lws_strncpy(lr->builder_name, sp->name, sizeof(lr->builder_name)); + lr->core_count = saib_get_cpu_count(); + lr->initial_free_ram_kib = saib_get_total_ram_kib(); + lr->initial_free_disk_kib = saib_get_total_disk_kib(builder.home); + lr->reserved_ram_kib = 0; + lr->reserved_disk_kib = 0; + lr->cpu_percent = (unsigned int)saib_get_system_cpu(&builder); + lr->active_steps = 0; + lws_dll2_owner_clear(&lr->active_tasks); + + if (is_active) { + lws_start_foreach_dll(struct lws_dll2 *, d, sp->nspawn_owner.head) { + struct sai_nspawn *ns = lws_container_of(d, + struct sai_nspawn, list); + if (ns->spm == spm && + ns->state == NSSTATE_EXECUTING_STEPS && ns->task) { + sai_active_task_info_t *ati = malloc(sizeof(*ati)); + if (ati) { + memset(ati, 0, sizeof(*ati)); + lws_strncpy(ati->task_uuid, ns->task->uuid, sizeof(ati->task_uuid)); + lws_strncpy(ati->task_name, ns->task->taskname, sizeof(ati->task_name)); + ati->build_step = ns->current_step; + ati->total_steps = ns->build_step_count; + ati->est_peak_mem_kib = ns->task->est_peak_mem_kib; + ati->est_cpu_load_pct = ns->task->est_cpu_load_pct; + ati->est_disk_kib = ns->task->est_disk_kib; + ati->started = ns->task->started; + lws_dll2_add_tail(&ati->list, &lr->active_tasks); + lr->active_steps++; + + lr->reserved_ram_kib += ns->task->est_peak_mem_kib; + lr->reserved_disk_kib += ns->task->est_disk_kib; + } + } + } lws_end_foreach_dll(d); + } + + lws_dll2_add_tail(&lr->list, &spm->load_report_owner); + if (lws_ss_request_tx(spm->ss)) + lwsl_debug("%s: request tx failed\n", __func__); + + ref->was_active = is_active; + +around: + ; + } lws_end_foreach_dll(p); + + if (any_platform_on_this_spm_active) /* Reschedule the timer only if at least one active instance */ lws_sul_schedule(builder.context, 0, &spm->sul_load_report, - saib_sul_load_report_cb, SAI_LOAD_REPORT_US); + saib_sul_load_report_cb, SAI_LOAD_REPORT_US); } static lws_ss_state_return_t diff --git a/src/builder/b-deletion.c b/src/builder/b-deletion.c index 5c26883..0690e4e 100644 --- a/src/builder/b-deletion.c +++ b/src/builder/b-deletion.c @@ -50,9 +50,6 @@ #if !defined(PATH_MAX) #define PATH_MAX MAX_PATH #endif - -int getpid(void) { return 0; } - #endif #include "b-private.h" diff --git a/src/builder/b-load.c b/src/builder/b-load.c index bdf3a4e..c2325a5 100644 --- a/src/builder/b-load.c +++ b/src/builder/b-load.c @@ -211,7 +211,7 @@ int saib_get_system_cpu(struct sai_builder *b) if (!GetSystemTimes((FILETIME *)&idle, (FILETIME *)&kernel, (FILETIME *)&user)) return -1; - if (b->last_sys_total.QuadPart) { + if (b->last_sys_kernel.QuadPart || b->last_sys_user.QuadPart) { ULONGLONG total_delta, idle_delta; total_delta = (kernel.QuadPart - b->last_sys_kernel.QuadPart) + @@ -220,8 +220,10 @@ int saib_get_system_cpu(struct sai_builder *b) if (total_delta) { n = (int)(((total_delta - idle_delta) * 1000) / total_delta); - if (n > 1000) - n = 1000; +// if (n > 1000) +// n = 1000; + if (n < 0) + n = 0; ret = n; } } diff --git a/src/builder/b-power.c b/src/builder/b-power.c index a174e30..4d13efa 100644 --- a/src/builder/b-power.c +++ b/src/builder/b-power.c @@ -50,9 +50,6 @@ #if !defined(PATH_MAX) #define PATH_MAX MAX_PATH #endif - -int getpid(void) { return 0; } - #endif #include "b-private.h" diff --git a/src/builder/b-suspender.c b/src/builder/b-suspender.c index 051df3c..eb9dce7 100644 --- a/src/builder/b-suspender.c +++ b/src/builder/b-suspender.c @@ -49,8 +49,6 @@ #if !defined(PATH_MAX) #define PATH_MAX MAX_PATH #endif - -int getpid(void) { return 0; } #endif #include "b-private.h" diff --git a/src/builder/b-task.c b/src/builder/b-task.c index 150c2c4..813a50d 100644 --- a/src/builder/b-task.c +++ b/src/builder/b-task.c @@ -255,6 +255,12 @@ saib_set_ns_state(struct sai_nspawn *ns, int state) saib_log_chunk_create(ns, log, (unsigned int)n, 3); + if (state == NSSTATE_EXECUTING_STEPS && ns->spm && + !ns->spm->sul_load_report.list.owner) + lws_sul_schedule(ns->builder->context, 0, + &ns->spm->sul_load_report, + saib_sul_load_report_cb, 1); + if (state == NSSTATE_UPLOADING_ARTIFACTS) saib_start_artifact_upload(ns); @@ -916,6 +922,7 @@ saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len) ns->task = task; /* we are owning this nspawn for the duration */ ns->current_step = task->build_step; + ns->build_step_count = task->build_step_count; if (!ns->current_step) { ns->spins = 0; ns->user_cancel = 0; @@ -1051,11 +1058,6 @@ saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len) lwsl_notice("%s: cancelling suspend grace time\n", __func__); lws_sul_cancel(&ns->builder->sul_idle); - /* if we weren't, we should report load on instances now we're busy */ - if (lws_dll2_is_detached(&spm->sul_load_report.list)) - lws_sul_schedule(ns->builder->context, 0, &spm->sul_load_report, - saib_sul_load_report_cb, 1); - /* * Let the mirror thread get on with things... * diff --git a/src/common/include/private.h b/src/common/include/private.h index ee63fe2..9177117 100644 --- a/src/common/include/private.h +++ b/src/common/include/private.h @@ -68,6 +68,7 @@ typedef struct sai_active_task_info { char task_uuid[65]; char task_name[96]; int build_step; + int total_steps; unsigned int est_peak_mem_kib; unsigned int est_cpu_load_pct; unsigned int est_disk_kib; @@ -78,8 +79,10 @@ typedef struct sai_load_report { lws_dll2_t list; /* For queuing on sai_plat_server */ char builder_name[64]; int core_count; - unsigned int free_ram_kib; - unsigned int free_disk_kib; + unsigned int initial_free_ram_kib; + unsigned int reserved_ram_kib; + unsigned int initial_free_disk_kib; + unsigned int reserved_disk_kib; unsigned int active_steps; unsigned int cpu_percent; lws_dll2_owner_t active_tasks; @@ -136,6 +139,7 @@ typedef struct { int state; int uid; int build_step; + int build_step_count; /* estimations for builder resource consumption */ unsigned int est_peak_mem_kib; @@ -413,6 +417,7 @@ struct sai_env { typedef struct sai_plat_server_ref { lws_dll2_t list; sai_plat_server_t *spm; + char was_active; } sai_plat_server_ref_t; /* @@ -538,7 +543,7 @@ extern const lws_struct_map_t lsm_schema_map_ta[1], lsm_schema_map_plat_simple[1], lsm_event[10], - lsm_task[28], + lsm_task[29], lsm_log[7], lsm_artifact[8], lsm_plat_list[1], @@ -553,7 +558,7 @@ extern const lws_struct_map_t lsm_schema_rebuild[1], lsm_schema_build_metric[1], lsm_schema_sq3_map_build_metric[1], - lsm_load_report_members[7] + lsm_load_report_members[9] ; extern const lws_struct_map_t lsm_build_metric[12]; extern const lws_struct_map_t lsm_plat[8]; diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c index dbc8a9f..2ffe9b3 100644 --- a/src/common/struct-metadata.c +++ b/src/common/struct-metadata.c @@ -25,6 +25,7 @@ const lws_struct_map_t lsm_active_task_info[] = { LSM_CARRAY (sai_active_task_info_t, task_uuid, "task_uuid"), LSM_CARRAY (sai_active_task_info_t, task_name, "task_name"), LSM_SIGNED (sai_active_task_info_t, build_step, "build_step"), + LSM_SIGNED (sai_active_task_info_t, total_steps, "total_steps"), LSM_UNSIGNED (sai_active_task_info_t, est_peak_mem_kib, "est_peak_mem_kib"), LSM_UNSIGNED (sai_active_task_info_t, est_cpu_load_pct, "est_cpu_load_pct"), LSM_UNSIGNED (sai_active_task_info_t, est_disk_kib, "est_disk_kib"), @@ -34,8 +35,10 @@ const lws_struct_map_t lsm_active_task_info[] = { const lws_struct_map_t lsm_load_report_members[] = { LSM_CARRAY (sai_load_report_t, builder_name, "builder_name"), LSM_SIGNED (sai_load_report_t, core_count, "core_count"), - LSM_UNSIGNED (sai_load_report_t, free_ram_kib, "free_ram_kib"), - LSM_UNSIGNED (sai_load_report_t, free_disk_kib, "free_disk_kib"), + LSM_UNSIGNED (sai_load_report_t, initial_free_ram_kib, "initial_free_ram_kib"), + LSM_UNSIGNED (sai_load_report_t, reserved_ram_kib, "reserved_ram_kib"), + LSM_UNSIGNED (sai_load_report_t, initial_free_disk_kib, "initial_free_disk_kib"), + LSM_UNSIGNED (sai_load_report_t, reserved_disk_kib, "reserved_disk_kib"), LSM_UNSIGNED (sai_load_report_t, active_steps, "active_steps"), LSM_UNSIGNED (sai_load_report_t, cpu_percent, "cpu_percent"), LSM_LIST (sai_load_report_t, active_tasks, sai_active_task_info_t, list, @@ -178,6 +181,7 @@ const lws_struct_map_t lsm_task[] = { LSM_STRING_PTR (sai_task_t, git_repo_url, "git_repo_url"), LSM_CARRAY (sai_task_t, script, "script"), LSM_SIGNED (sai_task_t, build_step, "build_step"), + LSM_SIGNED (sai_task_t, build_step_count, "build_step_count"), LSM_UNSIGNED (sai_task_t, est_peak_mem_kib, "est_peak_mem_kib"), LSM_UNSIGNED (sai_task_t, est_cpu_load_pct, "est_cpu_load_pct"), LSM_UNSIGNED (sai_task_t, est_disk_kib, "est_disk_kib"), diff --git a/src/server/s-notification.c b/src/server/s-notification.c index cbe44b3..29df3cf 100644 --- a/src/server/s-notification.c +++ b/src/server/s-notification.c @@ -481,6 +481,9 @@ sai_saifile_lejp_cb(struct lejp_ctx *ctx, char reason) } if (match) { + const char *p; + int c; + /* * For this platform, we want to create a task * associated with this event. Tasks and logs @@ -488,6 +491,14 @@ sai_saifile_lejp_cb(struct lejp_ctx *ctx, char reason) * specific database file for scalability. */ + c = 2; /* git mirror and checkout */ + p = pl->build; + while (*p) + if (*p++ == '\n') + c++; + + pss->sn.t.build_step_count = c; + lws_strexp_init(&sx, sn, exp_cmake, sn->t.build, sizeof(sn->t.build)); @@ -1044,3 +1055,4 @@ saifile_bail: return 0; } + diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c index f5469a4..8d35e54 100644 --- a/src/server/s-ws-builder.c +++ b/src/server/s-ws-builder.c @@ -705,14 +705,14 @@ bail: sai_load_report_t *lr = (sai_load_report_t *)pss->a.dest; lwsl_notice("%s: @@@@@@@@@@@@@@@@@@ loadreport from %s: ram %uk, disk %uk\n", - __func__, lr->builder_name, lr->free_ram_kib, - lr->free_disk_kib); + __func__, lr->builder_name, lr->reserved_ram_kib, + lr->reserved_disk_kib); ssize_t wr = write(2, buf, bl); if (wr != (ssize_t)bl) lwsl_notice("%s: write failed\n", __func__); } - // lwsl_wsi_user(pss->wsi, "SAIM_WSSCH_BUILDER_LOADREPORT broadcasting\n"); + lwsl_wsi_user(pss->wsi, "SAIM_WSSCH_BUILDER_LOADREPORT broadcasting\n"); sais_websrv_broadcast(vhd->h_ss_websrv, (const char *)buf, bl); break; diff --git a/src/web/w-websrv.c b/src/web/w-websrv.c index 0d32f3a..d83ee53 100644 --- a/src/web/w-websrv.c +++ b/src/web/w-websrv.c @@ -224,7 +224,8 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags) break; case SAIS_WS_WEBSRV_RX_LOADREPORT: - /* Forward the final fragment of the load report */ + lwsl_notice("%s: ^^^^^^^^^^^^^^ SAIS_WS_WEBSRV_RX_LOADREPORT forwarding to browser\n", __func__); + lwsl_hexdump_notice(buf, len); saiw_ws_broadcast_raw(vhd, buf, len - (unsigned int)n, 0, lws_write_ws_flags(LWS_WRITE_TEXT, flags & LWSSS_FLAG_SOM, flags & LWSSS_FLAG_EOM)); break; diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c index 1448ab2..28ea478 100644 --- a/src/web/w-ws-browser.c +++ b/src/web/w-ws-browser.c @@ -30,8 +30,6 @@ #include "w-private.h" -extern const lws_struct_map_t lsm_load_report_members[7]; - /* * For decoding specific event data request from browser */
Page fetched 0s ago, creation time: 12ms (vhost etag hits: 0%, cache hits: 0%)