141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
dnscfgpkg "selective-vpn-api/app/dnscfg"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
func handleDNSBenchmark(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var req DNSBenchmarkRequest
|
|
if r.Body != nil {
|
|
defer r.Body.Close()
|
|
if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&req); err != nil && err != io.EOF {
|
|
http.Error(w, "bad json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
upstreams := normalizeBenchmarkUpstreams(req.Upstreams)
|
|
if len(upstreams) == 0 {
|
|
pool := loadDNSUpstreamPoolState()
|
|
if len(pool) > 0 {
|
|
tmp := make([]DNSBenchmarkUpstream, 0, len(pool))
|
|
for _, item := range pool {
|
|
tmp = append(tmp, DNSBenchmarkUpstream{Addr: item.Addr, Enabled: item.Enabled})
|
|
}
|
|
upstreams = normalizeBenchmarkUpstreams(tmp)
|
|
}
|
|
if len(upstreams) == 0 {
|
|
cfg := loadDNSUpstreamsConf()
|
|
upstreams = dnscfgpkg.NormalizeBenchmarkUpstreamStrings([]string{
|
|
cfg.Default1,
|
|
cfg.Default2,
|
|
cfg.Meta1,
|
|
cfg.Meta2,
|
|
}, normalizeDNSUpstream)
|
|
}
|
|
}
|
|
if len(upstreams) == 0 {
|
|
http.Error(w, "no upstreams", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
domains := dnscfgpkg.NormalizeBenchmarkDomains(req.Domains)
|
|
if len(domains) == 0 {
|
|
domains = append(domains, dnsBenchmarkDefaultDomains...)
|
|
}
|
|
|
|
timeoutMS := req.TimeoutMS
|
|
if timeoutMS <= 0 {
|
|
timeoutMS = 1800
|
|
}
|
|
if timeoutMS < 300 {
|
|
timeoutMS = 300
|
|
}
|
|
if timeoutMS > 5000 {
|
|
timeoutMS = 5000
|
|
}
|
|
|
|
attempts := req.Attempts
|
|
if attempts <= 0 {
|
|
attempts = 1
|
|
}
|
|
if attempts > 3 {
|
|
attempts = 3
|
|
}
|
|
profile := dnscfgpkg.NormalizeBenchmarkProfile(req.Profile)
|
|
if profile == dnsBenchmarkProfileLoad && attempts < 2 {
|
|
// Load profile should emulate real resolver pressure.
|
|
attempts = 2
|
|
}
|
|
|
|
concurrency := req.Concurrency
|
|
if concurrency <= 0 {
|
|
concurrency = 6
|
|
}
|
|
if concurrency < 1 {
|
|
concurrency = 1
|
|
}
|
|
if concurrency > 32 {
|
|
concurrency = 32
|
|
}
|
|
if concurrency > len(upstreams) {
|
|
concurrency = len(upstreams)
|
|
}
|
|
opts := dnscfgpkg.MakeDNSBenchmarkOptions(profile, concurrency)
|
|
|
|
results := make([]DNSBenchmarkResult, 0, len(upstreams))
|
|
var mu sync.Mutex
|
|
var wg sync.WaitGroup
|
|
sem := make(chan struct{}, concurrency)
|
|
timeout := time.Duration(timeoutMS) * time.Millisecond
|
|
|
|
for _, upstream := range upstreams {
|
|
wg.Add(1)
|
|
sem <- struct{}{}
|
|
go func(upstream string) {
|
|
defer wg.Done()
|
|
defer func() { <-sem }()
|
|
result := benchmarkDNSUpstream(upstream, domains, timeout, attempts, opts)
|
|
mu.Lock()
|
|
results = append(results, result)
|
|
mu.Unlock()
|
|
}(upstream)
|
|
}
|
|
wg.Wait()
|
|
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if results[i].Score == results[j].Score {
|
|
if results[i].AvgMS == results[j].AvgMS {
|
|
if results[i].OK == results[j].OK {
|
|
return results[i].Upstream < results[j].Upstream
|
|
}
|
|
return results[i].OK > results[j].OK
|
|
}
|
|
return results[i].AvgMS < results[j].AvgMS
|
|
}
|
|
return results[i].Score > results[j].Score
|
|
})
|
|
|
|
resp := DNSBenchmarkResponse{
|
|
Results: results,
|
|
DomainsUsed: domains,
|
|
TimeoutMS: timeoutMS,
|
|
AttemptsPerDomain: attempts,
|
|
Profile: profile,
|
|
}
|
|
resp.RecommendedDefault = benchmarkTopN(results, 2, upstreams)
|
|
resp.RecommendedMeta = benchmarkTopN(results, 2, upstreams)
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|