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) }