#include "guard.h" #include #include void history_add(history_t *h, double at, uint64_t value) { h->p[h->next].at = at; h->p[h->next].value = value; h->next = (h->next + 1) % HIST; if (h->count < HIST) h->count++; } delta_t history_delta(const history_t *h, double now, unsigned seconds) { delta_t d = {0, 0, 0}; unsigned i, latest; double best = 1.25; if (h->count < 2) return d; latest = (h->next + HIST - 1) % HIST; if (now - h->p[latest].at > 1.25) return d; for (i = 0; i < h->count; i++) { double age = h->p[latest].at - h->p[i].at; double error = age - seconds; if (error < 0) error = -error; if (age > 0 && error < best && h->p[i].value <= INT64_MAX && h->p[latest].value <= INT64_MAX) { best = error; d.valid = 1; d.age = age; d.value = (int64_t)h->p[latest].value - (int64_t)h->p[i].value; } } return d; } uint64_t rank_score(uint64_t rss, delta_t growth) { uint64_t add = growth.valid && growth.value > 0 ? (uint64_t)growth.value : 0; if (add > (UINT64_MAX - rss) / 2) return UINT64_MAX; return rss + 2 * add; } int swap_physical_bytes_from_blocks(uint64_t total_blocks, uint64_t free_blocks, uint64_t *bytes) { uint64_t used; if (!bytes || free_blocks > total_blocks) return -1; used = total_blocks - free_blocks; if (used > UINT64_MAX / 512) return -1; *bytes = used * 512; return 0; } const char *state_name(enum state s) { static const char *names[] = {"NORMAL", "PRE-PRESSURE", "PRESSURE", "CRITICAL", "EMERGENCY", "RECOVERY"}; return names[s]; } void engine_init(engine_t *e) { memset(e, 0, sizeof (*e)); engine_gap(e); } void engine_gap(engine_t *e) { e->low_since = e->critical_since = e->recovery_since = -1; e->have_last = 0; e->danger = 0; e->floor = 0; } void engine_step(engine_t *e, const config_t *c, const sample_t *s, delta_t free_d5, delta_t physical_swap_d5, delta_t pageout_d5) { int stress; enum state desired = NORMAL; if (e->have_last && (s->at - e->last_at > 2.5 || s->at <= e->last_at)) engine_gap(e); /* Physical swap growth is corroboration only, never an independent trigger. */ stress = s->scan > 0 || (free_d5.valid && free_d5.value < 0) || (e->have_last && s->free < e->last_free) || (physical_swap_d5.valid && physical_swap_d5.value > 0 && pageout_d5.valid && pageout_d5.value > 0 && s->free < s->lots); e->floor = s->free < s->min / 2; if (s->free < s->lots) { if (e->low_since < 0) e->low_since = s->at; } else e->low_since = -1; if (s->free < s->des && stress) { if (e->critical_since < 0) e->critical_since = s->at; } else e->critical_since = -1; if (e->low_since >= 0 && s->at - e->low_since >= c->pre_s) desired = PRE_PRESSURE; if (e->low_since >= 0 && s->at - e->low_since >= c->pressure_s && stress) desired = PRESSURE; if (e->critical_since >= 0 && s->at - e->critical_since >= c->critical_s) desired = CRITICAL; if (s->free < s->min) desired = EMERGENCY; e->danger = desired == CRITICAL || desired == EMERGENCY; /* State hysteresis never authorizes an action on its own. */ if (desired >= PRE_PRESSURE && desired <= EMERGENCY) { e->recovery_since = -1; if (e->state == RECOVERY || desired > e->state) e->state = desired; } if (s->free >= s->lots && e->state != NORMAL) { e->state = RECOVERY; /* 10% exit margin, and no active scanner, for a full recovery. */ if (s->free - s->lots >= s->lots / 10 && s->scan == 0) { if (e->recovery_since < 0) e->recovery_since = s->at; if (s->at - e->recovery_since >= c->recovery_s) e->state = NORMAL; } else e->recovery_since = -1; } else if (s->free < s->lots) { e->recovery_since = -1; if (e->state == RECOVERY) e->state = PRE_PRESSURE; } e->last_at = s->at; e->last_free = s->free; e->have_last = 1; } int same_process(const process_t *a, const process_t *b) { return a->pid == b->pid && a->start_sec == b->start_sec && a->start_nsec == b->start_nsec && a->zid == b->zid && a->zone_generation == b->zone_generation && !strcmp(a->uuid, b->uuid); } int was_victim(const controller_t *c, const process_t *p) { unsigned i; for (i = 0; i < c->nvictims; i++) if (same_process(&c->victims[i], p)) return 1; return 0; } plan_t action_plan(controller_t *a, const config_t *c, const engine_t *e, const sample_t *s, const process_t *candidate) { plan_t p; unsigned i, n = 0; memset(&p, 0, sizeof (p)); p.reason = "no_pressure"; for (i = 0; i < a->nrate; i++) if (s->at - a->rate[i] < 60) a->rate[n++] = a->rate[i]; a->nrate = n; if (e->state == NORMAL) a->nvictims = 0; if (s->free >= s->lots) a->has_pending = 0; if (!e->danger) return p; if (a->has_pending) { p.reason = "term_grace"; if (e->floor || (s->free < s->min && s->at - a->term_at >= 1) || s->at - a->term_at >= c->term_s) { p.target = a->pending; p.action = KILL; p.reason = e->floor ? "floor" : "memory_not_recovered"; } return p; } if (a->nvictims >= c->max_victims || a->nrate >= c->max_per_minute) { p.reason = "victim_budget_exhausted"; return p; } /* Emergency bypasses the normal cooldown, not protection or budgets. */ if (s->free >= s->min && s->at < a->next_at) { p.reason = "cooldown"; return p; } if (!candidate || protected_process(c, candidate) || was_victim(a, candidate)) { p.reason = "no_authorized_candidate"; return p; } p.target = *candidate; p.action = e->floor ? KILL : TERM; p.reason = e->floor ? "floor" : s->free < s->min ? "minfree" : "critical"; return p; } void action_commit(controller_t *a, const config_t *c, const sample_t *s, const plan_t *p) { if (p->action == NONE) return; if (!was_victim(a, &p->target)) { if (a->nvictims < MAX_VICTIMS) a->victims[a->nvictims++] = p->target; if (a->nrate < 64) a->rate[a->nrate++] = s->at; } a->next_at = s->at + c->cooldown_s; if (p->action == TERM) { a->pending = p->target; a->term_at = s->at; a->has_pending = 1; } else a->has_pending = 0; }