mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 17:45:13 +08:00
94 lines
3.1 KiB
Ucode
94 lines
3.1 KiB
Ucode
'use strict';
|
|
|
|
// Один проход без I/O: статус + живая цепочка + uci -> решение.
|
|
// inp = { order, members: { iface: base_weight }, status: { iface: {...} }, doc, chain }
|
|
|
|
import { DEFAULTS, new_state, quality, targets, integerize, needs_apply } from 'mwan3_autobalancer.model';
|
|
import { vmap_rules, widths, name_by_id, rebuild_batch } from 'mwan3_autobalancer.chain';
|
|
|
|
// Усредняем track_ip, у которых есть живой замер. Нет данных -> null.
|
|
export function sample_of(st) {
|
|
if (st == null || st.status != 'online')
|
|
return null;
|
|
let n = 0, lat = 0.0, loss = 0.0;
|
|
for (let t in (st.track_ip ?? [])) {
|
|
if (t.status != 'up' || !(t.latency > 0))
|
|
continue;
|
|
n++;
|
|
lat += t.latency;
|
|
loss += (t.packetloss ?? 0);
|
|
}
|
|
return n ? { latency: lat / n, loss: loss / n } : null;
|
|
};
|
|
|
|
function rule_members(inp, vr) {
|
|
let names = [], list = [];
|
|
for (let e in vr.entries) {
|
|
let name = name_by_id(inp.order, e.id);
|
|
let base = (name != null) ? inp.members[name] : null;
|
|
if (base == null)
|
|
return { error: `id ${e.id}: iface not exist in this policy` };
|
|
push(names, name);
|
|
push(list, { base });
|
|
}
|
|
return { names, list };
|
|
}
|
|
|
|
function fq(q) { return (q == null) ? '-' : sprintf('%.2f', q); }
|
|
|
|
export function plan(inp, cfg, states) {
|
|
cfg = cfg ?? DEFAULTS;
|
|
let vrs = vmap_rules(inp.doc), lines = [], parts = [], weights = {}, any = false, q = {};
|
|
|
|
if (!length(vrs))
|
|
return { batch: null, lines: ['rule not exitst in numgen/vmap (one member or unknown form)'], summary: 'no vmap' };
|
|
|
|
for (let n = 0; n < length(vrs); n++) {
|
|
let vr = vrs[n], rm = rule_members(inp, vr);
|
|
if (rm.error) {
|
|
push(lines, `rule ${n}: ${rm.error}`);
|
|
push(parts, `${n}:пропуск`);
|
|
continue;
|
|
}
|
|
for (let i = 0; i < length(rm.names); i++) {
|
|
let name = rm.names[i];
|
|
if (!exists(q, name)) {
|
|
if (!exists(states, name))
|
|
states[name] = new_state();
|
|
q[name] = quality(sample_of((inp.status ?? {})[name]), states[name], cfg);
|
|
}
|
|
rm.list[i].q = q[name];
|
|
}
|
|
|
|
let t = targets(rm.list, cfg.total), w = integerize(t, cfg.total), cur = widths(vr);
|
|
for (let i = 0; i < length(rm.names); i++)
|
|
push(lines, sprintf('правило %d: %s вес %d (цель %.1f из %d) q=%s', n, rm.names[i], cur[i], t[i], cfg.total, fq(rm.list[i].q)));
|
|
|
|
let apply = (vr.mod != cfg.total) || needs_apply(cur, t, cfg.total, cfg.hyst);
|
|
push(parts, `${n}:` + join(',', map(rm.names, (nm, i) => `${nm}=${w[i]}`)) + (apply ? '*' : ''));
|
|
if (apply) {
|
|
weights[n] = w;
|
|
any = true;
|
|
}
|
|
}
|
|
|
|
return {
|
|
batch: any ? rebuild_batch(inp.doc, inp.chain, weights) : null,
|
|
lines,
|
|
summary: join(' ', parts)
|
|
};
|
|
};
|
|
|
|
// Откат к «стоку»: ширины диапазонов = базовые веса из uci
|
|
export function restore_plan(inp) {
|
|
let vrs = vmap_rules(inp.doc), weights = {}, any = false;
|
|
for (let n = 0; n < length(vrs); n++) {
|
|
let rm = rule_members(inp, vrs[n]);
|
|
if (rm.error)
|
|
continue;
|
|
weights[n] = map(rm.list, x => x.base);
|
|
any = true;
|
|
}
|
|
return any ? rebuild_batch(inp.doc, inp.chain, weights) : null;
|
|
};
|