2f61f2bb1b
- Directory: win/gatuna-client/ -> gatuna-win/ - Project file: gatuna-client.csproj -> gatuna.csproj - Assembly name: gatuna-client -> gatuna - Root namespace: gatuna_client -> gatuna - All .cs files: namespace gatuna_client -> gatuna - app.manifest: assemblyIdentity name -> gatuna - README: updated all paths and references
102 lines
2.6 KiB
C#
102 lines
2.6 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace gatuna;
|
|
|
|
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);
|