Use integral scores to select stream suppliers
This commit is contained in:
1 parent
83c652a810
commit
2cc9834e4c
5 files changed
+105
-39
No files matched your search
+2
-2
@@ -49,7 +49,7 @@ struct stream_candidate {
|
||||
uint8_t source;
|
||||
};
|
||||
|
||||
struct stream_selection_settings { uint32_t migration_confirm_ms, minimum_hold_ms, rtt_hysteresis_ms; };
|
||||
struct stream_selection_settings { uint32_t migration_confirm_ms, minimum_hold_ms, score_hysteresis_ms; };
|
||||
struct stream_selection_info {
|
||||
uint64_t probes_started, cycles, migrations, failures, restarts;
|
||||
unsigned candidates, probes_active, subflows, overlaps, providers;
|
||||
@@ -78,7 +78,7 @@ int stream_publish_open(struct UTUN_INSTANCE *inst, uint64_t group_id, const str
|
||||
int stream_subscribe_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out);
|
||||
/* Ручные выдачи для диагностики/явного управления; режим общий для всех owners экземпляра и не смешивается с automatic. */
|
||||
int stream_subscribe_manual_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out);
|
||||
/* По умолчанию: подтверждение улучшения 2000 мс, удержание 5000 мс, гистерезис RTT 20 мс. */
|
||||
/* По умолчанию: подтверждение улучшения 2000 мс, удержание 5000 мс, гистерезис интегральной оценки 20 мс. */
|
||||
int stream_selection_configure(struct STREAM_HANDLE *handle, const struct stream_selection_settings *settings);
|
||||
int stream_selection_get_info(const struct STREAM_HANDLE *handle, struct stream_selection_info *info);
|
||||
void stream_close(struct STREAM_HANDLE *handle);
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#define STREAM_PROBE_CYCLE_MS 30000
|
||||
#define STREAM_OFFER_TTL_MS 60000
|
||||
#define STREAM_MIGRATION_TIMEOUT_MS 5000
|
||||
#define STREAM_PROBLEM_PENALTY_MS 1000
|
||||
#define STREAM_HOP_PENALTY_MS 3
|
||||
|
||||
static uint64_t problem_penalty(const struct stream_metric_snapshot *metrics, int valid);
|
||||
|
||||
struct stream_offer {
|
||||
struct stream_receive_info info;
|
||||
@@ -124,12 +128,14 @@ static void probes_poll(struct stream_optimizer *o, struct STREAM_HANDLE *h, uin
|
||||
s->offers[s->probe_sub].info = info; s->failures = 0; s->retry_ms = 0;
|
||||
s->local_epoch = info.local_epoch; s->peer_epoch = info.peer_epoch;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "stream optimizer offer peer=%llx sub=%u range=%llu..%llu hops=%u"
|
||||
" rtt=%u/%d chain=%u/%d slots=%u accepting=%d lag=%llu stopped=%d age=%llu/%d queue=%llu",
|
||||
" rtt=%u/%d chain=%u/%d slots=%u accepting=%d lag=%llu stopped=%d age=%llu/%d queue=%llu penalty_ms=%llu score_ms=%llu",
|
||||
(unsigned long long)s->node, s->probe_sub, (unsigned long long)info.first_available,
|
||||
(unsigned long long)info.last_available, info.path.count, info.rtt_ms, info.rtt_valid,
|
||||
info.path_rtt_ms, info.path_rtt_valid, info.free_slots, info.accepting,
|
||||
(unsigned long long)info.supplier_metrics.lag_ms, info.supplier_metrics.stopped,
|
||||
(unsigned long long)info.progress_age_ms, info.progress_valid, (unsigned long long)info.supplier_queue_bytes);
|
||||
(unsigned long long)info.progress_age_ms, info.progress_valid, (unsigned long long)info.supplier_queue_bytes,
|
||||
(unsigned long long)problem_penalty(&info.supplier_metrics, info.supplier_metrics_valid),
|
||||
(unsigned long long)stream_supplier_score(&info, &info.supplier_metrics, info.supplier_metrics_valid));
|
||||
}
|
||||
unsigned next = s->probe_sub + 1;
|
||||
if (next == o->descriptor.n) { stream_receive_close(s->probe); s->probe = NULL; continue; }
|
||||
@@ -177,14 +183,21 @@ static unsigned usage(struct stream_optimizer *o, uint64_t node, unsigned except
|
||||
return count;
|
||||
}
|
||||
|
||||
static double problems(const struct stream_metric_snapshot *m, int valid) {
|
||||
/* Доля поздних блоков задаёт штраф в мс; текущая остановка/lag дают максимум, история остановок добавляет 10 мс. */
|
||||
static uint64_t problem_penalty(const struct stream_metric_snapshot *m, int valid) {
|
||||
if (!valid) return 0;
|
||||
if (m->stopped || m->lag_ms > STREAM_LATE_MS) return STREAM_PROBLEM_PENALTY_MS;
|
||||
double fraction = m->blocks ? (double)m->late_blocks / (double)m->blocks : 0;
|
||||
return (m->stopped || m->lag_ms > STREAM_LATE_MS ? 2 : 0) + fraction + (m->stops ? 0.01 : 0);
|
||||
double penalty = fraction * STREAM_PROBLEM_PENALTY_MS + (m->stops ? 10 : 0);
|
||||
return penalty >= STREAM_PROBLEM_PENALTY_MS ? STREAM_PROBLEM_PENALTY_MS : (uint64_t)penalty;
|
||||
}
|
||||
|
||||
static uint64_t total_rtt(const struct stream_receive_info *info) {
|
||||
return info->rtt_valid && info->path_rtt_valid ? (uint64_t)info->rtt_ms + info->path_rtt_ms : UINT64_MAX;
|
||||
/* path.count включает автора и поставщика: с последним переходом к нам это число hops от автора до получателя. */
|
||||
uint64_t stream_supplier_score(const struct stream_receive_info *info, const struct stream_metric_snapshot *metrics, int valid) {
|
||||
if (!info || !metrics) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream supplier score invalid arguments"); return UINT64_MAX; }
|
||||
if (!info->rtt_valid || !info->path_rtt_valid) return UINT64_MAX;
|
||||
return (uint64_t)info->path_rtt_ms + 2 * (uint64_t)info->rtt_ms + STREAM_HOP_PENALTY_MS * info->path.count
|
||||
+ problem_penalty(metrics, valid);
|
||||
}
|
||||
|
||||
static unsigned shared_path(struct stream_optimizer *o, const struct stream_receive_info *offer, unsigned sub) {
|
||||
@@ -212,18 +225,12 @@ static struct stream_supplier *supplier_choose(struct stream_optimizer *o, unsig
|
||||
if (!pass && o->descriptor.r && usage(o, s->node, sub) >= o->descriptor.r) continue;
|
||||
if (!best) { best = s; continue; }
|
||||
struct stream_receive_info *previous = &best->offers[sub].info;
|
||||
double score = problems(&info->supplier_metrics, info->supplier_metrics_valid);
|
||||
double old_score = problems(&previous->supplier_metrics, previous->supplier_metrics_valid);
|
||||
uint64_t rtt = total_rtt(info), old_rtt = total_rtt(previous);
|
||||
uint64_t score = stream_supplier_score(info, &info->supplier_metrics, info->supplier_metrics_valid);
|
||||
uint64_t old_score = stream_supplier_score(previous, &previous->supplier_metrics, previous->supplier_metrics_valid);
|
||||
int better = score < old_score;
|
||||
if (score == old_score) {
|
||||
better = rtt < old_rtt;
|
||||
if (rtt == old_rtt) {
|
||||
unsigned shared = shared_path(o, info, sub), old_shared = shared_path(o, previous, sub);
|
||||
better = shared < old_shared;
|
||||
if (shared == old_shared) better = info->path.count < previous->path.count
|
||||
|| (info->path.count == previous->path.count && s->node < best->node);
|
||||
}
|
||||
unsigned shared = shared_path(o, info, sub), old_shared = shared_path(o, previous, sub);
|
||||
better = shared < old_shared || (shared == old_shared && s->node < best->node);
|
||||
}
|
||||
if (better) best = s;
|
||||
}
|
||||
@@ -250,15 +257,12 @@ static void receive_failed(struct stream_optimizer *o, struct STREAM_RECEIVE **h
|
||||
|
||||
static int improved(struct stream_optimizer *o, unsigned sub, struct stream_supplier *s, const struct stream_receive_info *current) {
|
||||
struct stream_receive_info *offer = &s->offers[sub].info;
|
||||
double before = problems(¤t->metrics, current->metrics_valid);
|
||||
double after = problems(&offer->supplier_metrics, offer->supplier_metrics_valid);
|
||||
if (after + 0.10 < before) return 1;
|
||||
if (after > before) return 0;
|
||||
uint64_t rtt = total_rtt(offer), old = total_rtt(current);
|
||||
if (old != UINT64_MAX && rtt < old && old - rtt >= o->settings.rtt_hysteresis_ms) return 1;
|
||||
uint64_t before = stream_supplier_score(current, ¤t->metrics, current->metrics_valid);
|
||||
uint64_t after = stream_supplier_score(offer, &offer->supplier_metrics, offer->supplier_metrics_valid);
|
||||
if (after < before && (before == UINT64_MAX || before - after >= o->settings.score_hysteresis_ms)) return 1;
|
||||
return o->descriptor.r && usage(o, current->peer, sub) >= o->descriptor.r
|
||||
&& usage(o, s->node, sub) < o->descriptor.r && after <= before
|
||||
&& (rtt == UINT64_MAX || old == UINT64_MAX || rtt <= old + o->settings.rtt_hysteresis_ms);
|
||||
&& usage(o, s->node, sub) < o->descriptor.r
|
||||
&& (after <= before || after - before <= o->settings.score_hysteresis_ms);
|
||||
}
|
||||
|
||||
int stream_optimizer_create(struct UTUN_INSTANCE *inst, const struct stream_descriptor *d, struct stream_optimizer **out) {
|
||||
@@ -287,7 +291,7 @@ void stream_optimizer_destroy(struct stream_optimizer *o) {
|
||||
|
||||
int stream_optimizer_configure(struct stream_optimizer *o, const struct stream_selection_settings *settings) {
|
||||
if (!o || !settings || settings->migration_confirm_ms > 60000 || settings->minimum_hold_ms > 60000
|
||||
|| settings->rtt_hysteresis_ms > 60000) {
|
||||
|| settings->score_hysteresis_ms > 60000) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream optimizer settings invalid"); return STREAM_ERR_ARGUMENT;
|
||||
}
|
||||
o->settings = *settings; return STREAM_OK;
|
||||
@@ -376,8 +380,13 @@ void stream_optimizer_tick(struct stream_optimizer *o, struct STREAM_HANDLE *h,
|
||||
if (!pick->current) {
|
||||
if (stream_receive_open(h, s->node, (uint8_t)sub, seq, &pick->current) == STREAM_OK) {
|
||||
pick->opened_ms = now; pick->opened = 1; busy++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream optimizer selected peer=%llx sub=%u index=%llu",
|
||||
(unsigned long long)s->node, sub, (unsigned long long)seq);
|
||||
struct stream_receive_info *offer = &s->offers[sub].info;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream optimizer selected peer=%llx sub=%u index=%llu score_ms=%llu"
|
||||
" rtt_ms=%u chain_ms=%u hops=%u penalty_ms=%llu",
|
||||
(unsigned long long)s->node, sub, (unsigned long long)seq,
|
||||
(unsigned long long)stream_supplier_score(offer, &offer->supplier_metrics, offer->supplier_metrics_valid),
|
||||
offer->rtt_ms, offer->path_rtt_ms, offer->path.count,
|
||||
(unsigned long long)problem_penalty(&offer->supplier_metrics, offer->supplier_metrics_valid));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -387,11 +396,17 @@ void stream_optimizer_tick(struct stream_optimizer *o, struct STREAM_HANDLE *h,
|
||||
}
|
||||
if (pick->better_node != s->node) { pick->better_node = s->node; pick->better_ms = now; }
|
||||
if (now - pick->better_ms < o->settings.migration_confirm_ms) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "stream optimizer improvement sub=%u old=%llx new=%llx old_problems=%.3f new_problems=%.3f"
|
||||
" old_rtt=%llu new_rtt=%llu old_usage=%u new_usage=%u confirmed_ms=%llu",
|
||||
sub, (unsigned long long)info.peer, (unsigned long long)s->node, problems(&info.metrics, info.metrics_valid),
|
||||
problems(&s->offers[sub].info.supplier_metrics, s->offers[sub].info.supplier_metrics_valid),
|
||||
(unsigned long long)total_rtt(&info), (unsigned long long)total_rtt(&s->offers[sub].info),
|
||||
struct stream_receive_info *offer = &s->offers[sub].info;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "stream optimizer improvement sub=%u old=%llx new=%llx old_score_ms=%llu new_score_ms=%llu"
|
||||
" old_penalty_ms=%llu new_penalty_ms=%llu old_rtt_ms=%u/%d new_rtt_ms=%u/%d old_chain_ms=%u/%d new_chain_ms=%u/%d"
|
||||
" old_hops=%u new_hops=%u old_usage=%u new_usage=%u confirmed_ms=%llu",
|
||||
sub, (unsigned long long)info.peer, (unsigned long long)s->node,
|
||||
(unsigned long long)stream_supplier_score(&info, &info.metrics, info.metrics_valid),
|
||||
(unsigned long long)stream_supplier_score(offer, &offer->supplier_metrics, offer->supplier_metrics_valid),
|
||||
(unsigned long long)problem_penalty(&info.metrics, info.metrics_valid),
|
||||
(unsigned long long)problem_penalty(&offer->supplier_metrics, offer->supplier_metrics_valid),
|
||||
info.rtt_ms, info.rtt_valid, offer->rtt_ms, offer->rtt_valid,
|
||||
info.path_rtt_ms, info.path_rtt_valid, offer->path_rtt_ms, offer->path_rtt_valid, info.path.count, offer->path.count,
|
||||
usage(o, info.peer, sub), usage(o, s->node, sub), (unsigned long long)(now - pick->better_ms));
|
||||
if (stream_receive_open(h, s->node, (uint8_t)sub, seq, &pick->replacement) == STREAM_OK) {
|
||||
pick->replacement_ms = now; migrating = 1;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "stream.h"
|
||||
|
||||
struct stream_optimizer;
|
||||
struct stream_receive_info;
|
||||
/* Меньшая оценка лучше: chain RTT + 2 * peer RTT + 3 мс/hop + штраф 0..1000 мс; неизвестный RTT = UINT64_MAX. */
|
||||
uint64_t stream_supplier_score(const struct stream_receive_info *info, const struct stream_metric_snapshot *metrics, int valid);
|
||||
/* Только монотонное расписание; pending/active — незавершённый обход/пробы текущего цикла.
|
||||
* Возвращает допустимое число новых узлов, не адресов/субпотоков: 32 один раз, затем 0/1. */
|
||||
struct stream_probe_schedule { uint64_t cycle, started_ms, next_ms; };
|
||||
|
||||
Reference in new issue
Block a user