diff --git a/tests/netsim.cpp b/tests/netsim.cpp deleted file mode 100644 index ef45d90..0000000 --- a/tests/netsim.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include - -#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); - - - - - diff --git a/tests/netsim.h b/tests/netsim.h deleted file mode 100644 index 21a0fde..0000000 --- a/tests/netsim.h +++ /dev/null @@ -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 -#include -#include -#include - - -#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 _pending; - std::vector _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_ - - - diff --git a/tests/netstats.cpp b/tests/netstats.cpp deleted file mode 100644 index 8ad05fa..0000000 --- a/tests/netstats.cpp +++ /dev/null @@ -1,926 +0,0 @@ -//===================================================================== -// -// AbstractStats.cpp - -// -// Last Modified: 2020/04/03 17:47:58 -// -//===================================================================== -#include -#include - -#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); - - - diff --git a/tests/netstats.h b/tests/netstats.h deleted file mode 100644 index 664c0c4..0000000 --- a/tests/netstats.h +++ /dev/null @@ -1,351 +0,0 @@ -//===================================================================== -// -// AbstractStats.h - -// -// Last Modified: 2020/04/03 17:47:56 -// -//===================================================================== -#pragma once - -#include -#include -#include -#include -#include - -#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> 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 _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> _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> _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 _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); - - -