mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 23:15:13 +08:00
95 lines
3.4 KiB
Ucode
95 lines
3.4 KiB
Ucode
'use strict';
|
|
|
|
// Чистая логика без I/O: качество линка -> веса. Тестируется на хосте.
|
|
// Внимание: в ucode int / int делится нацело, поэтому везде умножаем на 1.0.
|
|
|
|
export const DEFAULTS = {
|
|
total: 20, // сумма целых весов в цепочке (шаг 5%)
|
|
alpha: 0.3, // EWMA для задержки
|
|
drift: 0.01, // как быстро «обычная» задержка ползёт вверх
|
|
lat_free: 1.5, // до x1.5 от обычной задержки штрафа нет
|
|
loss_alpha: 0.1, // EWMA потерь: одиночный потерянный пинг почти не виден
|
|
loss_free: 5, // сглаженные потери до 5% штрафа не дают
|
|
loss_div: 50, // каждые 50% сверх порога снимают весь вес (с запасом 0.9)
|
|
hyst: 1.0 // применять, когда цель ушла от живого веса на >= 1 шаг
|
|
};
|
|
|
|
export function new_state() {
|
|
return { lat: null, base: null, loss: null };
|
|
};
|
|
|
|
// sample: { latency (мс), loss (%) } из mwan3track; st: состояние линка (меняется)
|
|
// Возвращает q в (0..1] или null, если данных нет.
|
|
export function quality(sample, st, cfg) {
|
|
cfg = cfg ?? DEFAULTS;
|
|
if (sample == null || sample.latency == null || sample.latency <= 0)
|
|
return null;
|
|
|
|
let lat = sample.latency;
|
|
st.lat = (st.lat == null) ? lat : st.lat + (lat - st.lat) * cfg.alpha;
|
|
st.base = (st.base == null || st.lat < st.base)
|
|
? st.lat
|
|
: st.base + (st.lat - st.base) * cfg.drift;
|
|
|
|
let r = (st.lat * 1.0) / st.base;
|
|
let q_lat = (r <= cfg.lat_free) ? 1 : cfg.lat_free / r;
|
|
|
|
let loss = (sample.loss ?? 0) * 1.0;
|
|
st.loss = (st.loss == null) ? loss : st.loss + (loss - st.loss) * cfg.loss_alpha;
|
|
let l = (st.loss - cfg.loss_free) / cfg.loss_div;
|
|
if (l < 0) l = 0;
|
|
let q_loss = 1 - (l > 0.9 ? 0.9 : l);
|
|
|
|
return q_lat * q_loss;
|
|
};
|
|
|
|
// members: [{ base (вес из uci), q (или null) }] -> вещественные цели в масштабе total
|
|
export function targets(members, total) {
|
|
let sum = 0, t = [];
|
|
for (let m in members)
|
|
sum += m.base * (m.q == null ? 1 : m.q);
|
|
for (let m in members)
|
|
push(t, sum > 0 ? (total * 1.0) * m.base * (m.q == null ? 1 : m.q) / sum : (total * 1.0) / length(members));
|
|
return t;
|
|
};
|
|
|
|
// Метод наибольших остатков, минимум 1 на участника
|
|
export function integerize(t, total) {
|
|
let n = length(t), w = [], r = [], s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
let f = int(t[i]);
|
|
push(w, f < 1 ? 1 : f);
|
|
push(r, t[i] - f);
|
|
s += w[i];
|
|
}
|
|
for (let g = 0; s < total && g < total; g++) {
|
|
let b = 0;
|
|
for (let i = 1; i < n; i++)
|
|
if (r[i] > r[b]) b = i;
|
|
w[b]++; r[b] = -1; s++;
|
|
}
|
|
for (let g = 0; s > total && g < total; g++) {
|
|
let b = 0;
|
|
for (let i = 1; i < n; i++)
|
|
if (w[i] > w[b]) b = i;
|
|
if (w[b] <= 1) break;
|
|
w[b]--; s--;
|
|
}
|
|
return w;
|
|
};
|
|
|
|
// cur: ширины диапазонов в живой цепочке (любой масштаб)
|
|
export function needs_apply(cur, t, total, hyst) {
|
|
hyst = hyst ?? DEFAULTS.hyst;
|
|
let s = 0;
|
|
for (let x in cur) s += x;
|
|
if (s <= 0 || length(cur) != length(t))
|
|
return true;
|
|
for (let i = 0; i < length(t); i++) {
|
|
let d = (cur[i] * 1.0) * total / s - t[i];
|
|
if ((d < 0 ? -d : d) >= hyst)
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|