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
Author[]Andy Green <andy@warmcat.com> 2025-08-05 19:29 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-08-05 20:34 UTC
Tree725c9d98d9fdac781ea5fd7f409f9bd4eda1499c   Raw Patch
 
load hierarchy
load hierarchy
diff --git a/assets/sai.css b/assets/sai.css index de2a5bb..793e832 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -717,3 +717,45 @@ img.branch { .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 */ + width: 100%; + position: absolute; + bottom: 0; + left: 0; + transition: height 0.2s ease-out; /* Smoothly animate height changes */ +} + +.inst_idle { + /* An idle instance can have a subtle border or effect if desired */ + border-color: #555; +} + +.inst_busy { + /* A busy instance can have a brighter border */ + border-color: #999; +} + diff --git a/assets/sai.js b/assets/sai.js index 9933114..1786fba 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -845,6 +845,8 @@ function getBuilderGroupKey(platName) { return hostname; } + + function createBuilderDiv(plat) { const platDiv = document.createElement("div"); platDiv.className = "ibuil bdr"; @@ -861,13 +863,17 @@ function createBuilderDiv(plat) { 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 id="instload-` + plat.name.split('.')[0] + `">`; + innerHTML += `<div class="instload" id="instload-${plat.name}">`; // Changed class name for clarity + // Create initial idle squares for (let i = 0; i < plat.instances; i++) { - innerHTML += `<div class="inst_box inst_idle" title="instance ${i}: idle"></div>`; + innerHTML += `<div class="inst_box inst_idle" title="instance ${i}: idle">` + + `<div class="inst_bar"></div>` + + `</div>`; } innerHTML += `</div></td></tr></tbody></table>`; + platDiv.innerHTML = innerHTML; return platDiv; } @@ -933,9 +939,9 @@ function render_builders(jso) (e.peer_ip ? "<br>" + san(e.peer_ip) : ""); /* Add a container for the instance load boxes */ - s += "<div id=\"instload-" + san(e.name).split('.')[0] + "\">"; + s += "<div id=\"instload-" + san(e.name)/*.split('.')[0]*/ + "\">"; for (var i = 0; i < e.instances; i++) { - s += "<div class=\"inst_box inst_idle\" title=\"instance " + i + ": idle\"></div>"; + s += "<div class=\"inst_box inst_idle\" title=\"instance " + i + ": idle\"><div class=\"inst_bar\"></div></div>"; } s += "</div>"; @@ -1489,46 +1495,63 @@ function ws_open_sai() buildersContainer.appendChild(table); break; - case "com.warmcat.sai.loadreport": - - // console.log("received com.warmcat.sai.loadreport" + jso); + case "com.warmcat.sai.loadreport": + if (!jso.platforms || !Array.isArray(jso.platforms)) { + break; + } - if (!jso.builder_name) - break; + for (const platformReport of jso.platforms) { + const platformName = platformReport.platform_name; + if (!platformName) { + continue; + } + + const loadContainer = document.getElementById("instload-" + platformName); + if (!loadContainer) { + continue; + } - const platformName = jso.builder_name; + if (platformReport.loads && Array.isArray(platformReport.loads)) { + const instanceDivs = loadContainer.getElementsByClassName("inst_box"); - console.log("loadreport: builder name " + platformName); - - // The container for the load squares has a predictable ID - const loadContainerId = "instload-" + platformName; - const loadContainer = document.getElementById(loadContainerId); + for (let i = 0; i < platformReport.loads.length; i++) { + const instanceLoad = platformReport.loads[i]; + const instanceDiv = instanceDivs[i]; - if (loadContainer) { + if (!instanceDiv) { + break; + } - // Clear any old load indicators - loadContainer.innerHTML = ""; + let cpu = instanceLoad.cpu_percent / 10.0; + let stateText = instanceLoad.state ? 'busy' : 'idle'; + instanceDiv.title = `Instance ${i}: ${stateText}\nCPU: ${cpu.toFixed(1)}%`; - // Loop through the new load data and create the squares - if (jso.loads && Array.isArray(jso.loads)) { + if (instanceLoad.state) { + instanceDiv.classList.add("inst_busy"); + instanceDiv.classList.remove("inst_idle"); + } else { + instanceDiv.classList.add("inst_idle"); + instanceDiv.classList.remove("inst_busy"); + } - for (const instanceLoad of jso.loads) { - let instanceDiv = document.createElement("div"); - - // Set class for styling (e.g., green for idle, red for busy) - let stateClass = instanceLoad.state ? "inst_busy" : "inst_idle"; - instanceDiv.className = "inst_box " + stateClass; + // The bar is always the first (and only) child of the inst_box div. + const bar = instanceDiv.firstChild; + if (bar && bar.classList.contains("inst_bar")) { + let cpu_percentage = (instanceLoad.cpu_percent / 1000) * 100; + if (cpu_percentage > 100) cpu_percentage = 100; + + // Let's add a minimum height so even a tiny load is visible + if (cpu_percentage > 0 && cpu_percentage < 1) cpu_percentage = 1; - // Set tooltip to show the CPU percentage - let cpu = instanceLoad.cpu_percent / 10.0; - instanceDiv.title = `Instance: ${stateClass.split('_')[1]} \nCPU: ${cpu.toFixed(1)}%`; - - loadContainer.appendChild(instanceDiv); - } + bar.style.height = `${cpu_percentage}%`; + } else { + // This console log will tell us if the bar element is missing + console.error("Could not find .inst_bar child in .inst_box for", instanceDiv); } } - - break; + } + } + break; case "com-warmcat-sai-artifact": console.log(jso); diff --git a/src/builder/b-comms.c b/src/builder/b-comms.c index 958323a..1740f38 100644 --- a/src/builder/b-comms.c +++ b/src/builder/b-comms.c @@ -289,8 +289,8 @@ saib_m_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, n = (int)w; lws_dll2_remove(&lr->list); - lws_start_foreach_dll_safe(struct lws_dll2 *, il, il1, lr->loads.head) { - sai_instance_load_t *i = lws_container_of(il, sai_instance_load_t, list); + lws_start_foreach_dll_safe(struct lws_dll2 *, il, il1, lr->platforms.head) { + sai_platform_load_t *i = lws_container_of(il, sai_platform_load_t, list); lws_dll2_remove(&i->list); free(i); } lws_end_foreach_dll_safe(il, il1); @@ -599,16 +599,22 @@ saib_sul_load_report_cb(struct lws_sorted_usec_list *sul) * under one name. */ lws_strncpy(lr->builder_name, builder.host, sizeof(lr->builder_name)); - if (builder.sai_plat_owner.head) { - sai_plat_t *any_plat = lws_container_of(builder.sai_plat_owner.head, - sai_plat_t, sai_plat_list); - lws_strncpy(lr->platform_name, any_plat->name, sizeof(lr->platform_name)); - } int system_load = saib_get_system_cpu(&builder); + /* + * Iterate all platforms. For each platform, create a sai_platform_load_t + * and populate its list of instance loads. + */ + lws_start_foreach_dll(struct lws_dll2 *, p, builder.sai_plat_owner.head) { - sp = lws_container_of(p, sai_plat_t, sai_plat_list); + sp = lws_container_of(p, sai_plat_t, sai_plat_list); + + sai_platform_load_t *pl = calloc(1, sizeof(*pl)); + if (!pl) + continue; + + lws_strncpy(pl->platform_name, sp->name, sizeof(pl->platform_name)); lws_start_foreach_dll(struct lws_dll2 *, d, sp->nspawn_owner.head) { struct sai_nspawn *ns = lws_container_of(d, struct sai_nspawn, list); @@ -616,20 +622,28 @@ saib_sul_load_report_cb(struct lws_sorted_usec_list *sul) int load = -1; if (il) { - #if defined(__linux__) - /* On Linux, try cgroup first, then fall back to system */ - load = saib_get_cgroup_cpu(ns); - #endif - if (load < 0) - load = system_load; - if (load < 0) /* If system load also failed */ - load = 10; /* Default to 1% */ - il->state = (ns->state == NSSTATE_BUILD); + + if (!il->state) { + /* If the instance is idle, its load is 0, regardless of system load. */ + load = 0; + } else { + /* The instance is busy, try to get a specific load for it. */ + load = saib_get_cgroup_cpu(ns); + if (load < 0) + /* Fall back to system load for busy instances on non-cgroup platforms. */ + load = system_load; + } + + if (load < 0) /* Final fallback for errors */ + load = 10; /* Default to 1% if system load failed */ + il->cpu_percent = (uint16_t)load; - lws_dll2_add_tail(&il->list, &lr->loads); + lws_dll2_add_tail(&il->list, &pl->loads); } } lws_end_foreach_dll(d); + + lws_dll2_add_tail(&pl->list, &lr->platforms); } lws_end_foreach_dll(p); lws_dll2_add_tail(&lr->list, &spm->load_report_owner); diff --git a/src/common/include/private.h b/src/common/include/private.h index 26918cb..387c314 100644 --- a/src/common/include/private.h +++ b/src/common/include/private.h @@ -67,11 +67,17 @@ typedef struct sai_instance_load { /* * load report struct, sent in its own schema. */ -typedef struct sai_load_report { +typedef struct sai_platform_load { lws_dll2_t list; /* Not used, for schema mapping */ - char builder_name[64]; char platform_name[128]; lws_dll2_owner_t loads; +} sai_platform_load_t; + +/* The top-level load report message from a builder */ +typedef struct sai_load_report { + lws_dll2_t list; /* For queuing on sai_plat_server */ + char builder_name[64]; + lws_dll2_owner_t platforms; /* List of sai_platform_load_t */ } sai_load_report_t; /* diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c index 560e6ac..d8568b7 100644 --- a/src/common/struct-metadata.c +++ b/src/common/struct-metadata.c @@ -26,14 +26,19 @@ const lws_struct_map_t lsm_instance_load[] = { LSM_UNSIGNED (sai_instance_load_t, state, "state"), }; +/* Map for a sai_platform_load_t object */ +const lws_struct_map_t lsm_platform_load[] = { + LSM_CARRAY(sai_platform_load_t, platform_name, "platform_name"), + LSM_LIST(sai_platform_load_t, loads, sai_instance_load_t, list, + NULL, lsm_instance_load, "loads"), +}; + const lws_struct_map_t lsm_load_report_members[] = { LSM_CARRAY (sai_load_report_t, builder_name, "builder_name"), - LSM_CARRAY (sai_load_report_t, platform_name, "platform_name"), - LSM_LIST (sai_load_report_t, loads, sai_instance_load_t, list, - NULL, lsm_instance_load, "loads"), + LSM_LIST (sai_load_report_t, platforms, sai_platform_load_t, list, + NULL, lsm_platform_load, "platforms"), }; - static const lws_struct_map_t lsm_plat[] = { LSM_STRING_PTR (sai_plat_t, name, "name"), LSM_UNSIGNED (sai_plat_t, ongoing, "ongoing"), diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c index 17bc2f7..a44f309 100644 --- a/src/server/s-ws-builder.c +++ b/src/server/s-ws-builder.c @@ -46,12 +46,17 @@ static const lws_struct_map_t lsm_instance_load[] = { LSM_UNSIGNED(sai_instance_load_t, state, "state"), }; +static const lws_struct_map_t lsm_platform_load[] = { + LSM_CARRAY(sai_platform_load_t, platform_name, "platform_name"), + LSM_LIST(sai_platform_load_t, loads, sai_instance_load_t, list, + NULL, lsm_instance_load, "loads"), +}; + /* map for the members of the load report object */ static const lws_struct_map_t lsm_load_report_members[] = { LSM_CARRAY(sai_load_report_t, builder_name, "builder_name"), - LSM_CARRAY(sai_load_report_t, platform_name, "platform_name"), - LSM_LIST(sai_load_report_t, loads, sai_instance_load_t, list, - NULL, lsm_instance_load, "loads"), + LSM_LIST (sai_load_report_t, platforms, sai_platform_load_t, list, + NULL, lsm_platform_load, "platforms"), }; /*
Page fetched 0s ago, creation time: 23ms (vhost etag hits: 0%, cache hits: 0%)