add network test feature (PING/PONG with latency stats)
New frame types PING (0x0B) and PONG (0x0C), each carrying an 8-byte nonce. Server echoes PING nonce verbatim in PONG. Client sends pings at random 10-100ms intervals, correlates nonces to measure RTT. Stats shown live: sent/recv counts, loss %, average latency, jitter (mean absolute delta of consecutive RTTs). Test button toggles on/off. Updated both Rust server (echo in main.rs) and C# client (PingTest.cs, SessionManager routing, MainForm Test button + stats label).
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace gatuna_client;
|
||||
|
||||
sealed class PingTest
|
||||
{
|
||||
readonly TunnelLink _link;
|
||||
readonly byte[] _serverMac;
|
||||
readonly CancellationTokenSource _cts = new();
|
||||
readonly ConcurrentDictionary<ulong, long> _outstanding = new();
|
||||
readonly ConcurrentQueue<double> _rtts = new();
|
||||
long _sent;
|
||||
long _received;
|
||||
double _lastRttMs;
|
||||
double _jitterSum;
|
||||
long _jitterCount;
|
||||
|
||||
public event Action<PingStats>? StatsUpdated;
|
||||
public event Action<string>? Log;
|
||||
|
||||
public bool Running { get; private set; }
|
||||
|
||||
public PingTest(TunnelLink link, byte[] serverMac)
|
||||
{
|
||||
_link = link;
|
||||
_serverMac = serverMac;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Running = true;
|
||||
_ = RunLoop();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Running = false;
|
||||
_cts.Cancel();
|
||||
}
|
||||
|
||||
async Task RunLoop()
|
||||
{
|
||||
var rng = new Random();
|
||||
var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(10));
|
||||
while (!_cts.IsCancellationRequested)
|
||||
{
|
||||
var nonce = (ulong)Interlocked.Increment(ref _sent);
|
||||
var ticks = DateTime.UtcNow.Ticks;
|
||||
_outstanding[nonce] = ticks;
|
||||
|
||||
_link.SendTo(_serverMac, new Frame.Ping(nonce));
|
||||
|
||||
var interval = rng.Next(10, 101);
|
||||
try
|
||||
{
|
||||
await Task.Delay(interval, _cts.Token);
|
||||
}
|
||||
catch { break; }
|
||||
}
|
||||
Running = false;
|
||||
}
|
||||
|
||||
public void HandlePong(ulong nonce)
|
||||
{
|
||||
if (_outstanding.TryRemove(nonce, out var sentTicks))
|
||||
{
|
||||
var rttMs = (DateTime.UtcNow.Ticks - sentTicks) / (double)TimeSpan.TicksPerMillisecond;
|
||||
_rtts.Enqueue(rttMs);
|
||||
Interlocked.Increment(ref _received);
|
||||
|
||||
if (_jitterCount > 0)
|
||||
{
|
||||
_jitterSum += Math.Abs(rttMs - _lastRttMs);
|
||||
}
|
||||
_lastRttMs = rttMs;
|
||||
Interlocked.Increment(ref _jitterCount);
|
||||
|
||||
EmitStats();
|
||||
}
|
||||
}
|
||||
|
||||
void EmitStats()
|
||||
{
|
||||
var sent = Interlocked.Read(ref _sent);
|
||||
var recv = Interlocked.Read(ref _received);
|
||||
var loss = sent > 0 ? (1.0 - (double)recv / sent) * 100.0 : 0;
|
||||
|
||||
var rttList = _rtts.ToArray();
|
||||
var avg = rttList.Length > 0 ? rttList.Average() : 0;
|
||||
var jitter = _jitterCount > 1 ? _jitterSum / (_jitterCount - 1) : 0;
|
||||
|
||||
StatsUpdated?.Invoke(new PingStats(sent, recv, loss, avg, jitter));
|
||||
}
|
||||
}
|
||||
|
||||
readonly record struct PingStats(
|
||||
long Sent,
|
||||
long Received,
|
||||
double LossPct,
|
||||
double AvgLatencyMs,
|
||||
double JitterMs);
|
||||
Reference in New Issue
Block a user