|
|
|
|
@ -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; |
|
|
|
|
|