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-03 04:50 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-08-05 18:53 UTC
Tree3631b1075f250517fc2e019a95969ad2b7c8124a   Raw Patch
 
true-loads
true-loads
diff --git a/assets/index.html b/assets/index.html index 0053d50..ad020fb 100644 --- a/assets/index.html +++ b/assets/index.html @@ -12,7 +12,8 @@ <table><tr><td class="topregion"> <div class="logo"> <img class="logo" src="sai.svg"><br> - <a href="https://warmcat.com/git/sai">Sai git</a> + <span class="logo"><a href="https://warmcat.com/git/sai">Sai git</a></span> + <span class="logo" id="apirev"></span> <div> <div id="login" class="login"> <div id="creds" class="creds hide"> diff --git a/assets/sai.css b/assets/sai.css index 7aeef9c..de2a5bb 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -563,6 +563,18 @@ img.logo { height: auto; } +span.logo { + margin: 3px; + margin-right: 4px; + padding: 2px; + vertical-align: middle; + z-index: 1002; + width: 164px; + height: auto; + font-size: 7pt; + text-align: left; +} + td.summary { background: #f8f8f8; width: 100%; diff --git a/assets/sai.js b/assets/sai.js index 9248896..9933114 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -641,6 +641,10 @@ function sai_event_summary_render(o, now_ut, reset_all_icon) var s, q, ctn = "", wai, s1 = "", n, e = o.e; s = "<table class=\"comp"; + + if (!o.e) + return; + if (e.state == 3) s += " comp_pass"; if (e.state == 4 || e.state == 6) @@ -857,7 +861,7 @@ 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}">`; + innerHTML += `<div id="instload-` + plat.name.split('.')[0] + `">`; for (let i = 0; i < plat.instances; i++) { innerHTML += `<div class="inst_box inst_idle" title="instance ${i}: idle"></div>`; @@ -929,7 +933,7 @@ 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) + "\">"; + 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>"; } @@ -977,7 +981,10 @@ function after_delete() { function ws_open_sai() { var s = "", q, qa, qi, q5, q5s; - + + if (document.getElementById("apirev")) + document.getElementById("apirev").innerHTML = "API rev " + SAI_JS_API_VERSION; + q = window.location.href; console.log(q); qi = q.indexOf("/git/"); @@ -1073,7 +1080,7 @@ function ws_open_sai() var u, ci, n; var now_ut = Math.round((new Date().getTime() / 1000)); - // console.log(msg.data); + console.log(msg.data); // if (msg.data.length < 10) // return; jso = JSON.parse(msg.data); @@ -1483,11 +1490,16 @@ function ws_open_sai() break; case "com.warmcat.sai.loadreport": - if (!jso.platform_name) - break; - const platformName = jso.platform_name; + // console.log("received com.warmcat.sai.loadreport" + jso); + + if (!jso.builder_name) + break; + + const platformName = jso.builder_name; + 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); diff --git a/src/builder/CMakeLists.txt b/src/builder/CMakeLists.txt index f1aba7b..d5866cd 100644 --- a/src/builder/CMakeLists.txt +++ b/src/builder/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS b-artifacts.c b-logproxy.c b-refproxy.c + b-load.c ) set(requirements 1) @@ -54,7 +55,11 @@ if (requirements) endif() target_link_libraries(${SUB} ${SAI_GIT2_LIB_PATH}) - endif() + + if (APPLE) + target_link_libraries(sai-builder "-framework IOKit") + endif() + endif() set(CMAKE_REQUIRED_LIBRARIES ${SAI_GIT2_LIB_PATH}) diff --git a/src/builder/b-comms.c b/src/builder/b-comms.c index 9e7cf97..958323a 100644 --- a/src/builder/b-comms.c +++ b/src/builder/b-comms.c @@ -605,22 +605,28 @@ saib_sul_load_report_cb(struct lws_sorted_usec_list *sul) lws_strncpy(lr->platform_name, any_plat->name, sizeof(lr->platform_name)); } - /* - * Iterate all platforms and their nspawn instances to collect load. - * In a real implementation, you would query the system for CPU usage - * of each nspawn process. For now, we will simulate it. - */ + int system_load = saib_get_system_cpu(&builder); + 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); sai_instance_load_t *il = calloc(1, sizeof(*il)); + 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); - /* Simulate load: 50% if building, 1% if idle */ - il->cpu_percent = il->state ? 500 : 10; + il->cpu_percent = (uint16_t)load; lws_dll2_add_tail(&il->list, &lr->loads); } } lws_end_foreach_dll(d); diff --git a/src/builder/b-load.c b/src/builder/b-load.c new file mode 100644 index 0000000..9d37f16 --- /dev/null +++ b/src/builder/b-load.c @@ -0,0 +1,221 @@ +/* + * Sai builder - platform-specific load reporting + * + * Copyright (C) 2021 Andy Green <andy@warmcat.com> + * + * This file is part of Sai. + * + * Sai is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Sai is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <libwebsockets.h> +#include "b-private.h" + +#if defined(__APPLE__) +#include <mach/mach_host.h> +#endif + +/* + * Returns CPU usage as an integer from 0-1000 (for 0.0% to 100.0%) + * or -1 on error. + */ +#if defined(__linux__) +int +saib_get_cgroup_cpu(struct sai_nspawn *ns) +{ + uint64_t usage_usec = 0; + char path[256], buf[128]; + lws_usec_t now; + FILE *f; + int n, ret = -1; + + /* + * On systemd systems, nspawn creates a scope unit for the container, + * e.g., /sys/fs/cgroup/sai.slice/sai-builder-instance-1.scope + * We can get per-instance CPU usage from the cpu.stat file inside. + */ + lws_snprintf(path, sizeof(path), + "/sys/fs/cgroup/sai.slice/sai-%s.scope/cpu.stat", + ns->fsm.ovname); + + f = fopen(path, "r"); + if (!f) + return -1; /* cgroup file not found, fall back to system load */ + + while (fgets(buf, sizeof(buf) - 1, f)) { + if (sscanf(buf, "usage_usec %llu", + (unsigned long long *)&usage_usec) == 1) { + break; + } + } + fclose(f); + + if (!usage_usec) + return -1; + + now = lws_now_usecs(); + + if (ns->last_cpu_usec_time) { + uint64_t delta_usec = usage_usec - ns->last_cpu_usec; + uint64_t delta_time = (uint64_t)now - (uint64_t)ns->last_cpu_usec_time; + + if (delta_time) { + /* + * Percentage is (cpu_time / wall_time) * 100. + * We multiply by 10 for fixed-point, so * 1000. + */ + n = (int)((delta_usec * 1000) / delta_time); + if (n > 1000) + n = 1000; + ret = n; + } + } + + ns->last_cpu_usec = usage_usec; + ns->last_cpu_usec_time = now; + + return ret; +} + +int +saib_get_system_cpu(struct sai_builder *b) +{ + unsigned long long user, nice, system, idle, iowait, irq, softirq, steal; + uint64_t total, idle_all, total_delta, idle_delta; + int n, ret = -1; + char buf[256]; + FILE *f; + + f = fopen("/proc/stat", "r"); + if (!f) + return -1; + + if (!fgets(buf, sizeof(buf) -1, f)) { + fclose(f); + return -1; + } + fclose(f); + + n = sscanf(buf, "cpu %llu %llu %llu %llu %llu %llu %llu %llu", + &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal); + if (n < 4) + return -1; + + idle_all = idle + iowait; + total = user + nice + system + idle_all + irq + softirq + steal; + + if (b->last_sys_total) { + total_delta = total - b->last_sys_total; + idle_delta = idle_all - b->last_sys_idle; + + if (total_delta) { + n = (int)(((total_delta - idle_delta) * 1000) / total_delta); + if (n > 1000) + n = 1000; + ret = n; + } + } + + b->last_sys_total = total; + b->last_sys_idle = idle_all; + + return ret; +} +#elif defined(__APPLE__) +int saib_get_cgroup_cpu(struct sai_nspawn *ns) +{ + return -1; /* No cgroups on macOS */ +} + +int saib_get_system_cpu(struct sai_builder *b) +{ + host_cpu_load_info_data_t cpuinfo; + mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + uint64_t total_ticks = 0, idle_ticks = 0; + uint64_t total_delta, idle_delta; + int n, ret = -1; + + if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, + (host_info_t)&cpuinfo, &count) != KERN_SUCCESS) + return -1; + + total_ticks = cpuinfo.cpu_ticks[CPU_STATE_USER] + + cpuinfo.cpu_ticks[CPU_STATE_SYSTEM] + + cpuinfo.cpu_ticks[CPU_STATE_NICE] + + cpuinfo.cpu_ticks[CPU_STATE_IDLE]; + idle_ticks = cpuinfo.cpu_ticks[CPU_STATE_IDLE]; + + if (b->last_sys_total) { + total_delta = total_ticks - b->last_sys_total; + idle_delta = idle_ticks - b->last_sys_idle; + + if (total_delta) { + n = (int)(((total_delta - idle_delta) * 1000) / total_delta); + if (n > 1000) + n = 1000; + ret = n; + } + } + + b->last_sys_total = total_ticks; + b->last_sys_idle = idle_ticks; + + return ret; +} +#elif defined(WIN32) +int saib_get_cgroup_cpu(struct sai_nspawn *ns) +{ + return -1; /* No cgroups on Windows */ +} + +int saib_get_system_cpu(struct sai_builder *b) +{ + ULARGE_INTEGER idle, kernel, user; + int n, ret = -1; + + if (!GetSystemTimes((FILETIME *)&idle, (FILETIME *)&kernel, (FILETIME *)&user)) + return -1; + + if (b->last_sys_total.QuadPart) { + ULONGLONG total_delta, idle_delta; + + total_delta = (kernel.QuadPart - b->last_sys_kernel.QuadPart) + + (user.QuadPart - b->last_sys_user.QuadPart); + idle_delta = idle.QuadPart - b->last_sys_idle.QuadPart; + + if (total_delta) { + n = (int)(((total_delta - idle_delta) * 1000) / total_delta); + if (n > 1000) + n = 1000; + ret = n; + } + } + + b->last_sys_idle = idle; + b->last_sys_kernel = kernel; + b->last_sys_user = user; + + return ret; +} +#else +int saib_get_cgroup_cpu(struct sai_nspawn *ns) +{ + return -1; /* Not implemented on this platform */ +} +int saib_get_system_cpu(struct sai_builder *b) +{ + return 10; /* Return a dummy 1% for unsupported platforms */ +} +#endif diff --git a/src/builder/b-private.h b/src/builder/b-private.h index e37159f..61255f6 100644 --- a/src/builder/b-private.h +++ b/src/builder/b-private.h @@ -131,6 +131,16 @@ struct sai_builder { sai_mirror_instance_t mi; char path[256]; + +#if defined(__linux__) || defined(__APPLE__) + /* For system-wide load calculation */ + uint64_t last_sys_total; + uint64_t last_sys_idle; +#elif defined(WIN32) + ULARGE_INTEGER last_sys_idle; + ULARGE_INTEGER last_sys_kernel; + ULARGE_INTEGER last_sys_user; +#endif char stay; }; @@ -225,3 +235,9 @@ saib_handle_resource_result(struct sai_plat_server *spm, const char *in, size_t void saib_sul_load_report_cb(struct lws_sorted_usec_list *sul); + +int +saib_get_cgroup_cpu(struct sai_nspawn *ns); + +int +saib_get_system_cpu(struct sai_builder *b); diff --git a/src/common/include/private.h b/src/common/include/private.h index d6a75f2..26918cb 100644 --- a/src/common/include/private.h +++ b/src/common/include/private.h @@ -161,6 +161,9 @@ struct sai_nspawn { sai_plat_t *sp; /* the sai_plat */ struct sai_plat_server *spm; /* the sai plat / server with the ss / wsi */ + uint64_t last_cpu_usec; + lws_usec_t last_cpu_usec_time; + size_t chunk_cache_size; const char *server_name; /* sai-server name who triggered this, eg, 'warmcat' */ diff --git a/src/web/w-comms.c b/src/web/w-comms.c index ffe07c5..6c8a505 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -1219,7 +1219,8 @@ clean_spa: break; } - return saiw_ws_json_tx_browser(vhd, pss, buf, sizeof(buf)); + saiw_ws_json_tx_browser(vhd, pss, buf, sizeof(buf)); + break; default: passthru: diff --git a/src/web/w-private.h b/src/web/w-private.h index 4c88487..884a6c2 100644 --- a/src/web/w-private.h +++ b/src/web/w-private.h @@ -321,7 +321,7 @@ saiw_sched_destroy(struct lws_dll2 *d, void *user); void -saiw_ws_broadcast_raw(struct vhd *vhd, const void *buf, size_t len, unsigned int min_api_version); +saiw_ws_broadcast_raw(struct vhd *vhd, const void *buf, size_t len, unsigned int min_api_version, int flags); void saiw_browser_state_changed(struct pss *pss, int established); diff --git a/src/web/w-websrv.c b/src/web/w-websrv.c index 9c75049..803e557 100644 --- a/src/web/w-websrv.c +++ b/src/web/w-websrv.c @@ -89,10 +89,10 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags) return LWSSSSRET_DISCONNECT_ME; } - if (!(flags & LWSSS_FLAG_EOM)) - return 0; +// if (!(flags & LWSSS_FLAG_EOM)) +// return 0; - lwsl_notice("%s: schema idx %d parsed correctly from sai-server\n", __func__, m->a.top_schema_index); + // lwsl_notice("%s: schema idx %d parsed correctly from sai-server\n", __func__, m->a.top_schema_index); switch (m->a.top_schema_index) { @@ -117,7 +117,8 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags) m->a.ac = NULL; vhd->builders_owner = &((sai_plat_owner_t *)m->a.dest)->plat_owner; - saiw_ws_broadcast_raw(vhd, buf, len, 0); + saiw_ws_broadcast_raw(vhd, buf, len, 0, + lws_write_ws_flags(LWS_WRITE_TEXT, flags & LWSSS_FLAG_SOM, flags & LWSSS_FLAG_EOM)); break; case SAIS_WS_WEBSRV_RX_OVERVIEW: @@ -148,9 +149,10 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags) case SAIS_WS_WEBSRV_RX_LOADREPORT: /* A builder sent a load report, forward to all browsers */ - lwsl_notice("%s: ===== Received load report, broadcasting to %d browsers\n", - __func__, (int)vhd->browsers.count); - saiw_ws_broadcast_raw(vhd, buf, len, 2); + // lwsl_notice("%s: ===== Received load report, broadcasting to %d browsers\n", + // __func__, (int)vhd->browsers.count); + saiw_ws_broadcast_raw(vhd, buf, len, 2, + 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 9a44614..46f05d6 100644 --- a/src/web/w-ws-browser.c +++ b/src/web/w-ws-browser.c @@ -1,7 +1,7 @@ /* * Sai server - ./src/server/m-ws-browser.c * - * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com> + * Copyright (C) 2019 - 2025 Andy Green <andy@warmcat.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -35,7 +35,7 @@ * all connected browsers, eg, for load reports. */ void -saiw_ws_broadcast_raw(struct vhd *vhd, const void *buf, size_t len, unsigned int api_ver_min) +saiw_ws_broadcast_raw(struct vhd *vhd, const void *buf, size_t len, unsigned int api_ver_min, int flags) { int eff = 0; // lwsl_err("%s: sai-web broadcasting to browsers\n", __func__); @@ -43,13 +43,15 @@ saiw_ws_broadcast_raw(struct vhd *vhd, const void *buf, size_t len, unsigned int lws_start_foreach_dll(struct lws_dll2 *, p, vhd->browsers.head) { struct pss *pss = lws_container_of(p, struct pss, same); + int *pi = (int *)((const char *)buf -sizeof(int)); if (pss->js_api_version >= api_ver_min) { eff++; - if (lws_buflist_append_segment(&pss->raw_tx, buf, len) >= 0) + *pi = flags; + + if (lws_buflist_append_segment(&pss->raw_tx, buf - sizeof(int), len + sizeof(int)) > 0) lws_callback_on_writable(pss->wsi); } - } lws_end_foreach_dll(p); lwsl_notice("%s: broadcast to %d / %d browsers\n", __func__, @@ -625,29 +627,34 @@ again: // lwsl_notice("%s: send_state %d, pss %p, wsi %p\n", __func__, // pss->send_state, pss, pss->wsi); + /* + * Send anything waiting on broadcast_raw buflist first + */ + if (pss->raw_tx) { - char som, eom; - int used; + char som, eom, rb[4096]; + int used, *pi = (int *)rb; - p = start; /* buf + LWS_PRE */ - used = lws_buflist_fragment_use(&pss->raw_tx, p, - lws_ptr_diff_size_t(end, p), &som, &eom); + used = lws_buflist_fragment_use(&pss->raw_tx, (uint8_t *)rb, + sizeof(rb), &som, &eom); if (!used) return 0; - flags = lws_write_ws_flags(LWS_WRITE_TEXT, som, eom); - if (lws_write(pss->wsi, p, (size_t)used, (enum lws_write_protocol)flags) < 0) - return -1; + // lwsl_wsi_notice(pss->wsi, "writing %d bytes flags 0x%x: '%.*s'", + // (int)(used - (int)sizeof(int)), (int)*pi, + // (int)(used - (int)sizeof(int)), rb + sizeof(int)); - /* - * if there are more fragments, we must exit now and wait for - * the next writable callback to send the rest. Otherwise, we - * can fall through and check for other work to do. - */ - if (pss->raw_tx) { - lws_callback_on_writable(pss->wsi); - return 0; + if (lws_write(pss->wsi, (uint8_t *)rb + sizeof(int), + (size_t)used - sizeof(int), + (enum lws_write_protocol)*pi) < 0) { + lwsl_wsi_err(pss->wsi, "attempt to write %d failed", (int)used - (int)sizeof(int)); + + return -1; } + + lws_callback_on_writable(pss->wsi); + + return 0; } if (pss->sched.count)
Page fetched 0s ago, creation time: 8ms (vhost etag hits: 0%, cache hits: 0%)