skip to content
==> src/load_host.c <==
1/* The host sample (Linux readers), rates, the machine region and the member summary. */
2#include "mesh/load_host.h"
3#include "mesh/abi/backend.h"
4#include "mesh/buf.h"
5
6#include <dirent.h>
7#include <errno.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/resource.h>
12#include <sys/statvfs.h>
13
14/* f32::round of a value >= 0, without libm. */
15static float roundf_pos(float x) {
16    float t = (float)(uint32_t)x;
17    return x - t >= 0.5f ? t + 1 : t;
18}
19
20static void putf(uint8_t *b, size_t off, float v) {
21    uint32_t u;
22    memcpy(&u, &v, 4);
23    for (int i = 0; i < 4; i++) b[off + (size_t)i] = (uint8_t)(u >> (8 * i));
24}
25static void putu(uint8_t *b, size_t off, uint32_t v) {
26    for (int i = 0; i < 4; i++) b[off + (size_t)i] = (uint8_t)(v >> (8 * i));
27}
28static void putu64(uint8_t *b, size_t off, uint64_t v) {
29    for (int i = 0; i < 8; i++) b[off + (size_t)i] = (uint8_t)(v >> (8 * i));
30}
31static uint32_t getu(const uint8_t *b, size_t off) {
32    return (uint32_t)b[off] | (uint32_t)b[off + 1] << 8 | (uint32_t)b[off + 2] << 16 | (uint32_t)b[off + 3] << 24;
33}
34static uint64_t getu64(const uint8_t *b, size_t off) { return (uint64_t)getu(b, off) | (uint64_t)getu(b, off + 4) << 32; }
35
36/* A whole file as text, or NULL with *err ("<p>: <io error>"). */
37static char *slurp(const char *p, char **err) {
38    FILE *f = fopen(p, "r");
39    if (!f) {
40        if (err) *err = mesh_buf_fmt("%s: %s (os error %d)", p, strerror(errno), errno);
41        return NULL;
42    }
43    mesh_buf b = MESH_BUF_INIT;
44    char chunk[4096];
45    size_t got;
46    while ((got = fread(chunk, 1, sizeof chunk, f)) > 0) mesh_buf_put(&b, chunk, got);
47    fclose(f);
48    mesh_buf_reserve(&b, 0);
49    return mesh_buf_take(&b);
50}
51
52const char *mesh_core_classes(uint8_t *out, size_t n) {
53    static const char *const files[2] = {"cpu_capacity", "cpufreq/cpuinfo_max_freq"};
54    static const char *const what[2] = {"cpu_capacity", "cpuinfo_max_freq"};
55    uint64_t *v = mesh_xcalloc(n, sizeof *v);
56    bool *have = mesh_xcalloc(n, sizeof *have);
57    for (int f = 0; f < 2; f++) {
58        bool any = false;
59        for (size_t i = 0; i < n; i++) {
60            char p[128];
61            snprintf(p, sizeof p, "/sys/devices/system/cpu/cpu%zu/%s", i, files[f]);
62            char *t = slurp(p, NULL);
63            have[i] = false;
64            if (t) {
65                char *s = t, *e = t + strlen(t);
66                while (*s == ' ' || (*s >= 9 && *s <= 13)) s++;
67                while (e > s && (e[-1] == ' ' || (e[-1] >= 9 && e[-1] <= 13))) e--;
68                char *end;
69                errno = 0;
70                unsigned long long x = s < e && *s >= '0' && *s <= '9' ? strtoull(s, &end, 10) : 0;
71                if (s < e && *s >= '0' && *s <= '9' && errno == 0 && end == e) { v[i] = x; have[i] = true; any = true; }
72                free(t);
73            }
74        }
75        if (any) {
76            mesh_classify(v, have, n, out);
77            free(v);
78            free(have);
79            return what[f];
80        }
81    }
82    for (size_t i = 0; i < n; i++) out[i] = MESH_CORE_UNKNOWN;
83    free(v);
84    free(have);
85    return "none";
86}
87
88#if defined(__linux__)
89bool mesh_has_psi(void) {
90    FILE *f = fopen("/proc/pressure/cpu", "r");
91    if (!f) return errno != ENOENT && errno != ENOTDIR;
92    fclose(f);
93    return true;
94}
95
96static uint64_t self_cpu_us(void) {
97    struct rusage r;
98    getrusage(RUSAGE_SELF, &r);
99    return (uint64_t)r.ru_utime.tv_sec * 1000000 + (uint64_t)r.ru_utime.tv_usec + (uint64_t)r.ru_stime.tv_sec * 1000000 +
100           (uint64_t)r.ru_stime.tv_usec;
101}
102
103/* blocks x size / 2^20 in 128 bits (as the Rust's u128), clamped to u32. */
104static uint32_t mib(uint64_t a, uint64_t b) {
105    uint64_t al = a & 0xffffffffu, ah = a >> 32, bl = b & 0xffffffffu, bh = b >> 32;
106    uint64_t ll = al * bl, lh = al * bh, hl = ah * bl, hh = ah * bh;
107    uint64_t mid = (ll >> 32) + (lh & 0xffffffffu) + (hl & 0xffffffffu);
108    uint64_t lo = (ll & 0xffffffffu) | mid << 32;
109    uint64_t hi = hh + (lh >> 32) + (hl >> 32) + (mid >> 32);
110    uint64_t q_hi = hi >> 20, q_lo = lo >> 20 | hi << 44;
111    return q_hi || q_lo > UINT32_MAX ? UINT32_MAX : (uint32_t)q_lo;
112}
113
114static int disk(const char *dir, uint32_t *free_mb, uint32_t *total_mb, char **err) {
115    struct statvfs v;
116    if (statvfs(dir, &v) != 0) { *err = mesh_buf_fmt("statvfs %s: %s (os error %d)", dir, strerror(errno), errno); return -1; }
117    *free_mb = mib((uint64_t)v.f_bavail, (uint64_t)v.f_frsize);
118    *total_mb = mib((uint64_t)v.f_blocks, (uint64_t)v.f_frsize);
119    return 0;
120}
121
122int mesh_host_read(mesh_host_sample *h, const char *dir, bool psi_ok, char **err) {
123    memset(h, 0, sizeof *h);
124    char *t = slurp("/proc/stat", err);
125    if (!t) return -1;
126    int r = mesh_parse_proc_stat(t, &h->cpu_all, h->cpus, h->have, &h->ncpus, err);
127    free(t);
128    if (r) return -1;
129    if (!(t = slurp("/proc/meminfo", err))) return -1;
130    mesh_meminfo m;
131    r = mesh_parse_meminfo(t, &m, err);
132    free(t);
133    if (r) return -1;
134    if (psi_ok) {
135        static const char *const files[3] = {"/proc/pressure/cpu", "/proc/pressure/memory", "/proc/pressure/io"};
136        for (int i = 0; i < 3; i++) {
137            if (!(t = slurp(files[i], err))) return -1;
138            r = mesh_parse_psi(t, &h->psi[i], err);
139            free(t);
140            if (r) return -1;
141        }
142        h->has_psi = true;
143    }
144    char *st = slurp("/proc/self/status", err);
145    if (!st) return -1;
146    if (disk(dir, &h->disk_free_mb, &h->disk_total_mb, err)) { free(st); return -1; }
147    /* The hottest thermal zone. */
148    DIR *d = opendir("/sys/class/thermal");
149    if (d) {
150        struct dirent *e;
151        long long best = 0;
152        bool any = false;
153        while ((e = readdir(d))) {
154            if (strncmp(e->d_name, "thermal_zone", 12) != 0) continue;
155            char p[300];
156            snprintf(p, sizeof p, "/sys/class/thermal/%s/temp", e->d_name);
157            char *tt = slurp(p, NULL);
158            if (!tt) continue;
159            char *s = tt, *end;
160            while (*s == ' ' || *s == '\t') s++;
161            errno = 0;
162            long long x = strtoll(s, &end, 10);
163            while (*end == '\n' || *end == ' ' || *end == '\r' || *end == '\t') end++;
164            if (end != s && !*end && errno == 0 && (!any || x > best)) { best = x; any = true; }
165            free(tt);
166        }
167        closedir(d);
168        if (any) { h->has_temp = true; h->temp_c = (float)best / 1000.0f; }
169    }
170    if (!(t = slurp("/proc/loadavg", err))) { free(st); return -1; }
171    r = mesh_parse_loadavg(t, h->loadavg, err);
172    free(t);
173    if (r) { free(st); return -1; }
174    h->mem_total_mb = (uint32_t)(m.total_kb / 1024);
175    h->mem_avail_mb = (uint32_t)(m.avail_kb / 1024);
176    h->swap_total_mb = (uint32_t)(m.swap_total_kb / 1024);
177    h->swap_used_mb = (uint32_t)((m.swap_total_kb > m.swap_free_kb ? m.swap_total_kb - m.swap_free_kb : 0) / 1024);
178    h->mem_free = m.total_kb > 0 ? (float)m.avail_kb / (float)m.total_kb : 0.0f;
179    if (!(t = slurp("/proc/vmstat", err))) { free(st); return -1; }
180    bool ok = mesh_proc_field(t, "pswpout", &h->swap_out);
181    free(t);
182    if (!ok) { *err = mesh_xstrdup("/proc/vmstat: no pswpout"); free(st); return -1; }
183    h->self_cpu_us = self_cpu_us();
184    uint64_t rss, threads;
185    if (!mesh_proc_field(st, "VmRSS", &rss)) { *err = mesh_xstrdup("/proc/self/status: no VmRSS"); free(st); return -1; }
186    if (!mesh_proc_field(st, "Threads", &threads)) { *err = mesh_xstrdup("/proc/self/status: no Threads"); free(st); return -1; }
187    free(st);
188    h->self_rss_mb = (float)rss / 1024.0f;
189    h->self_threads = (uint32_t)threads;
190    return 0;
191}
192#else
193bool mesh_has_psi(void) { return false; }
194int mesh_host_read(mesh_host_sample *h, const char *dir, bool psi_ok, char **err) {
195    (void)dir, (void)psi_ok;
196    memset(h, 0, sizeof *h);
197    *err = mesh_xstrdup("load: host counters are read on Linux only in this port");
198    return -1;
199}
200#endif
201
202void mesh_host_derive(const mesh_host_sample *now, const mesh_host_sample *prev, const uint8_t *cls, size_t ncls, double dt,
203                      mesh_host_derived *d) {
204    memset(d, 0, sizeof *d);
205    d->n = now->ncpus;
206    for (size_t i = 0; i < now->ncpus; i++) {
207        if (!now->have[i] || i >= prev->ncpus || !prev->have[i]) continue;
208        float io;
209        mesh_cpu_times_since(&now->cpus[i], &prev->cpus[i], &d->util[i], &io);
210        d->have_util[i] = true;
211    }
212    /* class.get(i): a CPU past the class table has no class. */
213    uint8_t c[MESH_MAX_CPUS];
214    for (size_t i = 0; i < d->n; i++) c[i] = i < ncls ? cls[i] : 0xff;
215    float all;
216    mesh_class_util(d->util, d->have_util, c, d->n, &all, &d->big, &d->little);
217    mesh_cpu_times_since(&now->cpu_all, &prev->cpu_all, &d->all, &d->iowait);
218    uint64_t dt_us = (uint64_t)(dt * 1e6);
219    if (now->has_psi && prev->has_psi) {
220        d->has_psi = true;
221        d->psi[0] = mesh_psi_rate(now->psi[0].some_total, prev->psi[0].some_total, dt_us);
222        d->psi[1] = mesh_psi_rate(now->psi[1].some_total, prev->psi[1].some_total, dt_us);
223        d->psi[2] = mesh_psi_rate(now->psi[1].full_total, prev->psi[1].full_total, dt_us);
224        d->psi[3] = mesh_psi_rate(now->psi[2].some_total, prev->psi[2].some_total, dt_us);
225        d->psi[4] = mesh_psi_rate(now->psi[2].full_total, prev->psi[2].full_total, dt_us);
226    }
227    double dts = dt > 1e-3 ? dt : 1e-3;
228    d->meshd_cpu = (float)((double)(now->self_cpu_us > prev->self_cpu_us ? now->self_cpu_us - prev->self_cpu_us : 0) / 1e6 / dts);
229    d->swap_out_s = (float)((double)(now->swap_out > prev->swap_out ? now->swap_out - prev->swap_out : 0) / dts);
230}
231
232void mesh_machine_bytes(uint8_t b[MESH_MACHINE_BYTES], const mesh_host_sample *h, const mesh_host_derived *d, const uint8_t *cls,
233                        size_t ncls, const mesh_share *gpu1, const mesh_share *gpu10, uint32_t max_gap_ns, float gaps_s, float obs,
234                        uint64_t at_ms, uint32_t samples, uint32_t sample_us) {
235#define O(off) ((size_t)(off) - MESH_S_LOAD)
236    memset(b, 0, MESH_MACHINE_BYTES);
237    uint32_t flags = 0;
238    putu64(b, O(MESH_S_LOAD_AT_MS), at_ms);
239    putu(b, O(MESH_S_LOAD_SAMPLES), samples);
240    bool a_some = gpu1->has_share || gpu10->has_share;
241    float a = gpu1->has_share ? gpu1->share : gpu10->share;
242    if (a_some && gpu10->has_share) {
243        float t = gpu10->share;
244        flags |= MESH_LF_GPU;
245        putf(b, O(MESH_S_GPU_SHARE), a);
246        putf(b, O(MESH_S_GPU_SHARE10), t);
247        putf(b, O(MESH_S_GPU_SLICE_US), gpu10->slice_us);
248        putf(b, O(MESH_S_GPU_GAP_US), gpu10->gap_us);
249        putf(b, O(MESH_S_GPU_QUEUE_US), gpu10->queue_us);
250        float inf;
251        uint32_t ib = 0x7f800000u;
252        memcpy(&inf, &ib, 4);
253        putf(b, O(MESH_S_GPU_INFLATE), t > 0.0f ? 1.0f / t : inf);
254    }
255    putf(b, O(MESH_S_GPU_MAX_GAP_US), (float)max_gap_ns / 1e3f);
256    putf(b, O(MESH_S_GPU_GAPS_S), gaps_s);
257    putf(b, O(MESH_S_GPU_OBS), obs);
258    if (h->has_gpu_util) { flags |= MESH_LF_GPU_EXT; putf(b, O(MESH_S_GPU_UTIL_EXT), h->gpu_util); }
259    putf(b, O(MESH_S_CPU_UTIL), d->all);
260    putf(b, O(MESH_S_CPU_BIG), d->big);
261    putf(b, O(MESH_S_CPU_LITTLE), d->little);
262    putf(b, O(MESH_S_CPU_IOWAIT), d->iowait);
263    for (int i = 0; i < 3; i++) putf(b, O(MESH_S_LOADAVG) + 4 * (size_t)i, h->loadavg[i]);
264    size_t n = h->ncpus < MESH_MAX_CPUS ? h->ncpus : MESH_MAX_CPUS;
265    size_t m = n < ncls ? n : ncls;
266    uint32_t nb = 0, nl = 0, online = 0;
267    for (size_t i = 0; i < m; i++) { nb += cls[i] == MESH_CORE_BIG; nl += cls[i] == MESH_CORE_LITTLE; }
268    for (size_t i = 0; i < h->ncpus; i++) online += h->have[i];
269    if (nb + nl > 0) flags |= MESH_LF_CLASSES;
270    putu(b, O(MESH_S_CPUS), online);
271    putu(b, O(MESH_S_CPUS_BIG), nb);
272    putu(b, O(MESH_S_CPUS_LITTLE), nl);
273    if (d->has_psi && h->has_psi) {
274        flags |= MESH_LF_PSI;
275        const size_t offs[5] = {MESH_S_PSI_CPU, MESH_S_PSI_MEM, MESH_S_PSI_MEM_FULL, MESH_S_PSI_IO, MESH_S_PSI_IO_FULL};
276        for (int i = 0; i < 5; i++) putf(b, O(offs[i]), d->psi[i]);
277        const size_t avg[3] = {MESH_S_PSI_CPU10, MESH_S_PSI_MEM10, MESH_S_PSI_IO10};
278        for (int i = 0; i < 3; i++) putf(b, O(avg[i]), h->psi[i].some_avg10);
279    }
280    putu(b, O(MESH_S_MEM_TOTAL_MB), h->mem_total_mb);
281    putu(b, O(MESH_S_MEM_AVAIL_MB), h->mem_avail_mb);
282    putu(b, O(MESH_S_SWAP_TOTAL_MB), h->swap_total_mb);
283    putu(b, O(MESH_S_SWAP_USED_MB), h->swap_used_mb);
284    putu(b, O(MESH_S_MEM_COMPRESSED_MB), h->compressed_mb);
285    if (h->has_mem_level) { flags |= MESH_LF_MEM_LEVEL; putu(b, O(MESH_S_MEM_LEVEL), h->mem_level); }
286    putf(b, O(MESH_S_MEM_FREE), h->mem_free);
287    putf(b, O(MESH_S_SWAP_OUT_S), d->swap_out_s);
288    putf(b, O(MESH_S_MESHD_CPU), d->meshd_cpu);
289    putf(b, O(MESH_S_MESHD_RSS_MB), h->self_rss_mb);
290    putu(b, O(MESH_S_MESHD_THREADS), h->self_threads);
291    putu(b, O(MESH_S_DISK_FREE_MB), h->disk_free_mb);
292    putu(b, O(MESH_S_DISK_TOTAL_MB), h->disk_total_mb);
293    if (h->has_thermal) { flags |= MESH_LF_THERMAL; putu(b, O(MESH_S_THERMAL), h->thermal); }
294    if (h->has_temp) { flags |= MESH_LF_TEMP; putf(b, O(MESH_S_TEMP_C), h->temp_c); }
295    putu(b, O(MESH_S_SAMPLE_US), sample_us);
296    putu(b, O(MESH_S_LOAD_FLAGS), flags);
297    for (size_t i = 0; i < n; i++) {
298        uint8_t u = 0xff;
299        if (i < d->n && d->have_util[i]) {
300            float p = roundf_pos(d->util[i] * 100.0f);
301            u = (uint8_t)(p < 0 ? 0 : p > 100 ? 100 : p);
302        }
303        b[O(MESH_S_CORE_UTIL) + i] = u;
304        b[O(MESH_S_CORE_CLASS) + i] = i < ncls ? cls[i] : MESH_CORE_UNKNOWN;
305    }
306#undef O
307}
308
309void mesh_exec_totals(uint8_t m[MESH_MACHINE_BYTES], const uint8_t (*recs)[MESH_PLOAD_BYTES], const uint32_t *execs, size_t n,
310                      uint32_t workers, uint32_t spawns, uint32_t crashes, uint64_t log_dropped) {
311#define O(off) ((size_t)(off) - MESH_S_LOAD)
312    float cpu = 0;
313    uint64_t rss = 0;
314    uint32_t lanes = 0;
315    for (size_t i = 0; i < n; i++) {
316        const uint8_t *r = recs[i];
317        if (!(getu(r, MESH_PL_FLAGS) & MESH_PLF_HOST)) continue;
318        uint32_t cb = getu(r, MESH_PL_CPU);
319        float c;
320        memcpy(&c, &cb, 4);
321        cpu += c;
322        rss += getu64(r, MESH_PL_RSS);
323        if (execs[i] == MESH_BACKEND_WASM) lanes += getu(r, MESH_PL_UNITS);
324    }
325    putf(m, O(MESH_S_EXEC_CPU), cpu);
326    putu(m, O(MESH_S_WASM_LANES), lanes);
327    putu64(m, O(MESH_S_EXEC_RSS), rss);
328    putu(m, O(MESH_S_WORKERS), workers);
329    putu(m, O(MESH_S_WORKER_SPAWNS), spawns);
330    putu(m, O(MESH_S_WORKER_CRASHES), crashes);
331    putu(m, O(MESH_S_LOG_DROPPED), log_dropped > UINT32_MAX ? UINT32_MAX : (uint32_t)log_dropped);
332#undef O
333}
334
335void mesh_loadrep(uint8_t b[MESH_LOADREP_BYTES], const uint8_t m[MESH_MACHINE_BYTES]) {
336    static const size_t pairs[13][2] = {
337        {MESH_ML_SHARE, MESH_S_GPU_SHARE},       {MESH_ML_SHARE10, MESH_S_GPU_SHARE10},
338        {MESH_ML_MAX_GAP_US, MESH_S_GPU_MAX_GAP_US}, {MESH_ML_CPU, MESH_S_CPU_UTIL},
339        {MESH_ML_CPU_BIG, MESH_S_CPU_BIG},       {MESH_ML_CPU_LITTLE, MESH_S_CPU_LITTLE},
340        {MESH_ML_PSI_CPU, MESH_S_PSI_CPU},       {MESH_ML_PSI_MEM, MESH_S_PSI_MEM},
341        {MESH_ML_MESHD_CPU, MESH_S_MESHD_CPU},   {MESH_ML_MEM_AVAIL_MB, MESH_S_MEM_AVAIL_MB},
342        {MESH_ML_MEM_TOTAL_MB, MESH_S_MEM_TOTAL_MB}, {MESH_ML_DISK_FREE_MB, MESH_S_DISK_FREE_MB},
343        {MESH_ML_FLAGS, MESH_S_LOAD_FLAGS}};
344    memset(b, 0, MESH_LOADREP_BYTES);
345    for (int i = 0; i < 13; i++) memcpy(b + pairs[i][0] - MESH_ML_SHARE, m + pairs[i][1] - MESH_S_LOAD, 4);
346    uint32_t cpus = getu(m, MESH_S_CPUS_BIG - MESH_S_LOAD) | getu(m, MESH_S_CPUS_LITTLE - MESH_S_LOAD) << 16;
347    putu(b, MESH_ML_CPUS - MESH_ML_SHARE, cpus);
348}