mirror of
https://github.com/skywind3000/kcp.git
synced 2026-08-19 06:03:28 +08:00
remove files
This commit is contained in:
172
tests/emulator.h
172
tests/emulator.h
@@ -1,172 +0,0 @@
|
||||
//=====================================================================
|
||||
//
|
||||
// emulator.h - network emulator for testing KCP implementation
|
||||
//
|
||||
// Last Modified: 2026/05/14 15:50:26
|
||||
//
|
||||
//=====================================================================
|
||||
#ifndef _EMULATOR_H_
|
||||
#define _EMULATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../ikcp.h"
|
||||
#include "netstats.h"
|
||||
#include "netsim.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// kcp
|
||||
//---------------------------------------------------------------------
|
||||
class Kcp
|
||||
{
|
||||
public:
|
||||
inline ~Kcp();
|
||||
inline Kcp(IUINT32 conv);
|
||||
|
||||
public:
|
||||
std::function<int(const char *buf, int len)> output;
|
||||
std::function<void(const char*)> writelog;
|
||||
|
||||
inline ikcpcb* ptr() { return _kcp; }
|
||||
inline const ikcpcb* ptr() const { return _kcp; }
|
||||
|
||||
inline int recv(char *buffer, int len);
|
||||
inline int recv(std::string &buffer);
|
||||
|
||||
inline int send(const char *buffer, int len);
|
||||
inline int send(const std::string &buffer);
|
||||
|
||||
inline void update(IUINT32 current);
|
||||
inline IUINT32 check(IUINT32 current) const;
|
||||
|
||||
inline int input(const char *data, long size);
|
||||
inline void flush();
|
||||
|
||||
inline int peeksize() const;
|
||||
inline int setmtu(int mtu);
|
||||
inline int wndsize(int sndwnd, int rcvwnd);
|
||||
inline int waitsnd() const;
|
||||
|
||||
inline int nodelay(int nodelay, int interval, int resend, int nc);
|
||||
|
||||
inline int setcc(const struct IKCPOPS *ops);
|
||||
|
||||
private:
|
||||
static int udp_output(const char *buf, int len, ikcpcb *kcp, void *user);
|
||||
static void kcp_writelog(const char *log, ikcpcb *kcp, void *user);
|
||||
|
||||
private:
|
||||
ikcpcb *_kcp;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// implementation
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
inline Kcp::~Kcp() {
|
||||
ikcp_release(_kcp);
|
||||
}
|
||||
|
||||
inline Kcp::Kcp(IUINT32 conv) {
|
||||
_kcp = ikcp_create(conv, this);
|
||||
_kcp->output = udp_output;
|
||||
}
|
||||
|
||||
inline int Kcp::udp_output(const char *buf, int len, ikcpcb *kcp, void *user) {
|
||||
Kcp *self = (Kcp *)user;
|
||||
if (self->output != nullptr) {
|
||||
return self->output(buf, len);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline void Kcp::kcp_writelog(const char *log, ikcpcb *kcp, void *user) {
|
||||
Kcp *self = (Kcp *)user;
|
||||
if (self->writelog != nullptr) {
|
||||
self->writelog(log);
|
||||
}
|
||||
}
|
||||
|
||||
inline int Kcp::recv(char *buffer, int len) {
|
||||
return ikcp_recv(_kcp, buffer, len);
|
||||
}
|
||||
|
||||
inline int Kcp::recv(std::string &buffer) {
|
||||
int peek = ikcp_peeksize(_kcp);
|
||||
if (peek < 0) {
|
||||
buffer.resize(0);
|
||||
return peek;
|
||||
}
|
||||
buffer.resize(peek);
|
||||
return ikcp_recv(_kcp, &buffer[0], peek);
|
||||
}
|
||||
|
||||
inline int Kcp::send(const char *buffer, int len) {
|
||||
return ikcp_send(_kcp, buffer, len);
|
||||
}
|
||||
|
||||
inline int Kcp::send(const std::string &buffer) {
|
||||
return ikcp_send(_kcp, buffer.c_str(), (int)buffer.size());
|
||||
}
|
||||
|
||||
|
||||
inline void Kcp::update(IUINT32 current) {
|
||||
ikcp_update(_kcp, current);
|
||||
}
|
||||
|
||||
inline IUINT32 Kcp::check(IUINT32 current) const {
|
||||
return ikcp_check(_kcp, current);
|
||||
}
|
||||
|
||||
inline int Kcp::input(const char *data, long size) {
|
||||
return ikcp_input(_kcp, data, size);
|
||||
}
|
||||
|
||||
inline void Kcp::flush() {
|
||||
ikcp_flush(_kcp);
|
||||
}
|
||||
|
||||
inline int Kcp::peeksize() const {
|
||||
return ikcp_peeksize(_kcp);
|
||||
}
|
||||
|
||||
inline int Kcp::setmtu(int mtu) {
|
||||
return ikcp_setmtu(_kcp, mtu);
|
||||
}
|
||||
|
||||
inline int Kcp::wndsize(int sndwnd, int rcvwnd) {
|
||||
return ikcp_wndsize(_kcp, sndwnd, rcvwnd);
|
||||
}
|
||||
|
||||
inline int Kcp::waitsnd() const {
|
||||
return ikcp_waitsnd(_kcp);
|
||||
}
|
||||
|
||||
inline int Kcp::nodelay(int nodelay, int interval, int resend, int nc) {
|
||||
return ikcp_nodelay(_kcp, nodelay, interval, resend, nc);
|
||||
}
|
||||
|
||||
inline int Kcp::setcc(const struct IKCPOPS *ops) {
|
||||
return ikcp_setcc(_kcp, ops);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// KcpPacket
|
||||
//---------------------------------------------------------------------
|
||||
struct KcpPacket {
|
||||
int group;
|
||||
int direction; // 0 for client->server, 1 for server->client
|
||||
std::string data;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
674
tests/netsim.cpp
674
tests/netsim.cpp
@@ -1,674 +0,0 @@
|
||||
//=====================================================================
|
||||
//
|
||||
// NetSimulator.cpp - Pure State-Machine Weak Network Simulator
|
||||
//
|
||||
// Reimplementation of inetsim.c in standalone C++ (STL only).
|
||||
// Deterministic, no system calls, portable across projects.
|
||||
//
|
||||
//=====================================================================
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
|
||||
#include "netsim.h"
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// namespace System: general-purpose utilities and components
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_BEGIN(System);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// internal constants
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static const int FLAG_CORRUPT = 1;
|
||||
static const int64_t TIME_JUMP_US = 600000000LL;
|
||||
static const double LOSS_RANDOM_INIT = 5000.0;
|
||||
static const int MAX_OPTION = 9;
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// PRNG: xoshiro128++
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static inline uint32_t Rotl32(uint32_t x, int k)
|
||||
{
|
||||
return (x << k) | (x >> (32 - k));
|
||||
}
|
||||
|
||||
static inline uint32_t Xoshiro128PP(uint32_t* s)
|
||||
{
|
||||
uint32_t result = Rotl32(s[0] + s[3], 7) + s[0];
|
||||
uint32_t t = s[1] << 9;
|
||||
s[2] ^= s[0];
|
||||
s[3] ^= s[1];
|
||||
s[1] ^= s[2];
|
||||
s[0] ^= s[3];
|
||||
s[2] ^= t;
|
||||
s[3] ^= Rotl32(s[3], 11);
|
||||
return result;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// PRNG: SplitMix64 for seeding
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static inline uint64_t SplitMix64Next(uint64_t* state)
|
||||
{
|
||||
uint64_t z = (*state += 0x9e3779b97f4a7c15ULL);
|
||||
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
|
||||
z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
|
||||
return z ^ (z >> 31);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// PRNG output mapping
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static inline double PrngToPermyriad(uint32_t r)
|
||||
{
|
||||
return (double)r * 10000.0 / 4294967296.0;
|
||||
}
|
||||
|
||||
static inline double PrngToJitterTable(uint32_t r)
|
||||
{
|
||||
return (double)(int32_t)r / 2147483648.0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// safe multiply-divide (overflow protection)
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static int64_t SafeMulDiv(int64_t a, int64_t b, int64_t c)
|
||||
{
|
||||
if (a == 0 || b == 0 || c == 0) return 0;
|
||||
if (a <= INT64_MAX / b) {
|
||||
return (a * b) / c;
|
||||
}
|
||||
int64_t q = a / c;
|
||||
int64_t r = a % c;
|
||||
if (q <= INT64_MAX / b) {
|
||||
return q * b + (r * b) / c;
|
||||
}
|
||||
return (int64_t)((double)a * (double)b / (double)c);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// clamp permyriad value to [0, 10000]
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static inline int64_t ClampPermyriad(int64_t v)
|
||||
{
|
||||
if (v < 0) return 0;
|
||||
if (v > 10000) return 10000;
|
||||
return v;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// config key mapping
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
struct KeyMap {
|
||||
const char* key;
|
||||
Option what;
|
||||
};
|
||||
|
||||
static const KeyMap keymap[] = {
|
||||
{ "delay", Option::Delay },
|
||||
{ "jitter", Option::Jitter },
|
||||
{ "delay_corr", Option::DelayCorr },
|
||||
{ "loss", Option::Loss },
|
||||
{ "loss_corr", Option::LossCorr },
|
||||
{ "corrupt", Option::Corrupt },
|
||||
{ "reorder", Option::Reorder },
|
||||
{ "rate", Option::Rate },
|
||||
{ "burst", Option::Burst },
|
||||
{ "queue", Option::QueueLimit },
|
||||
};
|
||||
|
||||
static const int KEYMAP_SIZE = 10;
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// config suffix mapping (longest first within each group)
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
struct SuffixMap {
|
||||
const char* suffix;
|
||||
int64_t factor;
|
||||
};
|
||||
|
||||
static const SuffixMap suffixes[] = {
|
||||
/* rate suffixes */
|
||||
{ "Gbps", 1000000000 },
|
||||
{ "Mbps", 1000000 },
|
||||
{ "kbps", 1000 },
|
||||
{ "bps", 1 },
|
||||
/* size suffixes */
|
||||
{ "MB", 1048576 },
|
||||
{ "KB", 1024 },
|
||||
{ "B", 1 },
|
||||
/* time suffixes */
|
||||
{ "ms", 1000 },
|
||||
{ "us", 1 },
|
||||
{ "s", 1000000 },
|
||||
/* probability suffix */
|
||||
{ "%", 100 },
|
||||
};
|
||||
|
||||
static const int SUFFIX_SIZE = 11;
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// config helpers
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
static int FindKey(const char* key, size_t keylen)
|
||||
{
|
||||
for (int i = 0; i < KEYMAP_SIZE; i++) {
|
||||
size_t kl = strlen(keymap[i].key);
|
||||
if (kl == keylen && strncmp(key, keymap[i].key, keylen) == 0)
|
||||
return (int)keymap[i].what;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ParseValue(const char* val, size_t vallen, int64_t* out)
|
||||
{
|
||||
/* try suffixes from longest to shortest */
|
||||
for (int i = 0; i < SUFFIX_SIZE; i++) {
|
||||
size_t slen = strlen(suffixes[i].suffix);
|
||||
if (slen <= vallen) {
|
||||
size_t numlen = vallen - slen;
|
||||
if (strncmp(val + numlen, suffixes[i].suffix, slen) == 0 && numlen > 0) {
|
||||
std::string numstr(val, numlen);
|
||||
char* endptr;
|
||||
int64_t num = strtoll(numstr.c_str(), &endptr, 10);
|
||||
if (endptr != numstr.c_str() + numlen) return -1;
|
||||
*out = num * suffixes[i].factor;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* no suffix: parse as raw integer */
|
||||
std::string numstr(val, vallen);
|
||||
char* endptr;
|
||||
int64_t num = strtoll(numstr.c_str(), &endptr, 10);
|
||||
if (endptr != numstr.c_str() + vallen) return -1;
|
||||
*out = num;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: constructor
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
NetSimulator::NetSimulator(uint64_t seed)
|
||||
{
|
||||
PrngSeed(seed);
|
||||
|
||||
_delay = 0;
|
||||
_jitter = 0;
|
||||
_delay_corr = 0;
|
||||
_loss = 0;
|
||||
_loss_corr = 0;
|
||||
_corrupt = 0;
|
||||
_reorder = 0;
|
||||
_rate = 0;
|
||||
_burst = -1;
|
||||
_queue_limit = -1;
|
||||
|
||||
_jitter_table_prev = 0.0;
|
||||
_loss_random_prev = LOSS_RANDOM_INIT;
|
||||
|
||||
_next_depart_time = 0;
|
||||
_current_time = 0;
|
||||
|
||||
_next_push_seq = 0;
|
||||
_pending_bytes = 0;
|
||||
|
||||
_stats = {};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: destructor
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
NetSimulator::~NetSimulator()
|
||||
{
|
||||
assert(_pending.empty() && _immediate.empty());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: Push
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
int NetSimulator::Push(void* pkt, size_t size, int64_t time_us)
|
||||
{
|
||||
_stats.packets_enqueued++;
|
||||
_stats.bytes_enqueued += (int64_t)size;
|
||||
|
||||
//----- queue_limit check (before pipeline)
|
||||
if (_queue_limit >= 0) {
|
||||
size_t current_bytes = _pending_bytes;
|
||||
if (current_bytes + size > (size_t)_queue_limit) {
|
||||
_stats.packets_dropped_queue++;
|
||||
_stats.packets_dropped++;
|
||||
NetSimEvent evt;
|
||||
evt.pkt = pkt;
|
||||
evt.type = EventType::Drop;
|
||||
evt.time_us = time_us;
|
||||
_immediate.push_back(evt);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//----- consume 4 random numbers
|
||||
uint32_t r0 = PrngNext();
|
||||
uint32_t r1 = PrngNext();
|
||||
uint32_t r2 = PrngNext();
|
||||
uint32_t r3 = PrngNext();
|
||||
|
||||
//----- loss determination (using r0)
|
||||
double loss_random_new = PrngToPermyriad(r0);
|
||||
double loss_random = (_loss_corr / 10000.0) * _loss_random_prev
|
||||
+ (1.0 - _loss_corr / 10000.0) * loss_random_new;
|
||||
_loss_random_prev = loss_random;
|
||||
if (loss_random < (double)_loss) {
|
||||
_stats.packets_dropped_loss++;
|
||||
_stats.packets_dropped++;
|
||||
NetSimEvent evt;
|
||||
evt.pkt = pkt;
|
||||
evt.type = EventType::Drop;
|
||||
evt.time_us = time_us;
|
||||
_immediate.push_back(evt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----- corrupt determination (using r1)
|
||||
int flags = 0;
|
||||
double corrupt_random = PrngToPermyriad(r1);
|
||||
if (corrupt_random < (double)_corrupt) {
|
||||
flags |= FLAG_CORRUPT;
|
||||
_stats.packets_corrupted++;
|
||||
}
|
||||
|
||||
//----- delay/reorder calculation (using r2 and r3)
|
||||
int64_t time_to_send;
|
||||
double reorder_random = PrngToPermyriad(r2);
|
||||
if (reorder_random < (double)_reorder && _delay > 0) {
|
||||
time_to_send = time_us;
|
||||
}
|
||||
else {
|
||||
double jitter_table_new = PrngToJitterTable(r3);
|
||||
double jitter_table = (_delay_corr / 10000.0) * _jitter_table_prev
|
||||
+ (1.0 - _delay_corr / 10000.0) * jitter_table_new;
|
||||
_jitter_table_prev = jitter_table;
|
||||
int64_t actual_delay = _delay + (int64_t)(_jitter * jitter_table);
|
||||
if (actual_delay < 0) actual_delay = 0;
|
||||
time_to_send = time_us + actual_delay;
|
||||
}
|
||||
|
||||
//----- burst drop check (before entering TBF queue)
|
||||
if (_rate > 0 && size > (size_t)EffectiveBurst()) {
|
||||
_stats.packets_dropped_burst++;
|
||||
_stats.packets_dropped++;
|
||||
NetSimEvent evt;
|
||||
evt.pkt = pkt;
|
||||
evt.type = EventType::Drop;
|
||||
evt.time_us = time_us;
|
||||
_immediate.push_back(evt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----- create and insert node
|
||||
Node node;
|
||||
node.pkt = pkt;
|
||||
node.size = size;
|
||||
node.push_time = time_us;
|
||||
node.time_to_send = time_to_send;
|
||||
node.push_seq = _next_push_seq++;
|
||||
node.flags = flags;
|
||||
|
||||
auto it = std::lower_bound(_pending.begin(), _pending.end(), node,
|
||||
[](const Node& a, const Node& b) {
|
||||
return a.time_to_send < b.time_to_send ||
|
||||
(a.time_to_send == b.time_to_send && a.push_seq < b.push_seq);
|
||||
});
|
||||
_pending.insert(it, node);
|
||||
|
||||
_pending_bytes += size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: Update
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
bool NetSimulator::Update(int64_t current_time)
|
||||
{
|
||||
if (current_time < _current_time) return false;
|
||||
|
||||
int64_t delta = current_time - _current_time;
|
||||
|
||||
if (delta > TIME_JUMP_US) {
|
||||
//----- time jump reset: flush all pending nodes
|
||||
for (size_t i = 0; i < _pending.size(); i++) {
|
||||
Node& n = _pending[i];
|
||||
/* consume 4 PRNG per node (keep sequence consistent) */
|
||||
PrngNext(); PrngNext(); PrngNext(); PrngNext();
|
||||
NetSimEvent evt;
|
||||
evt.pkt = n.pkt;
|
||||
evt.time_us = current_time;
|
||||
evt.type = (n.flags & FLAG_CORRUPT)
|
||||
? EventType::Corrupt : EventType::Sent;
|
||||
_immediate.push_back(evt);
|
||||
}
|
||||
_pending.clear();
|
||||
_pending_bytes = 0;
|
||||
|
||||
//----- reset FIFO departure and correlation state
|
||||
_next_depart_time = 0;
|
||||
_jitter_table_prev = 0.0;
|
||||
_loss_random_prev = LOSS_RANDOM_INIT;
|
||||
_current_time = current_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
_current_time = current_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: Poll
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
bool NetSimulator::Poll(NetSimEvent& evt)
|
||||
{
|
||||
int64_t imm_time = INT64_MAX;
|
||||
int64_t pend_time = INT64_MAX;
|
||||
|
||||
if (!_immediate.empty())
|
||||
imm_time = _immediate[0].time_us;
|
||||
|
||||
if (!_pending.empty()) {
|
||||
Node& first = _pending[0];
|
||||
int64_t actual = first.time_to_send;
|
||||
if (_rate > 0 && actual < _next_depart_time)
|
||||
actual = _next_depart_time;
|
||||
if (actual <= _current_time)
|
||||
pend_time = actual;
|
||||
}
|
||||
|
||||
if (imm_time == INT64_MAX && pend_time == INT64_MAX)
|
||||
return false;
|
||||
|
||||
//----- same time: DROP (immediate) takes priority
|
||||
if (imm_time <= pend_time) {
|
||||
evt = _immediate[0];
|
||||
_immediate.erase(_immediate.begin());
|
||||
return true;
|
||||
}
|
||||
|
||||
//----- pending node is ready and has earlier time
|
||||
Node& node = _pending[0];
|
||||
int64_t actual = node.time_to_send;
|
||||
if (_rate > 0 && actual < _next_depart_time)
|
||||
actual = _next_depart_time;
|
||||
|
||||
if (_rate > 0) {
|
||||
int64_t tx_time = SafeMulDiv((int64_t)node.size * 8, 1000000, _rate);
|
||||
_next_depart_time = actual + tx_time;
|
||||
}
|
||||
|
||||
_pending_bytes -= node.size;
|
||||
_stats.packets_sent++;
|
||||
_stats.bytes_sent += (int64_t)node.size;
|
||||
|
||||
evt.pkt = node.pkt;
|
||||
evt.time_us = actual;
|
||||
evt.type = (node.flags & FLAG_CORRUPT)
|
||||
? EventType::Corrupt : EventType::Sent;
|
||||
|
||||
_pending.erase(_pending.begin());
|
||||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: Drain
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
bool NetSimulator::Drain(NetSimEvent& evt)
|
||||
{
|
||||
//----- check immediate first
|
||||
if (!_immediate.empty()) {
|
||||
evt = _immediate[0];
|
||||
_immediate.erase(_immediate.begin());
|
||||
return true;
|
||||
}
|
||||
|
||||
//----- then pending
|
||||
if (!_pending.empty()) {
|
||||
Node& node = _pending[0];
|
||||
int64_t actual = node.time_to_send;
|
||||
if (_rate > 0 && actual < _next_depart_time)
|
||||
actual = _next_depart_time;
|
||||
|
||||
if (_rate > 0) {
|
||||
int64_t tx_time = SafeMulDiv((int64_t)node.size * 8, 1000000, _rate);
|
||||
_next_depart_time = actual + tx_time;
|
||||
}
|
||||
|
||||
_pending_bytes -= node.size;
|
||||
_stats.packets_sent++;
|
||||
_stats.bytes_sent += (int64_t)node.size;
|
||||
|
||||
evt.pkt = node.pkt;
|
||||
evt.time_us = actual;
|
||||
evt.type = (node.flags & FLAG_CORRUPT)
|
||||
? EventType::Corrupt : EventType::Sent;
|
||||
|
||||
_pending.erase(_pending.begin());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: NextTime
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
int64_t NetSimulator::NextTime() const
|
||||
{
|
||||
if (!_immediate.empty())
|
||||
return _immediate[0].time_us;
|
||||
if (!_pending.empty()) {
|
||||
int64_t ts = _pending[0].time_to_send;
|
||||
if (_rate > 0 && ts < _next_depart_time)
|
||||
ts = _next_depart_time;
|
||||
return ts;
|
||||
}
|
||||
return INT64_MAX;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: SetOption
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
int NetSimulator::SetOption(Option what, int64_t value)
|
||||
{
|
||||
int w = (int)what;
|
||||
if (w < 0 || w > MAX_OPTION) return -1;
|
||||
|
||||
switch (what) {
|
||||
case Option::Delay: _delay = value; break;
|
||||
case Option::Jitter: _jitter = value; break;
|
||||
case Option::DelayCorr: _delay_corr = ClampPermyriad(value); break;
|
||||
case Option::Loss: _loss = ClampPermyriad(value); break;
|
||||
case Option::LossCorr: _loss_corr = ClampPermyriad(value); break;
|
||||
case Option::Corrupt: _corrupt = ClampPermyriad(value); break;
|
||||
case Option::Reorder: _reorder = ClampPermyriad(value); break;
|
||||
case Option::Rate: _rate = value; break;
|
||||
case Option::Burst: _burst = value; break;
|
||||
case Option::QueueLimit: _queue_limit = value; break;
|
||||
default: return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: Config
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
int NetSimulator::Config(const char* str)
|
||||
{
|
||||
//----- cache old values for rollback
|
||||
int64_t old[(int)Option::QueueLimit + 1];
|
||||
old[(int)Option::Delay] = _delay;
|
||||
old[(int)Option::Jitter] = _jitter;
|
||||
old[(int)Option::DelayCorr] = _delay_corr;
|
||||
old[(int)Option::Loss] = _loss;
|
||||
old[(int)Option::LossCorr] = _loss_corr;
|
||||
old[(int)Option::Corrupt] = _corrupt;
|
||||
old[(int)Option::Reorder] = _reorder;
|
||||
old[(int)Option::Rate] = _rate;
|
||||
old[(int)Option::Burst] = _burst;
|
||||
old[(int)Option::QueueLimit] = _queue_limit;
|
||||
|
||||
const char* p = str;
|
||||
while (*p) {
|
||||
// skip whitespace
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (*p == '\0') break;
|
||||
|
||||
// find '=' separator
|
||||
const char* eq = strchr(p, '=');
|
||||
if (!eq) goto rollback;
|
||||
|
||||
size_t keylen = (size_t)(eq - p);
|
||||
const char* val_start = eq + 1;
|
||||
|
||||
// find end of value (next whitespace or end)
|
||||
const char* val_end = val_start;
|
||||
while (*val_end && *val_end != ' ' && *val_end != '\t')
|
||||
val_end++;
|
||||
size_t vallen = (size_t)(val_end - val_start);
|
||||
|
||||
if (keylen == 0 || vallen == 0) goto rollback;
|
||||
|
||||
// find key
|
||||
int what = FindKey(p, keylen);
|
||||
if (what < 0) goto rollback;
|
||||
|
||||
// parse value
|
||||
int64_t value;
|
||||
if (ParseValue(val_start, vallen, &value) < 0)
|
||||
goto rollback;
|
||||
|
||||
// apply option
|
||||
if (SetOption((Option)what, value) < 0) goto rollback;
|
||||
|
||||
p = val_end;
|
||||
}
|
||||
return 0;
|
||||
|
||||
rollback:
|
||||
_delay = old[(int)Option::Delay];
|
||||
_jitter = old[(int)Option::Jitter];
|
||||
_delay_corr = old[(int)Option::DelayCorr];
|
||||
_loss = old[(int)Option::Loss];
|
||||
_loss_corr = old[(int)Option::LossCorr];
|
||||
_corrupt = old[(int)Option::Corrupt];
|
||||
_reorder = old[(int)Option::Reorder];
|
||||
_rate = old[(int)Option::Rate];
|
||||
_burst = old[(int)Option::Burst];
|
||||
_queue_limit = old[(int)Option::QueueLimit];
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetSimulator::Config(const std::string& str)
|
||||
{
|
||||
return Config(str.c_str());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: QueuedBytes
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
size_t NetSimulator::QueuedBytes() const
|
||||
{
|
||||
return _pending_bytes;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: QueuedCount
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
size_t NetSimulator::QueuedCount() const
|
||||
{
|
||||
return _pending.size() + _immediate.size();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: GetStats
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
NetSimStats NetSimulator::GetStats() const
|
||||
{
|
||||
NetSimStats stats = _stats;
|
||||
stats.packets_dropped = stats.packets_dropped_loss
|
||||
+ stats.packets_dropped_burst
|
||||
+ stats.packets_dropped_queue;
|
||||
return stats;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: EffectiveBurst
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
int64_t NetSimulator::EffectiveBurst() const
|
||||
{
|
||||
if (_burst < 0) {
|
||||
int64_t auto_burst = _rate / 8000;
|
||||
return (auto_burst < 1600) ? 1600 : auto_burst;
|
||||
}
|
||||
return _burst;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: PrngNext
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
uint32_t NetSimulator::PrngNext()
|
||||
{
|
||||
return Xoshiro128PP(_prng);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: PrngSeed
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
void NetSimulator::PrngSeed(uint64_t seed)
|
||||
{
|
||||
uint64_t sm_state = seed;
|
||||
uint64_t v0 = SplitMix64Next(&sm_state);
|
||||
_prng[0] = (uint32_t)(v0);
|
||||
_prng[1] = (uint32_t)(v0 >> 32);
|
||||
uint64_t v1 = SplitMix64Next(&sm_state);
|
||||
_prng[2] = (uint32_t)(v1);
|
||||
_prng[3] = (uint32_t)(v1 >> 32);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// namespace end
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_END(System);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
272
tests/netsim.h
272
tests/netsim.h
@@ -1,272 +0,0 @@
|
||||
//=====================================================================
|
||||
//
|
||||
// NetSimulator.h - Pure State-Machine Weak Network Simulator
|
||||
//
|
||||
// Reimplementation of inetsim.c in standalone C++ (STL only).
|
||||
// Deterministic, no system calls, portable across projects.
|
||||
//
|
||||
// Rate limiting uses a FIFO serialization model: packets depart
|
||||
// in order, each waiting for the previous one's transmission time.
|
||||
// This means evt.time_us = max(propagation_delay_ready, wire_free_time),
|
||||
// naturally producing queuing delay that increases with queue depth —
|
||||
// the signal BBR and WebRTC GCC rely on for congestion detection.
|
||||
//
|
||||
// Packet lifetime management:
|
||||
//
|
||||
// The simulator NEVER copies, frees, or owns the packet data.
|
||||
// Push() accepts an opaque void* pointer; the same pointer is
|
||||
// returned verbatim in every output event (Sent, Drop, Corrupt).
|
||||
// It is the caller's responsibility to:
|
||||
// 1. Keep the pointed-to object alive until the event is polled.
|
||||
// 2. Free or recycle the object AFTER Poll/Drain returns it.
|
||||
// A typical pattern is to allocate before Push and deallocate
|
||||
// inside the Poll loop — since every pushed packet is guaranteed
|
||||
// to produce exactly one output event (never silently swallowed).
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// #include "NetSimulator.h"
|
||||
// using namespace System;
|
||||
//
|
||||
// // Create simulator with seed (same seed = deterministic replay)
|
||||
// NetSimulator sim(12345);
|
||||
//
|
||||
// // Configure network impairments
|
||||
// sim.Config("delay=50ms jitter=10ms loss=5% reorder=2% rate=1Mbps");
|
||||
//
|
||||
// // Push packets into the simulator at given timestamps
|
||||
// sim.Push((void*)"pkt1", 100, 0); // 100-byte packet at t=0
|
||||
// sim.Push((void*)"pkt2", 200, 1000); // 200-byte packet at t=1ms
|
||||
//
|
||||
// // Advance clock and poll for output events
|
||||
// sim.Update(50000); // advance to t=50ms
|
||||
// NetSimEvent evt;
|
||||
// while (sim.Poll(evt)) {
|
||||
// switch (evt.type) {
|
||||
// case EventType::Sent: /* packet delivered */ break;
|
||||
// case EventType::Drop: /* packet lost */ break;
|
||||
// case EventType::Corrupt: /* packet delivered but bad */ break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Drain remaining packets before destruction
|
||||
// while (sim.Drain(evt)) { /* handle final events */ }
|
||||
//
|
||||
//=====================================================================
|
||||
#ifndef _NET_SIMULATOR_H_
|
||||
#define _NET_SIMULATOR_H_
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error This file can only be compiled in C++ mode !!
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#ifndef NAMESPACE_BEGIN
|
||||
#define NAMESPACE_BEGIN(x) namespace x {
|
||||
#endif
|
||||
|
||||
#ifndef NAMESPACE_END
|
||||
#define NAMESPACE_END(x) }
|
||||
#endif
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// namespace System: general-purpose utilities and components
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_BEGIN(System);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// EventType: event types emitted by NetSimulator
|
||||
//---------------------------------------------------------------------
|
||||
enum class EventType {
|
||||
Sent = 1, // Packet passed through the simulator normally
|
||||
Drop = 2, // Packet was dropped (loss, burst, or queue overflow)
|
||||
Corrupt = 3, // Packet passed but marked as corrupted
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Option: configuration parameters
|
||||
// Permyriad values range 0-10000 (e.g. 1000 = 10%, 10000 = 100%).
|
||||
// Negative values for Burst/QueueLimit mean "auto" / "unlimited".
|
||||
//---------------------------------------------------------------------
|
||||
enum class Option {
|
||||
Delay = 0, // Base delay in microseconds, default 0
|
||||
Jitter = 1, // Jitter amplitude in microseconds, default 0
|
||||
DelayCorr = 2, // Delay correlation in permyriad, default 0
|
||||
Loss = 3, // Loss probability in permyriad, default 0
|
||||
LossCorr = 4, // Loss correlation in permyriad, default 0
|
||||
Corrupt = 5, // Corrupt probability in permyriad, default 0
|
||||
Reorder = 6, // Reorder probability in permyriad, default 0
|
||||
Rate = 7, // Bandwidth limit in bits/s, default 0 (no limit)
|
||||
Burst = 8, // Max burst size in bytes (drop packets exceeding this), default -1 (auto)
|
||||
QueueLimit = 9, // Max queued bytes, default -1 (unlimited)
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimEvent: output event structure
|
||||
//---------------------------------------------------------------------
|
||||
struct NetSimEvent {
|
||||
void* pkt; // Caller-owned packet pointer (from Push)
|
||||
EventType type; // Sent, Drop, or Corrupt
|
||||
int64_t time_us; // Event time in microseconds
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimStats: statistics counters
|
||||
//---------------------------------------------------------------------
|
||||
struct NetSimStats {
|
||||
int64_t packets_enqueued; // Total packets pushed into simulator
|
||||
int64_t packets_sent; // Total packets successfully sent
|
||||
int64_t packets_dropped_loss; // Packets dropped by random loss
|
||||
int64_t packets_dropped_burst; // Packets dropped by burst overflow
|
||||
int64_t packets_dropped_queue; // Packets dropped by queue_limit overflow
|
||||
int64_t packets_dropped; // Total dropped (= loss + burst + queue)
|
||||
int64_t packets_corrupted; // Packets marked as corrupted
|
||||
int64_t bytes_enqueued; // Total bytes pushed into simulator
|
||||
int64_t bytes_sent; // Total bytes successfully sent
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// NetSimulator: pure state-machine weak network simulator
|
||||
//---------------------------------------------------------------------
|
||||
class NetSimulator
|
||||
{
|
||||
public:
|
||||
// Construct simulator with given PRNG seed.
|
||||
// Same seed + same input produces identical output (deterministic).
|
||||
NetSimulator(uint64_t seed);
|
||||
|
||||
// Destructor. Asserts that both queues are empty in debug mode.
|
||||
// Call Drain() in a loop before destruction to avoid the assert.
|
||||
~NetSimulator();
|
||||
|
||||
//----- core operations -----
|
||||
|
||||
// Enqueue a packet into the simulator.
|
||||
// pkt: caller-owned pointer, returned verbatim in output events.
|
||||
// size: packet size in bytes (used for rate serialization and queue_limit).
|
||||
// time_us: enqueue time in microseconds.
|
||||
// Returns 0 on success. Packet may result in Sent, Drop, or Corrupt event.
|
||||
int Push(void* pkt, size_t size, int64_t time_us);
|
||||
|
||||
// Advance the simulator clock to current_time.
|
||||
// Returns true if time advanced normally.
|
||||
// Returns false if current_time < previous clock (time backward).
|
||||
// If delta > 10 minutes, triggers time-jump reset (flushes queues).
|
||||
bool Update(int64_t current_time);
|
||||
|
||||
// Retrieve the next due event (by time priority).
|
||||
// Immediate (DROP) events take priority over pending events at same time.
|
||||
// For pending events under rate limiting, actual_send_time = max(time_to_send, wire_free_time).
|
||||
// Returns true and fills evt if an event is available, false otherwise.
|
||||
bool Poll(NetSimEvent& evt);
|
||||
|
||||
// Retrieve all remaining events regardless of time or token constraints.
|
||||
// Used before destruction to empty the queues.
|
||||
// Returns true and fills evt if an event remains, false if both queues empty.
|
||||
bool Drain(NetSimEvent& evt);
|
||||
|
||||
// Return the time_us of the next upcoming event.
|
||||
// Checks immediate queue first, then pending queue.
|
||||
// Returns INT64_MAX if both queues are empty.
|
||||
int64_t NextTime() const;
|
||||
|
||||
//----- configuration -----
|
||||
|
||||
// Set a single simulation parameter.
|
||||
// Permyraid-type options (Loss, Corrupt, etc.) are clamped to [0, 10000].
|
||||
// Returns 0 on success, -1 for invalid Option value.
|
||||
int SetOption(Option what, int64_t value);
|
||||
|
||||
// Batch configure from a key=value string, e.g. "delay=50ms loss=10%".
|
||||
// Supported units: us, ms, s, bps, kbps, Mbps, Gbps, B, KB, MB, %.
|
||||
// All-or-nothing: if any key=value fails, all parameters roll back.
|
||||
// Returns 0 on success, -1 on parse failure (parameters unchanged).
|
||||
int Config(const char* str);
|
||||
|
||||
// std::string overload for Config.
|
||||
int Config(const std::string& str);
|
||||
|
||||
//----- query -----
|
||||
|
||||
// Total bytes currently held in the pending queue.
|
||||
// Does not include immediate queue (DROP events are not "queued data").
|
||||
size_t QueuedBytes() const;
|
||||
|
||||
// Total number of events in both pending and immediate queues.
|
||||
size_t QueuedCount() const;
|
||||
|
||||
// Return a copy of the accumulated statistics.
|
||||
// packets_dropped is computed as the sum of loss + burst + queue drops.
|
||||
NetSimStats GetStats() const;
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
void* pkt;
|
||||
size_t size;
|
||||
int64_t push_time;
|
||||
int64_t time_to_send;
|
||||
uint32_t push_seq;
|
||||
int flags;
|
||||
};
|
||||
|
||||
int64_t EffectiveBurst() const;
|
||||
uint32_t PrngNext();
|
||||
void PrngSeed(uint64_t seed);
|
||||
|
||||
// PRNG state
|
||||
uint32_t _prng[4];
|
||||
|
||||
// configuration
|
||||
int64_t _delay;
|
||||
int64_t _jitter;
|
||||
int64_t _delay_corr;
|
||||
int64_t _loss;
|
||||
int64_t _loss_corr;
|
||||
int64_t _corrupt;
|
||||
int64_t _reorder;
|
||||
int64_t _rate;
|
||||
int64_t _burst;
|
||||
int64_t _queue_limit;
|
||||
|
||||
// correlation state
|
||||
double _jitter_table_prev;
|
||||
double _loss_random_prev;
|
||||
|
||||
// FIFO departure: time when last bit of previous packet left the wire
|
||||
int64_t _next_depart_time;
|
||||
|
||||
// clock
|
||||
int64_t _current_time;
|
||||
|
||||
// queues
|
||||
std::vector<Node> _pending;
|
||||
std::vector<NetSimEvent> _immediate;
|
||||
|
||||
// push sequence counter
|
||||
uint32_t _next_push_seq;
|
||||
|
||||
// running total of pending bytes
|
||||
size_t _pending_bytes;
|
||||
|
||||
// statistics
|
||||
NetSimStats _stats;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// namespace end
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_END(System);
|
||||
|
||||
|
||||
#endif // _NET_SIMULATOR_H_
|
||||
|
||||
|
||||
|
||||
@@ -1,926 +0,0 @@
|
||||
//=====================================================================
|
||||
//
|
||||
// AbstractStats.cpp -
|
||||
//
|
||||
// Last Modified: 2020/04/03 17:47:58
|
||||
//
|
||||
//=====================================================================
|
||||
#include <assert.h>
|
||||
#include <limits>
|
||||
|
||||
#include "netstats.h"
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Namespace begin
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_BEGIN(System);
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// unsined integer unwrapper
|
||||
//=====================================================================
|
||||
|
||||
UnsignedWrap::UnsignedWrap()
|
||||
{
|
||||
size = 16;
|
||||
last_value = -1;
|
||||
}
|
||||
|
||||
UnsignedWrap::UnsignedWrap(int size)
|
||||
{
|
||||
this->size = 16;
|
||||
last_value = -1;
|
||||
reset(size);
|
||||
}
|
||||
|
||||
UnsignedWrap::UnsignedWrap(const UnsignedWrap& src)
|
||||
{
|
||||
size = src.size;
|
||||
last_value = src.last_value;
|
||||
}
|
||||
|
||||
UnsignedWrap& UnsignedWrap::operator = (const UnsignedWrap& src)
|
||||
{
|
||||
size = src.size;
|
||||
last_value = src.last_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void UnsignedWrap::reset(int size)
|
||||
{
|
||||
if (size == 16 || size == 2) {
|
||||
this->size = 16;
|
||||
}
|
||||
else if (size == 32 || size == 4) {
|
||||
this->size = 32;
|
||||
}
|
||||
else {
|
||||
this->size = size;
|
||||
}
|
||||
assert(size == 16 || size == 2 || size == 32 || size == 4);
|
||||
last_value = -1;
|
||||
}
|
||||
|
||||
void UnsignedWrap::set_last(int64_t last)
|
||||
{
|
||||
last_value = last;
|
||||
}
|
||||
|
||||
bool UnsignedWrap::u16_is_newer(uint16_t val, uint16_t prev_val)
|
||||
{
|
||||
const uint16_t half = ((uint16_t)0x8000);
|
||||
if (val - prev_val == half)
|
||||
return (val > prev_val)? true : false;
|
||||
if (val != prev_val && ((uint16_t)(val - prev_val)) < half)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnsignedWrap::u32_is_newer(uint32_t val, uint32_t prev_val)
|
||||
{
|
||||
const uint32_t half = ((uint32_t)0x80000000);
|
||||
if (val - prev_val == half)
|
||||
return (val > prev_val)? true : false;
|
||||
if (val != prev_val && ((uint32_t)(val - prev_val)) < half)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t UnsignedWrap::u16_update(uint16_t val)
|
||||
{
|
||||
const int64_t max_plus = ((int64_t)0xffff) + 1;
|
||||
uint16_t cropped_last = (uint16_t)(last_value & 0xffff);
|
||||
int64_t delta = ((int64_t)val) - ((int64_t)cropped_last);
|
||||
if (u16_is_newer(val, cropped_last)) {
|
||||
if (delta < 0) {
|
||||
delta += max_plus;
|
||||
}
|
||||
}
|
||||
else if (delta > 0 && last_value + delta - max_plus >= 0) {
|
||||
delta -= max_plus;
|
||||
}
|
||||
return last_value + delta;
|
||||
}
|
||||
|
||||
int64_t UnsignedWrap::u32_update(uint32_t val)
|
||||
{
|
||||
const int64_t max_plus = ((int64_t)0xffffffff) + 1;
|
||||
uint32_t cropped_last = (uint32_t)(last_value & 0xffffffff);
|
||||
int64_t delta = ((int64_t)val) - ((int64_t)cropped_last);
|
||||
if (u32_is_newer(val, cropped_last)) {
|
||||
if (delta < 0) {
|
||||
delta += max_plus;
|
||||
}
|
||||
}
|
||||
else if (delta > 0 && last_value + delta - max_plus >= 0) {
|
||||
delta -= max_plus;
|
||||
}
|
||||
return last_value + delta;
|
||||
}
|
||||
|
||||
int64_t UnsignedWrap::wrap_uint16(uint16_t val)
|
||||
{
|
||||
assert(size == 16);
|
||||
if (last_value < 0) {
|
||||
last_value = val;
|
||||
} else {
|
||||
last_value = u16_update(val);
|
||||
}
|
||||
return last_value;
|
||||
}
|
||||
|
||||
int64_t UnsignedWrap::wrap_uint32(uint32_t val)
|
||||
{
|
||||
assert(size == 32);
|
||||
if (last_value < 0) {
|
||||
last_value = val;
|
||||
} else {
|
||||
last_value = u32_update(val);
|
||||
}
|
||||
return last_value;
|
||||
}
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// 丢包统计
|
||||
//=====================================================================
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ctor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractLossStats::AbstractLossStats()
|
||||
{
|
||||
init(4000, 200, 100);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// dtor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractLossStats::~AbstractLossStats()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy ctor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractLossStats::AbstractLossStats(const AbstractLossStats& src):
|
||||
_loss_window(src._loss_window),
|
||||
_wrapper(src._wrapper),
|
||||
_stat_ts(src._stat_ts),
|
||||
_max_id(src._max_id),
|
||||
_k_loss_stats_window_ms(src._k_loss_stats_window_ms),
|
||||
_k_max_stats_window_num(src._k_max_stats_window_num),
|
||||
_k_calculation_limit(src._k_calculation_limit)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// move ctor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractLossStats::AbstractLossStats(AbstractLossStats&& src):
|
||||
_loss_window(std::move(src._loss_window)),
|
||||
_wrapper(src._wrapper),
|
||||
_stat_ts(src._stat_ts),
|
||||
_max_id(src._max_id),
|
||||
_k_loss_stats_window_ms(src._k_loss_stats_window_ms),
|
||||
_k_max_stats_window_num(src._k_max_stats_window_num),
|
||||
_k_calculation_limit(src._k_calculation_limit)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy assignment
|
||||
//---------------------------------------------------------------------
|
||||
AbstractLossStats& AbstractLossStats::operator = (const AbstractLossStats& src)
|
||||
{
|
||||
_loss_window.clear();
|
||||
for (auto it: src._loss_window) {
|
||||
_loss_window[it.first] = it.second;
|
||||
}
|
||||
_wrapper = src._wrapper;
|
||||
_stat_ts = src._stat_ts;
|
||||
_max_id = src._max_id;
|
||||
_k_loss_stats_window_ms = src._k_loss_stats_window_ms;
|
||||
_k_max_stats_window_num = src._k_max_stats_window_num;
|
||||
_k_calculation_limit = src._k_calculation_limit;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 初始化统计
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractLossStats::init(int window_ms, int window_num, int limit)
|
||||
{
|
||||
if (limit < 10) limit = 10;
|
||||
if (window_num <= limit) window_num = limit + 1;
|
||||
_k_loss_stats_window_ms = window_ms;
|
||||
_k_max_stats_window_num = window_num;
|
||||
_k_calculation_limit = limit;
|
||||
_stat_ts = -1;
|
||||
_max_id = -1;
|
||||
_loss_window.clear();
|
||||
_wrapper.reset(16);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 复位统计
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractLossStats::reset()
|
||||
{
|
||||
_stat_ts = -1;
|
||||
_max_id = -1;
|
||||
_loss_window.clear();
|
||||
_wrapper.reset(16);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 淘汰超过窗口的太老的数据
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractLossStats::evict_oldest(int64_t now_ts)
|
||||
{
|
||||
while (_loss_window.size() > 0) {
|
||||
LossWindow::iterator it = _loss_window.begin();
|
||||
bool drop = false;
|
||||
if ((int)_loss_window.size() > _k_max_stats_window_num) {
|
||||
drop = true;
|
||||
}
|
||||
else if (it->second + _k_loss_stats_window_ms < now_ts) {
|
||||
drop = true;
|
||||
}
|
||||
if (drop == false) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
_loss_window.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 收到一个包时调用
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractLossStats::update(uint16_t seq, int64_t now_ts)
|
||||
{
|
||||
int64_t id = _wrapper.wrap_uint16(seq);
|
||||
if (_max_id < id) {
|
||||
_max_id = id;
|
||||
}
|
||||
_loss_window[id] = now_ts;
|
||||
evict_oldest(now_ts);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 计算丢包率:fraction_loss 是返回的小数丢包率,255 代表 100%
|
||||
//---------------------------------------------------------------------
|
||||
int AbstractLossStats::calculate(int64_t now_ts, uint8_t *fraction_loss, int *num)
|
||||
{
|
||||
*fraction_loss = 0;
|
||||
*num = 0;
|
||||
|
||||
evict_oldest(now_ts);
|
||||
|
||||
if (_max_id < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((int)_loss_window.size() < _k_calculation_limit) {
|
||||
// printf("not enough: size=%d limit=%d\n", (int)_loss_window.size(), _k_calculation_limit);
|
||||
return -2;
|
||||
}
|
||||
|
||||
int count = (int)_loss_window.size();
|
||||
|
||||
if (count <= 0) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
auto it = _loss_window.begin();
|
||||
int64_t oldest = it->first;
|
||||
|
||||
int distance = (int32_t)(_max_id - oldest + 1);
|
||||
if (distance <= 0)
|
||||
return -4;
|
||||
|
||||
if (distance <= count) {
|
||||
*fraction_loss = 0;
|
||||
}
|
||||
else {
|
||||
*fraction_loss = (distance - count) * 255 / distance;
|
||||
}
|
||||
|
||||
*num = distance;
|
||||
_stat_ts = now_ts;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// 速率统计
|
||||
//=====================================================================
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ctor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractRateStats::AbstractRateStats()
|
||||
{
|
||||
_wnd_size = 2000;
|
||||
_scale = 8000;
|
||||
init(2000, 8000);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// dtor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractRateStats::~AbstractRateStats()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy ctor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractRateStats::AbstractRateStats(const AbstractRateStats& src):
|
||||
_buckets(src._buckets),
|
||||
_oldest_ts(src._oldest_ts),
|
||||
_oldest_index(src._oldest_index),
|
||||
_wnd_size(src._wnd_size),
|
||||
_scale(src._scale),
|
||||
_accumulated_count(src._accumulated_count),
|
||||
_sample_num(src._sample_num)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// move ctor
|
||||
//---------------------------------------------------------------------
|
||||
AbstractRateStats::AbstractRateStats(AbstractRateStats&& src):
|
||||
_buckets(std::move(src._buckets)),
|
||||
_oldest_ts(src._oldest_ts),
|
||||
_oldest_index(src._oldest_index),
|
||||
_wnd_size(src._wnd_size),
|
||||
_scale(src._scale),
|
||||
_accumulated_count(src._accumulated_count),
|
||||
_sample_num(src._sample_num)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy assign
|
||||
//---------------------------------------------------------------------
|
||||
AbstractRateStats& AbstractRateStats::operator = (const AbstractRateStats& src)
|
||||
{
|
||||
_buckets = src._buckets;
|
||||
_oldest_ts = src._oldest_ts;
|
||||
_oldest_index = src._oldest_index;
|
||||
_wnd_size = src._wnd_size;
|
||||
_scale = src._scale;
|
||||
_accumulated_count = src._accumulated_count;
|
||||
_sample_num = src._sample_num;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// initialize
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractRateStats::init(int wnd_size, float scale)
|
||||
{
|
||||
if (wnd_size < 0) wnd_size = _wnd_size;
|
||||
if (scale < 0) scale = _scale;
|
||||
_wnd_size = wnd_size;
|
||||
_scale = scale;
|
||||
_buckets.resize(wnd_size);
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// reset
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractRateStats::reset()
|
||||
{
|
||||
_buckets.resize(_wnd_size);
|
||||
_accumulated_count = 0;
|
||||
_sample_num = 0;
|
||||
_oldest_index = 0;
|
||||
_oldest_ts = -1;
|
||||
for (int i = 0; i < _wnd_size; i++) {
|
||||
_buckets[i].sum = 0;
|
||||
_buckets[i].sample = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 删除过期数据
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractRateStats::evict_oldest(int64_t now_ts)
|
||||
{
|
||||
if (_oldest_ts < 0)
|
||||
return;
|
||||
|
||||
int64_t new_oldest_ts = now_ts - _wnd_size + 1;
|
||||
|
||||
if (new_oldest_ts < _oldest_ts)
|
||||
return;
|
||||
|
||||
while (_sample_num > 0 && _oldest_ts < new_oldest_ts) {
|
||||
RateBucket& bucket = _buckets[_oldest_index];
|
||||
_sample_num -= bucket.sample;
|
||||
_accumulated_count -= bucket.sum;
|
||||
bucket.sum = 0;
|
||||
bucket.sample = 0;
|
||||
|
||||
if (++_oldest_index >= _wnd_size) {
|
||||
_oldest_index = 0;
|
||||
}
|
||||
|
||||
_oldest_ts++;
|
||||
}
|
||||
|
||||
_oldest_ts = new_oldest_ts;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 收到一个包时调用
|
||||
//---------------------------------------------------------------------
|
||||
void AbstractRateStats::update(size_t count, int64_t now_ts)
|
||||
{
|
||||
if (_oldest_ts > now_ts)
|
||||
return;
|
||||
|
||||
evict_oldest(now_ts);
|
||||
|
||||
if (_oldest_ts < 0) {
|
||||
_oldest_ts = now_ts;
|
||||
}
|
||||
|
||||
int offset = (int)(now_ts - _oldest_ts);
|
||||
int index = (_oldest_index + offset) % _wnd_size;
|
||||
|
||||
_sample_num++;
|
||||
_buckets[index].sum += (int)count;
|
||||
_buckets[index].sample++;
|
||||
|
||||
_accumulated_count += count;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 统计码率,数据不够返回 -1,成功返回速率
|
||||
//---------------------------------------------------------------------
|
||||
int AbstractRateStats::calculate(int64_t now_ts)
|
||||
{
|
||||
evict_oldest(now_ts);
|
||||
|
||||
int active_wnd_size = (int)((int64_t)(now_ts - _oldest_ts + 1));
|
||||
|
||||
if (_sample_num <= 0 || active_wnd_size <= 1 || active_wnd_size < _wnd_size)
|
||||
return -1;
|
||||
|
||||
double rate = ((((double)_accumulated_count) * _scale) / _wnd_size) + 0.5;
|
||||
|
||||
return (int)rate;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Minimum Sliding Window
|
||||
//---------------------------------------------------------------------
|
||||
MinHistory::MinHistory()
|
||||
{
|
||||
init(4000, false);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// dtor
|
||||
//---------------------------------------------------------------------
|
||||
MinHistory::~MinHistory()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy ctor
|
||||
//---------------------------------------------------------------------
|
||||
MinHistory::MinHistory(const MinHistory& src)
|
||||
{
|
||||
this->operator=(src);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// moving ctor
|
||||
//---------------------------------------------------------------------
|
||||
MinHistory::MinHistory(MinHistory&& src):
|
||||
_history(std::move(src._history)),
|
||||
_wnd_size(src._wnd_size),
|
||||
_reverse(src._reverse)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy assignment
|
||||
//---------------------------------------------------------------------
|
||||
MinHistory& MinHistory::operator = (const MinHistory& src)
|
||||
{
|
||||
_history.clear();
|
||||
for (auto &x : src._history) {
|
||||
_history.push_back(x);
|
||||
}
|
||||
_wnd_size = src._wnd_size;
|
||||
_reverse = src._reverse;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// mode: 0 for minimal, 1 for maximal, win size in millisecs
|
||||
//---------------------------------------------------------------------
|
||||
void MinHistory::init(int wnd_size, bool reverse)
|
||||
{
|
||||
_wnd_size = wnd_size;
|
||||
_reverse = reverse;
|
||||
_history.clear();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// clear history
|
||||
//---------------------------------------------------------------------
|
||||
void MinHistory::clear()
|
||||
{
|
||||
_history.clear();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// update value
|
||||
//---------------------------------------------------------------------
|
||||
void MinHistory::update(int value, int64_t now_ts)
|
||||
{
|
||||
while (!_history.empty()) {
|
||||
if (now_ts - _history.front().first + 1 <= _wnd_size) break;
|
||||
_history.pop_front();
|
||||
}
|
||||
while (!_history.empty()) {
|
||||
if (_reverse == false) {
|
||||
if (_history.back().second < value) break;
|
||||
}
|
||||
else {
|
||||
if (_history.back().second > value) break;
|
||||
}
|
||||
_history.pop_back();
|
||||
}
|
||||
_history.push_back(std::make_pair(now_ts, value));
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ctor
|
||||
//---------------------------------------------------------------------
|
||||
MaxHistory::MaxHistory()
|
||||
{
|
||||
_min_history.init(4000, true);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// dtor
|
||||
//---------------------------------------------------------------------
|
||||
MaxHistory::~MaxHistory()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy ctor
|
||||
//---------------------------------------------------------------------
|
||||
MaxHistory::MaxHistory(const MaxHistory& src)
|
||||
{
|
||||
_min_history = src._min_history;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// move ctor
|
||||
//---------------------------------------------------------------------
|
||||
MaxHistory::MaxHistory(MaxHistory&& src):
|
||||
_min_history(std::move(src._min_history))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// copy assignment
|
||||
//---------------------------------------------------------------------
|
||||
MaxHistory& MaxHistory::operator=(const MaxHistory &src)
|
||||
{
|
||||
_min_history = src._min_history;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// init
|
||||
//---------------------------------------------------------------------
|
||||
void MaxHistory::init(int wnd_size)
|
||||
{
|
||||
_min_history.init(wnd_size, true);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// clear
|
||||
//---------------------------------------------------------------------
|
||||
void MaxHistory::clear()
|
||||
{
|
||||
_min_history.clear();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// update
|
||||
//---------------------------------------------------------------------
|
||||
void MaxHistory::update(int value, int64_t now_ts)
|
||||
{
|
||||
_min_history.update(value, now_ts);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ctor
|
||||
//---------------------------------------------------------------------
|
||||
MovingAverage::MovingAverage()
|
||||
{
|
||||
_wnd_size = 5000;
|
||||
_sum = 0;
|
||||
_average = 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// dtor
|
||||
//---------------------------------------------------------------------
|
||||
MovingAverage::~MovingAverage()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// reset
|
||||
//---------------------------------------------------------------------
|
||||
void MovingAverage::init(int wnd_size)
|
||||
{
|
||||
_wnd_size = wnd_size;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// clear
|
||||
//---------------------------------------------------------------------
|
||||
void MovingAverage::clear()
|
||||
{
|
||||
_history.clear();
|
||||
_sum = 0;
|
||||
_average = 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// update value
|
||||
//---------------------------------------------------------------------
|
||||
void MovingAverage::update(int value, int64_t now_ts)
|
||||
{
|
||||
while (!_history.empty()) {
|
||||
if (now_ts - _history.front().first + 1 <= _wnd_size) break;
|
||||
_sum -= _history.front().second;
|
||||
_history.pop_front();
|
||||
}
|
||||
_history.push_back(std::make_pair(now_ts, value));
|
||||
_sum += value;
|
||||
_average = (int)(_sum / _history.size());
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ctor
|
||||
//---------------------------------------------------------------------
|
||||
RttHistory::RttHistory()
|
||||
{
|
||||
init(200);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// dtor
|
||||
//---------------------------------------------------------------------
|
||||
RttHistory::~RttHistory()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// initialize, wnd_size is the number of samples to keep
|
||||
//---------------------------------------------------------------------
|
||||
void RttHistory::init(int wnd_size)
|
||||
{
|
||||
_wnd_size = wnd_size;
|
||||
_sum = 0;
|
||||
_avg = 0;
|
||||
_srtt = -1;
|
||||
_rttval = -1;
|
||||
_rto = 0;
|
||||
_deviation = 0;
|
||||
_min = 0;
|
||||
_max = 0;
|
||||
_jitter = 0;
|
||||
_samples.clear();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// clear history
|
||||
//---------------------------------------------------------------------
|
||||
void RttHistory::clear()
|
||||
{
|
||||
_samples.clear();
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// push a new rtt sample
|
||||
//---------------------------------------------------------------------
|
||||
void RttHistory::push(double rtt)
|
||||
{
|
||||
_samples.push_back(rtt);
|
||||
_sum += rtt;
|
||||
if ((int)_samples.size() > _wnd_size) {
|
||||
_sum -= _samples.front();
|
||||
_samples.pop_front();
|
||||
}
|
||||
if (_samples.size() > 0) {
|
||||
_avg = _sum / _samples.size();
|
||||
double sum = 0;
|
||||
double minimum = 0;
|
||||
double maximum = 0;
|
||||
bool first = true;
|
||||
for (double rtt : _samples) {
|
||||
double diff = rtt - _avg;
|
||||
sum += diff * diff;
|
||||
if (first) {
|
||||
minimum = rtt;
|
||||
maximum = rtt;
|
||||
first = false;
|
||||
}
|
||||
if (rtt < minimum) minimum = rtt;
|
||||
if (rtt > maximum) maximum = rtt;
|
||||
}
|
||||
_deviation = sum / _samples.size();
|
||||
_min = minimum;
|
||||
_max = maximum;
|
||||
_jitter = (maximum - minimum) / 2;
|
||||
}
|
||||
if (_srtt < 0) {
|
||||
_srtt = rtt;
|
||||
_rttval = rtt / 2;
|
||||
} else {
|
||||
double delta = rtt - _srtt;
|
||||
if (delta < 0) delta = -delta;
|
||||
_rttval = (3 * _rttval + delta) / 4;
|
||||
_srtt = (7 * _srtt + rtt) / 8;
|
||||
if (_srtt < 0) _srtt = 0;
|
||||
}
|
||||
_rto = _srtt + (_rttval < 0? 0 : (4 * _rttval));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//=====================================================================
|
||||
// 带宽预算(Token Bucket)
|
||||
//=====================================================================
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ctor
|
||||
//---------------------------------------------------------------------
|
||||
PacingBudget::PacingBudget()
|
||||
{
|
||||
init(0);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 初始化:bandwidth 为带宽(bytes/sec),burst 默认等于 bandwidth
|
||||
//---------------------------------------------------------------------
|
||||
void PacingBudget::init(int64_t bandwidth_bps)
|
||||
{
|
||||
_bandwidth = bandwidth_bps;
|
||||
_burst = bandwidth_bps;
|
||||
_budget = 0;
|
||||
_last_time = -1;
|
||||
_remainder = 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 重置状态
|
||||
//---------------------------------------------------------------------
|
||||
void PacingBudget::reset()
|
||||
{
|
||||
_budget = 0;
|
||||
_last_time = -1;
|
||||
_remainder = 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 查询当前可发送字节数(内部自动更新时间,累加预算)
|
||||
//---------------------------------------------------------------------
|
||||
int64_t PacingBudget::available(int64_t now_ms)
|
||||
{
|
||||
if (_last_time < 0) {
|
||||
_last_time = now_ms;
|
||||
return _budget;
|
||||
}
|
||||
|
||||
int64_t elapsed = now_ms - _last_time;
|
||||
|
||||
if (elapsed <= 0) {
|
||||
return (_budget > 0) ? _budget : 0;
|
||||
}
|
||||
|
||||
_last_time = now_ms;
|
||||
|
||||
// 累加预算:bandwidth * elapsed / 1000,用 remainder 保留余数
|
||||
int64_t total = _bandwidth * elapsed + _remainder;
|
||||
int64_t increment = total / 1000;
|
||||
_remainder = total % 1000;
|
||||
|
||||
_budget += increment;
|
||||
|
||||
// 限制预算不超过 burst
|
||||
if (_budget > _burst) {
|
||||
_budget = _burst;
|
||||
_remainder = 0;
|
||||
}
|
||||
|
||||
return (_budget > 0) ? _budget : 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 登记实际发送的字节数,扣减预算
|
||||
//---------------------------------------------------------------------
|
||||
void PacingBudget::consume(int64_t bytes_sent)
|
||||
{
|
||||
_budget -= bytes_sent;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 计算发送 bytes_needed 还需等多少毫秒(已够返回 0)
|
||||
//---------------------------------------------------------------------
|
||||
int64_t PacingBudget::wait_time(int64_t bytes_needed) const
|
||||
{
|
||||
int64_t deficit = bytes_needed - _budget;
|
||||
if (deficit <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (_bandwidth <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return (deficit * 1000 + _bandwidth - 1) / _bandwidth;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// namespace end
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_END(System);
|
||||
|
||||
|
||||
|
||||
351
tests/netstats.h
351
tests/netstats.h
@@ -1,351 +0,0 @@
|
||||
//=====================================================================
|
||||
//
|
||||
// AbstractStats.h -
|
||||
//
|
||||
// Last Modified: 2020/04/03 17:47:56
|
||||
//
|
||||
//=====================================================================
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
|
||||
#ifndef NAMESPACE_BEGIN
|
||||
#define NAMESPACE_BEGIN(x) namespace x {
|
||||
#endif
|
||||
|
||||
#ifndef NAMESPACE_END
|
||||
#define NAMESPACE_END(x) }
|
||||
#endif
|
||||
|
||||
|
||||
NAMESPACE_BEGIN(System);
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// unsined integer unwrapper
|
||||
//---------------------------------------------------------------------
|
||||
class UnsignedWrap
|
||||
{
|
||||
public:
|
||||
UnsignedWrap();
|
||||
UnsignedWrap(int size);
|
||||
UnsignedWrap(const UnsignedWrap& src);
|
||||
|
||||
UnsignedWrap& operator = (const UnsignedWrap& src);
|
||||
|
||||
public:
|
||||
int64_t wrap_uint16(uint16_t val);
|
||||
int64_t wrap_uint32(uint32_t val);
|
||||
|
||||
void reset(int size);
|
||||
void set_last(int64_t last);
|
||||
|
||||
protected:
|
||||
static bool u16_is_newer(uint16_t val, uint16_t prev_val);
|
||||
static bool u32_is_newer(uint32_t val, uint32_t prev_val);
|
||||
|
||||
int64_t u16_update(uint16_t val);
|
||||
int64_t u32_update(uint32_t val);
|
||||
|
||||
protected:
|
||||
int size;
|
||||
int64_t last_value;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 带时间窗口的丢包统计
|
||||
//---------------------------------------------------------------------
|
||||
class AbstractLossStats
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractLossStats();
|
||||
AbstractLossStats();
|
||||
AbstractLossStats(const AbstractLossStats& src);
|
||||
AbstractLossStats(AbstractLossStats&& src);
|
||||
|
||||
AbstractLossStats& operator = (const AbstractLossStats& src);
|
||||
|
||||
public:
|
||||
|
||||
// 初始化,默认统计 4 秒窗口,最大包数为 200,至少 100 个包开始统计
|
||||
void init(int window_ms = 4000, int window_num = 200, int limit = 100);
|
||||
|
||||
// 复位
|
||||
void reset();
|
||||
|
||||
// 收到一个包时调用
|
||||
void update(uint16_t seq, int64_t now_ts);
|
||||
|
||||
// 计算丢包率:fraction_loss 是返回的小数丢包率,255 代表 100%
|
||||
// 成功返回 0,包不够统计就返回 -1
|
||||
int calculate(int64_t now_ts, uint8_t *fraction_loss, int *num);
|
||||
|
||||
protected:
|
||||
|
||||
// 淘汰超过窗口的太老的数据
|
||||
void evict_oldest(int64_t now_ts);
|
||||
|
||||
protected:
|
||||
|
||||
// 使用有序字典保存收包信息,key=id, value=ts, 越小的 id 越在前面
|
||||
typedef std::map<int64_t, int64_t, std::less<int64_t>> LossWindow;
|
||||
|
||||
LossWindow _loss_window; // 保存一段时间窗口的数据
|
||||
UnsignedWrap _wrapper; // 整数展开:16 -> 32
|
||||
|
||||
int64_t _stat_ts; // 时间戳
|
||||
int64_t _max_id; // 最大序号
|
||||
|
||||
int64_t _k_loss_stats_window_ms; // 最大时间窗口
|
||||
int64_t _k_max_stats_window_num; // 最大丢包序号
|
||||
int _k_calculation_limit; // 最少多少个包开始统计
|
||||
};
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 收包速率统计
|
||||
//---------------------------------------------------------------------
|
||||
class AbstractRateStats
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractRateStats();
|
||||
AbstractRateStats();
|
||||
AbstractRateStats(const AbstractRateStats& src);
|
||||
AbstractRateStats(AbstractRateStats&& src);
|
||||
|
||||
AbstractRateStats& operator = (const AbstractRateStats& src);
|
||||
|
||||
public:
|
||||
|
||||
// 初始化:wnd_size 是窗口大小,scale 是速率的单位,比如 8000 代表速率单位是 kbps
|
||||
// wnd_size 和 scale 传负数表示不变,继续使用之前的值
|
||||
// (2000, 8000) 是比较合理的默认值,表示统计最近 2 秒的速率,单位是 kbps
|
||||
// (2000, 1000) 也是比较合理的默认值,表示统计最近 2 秒的速率,单位是 Bytes/s
|
||||
void init(int wnd_size, float scale);
|
||||
|
||||
// 复位
|
||||
void reset();
|
||||
|
||||
// 收到一个包时调用
|
||||
void update(size_t count, int64_t now_ts);
|
||||
|
||||
// 统计码率,数据不够返回 -1,成功返回速率
|
||||
int calculate(int64_t now_ts);
|
||||
|
||||
// 取得积累值
|
||||
int64_t accumulated() const { return _accumulated_count; }
|
||||
|
||||
// 取得样本数
|
||||
int samples() const { return _sample_num; }
|
||||
|
||||
protected:
|
||||
void evict_oldest(int64_t now_ts);
|
||||
|
||||
protected:
|
||||
struct RateBucket { int sum, sample; };
|
||||
std::vector<RateBucket> _buckets;
|
||||
|
||||
protected:
|
||||
int64_t _oldest_ts;
|
||||
int _oldest_index;
|
||||
int _wnd_size;
|
||||
float _scale;
|
||||
int64_t _accumulated_count;
|
||||
int _sample_num;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Minimum Sliding Window
|
||||
//---------------------------------------------------------------------
|
||||
class MinHistory
|
||||
{
|
||||
public:
|
||||
virtual ~MinHistory();
|
||||
MinHistory();
|
||||
MinHistory(const MinHistory& src);
|
||||
MinHistory(MinHistory&& src);
|
||||
|
||||
MinHistory& operator = (const MinHistory& src);
|
||||
|
||||
public:
|
||||
|
||||
// reverse 为假时求最小值,否则求最大值
|
||||
void init(int wnd_size, bool reverse = false);
|
||||
|
||||
// clear history
|
||||
void clear();
|
||||
|
||||
// update value
|
||||
void update(int value, int64_t now_ts);
|
||||
|
||||
// check if empty
|
||||
inline bool empty() const { return _history.empty(); }
|
||||
|
||||
// get value
|
||||
inline int value() const { return empty()? 0 : _history.front().second; }
|
||||
|
||||
protected:
|
||||
std::deque<std::pair<int64_t, int>> _history;
|
||||
int _wnd_size = 2000;
|
||||
bool _reverse = false;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 求时间窗口内的最大值
|
||||
//---------------------------------------------------------------------
|
||||
class MaxHistory
|
||||
{
|
||||
public:
|
||||
virtual ~MaxHistory();
|
||||
MaxHistory();
|
||||
MaxHistory(const MaxHistory& src);
|
||||
MaxHistory(MaxHistory&& src);
|
||||
|
||||
MaxHistory& operator = (const MaxHistory& src);
|
||||
|
||||
public:
|
||||
void init(int wnd_size);
|
||||
|
||||
void clear();
|
||||
|
||||
void update(int value, int64_t now_ts);
|
||||
|
||||
inline bool empty() const { return _min_history.empty(); }
|
||||
|
||||
inline int value() const { return _min_history.value(); }
|
||||
|
||||
protected:
|
||||
MinHistory _min_history;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 平均值
|
||||
//---------------------------------------------------------------------
|
||||
class MovingAverage
|
||||
{
|
||||
public:
|
||||
virtual ~MovingAverage();
|
||||
MovingAverage();
|
||||
|
||||
public:
|
||||
void init(int wnd_size);
|
||||
|
||||
void clear();
|
||||
|
||||
void update(int value, int64_t now_ts);
|
||||
|
||||
inline bool empty() const { return _history.empty(); }
|
||||
|
||||
inline int value() const { return _average; }
|
||||
|
||||
inline int count() const { return (int)_history.size(); }
|
||||
|
||||
protected:
|
||||
std::deque<std::pair<int64_t, int>> _history;
|
||||
int64_t _sum;
|
||||
int _average;
|
||||
int _wnd_size;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// RttHistory
|
||||
//---------------------------------------------------------------------
|
||||
class RttHistory
|
||||
{
|
||||
public:
|
||||
virtual ~RttHistory();
|
||||
RttHistory();
|
||||
|
||||
public:
|
||||
|
||||
// initialize, wnd_size is the number of samples to keep
|
||||
void init(int wnd_size);
|
||||
|
||||
// clear history
|
||||
void clear();
|
||||
|
||||
// push a new rtt sample
|
||||
void push(double rtt);
|
||||
|
||||
inline int count() const { return (int)_samples.size(); }
|
||||
inline bool empty() const { return count() == 0; }
|
||||
|
||||
inline double rto() const { return _rto; }
|
||||
inline double avg() const { return _avg; }
|
||||
inline double deviation() const { return _deviation; }
|
||||
inline double minimum() const { return _min; }
|
||||
inline double maximum() const { return _max; }
|
||||
inline double jitter() const { return _jitter; }
|
||||
|
||||
private:
|
||||
std::deque<double> _samples;
|
||||
int _wnd_size;
|
||||
double _sum;
|
||||
double _avg;
|
||||
double _rto;
|
||||
double _srtt;
|
||||
double _rttval;
|
||||
double _deviation;
|
||||
double _min;
|
||||
double _max;
|
||||
double _jitter;
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// 带宽预算(Token Bucket)
|
||||
//---------------------------------------------------------------------
|
||||
class PacingBudget
|
||||
{
|
||||
public:
|
||||
PacingBudget();
|
||||
|
||||
public:
|
||||
|
||||
// 初始化:bandwidth 为带宽(bytes/sec),burst 默认等于 bandwidth
|
||||
void init(int64_t bandwidth_bps);
|
||||
|
||||
// 重置状态
|
||||
void reset();
|
||||
|
||||
// 查询当前可发送字节数(内部自动更新时间,累加预算)
|
||||
int64_t available(int64_t now_ms);
|
||||
|
||||
// 登记实际发送的字节数,扣减预算
|
||||
void consume(int64_t bytes_sent);
|
||||
|
||||
// 计算发送 bytes_needed 还需等多少毫秒(已够返回 0)
|
||||
int64_t wait_time(int64_t bytes_needed) const;
|
||||
|
||||
// 取得当前预算
|
||||
inline int64_t budget() const { return _budget; }
|
||||
|
||||
// 取得带宽设置
|
||||
inline int64_t bandwidth() const { return _bandwidth; }
|
||||
|
||||
protected:
|
||||
int64_t _bandwidth; // bytes per second
|
||||
int64_t _burst; // 最大累积预算(字节)
|
||||
int64_t _budget; // 当前可用预算(字节),可为负数
|
||||
int64_t _last_time; // 上次更新的时间戳(毫秒)
|
||||
int64_t _remainder; // 累积余数(微秒级精度补偿)
|
||||
};
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// namespace end
|
||||
//---------------------------------------------------------------------
|
||||
NAMESPACE_END(System);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user