Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 117 additions & 6 deletions src/foundation/system_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
*
* macOS: sysctlbyname for core counts, hw.memsize for RAM.
* BSD: sysconf + sysctl(HW_PHYSMEM64 / HW_PHYSMEM).
* Linux: sysconf + sysinfo().
* Linux: sysconf + sysinfo(), with cgroup-aware overrides when running
* inside a container so the limits reflect the cgroup's effective
* CPU quota and memory cap rather than the host's totals.
* Windows: GetSystemInfo + GlobalMemoryStatusEx.
*
* Results are cached after first call (immutable hardware properties).
Expand All @@ -12,6 +14,7 @@

enum { DEFAULT_CORES = 1, MIN_WORKERS = 1 };
#include "foundation/platform.h"
#include "foundation/system_info_internal.h"
#include <stdint.h> // uint64_t
#include <string.h>

Expand All @@ -27,9 +30,12 @@ enum { DEFAULT_CORES = 1, MIN_WORKERS = 1 };
#include <sys/types.h>
#include <sys/sysctl.h>
#else /* Linux */
#include <unistd.h>
/* limits.h for ULLONG_MAX, stdio.h for fopen/fread, stdlib.h for strto*. */
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysinfo.h>

#include <unistd.h>
#endif

/* ── macOS detection ─────────────────────────────────────────────── */
Expand Down Expand Up @@ -103,19 +109,124 @@ static cbm_system_info_t detect_system_bsd(void) {

#else /* Linux */

/* Read up to (bufsz-1) bytes from `path` into `buf`, NUL-terminate, and strip
* trailing whitespace. Returns the (stripped) byte count, or -1 if the file
* could not be opened or read. */
static int read_small_file(const char *path, char *buf, size_t bufsz) {
FILE *fp = fopen(path, "re");
if (fp == NULL) {
return -1;
}
size_t n = fread(buf, 1, bufsz - 1, fp);
fclose(fp);
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == ' ' || buf[n - 1] == '\t')) {
n--;
}
buf[n] = '\0';
return (int)n;
}

/* Effective CPU count from a cgroup file tree. See header for contract. */
int cbm_detect_cgroup_cpus(const char *cgroup_root) {
char path[CBM_PATH_MAX];
char buf[CBM_SZ_64];

/* cgroup v2: "<root>/cpu.max" — "<quota> <period>" or "max <period>". */
snprintf(path, sizeof(path), "%s/cpu.max", cgroup_root);
if (read_small_file(path, buf, sizeof(buf)) > 0) {
if (strncmp(buf, "max", 3) == 0) {
return -1; /* no quota → caller falls back to sysconf */
}
long quota = 0;
long period = 0;
if (sscanf(buf, "%ld %ld", &quota, &period) == 2 && quota > 0 && period > 0) {
long n = (quota + period - 1) / period; /* ceil(quota/period) */
return n > 0 ? (int)n : MIN_WORKERS;
}
return -1;
}

/* cgroup v1: ".../cpu/cpu.cfs_quota_us" and ".../cpu/cpu.cfs_period_us".
* A quota of -1 means unlimited in cgroup v1. */
snprintf(path, sizeof(path), "%s/cpu/cpu.cfs_quota_us", cgroup_root);
if (read_small_file(path, buf, sizeof(buf)) <= 0) {
return -1;
}
long quota = strtol(buf, NULL, CBM_DECIMAL_BASE);
if (quota <= 0) {
return -1;
}

snprintf(path, sizeof(path), "%s/cpu/cpu.cfs_period_us", cgroup_root);
if (read_small_file(path, buf, sizeof(buf)) <= 0) {
return -1;
}
long period = strtol(buf, NULL, CBM_DECIMAL_BASE);
if (period <= 0) {
return -1;
}

long n = (quota + period - 1) / period;
return n > 0 ? (int)n : MIN_WORKERS;
}

/* Effective memory limit from a cgroup file tree. See header for contract. */
size_t cbm_detect_cgroup_mem(const char *cgroup_root) {
char path[CBM_PATH_MAX];
char buf[CBM_SZ_64];

/* cgroup v2: "<root>/memory.max" — "max" or integer bytes. */
snprintf(path, sizeof(path), "%s/memory.max", cgroup_root);
if (read_small_file(path, buf, sizeof(buf)) > 0) {
if (strncmp(buf, "max", 3) == 0) {
return 0;
}
char *end = NULL;
unsigned long long n = strtoull(buf, &end, CBM_DECIMAL_BASE);
if (end == buf || n == 0) {
return 0;
}
return (size_t)n;
}

/* cgroup v1: ".../memory/memory.limit_in_bytes". The sentinel for
* "unlimited" is a very large value (~PAGE_COUNTER_MAX); treat anything
* past half of ULLONG_MAX as effectively unlimited. */
snprintf(path, sizeof(path), "%s/memory/memory.limit_in_bytes", cgroup_root);
if (read_small_file(path, buf, sizeof(buf)) <= 0) {
return 0;
}
char *end = NULL;
unsigned long long n = strtoull(buf, &end, CBM_DECIMAL_BASE);
if (end == buf || n == 0 || n >= (ULLONG_MAX / 2)) {
return 0;
}
return (size_t)n;
}

static cbm_system_info_t detect_system_linux(void) {
cbm_system_info_t info;
memset(&info, 0, sizeof(info));

/* Host fallbacks. */
long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
info.total_cores = nprocs > 0 ? (int)nprocs : 1;
info.perf_cores = info.total_cores; /* Linux doesn't distinguish P/E */
int host_cpus = nprocs > 0 ? (int)nprocs : DEFAULT_CORES;

size_t host_ram = 0;
struct sysinfo si;
if (sysinfo(&si) == 0) {
info.total_ram = (size_t)si.totalram * (size_t)si.mem_unit;
host_ram = (size_t)si.totalram * (size_t)si.mem_unit;
}

/* Cgroup-aware overrides. min(cgroup, host) defends against
* mis-mounted cgroups that report values larger than the host. */
int cg_cpus = cbm_detect_cgroup_cpus("/sys/fs/cgroup");
info.total_cores = (cg_cpus > 0 && cg_cpus < host_cpus) ? cg_cpus : host_cpus;
info.perf_cores = info.total_cores; /* Linux doesn't distinguish P/E */

size_t cg_ram = cbm_detect_cgroup_mem("/sys/fs/cgroup");
info.total_ram = (cg_ram > 0 && (host_ram == 0 || cg_ram < host_ram)) ? cg_ram : host_ram;

return info;
}

Expand Down
44 changes: 44 additions & 0 deletions src/foundation/system_info_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* system_info_internal.h — Internal helpers exposed for testing.
*
* These functions are implementation details of system_info.c; they are
* declared here only so that test_platform.c can drive them against a
* fake cgroup filesystem. Production code outside system_info.c should
* use the public APIs in platform.h instead.
*/
#ifndef CBM_FOUNDATION_SYSTEM_INFO_INTERNAL_H
#define CBM_FOUNDATION_SYSTEM_INFO_INTERNAL_H

#include <stddef.h>

#ifdef __linux__

/*
* Effective CPU count for the cgroup rooted at `cgroup_root`.
*
* Reads (in order):
* 1. cgroup v2: "<cgroup_root>/cpu.max" ("<quota> <period>" or "max ...")
* 2. cgroup v1: "<cgroup_root>/cpu/cpu.cfs_quota_us" + ".../cpu.cfs_period_us"
*
* Returns ceil(quota / period) (>= 1) when a valid CPU quota is in place.
* Returns -1 when no cgroup limit is present (caller should fall back to
* sysconf(_SC_NPROCESSORS_ONLN)).
*/
int cbm_detect_cgroup_cpus(const char *cgroup_root);

/*
* Effective memory limit (bytes) for the cgroup rooted at `cgroup_root`.
*
* Reads (in order):
* 1. cgroup v2: "<cgroup_root>/memory.max" ("max" or integer bytes)
* 2. cgroup v1: "<cgroup_root>/memory/memory.limit_in_bytes"
*
* Returns the byte count when a finite limit is in place. Returns 0 when
* no cgroup limit is present, the limit is "max"/unlimited, or the value
* is so large it represents the cgroup-v1 "unlimited" sentinel.
*/
size_t cbm_detect_cgroup_mem(const char *cgroup_root);

#endif /* __linux__ */

#endif /* CBM_FOUNDATION_SYSTEM_INFO_INTERNAL_H */
Loading