| /*
* Sai builder - platform-specific load reporting
*
* Copyright (C) 2025 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
#if defined(WIN32)
#include <windows.h>
#else
#include <unistd.h>
#endif
int
saib_get_cpu_count(void)
{
long nprocs = -1;
#if defined(WIN32)
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
nprocs = sysinfo.dwNumberOfProcessors;
#elif defined(_SC_NPROCESSORS_ONLN)
nprocs = sysconf(_SC_NPROCESSORS_ONLN);
#endif
if (nprocs < 1)
return 1; /* Fallback */
return (int)nprocs;
}
/*
* 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 = 0;
/*
* 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 0; /* 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 0;
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 = 0;
char buf[256];
FILE *f;
f = fopen("/proc/stat", "r");
if (!f)
return 0;
if (!fgets(buf, sizeof(buf) -1, f)) {
fclose(f);
return 0;
}
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 0;
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 0; /* 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 = 0;
if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
(host_info_t)&cpuinfo, &count) != KERN_SUCCESS)
return 0;
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 0; /* No cgroups on Windows */
}
int saib_get_system_cpu(struct sai_builder *b)
{
ULARGE_INTEGER idle, kernel, user;
int n, ret = 0;
if (!GetSystemTimes((FILETIME *)&idle, (FILETIME *)&kernel, (FILETIME *)&user))
return 0;
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) +
(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;
if (n < 0)
n = 0;
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 0; /* Not implemented on this platform */
}
int saib_get_system_cpu(struct sai_builder *b)
{
return 10; /* Return a dummy 1% for unsupported platforms */
}
#endif
|