skip to content
==> src/snap.c <==
1/* crates/mesh-top/src/snap.rs, ported. */
2#include "mesh/top_snap.h"
3
4#include "tui/utf8.h"
5
6#include <math.h>
7#include <stdarg.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12/* ---- byte helpers */
13
14uint32_t mt_get_u32(const uint8_t *b, size_t len, size_t at) {
15    if (at + 4 > len || at + 4 < at) return 0;
16    return (uint32_t)b[at] | (uint32_t)b[at + 1] << 8 | (uint32_t)b[at + 2] << 16 | (uint32_t)b[at + 3] << 24;
17}
18
19uint64_t mt_get_u64(const uint8_t *b, size_t len, size_t at) {
20    if (at + 8 > len || at + 8 < at) return 0;
21    return (uint64_t)mt_get_u32(b, len, at) | (uint64_t)mt_get_u32(b, len, at + 4) << 32;
22}
23
24float mt_get_f32(const uint8_t *b, size_t len, size_t at) {
25    uint32_t u = mt_get_u32(b, len, at);
26    float f;
27    memcpy(&f, &u, sizeof f);
28    return f;
29}
30
31void mt_get_str(const uint8_t *b, size_t len, size_t at, size_t n, char *out) {
32    out[0] = 0;
33    if (at + n > len || at + n < at) return;
34    size_t end = 0;
35    while (end < n && b[at + end]) end++;
36    size_t m;
37    char *s = tui_utf8_lossy(b + at, end, &m);
38    memcpy(out, s, m + 1);
39    free(s);
40}
41
42void mt_put_u32(uint8_t *b, size_t at, uint32_t v) {
43    for (int i = 0; i < 4; i++) b[at + i] = (uint8_t)(v >> (8 * i));
44}
45
46void mt_put_u64(uint8_t *b, size_t at, uint64_t v) {
47    for (int i = 0; i < 8; i++) b[at + i] = (uint8_t)(v >> (8 * i));
48}
49
50void mt_put_f32(uint8_t *b, size_t at, float v) {
51    uint32_t u;
52    memcpy(&u, &v, sizeof u);
53    mt_put_u32(b, at, u);
54}
55
56void mt_put_str(uint8_t *b, size_t at, size_t n, const char *s) {
57    memset(b + at, 0, n);
58    size_t k = strlen(s);
59    if (k > n - 1) k = n - 1;
60    memcpy(b + at, s, k);
61}
62
63/* ---- sections */
64
65mt_section mt_section_new(uint32_t slot, uint32_t nodes, uint32_t view) {
66    mt_section s;
67    s.len = mesh_section_bytes(nodes, view);
68    s.bytes = calloc(1, s.len);
69    if (!s.bytes) abort();
70    mt_put_u32(s.bytes, MESH_SC_SLOT, slot);
71    mt_put_u32(s.bytes, MESH_SC_BYTES, (uint32_t)s.len);
72    mt_put_u32(s.bytes, MESH_SC_NODES, nodes);
73    mt_put_u32(s.bytes, MESH_SC_VIEW, view);
74    return s;
75}
76
77void mt_section_free(mt_section *s) {
78    free(s->bytes);
79    s->bytes = NULL;
80    s->len = 0;
81}
82
83mt_section mt_section_clone(const mt_section *s) {
84    mt_section c = {malloc(s->len ? s->len : 1), s->len};
85    if (!c.bytes) abort();
86    memcpy(c.bytes, s->bytes, s->len);
87    return c;
88}
89
90uint32_t mt_section_slot(const mt_section *s) { return mt_get_u32(s->bytes, s->len, MESH_SC_SLOT); }
91size_t mt_section_nodes(const mt_section *s) { return mt_get_u32(s->bytes, s->len, MESH_SC_NODES); }
92uint8_t *mt_section_entry(const mt_section *s) { return s->bytes + MESH_SC_ENTRY; }
93uint8_t *mt_section_ctl(const mt_section *s) { return s->bytes + MESH_SC_CTL; }
94uint8_t *mt_section_ext(const mt_section *s) { return s->bytes + MESH_SC_EXT; }
95uint8_t *mt_section_node_rec(const mt_section *s, size_t n) { return s->bytes + MESH_SC_NODE_RECS + MESH_NODE_STRIDE * n; }
96uint8_t *mt_section_view(const mt_section *s) { return s->bytes + MESH_SC_NODE_RECS + MESH_NODE_STRIDE * mt_section_nodes(s); }
97size_t mt_section_view_len(const mt_section *s) { return mt_get_u32(s->bytes, s->len, MESH_SC_VIEW); }
98
99void mt_section_name(const mt_section *s, char *out) {
100    mt_get_str(mt_section_entry(s), MESH_PT_BYTES, MESH_PT_NAME, MESH_NAME_BYTES, out);
101}
102
103bool mt_section_name_is(const mt_section *s, const char *name) {
104    char n[3 * MESH_NAME_BYTES + 1];
105    mt_section_name(s, n);
106    return strcmp(n, name) == 0;
107}
108
109uint32_t mt_section_pt(const mt_section *s, size_t off) { return mt_get_u32(mt_section_entry(s), MESH_PT_BYTES, off); }
110uint32_t mt_section_state(const mt_section *s) { return mt_section_pt(s, MESH_PT_STATE); }
111const char *mt_section_state_name(const mt_section *s) {
112    uint32_t st = mt_section_state(s);
113    return st < MESH_STATES_LEN ? MESH_STATES[st] : "?";
114}
115bool mt_section_running(const mt_section *s) { return mt_section_state(s) == MESH_ST_RUNNING; }
116uint64_t mt_section_ptx_hash(const mt_section *s) { return mt_get_u64(mt_section_entry(s), MESH_PT_BYTES, MESH_PT_PTX_HASH); }
117uint32_t mt_section_p(const mt_section *s, size_t off) { return mt_get_u32(mt_section_ctl(s), MESH_SC_CTL_BYTES, off); }
118uint64_t mt_section_p64(const mt_section *s, size_t off) { return mt_get_u64(mt_section_ctl(s), MESH_SC_CTL_BYTES, off); }
119bool mt_section_native(const mt_section *s) {
120    return mt_get_u32(mt_section_ext(s), MESH_PX_BYTES, MESH_PX_EXEC) == MESH_BACKEND_NATIVE;
121}
122uint32_t mt_section_counter(const mt_section *s, size_t i) { return mt_section_p(s, MESH_P_COUNTERS + 4 * i); }
123void mt_section_counter_name(const mt_section *s, size_t i, char *out) {
124    mt_get_str(mt_section_ext(s), MESH_PX_BYTES, MESH_PX_COUNTER_NAMES + MESH_NAME_BYTES * i, MESH_NAME_BYTES, out);
125}
126
127size_t mt_section_fields(const mt_section *s, mt_field *out) {
128    const uint8_t *x = mt_section_ext(s);
129    size_t n = mt_get_u32(x, MESH_PX_BYTES, MESH_PX_FIELD_COUNT);
130    if (n > MESH_MAX_FIELDS) n = MESH_MAX_FIELDS;
131    for (size_t i = 0; i < n; i++) {
132        size_t at = MESH_PX_FIELDS + MESH_FIELD_BYTES * i;
133        mt_get_str(x, MESH_PX_BYTES, at + MESH_FD_NAME, MESH_FD_NAME_MAX + 1, out[i].name);
134        out[i].ty = mt_get_u32(x, MESH_PX_BYTES, at + MESH_FD_TYPE);
135        out[i].off = mt_get_u32(x, MESH_PX_BYTES, at + MESH_FD_OFF);
136    }
137    return n;
138}
139
140void mt_section_field_value(const mt_section *s, const mt_field *f, char *out, size_t cap) {
141    const uint8_t *v = mt_section_view(s);
142    size_t vl = mt_section_view_len(s);
143    size_t size = f->ty == MESH_FT_U64 ? 8 : f->ty == MESH_FT_U8 ? 1 : 4;
144    if (f->off + size > vl) {
145        snprintf(out, cap, "-");
146        return;
147    }
148    switch (f->ty) {
149    case MESH_FT_U32: snprintf(out, cap, "%u", mt_get_u32(v, vl, f->off)); break;
150    case MESH_FT_I32: snprintf(out, cap, "%d", (int32_t)mt_get_u32(v, vl, f->off)); break;
151    case MESH_FT_F32: {
152        float x = mt_get_f32(v, vl, f->off);
153        if (isnan(x)) snprintf(out, cap, "NaN");
154        else if (isinf(x)) snprintf(out, cap, x < 0 ? "-inf" : "inf");
155        else snprintf(out, cap, "%.6f", (double)x);
156        break;
157    }
158    case MESH_FT_U64: snprintf(out, cap, "%llu", (unsigned long long)mt_get_u64(v, vl, f->off)); break;
159    case MESH_FT_U8: snprintf(out, cap, "%u", v[f->off]); break;
160    default: snprintf(out, cap, "?type%u", f->ty); break;
161    }
162}
163
164mt_node mt_section_node(const mt_section *s, size_t n) {
165    mt_node r = {0, 0, 0, 0, 0};
166    if (n >= mt_section_nodes(s)) return r;
167    size_t at = MESH_SC_NODE_RECS + MESH_NODE_STRIDE * n;
168    r.sm = mt_get_u32(s->bytes, s->len, at + MESH_N_SM);
169    r.heartbeat = mt_get_u32(s->bytes, s->len, at + MESH_N_HEARTBEAT);
170    r.tasks = mt_get_u32(s->bytes, s->len, at + MESH_N_TASKS);
171    r.last_op = mt_get_u32(s->bytes, s->len, at + MESH_N_LAST_OP);
172    r.busy_ns = mt_get_u64(s->bytes, s->len, at + MESH_N_BUSY_NS);
173    return r;
174}
175
176uint32_t mt_section_view_u32(const mt_section *s, size_t off) { return mt_get_u32(mt_section_view(s), mt_section_view_len(s), off); }
177float mt_section_view_f32(const mt_section *s, size_t off) { return mt_get_f32(mt_section_view(s), mt_section_view_len(s), off); }
178
179/* ---- snapshots */
180
181static char *fmt(const char *f, ...)
182#if defined(__GNUC__) || defined(__clang__)
183    __attribute__((format(printf, 1, 2)))
184#endif
185    ;
186static char *fmt(const char *f, ...) {
187    va_list ap;
188    va_start(ap, f);
189    char tmp[256];
190    vsnprintf(tmp, sizeof tmp, f, ap);
191    va_end(ap);
192    char *s = malloc(strlen(tmp) + 1);
193    if (!s) abort();
194    strcpy(s, tmp);
195    return s;
196}
197
198bool mt_snapshot_parse(const uint8_t *bytes, size_t len, mt_snapshot *out, char **err) {
199    size_t min = MESH_SN_HEAD_BYTES + MESH_H_BYTES + MESH_SNAP_HOST;
200    *out = (mt_snapshot){NULL, 0, NULL, 0};
201    if (len < min) {
202        *err = fmt("snapshot is %zu bytes, v%u needs at least %zu", len, (unsigned)MESH_SNAP_VERSION, min);
203        return false;
204    }
205    size_t total = mt_get_u32(bytes, len, MESH_SN_TOTAL), count = mt_get_u32(bytes, len, MESH_SN_COUNT);
206    uint32_t version = mt_get_u32(bytes, len, MESH_SN_VERSION);
207    size_t host_at = mt_get_u32(bytes, len, MESH_SN_HOST_AT);
208    if (version != MESH_SNAP_VERSION) {
209        *err = fmt("snapshot version %u, this viewer speaks %u", version, (unsigned)MESH_SNAP_VERSION);
210        return false;
211    }
212    if (total != len || host_at + MESH_SNAP_HOST != total) {
213        *err = fmt("snapshot framing: total %zu, host at %zu, received %zu", total, host_at, len);
214        return false;
215    }
216    if (count > MESH_PROG_SLOTS) {
217        *err = fmt("snapshot has %zu sections, at most %u", count, (unsigned)MESH_PROG_SLOTS);
218        return false;
219    }
220    mt_section *secs = calloc(count ? count : 1, sizeof *secs);
221    if (!secs) abort();
222    size_t at = MESH_SN_HEAD_BYTES + MESH_H_BYTES, n = 0;
223    for (size_t i = 0; i < count; i++) {
224        if (at + MESH_SC_HEAD_BYTES > host_at) {
225            *err = fmt("section %zu starts past the host block", i);
226            goto fail;
227        }
228        uint32_t slot = mt_get_u32(bytes, len, at + MESH_SC_SLOT);
229        size_t sl = mt_get_u32(bytes, len, at + MESH_SC_BYTES);
230        uint32_t nodes = mt_get_u32(bytes, len, at + MESH_SC_NODES), view = mt_get_u32(bytes, len, at + MESH_SC_VIEW);
231        if (slot >= MESH_PROG_SLOTS || (n > 0 && mt_section_slot(&secs[n - 1]) >= slot)) {
232            *err = fmt("section %zu: slot %u out of range or out of order", i, slot);
233            goto fail;
234        }
235        if (nodes > MESH_PROG_MAX_NODES || view > MESH_VIEW_HEAP_END - MESH_VIEW_HEAP) {
236            *err = fmt("section %zu: %u nodes, %u view bytes", i, nodes, view);
237            goto fail;
238        }
239        if (sl != mesh_section_bytes(nodes, view) || at + sl > host_at) {
240            *err = fmt("section %zu: %zu bytes, the layout says %zu", i, sl, mesh_section_bytes(nodes, view));
241            goto fail;
242        }
243        secs[n].bytes = malloc(sl);
244        if (!secs[n].bytes) abort();
245        memcpy(secs[n].bytes, bytes + at, sl);
246        secs[n].len = sl;
247        n++;
248        at += sl;
249    }
250    if (at != host_at) {
251        *err = fmt("%zu stray bytes before the host block", host_at - at);
252        goto fail;
253    }
254    out->bytes = malloc(len);
255    if (!out->bytes) abort();
256    memcpy(out->bytes, bytes, len);
257    out->len = len;
258    out->sections = secs;
259    out->nsections = n;
260    return true;
261fail:
262    for (size_t k = 0; k < n; k++) free(secs[k].bytes);
263    free(secs);
264    return false;
265}
266
267static int by_slot(const void *a, const void *b) {
268    uint32_t x = mt_section_slot(a), y = mt_section_slot(b);
269    return x < y ? -1 : x > y;
270}
271
272mt_snapshot mt_snapshot_assemble(const uint8_t *head, const mt_section *sections, size_t n, const uint8_t *host) {
273    mt_section *sorted = malloc((n ? n : 1) * sizeof *sorted);
274    if (!sorted) abort();
275    if (n) memcpy(sorted, sections, n * sizeof *sorted);
276    /* A stable sort by slot, as Rust's sort_by_key. */
277    for (size_t i = 1; i < n; i++)
278        for (size_t j = i; j > 0 && by_slot(&sorted[j - 1], &sorted[j]) > 0; j--) {
279            mt_section t = sorted[j];
280            sorted[j] = sorted[j - 1];
281            sorted[j - 1] = t;
282        }
283    size_t len = MESH_SN_HEAD_BYTES + MESH_H_BYTES + MESH_SNAP_HOST;
284    for (size_t i = 0; i < n; i++) len += sorted[i].len;
285    uint8_t *b = calloc(1, len);
286    if (!b) abort();
287    memcpy(b + MESH_SN_HEAD_BYTES, head, MESH_H_BYTES);
288    size_t at = MESH_SN_HEAD_BYTES + MESH_H_BYTES;
289    for (size_t i = 0; i < n; i++) {
290        memcpy(b + at, sorted[i].bytes, sorted[i].len);
291        at += sorted[i].len;
292    }
293    size_t host_at = at;
294    memcpy(b + at, host, MESH_SNAP_HOST);
295    mt_put_u32(b, MESH_SN_TOTAL, (uint32_t)len);
296    mt_put_u32(b, MESH_SN_COUNT, (uint32_t)n);
297    mt_put_u32(b, MESH_SN_VERSION, MESH_SNAP_VERSION);
298    mt_put_u32(b, MESH_SN_HOST_AT, (uint32_t)host_at);
299    free(sorted);
300    mt_snapshot s;
301    char *err = NULL;
302    if (!mt_snapshot_parse(b, len, &s, &err)) {
303        fprintf(stderr, "assembled snapshot does not parse: %s\n", err);
304        abort();
305    }
306    free(b);
307    return s;
308}
309
310mt_snapshot mt_snapshot_clone(const mt_snapshot *s) {
311    mt_snapshot c;
312    char *err = NULL;
313    if (!mt_snapshot_parse(s->bytes, s->len, &c, &err)) abort();
314    return c;
315}
316
317void mt_snapshot_free(mt_snapshot *s) {
318    for (size_t i = 0; i < s->nsections; i++) free(s->sections[i].bytes);
319    free(s->sections);
320    free(s->bytes);
321    *s = (mt_snapshot){NULL, 0, NULL, 0};
322}
323
324const mt_section *mt_snapshot_by_name(const mt_snapshot *s, const char *name) {
325    for (size_t i = 0; i < s->nsections; i++)
326        if (mt_section_name_is(&s->sections[i], name)) return &s->sections[i];
327    return NULL;
328}
329
330const mt_section *mt_snapshot_by_slot(const mt_snapshot *s, uint32_t slot) {
331    for (size_t i = 0; i < s->nsections; i++)
332        if (mt_section_slot(&s->sections[i]) == slot) return &s->sections[i];
333    return NULL;
334}
335
336uint32_t mt_snapshot_nodes_online(const mt_snapshot *s) {
337    uint32_t n = 0;
338    for (size_t i = 0; i < s->nsections; i++) n += mt_section_p(&s->sections[i], MESH_P_ONLINE);
339    return n;
340}
341
342uint32_t mt_snapshot_head(const mt_snapshot *s, size_t off) { return mt_get_u32(s->bytes, s->len, MESH_SN_HEAD_BYTES + off); }
343const uint8_t *mt_snapshot_host(const mt_snapshot *s) { return s->bytes + s->len - MESH_SNAP_HOST; }
344uint32_t mt_snapshot_host_u32(const mt_snapshot *s, size_t off) { return mt_get_u32(mt_snapshot_host(s), MESH_SNAP_HOST, off); }
345uint64_t mt_snapshot_host_u64(const mt_snapshot *s, size_t off) { return mt_get_u64(mt_snapshot_host(s), MESH_SNAP_HOST, off); }
346float mt_snapshot_host_f32(const mt_snapshot *s, size_t off) { return mt_get_f32(mt_snapshot_host(s), MESH_SNAP_HOST, off); }
347void mt_snapshot_host_str(const mt_snapshot *s, size_t off, size_t n, char *out) {
348    mt_get_str(mt_snapshot_host(s), MESH_SNAP_HOST, off, n, out);
349}
350void mt_snapshot_hostname(const mt_snapshot *s, char out[97]) { mt_snapshot_host_str(s, MESH_S_HOSTNAME, 32, out); }
351void mt_snapshot_gpu(const mt_snapshot *s, char out[97]) { mt_snapshot_host_str(s, MESH_S_GPU, 32, out); }
352
353float mt_snapshot_rate(const mt_snapshot *s, uint32_t slot) {
354    float r = mt_snapshot_host_f32(s, MESH_S_RATES + 4 * (size_t)slot);
355    return isfinite(r) && r > 0.0f ? r : 0.0f;
356}
357
358bool mt_snapshot_system(const mt_snapshot *s, uint32_t *k) {
359    uint32_t v = mt_snapshot_host_u32(s, MESH_S_SYSTEM);
360    if (v == MESH_PEER_NONE) return false;
361    if (k) *k = v;
362    return true;
363}
364
365uint32_t mt_snapshot_systems(const mt_snapshot *s) {
366    uint32_t n = mt_snapshot_host_u32(s, MESH_S_SYSTEMS);
367    return n < MESH_MAX_SYSTEMS ? n : MESH_MAX_SYSTEMS;
368}
369
370uint64_t mt_snapshot_uptime_ms(const mt_snapshot *s) { return mt_snapshot_host_u64(s, MESH_S_UPTIME_MS); }
371
372mt_member mt_snapshot_member(const mt_snapshot *s, uint32_t k) {
373    const uint8_t *h = mt_snapshot_host(s) + mesh_member(k);
374    size_t n = MESH_MEMBER_BYTES;
375    mt_member m;
376    m.system = k;
377    m.state = mt_get_u32(h, n, MESH_M_STATE);
378    m.cc = mt_get_u32(h, n, MESH_M_CC);
379    m.sms = mt_get_u32(h, n, MESH_M_SMS);
380    m.sms_total = mt_get_u32(h, n, MESH_M_SMS_TOTAL);
381    m.blocks_per_sm = mt_get_u32(h, n, MESH_M_BLOCKS_PER_SM);
382    m.rtt_us = mt_get_f32(h, n, MESH_M_RTT_US);
383    m.wait_us = mt_get_f32(h, n, MESH_M_WAIT_US);
384    m.rounds = mt_get_u32(h, n, MESH_M_ROUNDS);
385    m.round = mt_get_u64(h, n, MESH_M_ROUND);
386    mt_get_str(h, n, MESH_M_GPU, 32, m.gpu);
387    mt_get_str(h, n, MESH_M_ARCH, 16, m.arch);
388    mt_get_str(h, n, MESH_M_HOST, 32, m.host);
389    m.backend = mt_get_u32(h, n, MESH_M_BACKEND);
390    return m;
391}
392
393size_t mt_snapshot_members(const mt_snapshot *s, mt_member *out) {
394    if (!mt_snapshot_system(s, NULL)) return 0;
395    uint32_t n = mt_snapshot_systems(s);
396    for (uint32_t k = 0; k < n; k++) out[k] = mt_snapshot_member(s, k);
397    return n;
398}
399
400bool mt_snapshot_fleet(const mt_snapshot *s, mt_fleet *f) {
401    if (!mt_snapshot_system(s, NULL)) return false;
402    const uint8_t *h = mt_snapshot_host(s);
403    size_t hl = MESH_SNAP_HOST;
404    memset(f, 0, sizeof *f);
405    uint32_t leader = mt_get_u32(h, hl, MESH_S_LEADER);
406    f->has_leader = leader != MESH_PEER_NONE;
407    f->leader = leader;
408    uint32_t n = mt_snapshot_systems(s);
409    f->nmembers = n;
410    for (uint32_t k = 0; k < n; k++) {
411        size_t at = mesh_fmember(k);
412        f->members[k].status = mt_get_u32(h, hl, at + MESH_FM_STATUS);
413        f->members[k].group = mt_get_u32(h, hl, at + MESH_FM_GROUP);
414        f->members[k].applied = mt_get_u64(h, hl, at + MESH_FM_APPLIED);
415        f->members[k].heard_ms = mt_get_u64(h, hl, at + MESH_FM_HEARD_MS);
416        mt_get_str(h, hl, at + MESH_FM_REASON, 40, f->members[k].reason);
417    }
418    uint32_t gstate = mt_get_u32(h, hl, MESH_SG_STATE);
419    if (gstate != 0) {
420        f->has_group = true;
421        mt_fleet_group *g = &f->group;
422        mt_get_str(h, hl, MESH_SG_NAME, MESH_NAME_BYTES, g->name);
423        g->epoch = mt_get_u32(h, hl, MESH_SG_EPOCH);
424        g->cycle = mt_get_u32(h, hl, MESH_SG_CYCLE);
425        g->mask = mt_get_u32(h, hl, MESH_SG_MASK);
426        memcpy(g->order, h + MESH_SG_ORDER, MESH_MAX_SYSTEMS);
427        g->running = gstate == 1;
428        g->local = mt_get_u32(h, hl, MESH_SG_LOCAL);
429        g->rank = mt_get_u32(h, hl, MESH_SG_RANK);
430        g->reform = mt_get_u32(h, hl, MESH_SG_ON_LOSS) == 1;
431        g->auto_ms = mt_get_u32(h, hl, MESH_SG_AUTO_MS);
432    }
433    f->role = mt_get_u32(h, hl, MESH_S_ROLE);
434    f->term = mt_get_u64(h, hl, MESH_S_TERM);
435    f->version = mt_get_u64(h, hl, MESH_S_STATE_VER);
436    f->applied = mt_get_u64(h, hl, MESH_S_APPLIED_VER);
437    f->up = mt_get_u32(h, hl, MESH_S_UP_MASK);
438    f->suspect = mt_get_u32(h, hl, MESH_S_SUSPECT_MASK);
439    f->quorum = mt_get_u32(h, hl, MESH_S_QUORUM);
440    f->flags = mt_get_u32(h, hl, MESH_S_FLEET_FLAGS);
441    f->dead_ms = mt_get_u32(h, hl, MESH_S_DEAD_MS);
442    return true;
443}
444
445mt_jobs mt_snapshot_jobs(const mt_snapshot *s) {
446    const uint8_t *h = mt_snapshot_host(s);
447    size_t hl = MESH_SNAP_HOST;
448    return (mt_jobs){mt_get_u32(h, hl, MESH_S_JOBS_ACTIVE), mt_get_u32(h, hl, MESH_S_JOBS_TOTAL),
449                     mt_get_u64(h, hl, MESH_S_JOB_DONE),    mt_get_u64(h, hl, MESH_S_JOB_FAILED),
450                     mt_get_u32(h, hl, MESH_S_JOB_LEASED),  mt_get_f32(h, hl, MESH_S_JOB_RATE),
451                     mt_get_u64(h, hl, MESH_S_JOB_DUPES),   mt_get_u32(h, hl, MESH_S_JOB_FLAGS),
452                     mt_get_u32(h, hl, MESH_S_JOB_HELD)};
453}
454
455static mt_opt_f32 opt(bool on, float v) { return (mt_opt_f32){on, on ? v : 0.0f}; }
456
457bool mt_snapshot_load(const mt_snapshot *s, mt_load *l) {
458    uint64_t at = mt_snapshot_host_u64(s, MESH_S_LOAD_AT_MS);
459    if (at == 0) return false;
460    memset(l, 0, sizeof *l);
461    const uint8_t *h = mt_snapshot_host(s);
462    uint32_t flags = mt_snapshot_host_u32(s, MESH_S_LOAD_FLAGS);
463#define F(o) mt_snapshot_host_f32(s, (o))
464#define U(o) mt_snapshot_host_u32(s, (o))
465    size_t n = U(MESH_S_CPUS);
466    if (n > MESH_MAX_CPUS) n = MESH_MAX_CPUS;
467    uint64_t up = mt_snapshot_uptime_ms(s);
468    l->at_ms = at;
469    l->age_ms = up > at ? up - at : 0;
470    l->samples = U(MESH_S_LOAD_SAMPLES);
471    l->flags = flags;
472    l->share = opt(flags & MESH_LF_GPU, F(MESH_S_GPU_SHARE));
473    l->share10 = opt(flags & MESH_LF_GPU, F(MESH_S_GPU_SHARE10));
474    l->slice_us = F(MESH_S_GPU_SLICE_US);
475    l->gap_us = F(MESH_S_GPU_GAP_US);
476    l->max_gap_us = F(MESH_S_GPU_MAX_GAP_US);
477    l->gaps_s = F(MESH_S_GPU_GAPS_S);
478    l->observed = F(MESH_S_GPU_OBS);
479    l->util_ext = opt(flags & MESH_LF_GPU_EXT, F(MESH_S_GPU_UTIL_EXT));
480    l->queue_us = F(MESH_S_GPU_QUEUE_US);
481    l->inflate = opt(flags & MESH_LF_GPU, F(MESH_S_GPU_INFLATE));
482    l->cpu = F(MESH_S_CPU_UTIL);
483    l->cpu_big = F(MESH_S_CPU_BIG);
484    l->cpu_little = F(MESH_S_CPU_LITTLE);
485    l->iowait = F(MESH_S_CPU_IOWAIT);
486    l->loadavg[0] = F(MESH_S_LOADAVG);
487    l->loadavg[1] = F(MESH_S_LOADAVG + 4);
488    l->loadavg[2] = F(MESH_S_LOADAVG + 8);
489    l->cpus = U(MESH_S_CPUS);
490    l->cpus_big = U(MESH_S_CPUS_BIG);
491    l->cpus_little = U(MESH_S_CPUS_LITTLE);
492    l->has_psi = flags & MESH_LF_PSI;
493    if (l->has_psi) {
494        l->psi[0] = F(MESH_S_PSI_CPU);
495        l->psi[1] = F(MESH_S_PSI_MEM);
496        l->psi[2] = F(MESH_S_PSI_MEM_FULL);
497        l->psi[3] = F(MESH_S_PSI_IO);
498        l->psi[4] = F(MESH_S_PSI_IO_FULL);
499    }
500    l->mem_total_mb = U(MESH_S_MEM_TOTAL_MB);
501    l->mem_avail_mb = U(MESH_S_MEM_AVAIL_MB);
502    l->mem_free = F(MESH_S_MEM_FREE);
503    l->swap_total_mb = U(MESH_S_SWAP_TOTAL_MB);
504    l->swap_used_mb = U(MESH_S_SWAP_USED_MB);
505    l->swap_out_s = F(MESH_S_SWAP_OUT_S);
506    l->compressed_mb = U(MESH_S_MEM_COMPRESSED_MB);
507    l->has_mem_level = flags & MESH_LF_MEM_LEVEL;
508    l->mem_level = l->has_mem_level ? U(MESH_S_MEM_LEVEL) : 0;
509    l->meshd_cpu = F(MESH_S_MESHD_CPU);
510    l->meshd_rss_mb = F(MESH_S_MESHD_RSS_MB);
511    l->meshd_threads = U(MESH_S_MESHD_THREADS);
512    l->disk_free_mb = U(MESH_S_DISK_FREE_MB);
513    l->disk_total_mb = U(MESH_S_DISK_TOTAL_MB);
514    l->has_thermal = flags & MESH_LF_THERMAL;
515    l->thermal = l->has_thermal ? U(MESH_S_THERMAL) : 0;
516    l->temp_c = opt(flags & MESH_LF_TEMP, F(MESH_S_TEMP_C));
517    l->ncores = n;
518    for (size_t i = 0; i < n; i++) {
519        l->cores[i][0] = h[MESH_S_CORE_UTIL + i];
520        l->cores[i][1] = h[MESH_S_CORE_CLASS + i];
521    }
522#undef F
523#undef U
524    return true;
525}
526
527bool mt_snapshot_prog_load(const mt_snapshot *s, uint32_t slot, mt_prog_load *p) {
528    size_t at = mesh_pload(slot);
529    uint32_t flags = mt_snapshot_host_u32(s, at + MESH_PL_FLAGS);
530    if (!(flags & MESH_PLF_LOADED)) return false;
531#define F(o) mt_snapshot_host_f32(s, at + (o))
532    bool g = flags & MESH_PLF_GPU, l = flags & MESH_PLF_LAT;
533    p->flags = flags;
534    p->share = opt(g, F(MESH_PL_SHARE));
535    p->share10 = opt(g, F(MESH_PL_SHARE10));
536    p->slice_us = F(MESH_PL_SLICE_US);
537    p->gap_us = F(MESH_PL_GAP_US);
538    p->max_gap_us = F(MESH_PL_MAX_GAP_US);
539    p->observed = F(MESH_PL_OBS);
540    p->lat_n = mt_snapshot_host_u32(s, at + MESH_PL_LAT_N);
541    p->p50_us = opt(l, F(MESH_PL_P50_US));
542    p->p90_us = opt(l, F(MESH_PL_P90_US));
543    p->p99_us = opt(l, F(MESH_PL_P99_US));
544    p->max_us = opt(l, F(MESH_PL_MAX_US));
545    p->task_us = opt(flags & MESH_PLF_TASK, F(MESH_PL_TASK_US));
546    p->tasks_s = F(MESH_PL_TASKS_S);
547    p->busy = F(MESH_PL_BUSY);
548    p->queue_us = F(MESH_PL_QUEUE_US);
549#undef F
550    return true;
551}
552
553bool mt_snapshot_member_load(const mt_snapshot *s, uint32_t k, mt_member_load *m) {
554    size_t at = mesh_mload(k);
555    uint64_t t = mt_snapshot_host_u64(s, at + MESH_ML_AT_MS);
556    if (t == 0) return false;
557#define F(o) mt_snapshot_host_f32(s, at + (o))
558    uint32_t flags = mt_snapshot_host_u32(s, at + MESH_ML_FLAGS);
559    uint32_t cpus = mt_snapshot_host_u32(s, at + MESH_ML_CPUS);
560    uint64_t up = mt_snapshot_uptime_ms(s);
561    m->age_ms = up > t ? up - t : 0;
562    m->flags = flags;
563    m->share = opt(flags & MESH_LF_GPU, F(MESH_ML_SHARE));
564    m->share10 = opt(flags & MESH_LF_GPU, F(MESH_ML_SHARE10));
565    m->max_gap_us = F(MESH_ML_MAX_GAP_US);
566    m->cpu = F(MESH_ML_CPU);
567    m->cpu_big = F(MESH_ML_CPU_BIG);
568    m->cpu_little = F(MESH_ML_CPU_LITTLE);
569    m->psi_cpu = opt(flags & MESH_LF_PSI, F(MESH_ML_PSI_CPU));
570    m->psi_mem = opt(flags & MESH_LF_PSI, F(MESH_ML_PSI_MEM));
571    m->meshd_cpu = F(MESH_ML_MESHD_CPU);
572    m->mem_avail_mb = mt_snapshot_host_u32(s, at + MESH_ML_MEM_AVAIL_MB);
573    m->mem_total_mb = mt_snapshot_host_u32(s, at + MESH_ML_MEM_TOTAL_MB);
574    m->disk_free_mb = mt_snapshot_host_u32(s, at + MESH_ML_DISK_FREE_MB);
575    m->cpus_big = cpus & 0xffff;
576    m->cpus_little = cpus >> 16;
577#undef F
578    return true;
579}
580
581mt_link_stat mt_snapshot_link(const mt_snapshot *s, uint32_t k) {
582    const uint8_t *h = mt_snapshot_host(s) + mesh_link(k);
583    size_t n = MESH_LINK_BYTES;
584    mt_link_stat l;
585    l.system = k;
586    l.rx_bytes = mt_get_u64(h, n, MESH_L_RX_BYTES);
587    l.tx_bytes = mt_get_u64(h, n, MESH_L_TX_BYTES);
588    for (size_t i = 0; i < MESH_NET_KINDS; i++) {
589        l.rx_frames[i] = mt_get_u64(h, n, MESH_L_RX_FRAMES + 8 * i);
590        l.tx_frames[i] = mt_get_u64(h, n, MESH_L_TX_FRAMES + 8 * i);
591    }
592    mt_get_str(h, n, MESH_L_LOCAL, 32, l.local);
593    mt_get_str(h, n, MESH_L_IFACE, 16, l.iface);
594    return l;
595}
596
597const size_t MT_NIC_COUNTERS[8] = {MESH_NI_RX_BYTES, MESH_NI_TX_BYTES, MESH_NI_RX_PACKETS, MESH_NI_TX_PACKETS,
598                                   MESH_NI_RX_ERRS,  MESH_NI_TX_ERRS,  MESH_NI_RX_DROP,    MESH_NI_TX_DROP};
599
600void mt_snapshot_net_at(const mt_snapshot *s, uint64_t *at_ms, uint32_t *samples) {
601    *at_ms = mt_snapshot_host_u64(s, MESH_S_NET_AT_MS);
602    *samples = mt_snapshot_host_u32(s, MESH_S_NET_SAMPLES);
603}
604
605static mt_nic nic_at(const mt_snapshot *s, size_t at) {
606    const uint8_t *h = mt_snapshot_host(s) + at;
607    size_t n = MESH_NIC_BYTES;
608    mt_nic x;
609    mt_get_str(h, n, MESH_NI_NAME, 16, x.name);
610    for (int i = 0; i < 8; i++) x.v[i] = mt_get_u64(h, n, MT_NIC_COUNTERS[i]);
611    x.links = mt_get_u32(h, n, MESH_NI_LINKS);
612    x.count = mt_get_u32(h, n, MESH_NI_COUNT);
613    return x;
614}
615
616size_t mt_snapshot_nics(const mt_snapshot *s, mt_nic *out) {
617    size_t n = mt_snapshot_host_u32(s, MESH_S_NET_NICS);
618    if (n > MESH_MAX_NICS) n = MESH_MAX_NICS;
619    for (size_t i = 0; i < n; i++) out[i] = nic_at(s, mesh_nic(i));
620    return n;
621}
622
623mt_nic mt_snapshot_net_total(const mt_snapshot *s) { return nic_at(s, MESH_S_NET_TOTAL); }
624
625void mt_snapshot_ports(const mt_snapshot *s, uint64_t ctl[3], uint64_t http[3]) {
626    ctl[0] = mt_snapshot_host_u64(s, MESH_S_CTL_RX);
627    ctl[1] = mt_snapshot_host_u64(s, MESH_S_CTL_TX);
628    ctl[2] = mt_snapshot_host_u64(s, MESH_S_CTL_REQS);
629    http[0] = mt_snapshot_host_u64(s, MESH_S_HTTP_RX);
630    http[1] = mt_snapshot_host_u64(s, MESH_S_HTTP_TX);
631    http[2] = mt_snapshot_host_u64(s, MESH_S_HTTP_CONNS);
632}
633
634void mt_snapshot_lock_pair(const mt_snapshot *s, char name[3 * MESH_NAME_BYTES + 1], uint64_t *step, uint8_t view[32]) {
635    const uint8_t *h = mt_snapshot_host(s);
636    memcpy(view, h + MESH_S_LOCK_VIEW, 32);
637    mt_snapshot_host_str(s, MESH_S_LOCK_NAME, MESH_NAME_BYTES, name);
638    *step = mt_get_u64(h, MESH_SNAP_HOST, MESH_S_LOCK_STEP);
639}
640
641void mt_cc_label(uint32_t cc, char *out) {
642    if (cc == 0) snprintf(out, 32, "-");
643    else snprintf(out, 32, "%u.%u", cc / 10, cc % 10);
644}
645
646void mt_gpu_label(uint32_t cc, uint32_t backend, char *out) {
647    if (mesh_backend_kind(backend) == MESH_BACKEND_METAL) {
648        if (mesh_backend_family(backend) > 0) snprintf(out, 32, "apple%u", mesh_backend_family(backend));
649        else snprintf(out, 32, "metal");
650        return;
651    }
652    mt_cc_label(cc, out);
653}
654
655void mt_blocks_label(uint32_t blocks, uint32_t backend, char *out) {
656    if (mesh_backend_kind(backend) == MESH_BACKEND_CUDA) snprintf(out, 32, "%u", blocks);
657    else snprintf(out, 32, "-");
658}
659
660/* ---- trace */
661
662bool mt_parse_trace(const uint8_t *b, size_t len, mt_trace_prog **out, size_t *n, char **err) {
663    size_t per = 8 + (size_t)MESH_TRACE_PER_PROG * MESH_TRACE_BYTES;
664    *out = NULL;
665    *n = 0;
666    if (len % per != 0 || len / per > MESH_PROG_SLOTS) {
667        *err = fmt("trace reply is %zu bytes, not a multiple of %zu", len, per);
668        return false;
669    }
670    size_t count = len / per;
671    mt_trace_prog *t = calloc(count ? count : 1, sizeof *t);
672    if (!t) abort();
673    for (size_t c = 0; c < count; c++) {
674        const uint8_t *chunk = b + c * per;
675        uint32_t slot = mt_get_u32(chunk, per, 0), cnt = mt_get_u32(chunk, per, 4);
676        if (slot >= MESH_PROG_SLOTS) {
677            *err = fmt("trace slot %u", slot);
678            mt_trace_free(t, c);
679            return false;
680        }
681        uint32_t first = cnt > MESH_TRACE_PER_PROG ? cnt - MESH_TRACE_PER_PROG : 0;
682        t[c].slot = slot;
683        t[c].n = cnt;
684        t[c].nrecs = cnt - first;
685        t[c].recs = malloc((t[c].nrecs ? t[c].nrecs : 1) * sizeof *t[c].recs);
686        if (!t[c].recs) abort();
687        for (uint32_t i = first; i < cnt; i++) {
688            size_t at = 8 + MESH_TRACE_BYTES * (size_t)(i % MESH_TRACE_PER_PROG);
689            uint32_t w0 = mt_get_u32(chunk, per, at);
690            t[c].recs[i - first] = (mt_rec){w0 & 0xff, w0 >> 8 & 0xff, w0 >> 16, mt_get_u32(chunk, per, at + 4),
691                                            mt_get_u32(chunk, per, at + 8), mt_get_u32(chunk, per, at + 12)};
692        }
693    }
694    *out = t;
695    *n = count;
696    return true;
697}
698
699void mt_trace_free(mt_trace_prog *t, size_t n) {
700    if (!t) return;
701    for (size_t i = 0; i < n; i++) free(t[i].recs);
702    free(t);
703}
704
705static int cmp_u32(const void *a, const void *b) {
706    uint32_t x = *(const uint32_t *)a, y = *(const uint32_t *)b;
707    return x < y ? -1 : x > y;
708}
709
710bool mt_quantile_us(const mt_rec *recs, size_t n, double q, double *out) {
711    if (n == 0) return false;
712    uint32_t *d = malloc(n * sizeof *d);
713    if (!d) abort();
714    for (size_t i = 0; i < n; i++) d[i] = mt_rec_ns(&recs[i]);
715    qsort(d, n, sizeof *d, cmp_u32);
716    size_t i = (size_t)round((double)(n - 1) * q);
717    *out = (double)d[i] / 1000.0;
718    free(d);
719    return true;
720}