This commit is contained in:
2025-05-16 04:29:05 -07:00
parent 26e7fd997f
commit 3d543f9c5b
+84 -32
View File
@@ -51,17 +51,26 @@ const (
)
// -----------------------------------------------------------------------------
// Data
// Data structures
// -----------------------------------------------------------------------------
type InterfaceStatus struct {
Name string
Label string
IPInfo string
Reachable map[string]bool
Loss map[string]float64
TCP map[string][]float64
TotalICMPReq int
TotalICMPRep int
DroppedCount int
LastDrop time.Time
LastPing time.Time
SpinFrame int
mu sync.RWMutex
}
@@ -84,12 +93,12 @@ func minMaxAvgStd(nums []float64) (min, max, avg, std float64) {
sum += v
}
avg = sum / float64(len(nums))
var vSum float64
var vsum float64
for _, v := range nums {
diff := v - avg
vSum += diff * diff
d := v - avg
vsum += d * d
}
std = math.Sqrt(vSum / float64(len(nums)))
std = math.Sqrt(vsum / float64(len(nums)))
return
}
@@ -115,7 +124,7 @@ func fetchIPInfo(iface string) string {
}
// -----------------------------------------------------------------------------
// Ping + loss
// Ping helpers
// -----------------------------------------------------------------------------
func pingOnce(iface, host string) bool {
ctx, cancel := context.WithTimeout(context.Background(), icmpTimeout)
@@ -147,7 +156,7 @@ func lossPercent(iface, host string) float64 {
}
// -----------------------------------------------------------------------------
// TCP timing
// TCP helpers
// -----------------------------------------------------------------------------
func localAddr(iface string) (net.Addr, error) {
ni, err := net.InterfaceByName(iface)
@@ -179,7 +188,7 @@ func tcpDuration(iface, hostPort string) time.Duration {
}
// -----------------------------------------------------------------------------
// Spinners
// Spinner helpers
// -----------------------------------------------------------------------------
var spinner = []rune{'|', '/', '-', '\\'}
@@ -205,13 +214,25 @@ func reachLoop(ctx context.Context, st *InterfaceStatus, hosts []string) {
wg.Add(1)
go func(host string) {
defer wg.Done()
st.mu.Lock()
st.TotalICMPReq++
st.mu.Unlock()
ok := pingOnce(st.Name, host)
mu.Lock()
res[host] = ok
if ok {
st.tickSpinner()
}
mu.Unlock()
st.mu.Lock()
if ok {
st.TotalICMPRep++
st.tickSpinner()
} else {
st.DroppedCount++
st.LastDrop = time.Now()
}
st.mu.Unlock()
}(h)
}
wg.Wait()
@@ -291,12 +312,13 @@ func write(s tcell.Screen, x, y int, str string) {
}
}
func divider(w int) string { return strings.Repeat("=", w) }
func drawDivider(s tcell.Screen, y, w int) {
line := strings.Repeat("=", w)
write(s, 0, y, line)
write(s, 0, y, divider(w))
}
func drawHeaderLine(s tcell.Screen, y, w int, spin rune, txt string) {
func drawHeader(s tcell.Screen, y, w int, spin rune, txt string) {
line := fmt.Sprintf("== %c %s", spin, txt)
if len(line) > w {
line = line[:w]
@@ -311,11 +333,12 @@ func drawInterface(s tcell.Screen, y, w int, st *InterfaceStatus) int {
spin := spinner[st.SpinFrame]
head := fmt.Sprintf("%s — %s", st.Label, st.IPInfo)
st.mu.RUnlock()
drawHeaderLine(s, y+1, w, spin, head)
drawHeader(s, y+1, w, spin, head)
drawDivider(s, y+2, w)
y += 4 // skip blank line after
// contents
y += 4
st.mu.RLock()
// Reachability
total := len(st.Reachable)
rc := 0
var down []string
@@ -336,6 +359,8 @@ func drawInterface(s tcell.Screen, y, w int, st *InterfaceStatus) int {
write(s, 0, y, "Unreachable: none")
}
y += 2
// Packet loss
write(s, 0, y, "Packet Loss:")
y++
for _, h := range packetLossHosts {
@@ -343,7 +368,16 @@ func drawInterface(s tcell.Screen, y, w int, st *InterfaceStatus) int {
write(s, 0, y, fmt.Sprintf("%-16s %.0f%%", h+":", lp))
y++
}
y++
dropAge := "N/A"
if !st.LastDrop.IsZero() {
dropAge = fmt.Sprintf("%s", time.Since(st.LastDrop).Round(time.Second))
}
write(s, 0, y, fmt.Sprintf("Dropped: %d (last at %s, age %s)",
st.DroppedCount,
st.LastDrop.Format("15:04:05"), dropAge))
y += 2
// TCP stats
write(s, 0, y, "TCP Connect Stats:")
y++
for _, hp := range tcpTestHosts {
@@ -356,34 +390,48 @@ func drawInterface(s tcell.Screen, y, w int, st *InterfaceStatus) int {
hp, mi, av, ma, sd))
y++
}
y++
// ICMP totals
write(s, 0, y, fmt.Sprintf("Total ICMP Requests: %d", st.TotalICMPReq))
y++
write(s, 0, y, fmt.Sprintf("Total ICMP Replies: %d", st.TotalICMPRep))
y += 2
st.mu.RUnlock()
return y + 2 // leave a blank line after section
return y
}
// -----------------------------------------------------------------------------
// UI main loop
// UI loop
// -----------------------------------------------------------------------------
func uiLoop(ctx context.Context, scr tcell.Screen, a, b *InterfaceStatus) {
func uiLoop(ctx context.Context, scr tcell.Screen, a, b *InterfaceStatus, start time.Time) {
defer scr.Fini()
ticker := time.NewTicker(screenRefresh)
defer ticker.Stop()
tick := time.NewTicker(screenRefresh)
defer tick.Stop()
topSpin := 0
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
case <-tick.C:
w, _ := scr.Size()
scr.Clear()
// top banner
drawDivider(scr, 0, w)
drawHeaderLine(scr, 1, w, spinner[topSpin%len(spinner)],
drawHeader(scr, 1, w, spinner[topSpin%len(spinner)],
time.Now().Format(time.RFC1123Z))
drawDivider(scr, 2, w)
topSpin++
y := 4
write(scr, 0, 3, fmt.Sprintf("Runtime: %s", time.Since(start).Round(time.Second)))
y := 5
y = drawInterface(scr, y, w, a)
y = drawInterface(scr, y, w, b)
_ = drawInterface(scr, y, w, b)
scr.Show()
}
}
@@ -394,10 +442,14 @@ func uiLoop(ctx context.Context, scr tcell.Screen, a, b *InterfaceStatus) {
// -----------------------------------------------------------------------------
var (
reachHosts []string
packetLossHosts = []string{"datavi.be", "google.com", "fast.com",
"cloudflare.com", "github.com"}
tcpTestHosts = []string{"google.com:443", "github.com:443",
"1.1.1.1:443", "8.8.8.8:53"}
packetLossHosts = []string{
"datavi.be", "google.com", "fast.com",
"cloudflare.com", "github.com",
}
tcpTestHosts = []string{
"google.com:443", "github.com:443",
"1.1.1.1:443", "8.8.8.8:53",
}
)
func main() {
@@ -439,5 +491,5 @@ func main() {
go tcpLoop(ctx, a, tcpTestHosts)
go tcpLoop(ctx, b, tcpTestHosts)
uiLoop(ctx, scr, a, b)
uiLoop(ctx, scr, a, b, time.Now())
}