summaryrefslogtreecommitdiff
path: root/internal/sync/latency.go
blob: dd3268b7c77cf4f6ea392740014fe53442b9368f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package sync

import (
	"fmt"
	"net"
	"sort"
	"sync"
	"time"

	"vpnem/internal/models"
)

// LatencyResult holds a server's latency measurement.
type LatencyResult struct {
	Tag     string `json:"tag"`
	Region  string `json:"region"`
	Latency int    `json:"latency_ms"` // -1 means unreachable
}

// MeasureLatency pings all servers concurrently and returns results sorted by latency.
func MeasureLatency(servers []models.Server, timeout time.Duration) []LatencyResult {
	var wg sync.WaitGroup
	results := make([]LatencyResult, len(servers))

	for i, s := range servers {
		wg.Add(1)
		go func(idx int, srv models.Server) {
			defer wg.Done()
			ms := tcpPing(srv.Server, srv.ServerPort, timeout)
			results[idx] = LatencyResult{
				Tag:     srv.Tag,
				Region:  srv.Region,
				Latency: ms,
			}
		}(i, s)
	}

	wg.Wait()

	sort.Slice(results, func(i, j int) bool {
		if results[i].Latency == -1 {
			return false
		}
		if results[j].Latency == -1 {
			return true
		}
		return results[i].Latency < results[j].Latency
	})

	return results
}

func tcpPing(host string, port int, timeout time.Duration) int {
	addr := fmt.Sprintf("%s:%d", host, port)
	start := time.Now()
	conn, err := net.DialTimeout("tcp", addr, timeout)
	if err != nil {
		return -1
	}
	conn.Close()
	return int(time.Since(start).Milliseconds())
}