==> 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}