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