|
using Spectre.Console; |
|
using System.Collections.Concurrent; |
|
using System.Diagnostics; |
|
using System.Net.NetworkInformation; |
|
|
|
Console.Title = "BOOSTEROID - PING TEST"; |
|
|
|
List<Entry> hosts = []; |
|
|
|
{ |
|
var regions = new (string key, int count)[] |
|
{ |
|
("so", 7), |
|
("sp", 7), |
|
("jv", 7) |
|
}; |
|
|
|
foreach (var region in regions) |
|
{ |
|
for (var i = 0; i <= region.count; i++) |
|
hosts.Add(new(region.key, $"{region.key}{i}.cloud.boosteroid.com")); |
|
} |
|
} |
|
|
|
var comparer = Comparer<Entry>.Create((x, y) => |
|
{ |
|
var r1 = x.Hostname.Split('.')[0]; |
|
var r2 = y.Hostname.Split('.')[0]; |
|
return r1.CompareTo(r2); |
|
}); |
|
|
|
hosts.Sort(comparer); |
|
|
|
using var ping = new PingService(); |
|
|
|
var tasks = new List<Task>(); |
|
|
|
Console.CursorVisible = false; |
|
|
|
var process = true; |
|
|
|
_ = Task.Run(() => |
|
{ |
|
while (true) |
|
{ |
|
if (Console.KeyAvailable) |
|
{ |
|
if (Console.ReadKey(true).KeyChar == 'q') |
|
{ |
|
process = false; |
|
break; |
|
} |
|
} |
|
} |
|
}); |
|
|
|
while (process) |
|
{ |
|
await Task.Delay(1500); |
|
|
|
try |
|
{ |
|
Console.Clear(); |
|
|
|
var grid = new Grid().AddColumns(6); |
|
|
|
grid.AddRow([ |
|
new Text("Endereço").Centered(), |
|
new Text("Min").Centered(), |
|
new Text("Max").Centered(), |
|
new Text("Atual").Centered(), |
|
new Text("Media").Centered(), |
|
]); |
|
|
|
foreach (var entries in hosts.GroupBy(x => x.Region)) |
|
{ |
|
foreach (var entry in entries) |
|
{ |
|
var stats = entry.GetStats(); |
|
|
|
static Color? MapColor(long last, long min, long max) |
|
{ |
|
if (min < last) return Color.Green; |
|
if (max > last) return Color.Red; |
|
return null; |
|
} |
|
|
|
var columns = new Text[] |
|
{ |
|
new Text(entry.Hostname).Centered(), |
|
new Text(stats.MinRtt + "ms", new(Color.Aqua)).Centered(), |
|
new Text(stats.MaxRtt + "ms", new(Color.Orange1)).Centered(), |
|
new Text(stats.LastRtt + "ms", |
|
new Style( |
|
foreground: MapColor(stats.LastRtt, stats.MinRtt, stats.MaxRtt) |
|
) |
|
).Centered(), |
|
new Text($"{stats.AvgRtt:###}ms", |
|
new Style( |
|
foreground: MapColor((long)stats.AvgRtt, stats.MinRtt, stats.MaxRtt) |
|
) |
|
).Centered() |
|
}; |
|
|
|
grid.AddRow(columns); |
|
} |
|
} |
|
|
|
AnsiConsole.Write(grid); |
|
|
|
foreach (var entry in hosts) |
|
tasks.Add(entry.ExecuteAsync(ping)); |
|
|
|
await Task.WhenAll(tasks).ConfigureAwait(false); |
|
} |
|
finally |
|
{ |
|
tasks.Clear(); |
|
} |
|
} |
|
|
|
class Entry |
|
{ |
|
readonly Queue<long> _samples = []; |
|
long _minTime, _maxTime; |
|
float _avgTime; |
|
|
|
public string Region { get; } |
|
public string Hostname { get; } |
|
|
|
public Entry(string region, string hostname) |
|
{ |
|
Region = region; |
|
Hostname = hostname; |
|
} |
|
|
|
const int NumKeepMaxSamples = 16; |
|
|
|
void AddSample(long latency) |
|
{ |
|
lock (this) |
|
{ |
|
while (_samples.Count > NumKeepMaxSamples) |
|
_samples.Dequeue(); |
|
|
|
_samples.Enqueue(latency); |
|
|
|
|
|
if (_minTime == 0) _minTime = latency; |
|
else _minTime = Math.Min(_minTime, latency); |
|
|
|
if (_maxTime == 0) _maxTime = latency; |
|
else _maxTime = Math.Max(_maxTime, latency); |
|
|
|
long sum = 0; |
|
|
|
foreach (var sample in _samples) |
|
{ |
|
if (sample >= 0) |
|
sum += sample; |
|
} |
|
|
|
_avgTime = (float)(sum / (float)_samples.Count); |
|
} |
|
} |
|
|
|
public (long MinRtt, long MaxRtt, long LastRtt, float AvgRtt) GetStats() |
|
{ |
|
lock (this) |
|
{ |
|
return ( |
|
_minTime, |
|
_maxTime, |
|
_samples.LastOrDefault(), |
|
_avgTime |
|
); |
|
} |
|
} |
|
|
|
public async Task ExecuteAsync(PingService ping) |
|
{ |
|
var reply = await ping.SendAsync(Hostname); |
|
|
|
if (reply.Status != IPStatus.Success) |
|
AddSample(-1); |
|
else |
|
AddSample(reply.RoundtripTime); |
|
} |
|
} |
|
|
|
public class PingService : IDisposable |
|
{ |
|
readonly Ping _ping = new(); |
|
readonly ConcurrentQueue<(string Hostname, TaskCompletionSource<PingReply> Completion)> _queue = []; |
|
volatile bool _disposed; |
|
|
|
public PingService() |
|
{ |
|
_ = Task.Run(async () => |
|
{ |
|
try |
|
{ |
|
while (!_disposed) |
|
{ |
|
if (_queue.TryDequeue(out var entry)) |
|
{ |
|
try |
|
{ |
|
var result = await _ping.SendPingAsync(entry.Hostname); |
|
|
|
entry.Completion.TrySetResult(result); |
|
} |
|
catch (Exception ex) |
|
{ |
|
entry.Completion.TrySetException(ex); |
|
} |
|
} |
|
|
|
await Task.Delay(1); |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
Debug.WriteLine(ex); |
|
} |
|
|
|
while (_queue.TryDequeue(out var entry)) |
|
entry.Completion.TrySetCanceled(); |
|
}); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
if (!_disposed) |
|
{ |
|
_disposed = true; |
|
|
|
while (_queue.TryDequeue(out var entry)) |
|
entry.Completion.TrySetCanceled(); |
|
|
|
_ping.Dispose(); |
|
} |
|
} |
|
|
|
public Task<PingReply> SendAsync(string hostname) |
|
{ |
|
var tcs = new TaskCompletionSource<PingReply>(); |
|
_queue.Enqueue((hostname, tcs)); |
|
return tcs.Task; |
|
} |
|
} |