==> src/sched_tracker.c <==1/* sched.rs: Move, Tracker, unit, start_move, resume_moves, MigOutcome, mig_verdict, end_move. */
2#include "mesh/sched_tracker.h"
3#include "mesh/mt.h"
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9void mst_move_free(mst_move *m) {
10 mt_strs_free(&m->programs);
11 free(m->why);
12 *m = (mst_move){0};
13}
14
15mst_move mst_move_clone(const mst_move *m) { return (mst_move){mt_strs_clone(&m->programs), m->from, m->to, m->state, mt_strdup(m->why), m->forced}; }
16
17bool mst_move_eq(const mst_move *a, const mst_move *b) {
18 return mt_strs_eq(&a->programs, &b->programs) && a->from == b->from && a->to == b->to && a->state == b->state && strcmp(a->why, b->why) == 0 &&
19 a->forced == b->forced;
20}
21
22typedef struct {
23 char *name;
24 uint32_t from;
25 uint64_t since;
26} cand_t;
27
28typedef struct {
29 uint32_t k;
30 uint64_t since;
31 bool timing, busy;
32} owner_t;
33
34struct mst_tracker {
35 cand_t *cand; /* sorted by name */
36 size_t ncand;
37 ms_map last_move; /* name -> decimal ms */
38 uint64_t *moves; /* a queue of start times */
39 size_t nmoves;
40 owner_t owner[64]; /* by member */
41 size_t nowner;
42 bool has_group;
43 char *group_name;
44 ml_list group_members;
45 uint64_t group_since;
46 mt_strs notes;
47};
48
49mst_tracker *mst_new(void) { return mt_alloc(sizeof(mst_tracker)); }
50
51static void cand_remove_at(mst_tracker *t, size_t i) {
52 free(t->cand[i].name);
53 memmove(t->cand + i, t->cand + i + 1, (t->ncand - i - 1) * sizeof *t->cand);
54 t->ncand--;
55}
56
57static long cand_find(const mst_tracker *t, const char *name) {
58 for (size_t i = 0; i < t->ncand; i++)
59 if (strcmp(t->cand[i].name, name) == 0) return (long)i;
60 return -1;
61}
62
63static bool cand_remove(mst_tracker *t, const char *name) {
64 long i = cand_find(t, name);
65 if (i < 0) return false;
66 cand_remove_at(t, (size_t)i);
67 return true;
68}
69
70void mst_free(mst_tracker *t) {
71 if (!t) return;
72 while (t->ncand) cand_remove_at(t, t->ncand - 1);
73 free(t->cand);
74 ms_free(&t->last_move);
75 free(t->moves);
76 free(t->group_name);
77 ml_free(&t->group_members);
78 mt_strs_free(&t->notes);
79 free(t);
80}
81
82const mt_strs *mst_notes(const mst_tracker *t) { return &t->notes; }
83
84static owner_t *owner_entry(mst_tracker *t, uint32_t k, uint64_t now) {
85 for (size_t i = 0; i < t->nowner; i++)
86 if (t->owner[i].k == k) return &t->owner[i];
87 t->owner[t->nowner] = (owner_t){k, now, false, false};
88 return &t->owner[t->nowner++];
89}
90
91static void owner_remove(mst_tracker *t, uint32_t k) {
92 for (size_t i = 0; i < t->nowner; i++)
93 if (t->owner[i].k == k) {
94 t->owner[i] = t->owner[--t->nowner];
95 return;
96 }
97}
98
99void mst_owner(mst_tracker *t, msm_member *ms, size_t n, uint64_t now, const msw_weights *w) {
100 for (size_t i = 0; i < n; i++) {
101 msm_member *m = &ms[i];
102 if (!m->up || !m->load_known) {
103 free(m->busy);
104 m->busy = NULL;
105 owner_remove(t, m->id);
106 continue;
107 }
108 mof32 share = strcmp(m->gpu, "wasm") != 0 ? m->gpu_share : mof32_none();
109 bool hot = (share.some && share.v < w->owner_share) || (m->psi_cpu.some && m->psi_cpu.v > w->owner_psi);
110 bool cool = (!share.some || share.v > w->owner_share * 1.33f) && (!m->psi_cpu.some || m->psi_cpu.v < w->owner_psi * 0.6f);
111 owner_t *e = owner_entry(t, m->id, now);
112 bool want = e->busy ? !cool : hot;
113 if (want != e->busy) {
114 if (!e->timing) e->since = now, e->timing = true;
115 else if (now - (now > e->since ? e->since : now) >= w->owner_ms) e->since = now, e->timing = false, e->busy = want;
116 } else if (e->timing) e->since = now, e->timing = false;
117 free(m->busy);
118 m->busy = NULL;
119 if (e->busy) {
120 char s[32], p[32];
121 if (share.some) snprintf(s, sizeof s, "%.2f", (double)share.v);
122 else strcpy(s, "-");
123 if (m->psi_cpu.some) snprintf(p, sizeof p, "%.3f", (double)m->psi_cpu.v);
124 else strcpy(p, "-");
125 m->busy = mt_fmt("other tenants load it (share %s, PSI cpu %s)", s, p);
126 }
127 }
128}
129
130uint32_t mst_recent(mst_tracker *t, uint64_t now) {
131 size_t drop = 0;
132 while (drop < t->nmoves && now - (now > t->moves[drop] ? t->moves[drop] : now) >= 60000) drop++;
133 if (drop) memmove(t->moves, t->moves + drop, (t->nmoves - drop) * sizeof *t->moves);
134 t->nmoves -= drop;
135 return (uint32_t)t->nmoves;
136}
137
138static void last_move_set(mst_tracker *t, const char *name, uint64_t now) {
139 char v[24];
140 snprintf(v, sizeof v, "%llu", (unsigned long long)now);
141 ms_set(&t->last_move, name, v, true);
142}
143
144void mst_moved(mst_tracker *t, const mst_move *m, uint64_t now) {
145 t->moves = mt_realloc(t->moves, (t->nmoves + 1) * sizeof *t->moves);
146 t->moves[t->nmoves++] = now;
147 for (size_t i = 0; i < m->programs.n; i++) {
148 last_move_set(t, m->programs.v[i], now);
149 cand_remove(t, m->programs.v[i]);
150 }
151}
152
153mt_strs mst_unit(const mfd_doc *doc, const char *name, uint32_t from) {
154 mt_strs v = {0};
155 mt_push(&v, name);
156 for (size_t i = 0; i < v.n; i++) {
157 char *a = mt_strdup(v.v[i]);
158 for (size_t j = 0; j < doc->nprogs; j++) {
159 const mfd_prog *p = &doc->prog[j];
160 if (p->needs.colocate_with && strcmp(p->needs.colocate_with, a) == 0 && !mt_strs_has(&v, doc->pname[j]) && ml_opt_has_or_all(&p->at, from))
161 mt_push(&v, doc->pname[j]);
162 }
163 free(a);
164 }
165 /* Stop order: a program that feeds another in the unit stops before it. */
166 mt_strs out = {0};
167 while (v.n) {
168 size_t pick = 0;
169 bool found = false;
170 for (size_t i = 0; i < v.n && !found; i++) {
171 bool fed = false;
172 for (size_t j = 0; j < v.n; j++) {
173 if (j == i || strcmp(v.v[j], v.v[i]) == 0) continue;
174 const mfd_prog *o = mfd_prog_get(doc, v.v[j]);
175 if (o && msn_strs_has(o->needs.feeds, o->needs.nfeeds, v.v[i])) fed = true;
176 }
177 if (!fed) pick = i, found = true;
178 }
179 mt_push(&out, v.v[pick]);
180 mt_strs_remove(&v, pick);
181 }
182 mt_strs_free(&v);
183 return out;
184}
185
186static char *list_text(const ml_list *l) { return ml_text(l); }
187
188bool mst_step(mst_tracker *t, const msp_input *in, uint64_t now, bool busy, mst_move *out) {
189 mt_strs_free(&t->notes);
190 const mfd_doc *doc = in->doc;
191 mt_strs ord = msp_order(doc);
192 bool result = false;
193 /* Forced: a pin the placement left for a migration. */
194 for (size_t oi = 0; oi < ord.n; oi++) {
195 const char *name = ord.v[oi];
196 const mfd_prog *p = mfd_prog_get(doc, name);
197 if (!p->pin.some || !p->at.some) continue;
198 if (p->has_from || ml_eq(&p->pin.l, &p->at.l) || p->needs.migrate != MESH_MIGRATE_STATE) continue;
199 /* Colocated with a loaded anchor: it moves with the anchor's unit, never on its own pin. */
200 if (p->needs.colocate_with && mfd_prog_get(doc, p->needs.colocate_with)) continue;
201 long fi = -1, ti = -1;
202 for (size_t i = 0; i < p->at.l.n && fi < 0; i++)
203 if (!ml_has(&p->pin.l, p->at.l.v[i])) fi = (long)i;
204 if (fi < 0) continue;
205 for (size_t i = 0; i < p->pin.l.n && ti < 0; i++)
206 if (!ml_has(&p->at.l, p->pin.l.v[i])) ti = (long)i;
207 if (ti < 0) continue;
208 char *pl = list_text(&p->pin.l);
209 if (busy) {
210 mt_push_own(&t->notes, mt_fmt("%s: pinned to %s: waits for the migration in flight", name, pl));
211 free(pl);
212 goto done;
213 }
214 uint32_t from = p->at.l.v[fi];
215 *out = (mst_move){mst_unit(doc, name, from), from, p->pin.l.v[ti], true, mt_fmt("pinned by the operator to %s", pl), true};
216 free(pl);
217 result = true;
218 goto done;
219 }
220 if (doc->sched_paused) {
221 mt_push(&t->notes, "paused (`sched resume`)");
222 goto done;
223 }
224 msp_books books = msp_books_new(in->nmembers);
225 for (size_t oi = 0; oi < ord.n; oi++) {
226 const mfd_prog *p = mfd_prog_get(doc, ord.v[oi]);
227 ml_list on = msp_members_of_at(&p->at, in->members, in->nmembers, &p->needs);
228 for (size_t i = 0; i < on.n; i++)
229 if (on.v[i] < in->nmembers) msp_books_add(&books, on.v[i], &p->needs);
230 ml_free(&on);
231 }
232 /* ready: (gain, move) */
233 double *gain = NULL;
234 mst_move *ready = NULL;
235 size_t nready = 0;
236 mt_strs seen = {0};
237 for (size_t oi = 0; oi < ord.n; oi++) {
238 const char *name = ord.v[oi];
239 const mfd_prog *p = mfd_prog_get(doc, name);
240 const mesh_needs *n = &p->needs;
241 bool movable = !n->replicas.all && !p->pin.some && !n->lockstep && n->migrate != MESH_MIGRATE_NEVER && !p->has_from &&
242 (!n->colocate_with || !mfd_prog_get(doc, n->colocate_with));
243 if (!p->at.some || !p->at.l.n || !movable) {
244 cand_remove(t, name);
245 continue;
246 }
247 mt_push(&seen, name);
248 msp_row *r;
249 size_t nr;
250 msp_rows(in, name, &p->at.l, &books, NULL, &r, &nr);
251 /* The worst-placed replica (ties: the higher id) against the best member it is not on. */
252 long from_i = -1;
253 double s_from = 0;
254 uint32_t from = 0;
255 for (size_t i = 0; i < p->at.l.n; i++) {
256 uint32_t k = p->at.l.v[i];
257 for (size_t j = 0; j < nr; j++)
258 if (r[j].k == k && r[j].ok) {
259 double s = r[j].score.total;
260 if (from_i < 0 || s < s_from || (s == s_from && k > from)) from_i = (long)i, s_from = s, from = k;
261 break;
262 }
263 }
264 if (from_i < 0) {
265 msp_rows_free(r, nr);
266 continue;
267 }
268 uint32_t *ks = mt_alloc((nr ? nr : 1) * sizeof *ks);
269 double *ts = mt_alloc((nr ? nr : 1) * sizeof *ts);
270 size_t nok = msp_sorted_ok(r, nr, ks, ts);
271 long to_i = -1;
272 for (size_t i = 0; i < nok && to_i < 0; i++)
273 if (!ml_has(&p->at.l, ks[i])) to_i = (long)i;
274 if (to_i < 0) {
275 cand_remove(t, name);
276 free(ks), free(ts);
277 msp_rows_free(r, nr);
278 continue;
279 }
280 uint32_t to = ks[to_i];
281 double s_to = ts[to_i];
282 free(ks), free(ts);
283 msp_rows_free(r, nr);
284 double g = s_to / s_from - 1.0;
285 if (g <= in->w->improve) {
286 if (cand_remove(t, name)) mt_push_own(&t->notes, mt_fmt("%s: the gain fell to %.0f %%: stays on %u", name, g * 100.0, from));
287 continue;
288 }
289 long ci = cand_find(t, name);
290 if (ci < 0) {
291 t->cand = mt_realloc(t->cand, (t->ncand + 1) * sizeof *t->cand);
292 t->cand[t->ncand] = (cand_t){mt_strdup(name), from, now};
293 ci = (long)t->ncand++;
294 }
295 cand_t *c = &t->cand[ci];
296 if (c->from != from) c->from = from, c->since = now;
297 uint64_t held = now > c->since ? now - c->since : 0;
298 const char *lm = ms_get(&t->last_move, name);
299 bool has_dwell = lm != NULL;
300 uint64_t dwell = 0;
301 if (lm) {
302 uint64_t tm = strtoull(lm, NULL, 10);
303 dwell = now > tm ? now - tm : 0;
304 }
305 char *why = mt_fmt("system %u %.3f -> system %u %.3f (+%.0f %% for %.1f s)", from, s_from, to, s_to, g * 100.0, (double)held / 1e3);
306 if (held < in->w->hold_ms) {
307 mt_push_own(&t->notes, mt_fmt("%s: %s: holding (%llu ms needed)", name, why, (unsigned long long)in->w->hold_ms));
308 free(why);
309 continue;
310 }
311 if (has_dwell && dwell < in->w->dwell_ms) {
312 mt_push_own(&t->notes, mt_fmt("%s: %s: moved %.1f s ago (dwell %llu ms)", name, why, (double)dwell / 1e3, (unsigned long long)in->w->dwell_ms));
313 free(why);
314 continue;
315 }
316 gain = mt_realloc(gain, (nready + 1) * sizeof *gain);
317 ready = mt_realloc(ready, (nready + 1) * sizeof *ready);
318 gain[nready] = g;
319 ready[nready++] = (mst_move){mst_unit(doc, name, from), from, to, n->migrate == MESH_MIGRATE_STATE, why, false};
320 }
321 for (size_t i = t->ncand; i-- > 0;)
322 if (!mt_strs_has(&seen, t->cand[i].name)) cand_remove_at(t, i);
323 mt_strs_free(&seen);
324 uint32_t recent = mst_recent(t, now);
325 /* The biggest gain first (ties: the name). */
326 size_t best = 0;
327 for (size_t i = 1; i < nready; i++)
328 if (gain[i] > gain[best] || (gain[i] == gain[best] && strcmp(ready[i].programs.v[0], ready[best].programs.v[0]) < 0)) best = i;
329 if (nready) {
330 mst_move *m = &ready[best];
331 if (busy) mt_push_own(&t->notes, mt_fmt("%s: %s: waits for the migration in flight", m->programs.v[0], m->why));
332 else if (recent >= in->w->per_min)
333 mt_push_own(&t->notes, mt_fmt("%s: %s: budget spent (%u migrations in the last minute, at most %u)", m->programs.v[0], m->why, recent, in->w->per_min));
334 else {
335 *out = *m;
336 *m = (mst_move){0};
337 result = true;
338 }
339 }
340 for (size_t i = 0; i < nready; i++) mst_move_free(&ready[i]);
341 free(ready);
342 free(gain);
343 msp_books_free(&books);
344done:
345 mt_strs_free(&ord);
346 return result;
347}
348
349bool mst_group_step(mst_tracker *t, const msp_input *in, uint64_t now, char **group, ml_list *members, char **why) {
350 if (!in->doc->ngroups) return false;
351 const mfd_group *g = &in->doc->groups[0];
352 ml_list ideal;
353 char *w;
354 if (!msp_group_choice(in, g, &ideal, &w)) return false;
355 bool ok = false;
356 if (ml_eq(&ideal, &g->members)) {
357 t->has_group = false;
358 goto done;
359 }
360 double cur = msp_group_score(in, g, &g->members), nw = msp_group_score(in, g, &ideal);
361 bool lost = false;
362 for (size_t i = 0; i < g->members.n; i++)
363 if (g->members.v[i] >= in->nmembers || !in->members[g->members.v[i]].up) lost = true;
364 if (!lost && nw <= cur * (1.0 + in->w->improve) && ideal.n <= g->members.n) {
365 t->has_group = false;
366 goto done;
367 }
368 uint64_t since;
369 if (t->has_group && strcmp(t->group_name, g->name) == 0 && ml_eq(&t->group_members, &ideal)) since = t->group_since;
370 else {
371 mt_set(&t->group_name, g->name);
372 ml_free(&t->group_members);
373 t->group_members = ml_clone(&ideal);
374 t->group_since = now;
375 t->has_group = true;
376 since = now;
377 }
378 if ((now > since ? now - since : 0) < in->w->hold_ms) goto done;
379 char *l = ml_text(&g->members);
380 *why = mt_fmt("%s; now %s scores %.3f, proposed %.3f", w, l, cur, nw);
381 free(l);
382 *group = mt_strdup(g->name);
383 *members = ideal;
384 ideal = (ml_list){0};
385 ok = true;
386done:
387 ml_free(&ideal);
388 free(w);
389 return ok;
390}
391
392mfd_doc mst_start_move(const mfd_doc *doc, const mst_move *m, uint64_t since) {
393 mfd_doc d = mfd_doc_clone(doc);
394 for (size_t i = 0; i < m->programs.n; i++) {
395 mfd_prog *p = mfd_prog_get(&d, m->programs.v[i]);
396 if (!p) continue;
397 ml_list at = p->at.some ? ml_clone(&p->at.l) : (ml_list){0};
398 ml_remove_value(&at, m->from);
399 if (!ml_has(&at, m->to)) ml_push(&at, m->to);
400 ml_sort(&at);
401 ml_opt_free(&p->at);
402 p->at = (ml_opt){true, at};
403 p->has_from = p->has_to = m->state;
404 p->from = m->state ? m->from : 0;
405 p->to = m->state ? m->to : 0;
406 p->since = m->state ? since : 0;
407 free(p->why);
408 p->why = mt_fmt("moved from system %u to %u: %s", m->from, m->to, m->why);
409 }
410 return d;
411}
412
413mst_resumed mst_resume_moves(const mfd_doc *doc) {
414 mst_resumed r = {0};
415 mt_strs ord = msp_order(doc);
416 for (size_t oi = 0; oi < ord.n; oi++) {
417 const mfd_prog *p = mfd_prog_get(doc, ord.v[oi]);
418 if (!p->has_from || !p->has_to) continue;
419 size_t i = 0;
420 for (; i < r.n; i++)
421 if (r.moves[i].from == p->from && r.moves[i].to == p->to && r.since[i] == p->since) break;
422 if (i < r.n) mt_push(&r.moves[i].programs, ord.v[oi]);
423 else {
424 r.moves = mt_realloc(r.moves, (r.n + 1) * sizeof *r.moves);
425 r.since = mt_realloc(r.since, (r.n + 1) * sizeof *r.since);
426 mst_move m = {{0}, p->from, p->to, true, mt_strdup("taken over by a new leader"), false};
427 mt_push(&m.programs, ord.v[oi]);
428 r.moves[r.n] = m;
429 r.since[r.n++] = p->since;
430 }
431 }
432 mt_strs_free(&ord);
433 return r;
434}
435
436void mst_resumed_free(mst_resumed *r) {
437 for (size_t i = 0; i < r->n; i++) mst_move_free(&r->moves[i]);
438 free(r->moves);
439 free(r->since);
440 *r = (mst_resumed){0};
441}
442
443bool mst_mig_verdict(const mst_move *m, uint64_t since, bool target_up, const mst_outcome *outs, size_t nouts, const char *reason, uint64_t waited_ms,
444 uint64_t limit_ms, bool *ok, char **why, double *gap_ms) {
445 *gap_ms = -1;
446 if (!target_up) {
447 *ok = false;
448 *why = mt_fmt("the target, system %u, was lost", m->to);
449 return true;
450 }
451 size_t got = 0;
452 uint32_t gap = 0;
453 mt_strs bad = {0};
454 for (size_t i = 0; i < m->programs.n; i++)
455 for (size_t j = 0; j < nouts; j++)
456 if (strcmp(outs[j].name, m->programs.v[i]) == 0 && outs[j].version >= since) {
457 got++;
458 if (!outs[j].ok) mt_push(&bad, outs[j].name);
459 if (outs[j].gap_us > gap) gap = outs[j].gap_us;
460 break;
461 }
462 if (got == m->programs.n) {
463 if (!bad.n) {
464 char *names = mt_join(&m->programs, ",");
465 *ok = true;
466 *why = mt_fmt("system %u runs %s", m->to, names);
467 free(names);
468 if (gap > 0) *gap_ms = (double)gap / 1e3;
469 } else {
470 char *names = mt_join(&bad, ",");
471 *ok = false;
472 *why = mt_fmt("system %u could not take %s (%s)", m->to, names, reason);
473 free(names);
474 }
475 mt_strs_free(&bad);
476 return true;
477 }
478 mt_strs_free(&bad);
479 if (waited_ms > limit_ms) {
480 *ok = false;
481 *why = mt_fmt("not done in %llu s", (unsigned long long)(limit_ms / 1000));
482 return true;
483 }
484 return false;
485}
486
487mfd_doc mst_end_move(const mfd_doc *doc, const mst_move *m, bool ok, const char *why) {
488 mfd_doc d = mfd_doc_clone(doc);
489 for (size_t i = 0; i < m->programs.n; i++) {
490 mfd_prog *p = mfd_prog_get(&d, m->programs.v[i]);
491 if (!p) continue;
492 p->has_from = p->has_to = false;
493 p->from = p->to = 0;
494 p->since = 0;
495 if (!ok) {
496 ml_list at = p->at.some ? ml_clone(&p->at.l) : (ml_list){0};
497 ml_remove_value(&at, m->to);
498 if (!ml_has(&at, m->from)) ml_push(&at, m->from);
499 ml_sort(&at);
500 ml_opt_free(&p->at);
501 p->at = (ml_opt){true, at};
502 }
503 mt_set(&p->why, why);
504 }
505 return d;
506}