2 changed files with 70 additions and 0 deletions
@ -0,0 +1,45 @@
|
||||
#include "route_tx_stats.h" |
||||
#include "../../lib/debug_config.h" |
||||
#include <string.h> |
||||
|
||||
/* Эпоха принадлежит каналу; освобождение неподтверждённых пакетов не вызывает ack. */ |
||||
void route_tx_meter_reset(struct route_tx_meter *meter, uint64_t now_us) { |
||||
if (!meter) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "TX meter reset without state"); return; } |
||||
memset(meter, 0, sizeof(*meter)); |
||||
meter->started_us = meter->last_us = now_us; |
||||
} |
||||
|
||||
/* Сохраняем реальные моменты ACK, независимо от задержки таймера потребителя статистики. */ |
||||
int route_tx_meter_ack(struct route_tx_meter *meter, uint64_t now_us, uint64_t bytes) { |
||||
if (!meter || !bytes || now_us < meter->last_us || bytes > UINT64_MAX - meter->bytes || meter->packets == UINT64_MAX) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "TX meter invalid ACK time=%llu bytes=%llu", |
||||
(unsigned long long)now_us, (unsigned long long)bytes); |
||||
return -1; |
||||
} |
||||
uint64_t tick = now_us / ROUTE_TX_SAMPLE_US; |
||||
struct route_tx_bucket *bucket = &meter->buckets[tick % ROUTE_TX_BUCKETS]; |
||||
if (bucket->tick_plus_one != tick + 1) *bucket = (struct route_tx_bucket){.tick_plus_one = tick + 1}; |
||||
bucket->bytes += bytes; bucket->packets++; |
||||
meter->bytes += bytes; meter->packets++; meter->last_us = now_us; |
||||
return 0; |
||||
} |
||||
|
||||
/* Сумма только завершённых ячеек: idle интервалы остаются нулевыми. */ |
||||
int route_tx_meter_window(const struct route_tx_meter *meter, uint64_t now_us, struct route_tx_window *out) { |
||||
if (!meter || !out || now_us < meter->last_us) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "TX meter invalid snapshot time=%llu", (unsigned long long)now_us); |
||||
return -1; |
||||
} |
||||
uint64_t end = now_us / ROUTE_TX_SAMPLE_US; |
||||
struct route_tx_window result = {.end_us = end * ROUTE_TX_SAMPLE_US}; |
||||
result.valid = result.end_us >= meter->started_us && result.end_us - meter->started_us >= ROUTE_TX_WINDOW_US; |
||||
for (unsigned i = 0; i < ROUTE_TX_BUCKETS; i++) { |
||||
const struct route_tx_bucket *bucket = &meter->buckets[i]; |
||||
if (!bucket->tick_plus_one) continue; |
||||
uint64_t tick = bucket->tick_plus_one - 1; |
||||
if (tick >= end || end - tick > 10) continue; |
||||
result.bytes += bucket->bytes; result.packets += bucket->packets; |
||||
} |
||||
*out = result; |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,25 @@
|
||||
/* Учёт первой доставки payload логического канала. Один uasync-поток.
|
||||
* Десять завершённых интервалов по 50мс и текущий интервал; reset начинает новую историю. |
||||
* Повторы учитывает вызывающий: add вызывается только при снятии подтверждённого inflight. */ |
||||
#ifndef ROUTE_TX_STATS_H |
||||
#define ROUTE_TX_STATS_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#define ROUTE_TX_SAMPLE_US 50000u |
||||
#define ROUTE_TX_WINDOW_US 500000u |
||||
#define ROUTE_TX_BUCKETS 11 |
||||
|
||||
struct route_tx_bucket { uint64_t tick_plus_one, bytes, packets; }; |
||||
struct route_tx_meter { |
||||
struct route_tx_bucket buckets[ROUTE_TX_BUCKETS]; |
||||
uint64_t started_us, last_us, bytes, packets; |
||||
}; |
||||
struct route_tx_window { uint64_t end_us, bytes, packets; int valid; }; |
||||
|
||||
void route_tx_meter_reset(struct route_tx_meter *meter, uint64_t now_us); |
||||
int route_tx_meter_ack(struct route_tx_meter *meter, uint64_t now_us, uint64_t bytes); |
||||
/* Неполное окно после reset возвращает valid=0; текущая незавершённая ячейка исключается. */ |
||||
int route_tx_meter_window(const struct route_tx_meter *meter, uint64_t now_us, struct route_tx_window *out); |
||||
|
||||
#endif |
||||
Loading…
Reference in new issue