mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-10-24 17:04:27 +00:00
545 lines
16 KiB
Go
545 lines
16 KiB
Go
package core
|
||||
|
|
|
|||
|
|
import (
|
|||
|
|
"sync"
|
|||
|
|
"sync/atomic"
|
|||
|
|
"testing"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 1:探测 → 参数调整 → 线程池创建 完整链路
|
|||
|
|
// 验证从 NetworkProfile 到 TuneConfig 到 AdaptivePool 的端到端数据流
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_ProbeToPool_LAN(t *testing.T) {
|
|||
|
|
// 模拟内网探测结果
|
|||
|
|
profile := classifyNetwork(
|
|||
|
|
makeDurations([]int{1, 1, 2, 2, 2, 3, 3, 3, 4, 5}), // ms
|
|||
|
|
0, 10,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if profile.Env != EnvLAN {
|
|||
|
|
t.Fatalf("探测环境 = %v, want LAN", profile.Env)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建 Config + TuneConfig
|
|||
|
|
config := makeDefaultConfig()
|
|||
|
|
session := makeTestSession(config)
|
|||
|
|
sys := ProbeSystem()
|
|||
|
|
|
|||
|
|
ep := &EnvironmentProfile{Net: *profile, System: sys}
|
|||
|
|
ep.TuneConfig(config, session)
|
|||
|
|
|
|||
|
|
// 验证参数被合理调整
|
|||
|
|
if config.Timeout > 3*time.Second {
|
|||
|
|
t.Errorf("内网 Timeout = %v, 不应 > 3s", config.Timeout)
|
|||
|
|
}
|
|||
|
|
if config.MaxRetries != 1 {
|
|||
|
|
t.Errorf("内网零丢包 MaxRetries = %d, want 1", config.MaxRetries)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 用调整后的参数创建线程池
|
|||
|
|
target, ceiling := profile.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit)
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
if pool.Cap() <= 0 {
|
|||
|
|
t.Errorf("池容量 = %d, 应该 > 0", pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("内网完整链路: Timeout=%v MT=%d Retry=%d ICMP=%.2f target=%d ceiling=%d poolCap=%d",
|
|||
|
|
config.Timeout, config.ModuleThreadNum, config.MaxRetries,
|
|||
|
|
config.Network.ICMPRate, target, ceiling, pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIntegration_ProbeToPool_Internet(t *testing.T) {
|
|||
|
|
profile := classifyNetwork(
|
|||
|
|
makeDurations([]int{60, 70, 80, 90, 100, 110, 120, 130, 140, 150}),
|
|||
|
|
0, 10,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if profile.Env != EnvInternet {
|
|||
|
|
t.Fatalf("探测环境 = %v, want Internet", profile.Env)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
config := makeDefaultConfig()
|
|||
|
|
session := makeTestSession(config)
|
|||
|
|
ep := &EnvironmentProfile{Net: *profile, System: SystemProfile{FDLimit: 4096, NumCPU: 4}}
|
|||
|
|
ep.TuneConfig(config, session)
|
|||
|
|
|
|||
|
|
target, ceiling := profile.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit)
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
// 公网并发应该明显低于默认 600
|
|||
|
|
if target >= 600 {
|
|||
|
|
t.Errorf("公网 target = %d, 应该 < 600", target)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("公网完整链路: Timeout=%v MT=%d Retry=%d target=%d ceiling=%d poolCap=%d",
|
|||
|
|
config.Timeout, config.ModuleThreadNum, config.MaxRetries, target, ceiling, pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 2:AdaptivePool + ScanMetrics 联动
|
|||
|
|
// 验证:任务执行 → metrics 记录 → 池读取 metrics → 做出调整决策
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_PoolMetrics_HealthyTraffic(t *testing.T) {
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
var taskCount atomic.Int64
|
|||
|
|
|
|||
|
|
pool, err := NewAdaptivePool(100, 100, func(i interface{}) {
|
|||
|
|
taskCount.Add(1)
|
|||
|
|
}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
pool.inSlowStart = false
|
|||
|
|
pool.tune(100)
|
|||
|
|
|
|||
|
|
// 注入健康 metrics
|
|||
|
|
for i := 0; i < 200; i++ {
|
|||
|
|
metrics.RecordConnect(time.Millisecond)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 运行任务
|
|||
|
|
var wg sync.WaitGroup
|
|||
|
|
for i := 0; i < 200; i++ {
|
|||
|
|
wg.Add(1)
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
_ = pool.Invoke(nil)
|
|||
|
|
}()
|
|||
|
|
}
|
|||
|
|
wg.Wait()
|
|||
|
|
pool.Wait()
|
|||
|
|
|
|||
|
|
// 触发调整
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
|
|||
|
|
if pool.Cap() < 90 {
|
|||
|
|
t.Errorf("健康流量池容量不应大幅下降: cap = %d", pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("健康流量: tasks=%d connects=%d cap=%d",
|
|||
|
|
taskCount.Load(), metrics.Snapshot().Connects, pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIntegration_PoolMetrics_ExhaustedTraffic(t *testing.T) {
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
|
|||
|
|
pool, err := NewAdaptivePool(100, 100, func(i interface{}) {}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
pool.inSlowStart = false
|
|||
|
|
pool.tune(100)
|
|||
|
|
|
|||
|
|
// 直接向 metrics 注入大量资源耗尽事件(模拟扫描过程中的 fd 不足)
|
|||
|
|
for i := 0; i < 200; i++ {
|
|||
|
|
metrics.RecordExhausted()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 手动触发调整(清除时间守卫)
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
|
|||
|
|
// 资源耗尽率 100% → 应该降速
|
|||
|
|
if pool.Cap() >= 100 {
|
|||
|
|
t.Errorf("资源耗尽后池应该降速: cap = %d", pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("资源耗尽: exhausted=%d cap=%d", metrics.Snapshot().Exhausted, pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 3:慢启动 → 稳态 AIMD 过渡
|
|||
|
|
// 验证慢启动阶段的翻倍行为和过渡到稳态的时机
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_SlowStartToSteady(t *testing.T) {
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
|
|||
|
|
pool, err := NewAdaptivePool(100, 100, func(i interface{}) {
|
|||
|
|
metrics.RecordConnect(time.Millisecond)
|
|||
|
|
}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
if !pool.inSlowStart {
|
|||
|
|
t.Fatal("初始应该在慢启动状态")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
initialCap := pool.Cap()
|
|||
|
|
t.Logf("慢启动初始: cap=%d", initialCap)
|
|||
|
|
|
|||
|
|
// 喂入足够的健康 metrics
|
|||
|
|
for i := 0; i < 100; i++ {
|
|||
|
|
metrics.RecordConnect(time.Millisecond)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模拟多次调整周期
|
|||
|
|
caps := []int{initialCap}
|
|||
|
|
for i := 0; i < 10; i++ {
|
|||
|
|
pool.lastCheck.Store(0) // 强制触发检查
|
|||
|
|
pool.adjust()
|
|||
|
|
caps = append(caps, pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证:容量应该逐步增长
|
|||
|
|
growing := false
|
|||
|
|
for i := 1; i < len(caps); i++ {
|
|||
|
|
if caps[i] > caps[i-1] {
|
|||
|
|
growing = true
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if !growing {
|
|||
|
|
t.Errorf("慢启动期间容量没有增长: %v", caps)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 最终应该退出慢启动
|
|||
|
|
finalCap := pool.Cap()
|
|||
|
|
if finalCap < initialCap {
|
|||
|
|
t.Errorf("最终容量 %d < 初始 %d, 不合理", finalCap, initialCap)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("慢启动过渡: %v, inSlowStart=%v", caps, pool.inSlowStart)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 4:拥塞 → 降速 → 恢复 完整周期
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_CongestionRecovery(t *testing.T) {
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
|
|||
|
|
pool, err := NewAdaptivePool(200, 200, func(i interface{}) {}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
// 直接到稳态,满容量
|
|||
|
|
pool.inSlowStart = false
|
|||
|
|
pool.tune(200)
|
|||
|
|
|
|||
|
|
// === 阶段 1: 正常运行 ===
|
|||
|
|
for i := 0; i < 100; i++ {
|
|||
|
|
metrics.RecordConnect(time.Millisecond)
|
|||
|
|
}
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
normalCap := pool.Cap()
|
|||
|
|
t.Logf("正常阶段: cap=%d", normalCap)
|
|||
|
|
|
|||
|
|
// === 阶段 2: 突发拥塞(大量资源耗尽)===
|
|||
|
|
for i := 0; i < 200; i++ {
|
|||
|
|
metrics.RecordExhausted()
|
|||
|
|
}
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
congestedCap := pool.Cap()
|
|||
|
|
|
|||
|
|
if congestedCap >= normalCap {
|
|||
|
|
t.Errorf("拥塞后应降速: normal=%d congested=%d", normalCap, congestedCap)
|
|||
|
|
}
|
|||
|
|
t.Logf("拥塞阶段: cap=%d (降幅 %d%%)", congestedCap, (normalCap-congestedCap)*100/normalCap)
|
|||
|
|
|
|||
|
|
// === 阶段 3: 恢复(大量成功连接)===
|
|||
|
|
for i := 0; i < 500; i++ {
|
|||
|
|
metrics.RecordConnect(time.Millisecond)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 多次调整模拟恢复过程
|
|||
|
|
for i := 0; i < 20; i++ {
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
}
|
|||
|
|
recoveredCap := pool.Cap()
|
|||
|
|
|
|||
|
|
if recoveredCap <= congestedCap {
|
|||
|
|
t.Errorf("恢复后应提速: congested=%d recovered=%d", congestedCap, recoveredCap)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 恢复后不应超过 ceiling
|
|||
|
|
if recoveredCap > 200 {
|
|||
|
|
t.Errorf("恢复后不应超过 ceiling: cap=%d ceiling=200", recoveredCap)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("恢复阶段: cap=%d", recoveredCap)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 5:RTT 趋势检测 → 池调整
|
|||
|
|
// 验证 ScanMetrics 的 RTT EMA 趋势信号能正确传导到池的健康判断
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_RTTTrend_DrivesPoolAdjustment(t *testing.T) {
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
|
|||
|
|
pool, err := NewAdaptivePool(100, 100, func(i interface{}) {}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
pool.inSlowStart = false
|
|||
|
|
pool.tune(100)
|
|||
|
|
|
|||
|
|
// 建立基线:100 个 5ms RTT
|
|||
|
|
for i := 0; i < 200; i++ {
|
|||
|
|
metrics.RecordConnect(5 * time.Millisecond)
|
|||
|
|
}
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
baselineCap := pool.Cap()
|
|||
|
|
|
|||
|
|
// RTT 突增到 100ms(20 倍)
|
|||
|
|
for i := 0; i < 100; i++ {
|
|||
|
|
metrics.RecordConnect(100 * time.Millisecond)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ratio := metrics.RTTRatio()
|
|||
|
|
if ratio <= 1.0 {
|
|||
|
|
t.Logf("RTT ratio = %.2f, EMA 可能还没追上(正常)", ratio)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 多次调整看池是否响应
|
|||
|
|
for i := 0; i < 5; i++ {
|
|||
|
|
pool.lastCheck.Store(0)
|
|||
|
|
pool.adjust()
|
|||
|
|
}
|
|||
|
|
afterRTTSpike := pool.Cap()
|
|||
|
|
|
|||
|
|
t.Logf("RTT 趋势: baseline_cap=%d after_spike=%d rtt_ratio=%.2f",
|
|||
|
|
baselineCap, afterRTTSpike, ratio)
|
|||
|
|
|
|||
|
|
// 如果 ratio 足够高,池应该降速
|
|||
|
|
if ratio > 2.0 && afterRTTSpike >= baselineCap {
|
|||
|
|
t.Errorf("RTT ratio=%.2f 但池没有降速: %d -> %d", ratio, baselineCap, afterRTTSpike)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 6:不同网络环境下的参数一致性
|
|||
|
|
// 验证同一组目标在不同环境下参数调整的合理递进关系
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_ParameterProgression(t *testing.T) {
|
|||
|
|
environments := []struct {
|
|||
|
|
name string
|
|||
|
|
rtts []int // ms
|
|||
|
|
loss int // failures out of 10
|
|||
|
|
wantEnv NetworkEnv
|
|||
|
|
}{
|
|||
|
|
{"内网", []int{1, 1, 2, 2, 3, 3, 4, 4, 5, 5}, 0, EnvLAN},
|
|||
|
|
{"局域网", []int{10, 15, 20, 25, 30, 35, 40, 45, 48, 49}, 0, EnvWAN},
|
|||
|
|
{"公网", []int{60, 70, 80, 90, 100, 120, 140, 160, 180, 195}, 0, EnvInternet},
|
|||
|
|
{"慢速", []int{200, 300, 400, 500, 600, 700, 800, 900, 1000, 1500}, 0, EnvSlow},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type params struct {
|
|||
|
|
timeout time.Duration
|
|||
|
|
mt int
|
|||
|
|
retry int
|
|||
|
|
icmpRate float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var results []params
|
|||
|
|
|
|||
|
|
for _, env := range environments {
|
|||
|
|
profile := classifyNetwork(makeDurations(env.rtts), env.loss, 10)
|
|||
|
|
if profile.Env != env.wantEnv {
|
|||
|
|
t.Errorf("%s: env = %v, want %v", env.name, profile.Env, env.wantEnv)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
config := makeDefaultConfig()
|
|||
|
|
session := makeTestSession(config)
|
|||
|
|
ep := &EnvironmentProfile{
|
|||
|
|
Net: *profile,
|
|||
|
|
System: SystemProfile{FDLimit: 65536, NumCPU: 8},
|
|||
|
|
}
|
|||
|
|
ep.TuneConfig(config, session)
|
|||
|
|
|
|||
|
|
results = append(results, params{
|
|||
|
|
timeout: config.Timeout,
|
|||
|
|
mt: config.ModuleThreadNum,
|
|||
|
|
retry: config.MaxRetries,
|
|||
|
|
icmpRate: config.Network.ICMPRate,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.Logf("%s: Timeout=%v MT=%d Retry=%d ICMP=%.2f",
|
|||
|
|
env.name, config.Timeout, config.ModuleThreadNum, config.MaxRetries, config.Network.ICMPRate)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证递进关系:从内网到慢速,Timeout 应递增
|
|||
|
|
for i := 1; i < len(results); i++ {
|
|||
|
|
if results[i].timeout < results[i-1].timeout {
|
|||
|
|
t.Errorf("Timeout 不递增: %v (env[%d]) < %v (env[%d])",
|
|||
|
|
results[i].timeout, i, results[i-1].timeout, i-1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ICMPRate 应递减(内网最高,慢速最低)
|
|||
|
|
for i := 1; i < len(results); i++ {
|
|||
|
|
if results[i].icmpRate > results[i-1].icmpRate {
|
|||
|
|
t.Errorf("ICMPRate 不递减: %.2f (env[%d]) > %.2f (env[%d])",
|
|||
|
|
results[i].icmpRate, i, results[i-1].icmpRate, i-1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 7:用户显式 -t + 网络探测 完整流程
|
|||
|
|
// 验证用户指定值作为 ceiling 但探测仍然影响其他参数
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_ExplicitThreadNum_WithProbe(t *testing.T) {
|
|||
|
|
profile := classifyNetwork(
|
|||
|
|
makeDurations([]int{100, 120, 140, 160, 180, 200, 220, 240, 260, 300}),
|
|||
|
|
2, 12, // 部分丢包
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
config := makeDefaultConfig()
|
|||
|
|
config.ThreadNum = 200
|
|||
|
|
config.ThreadNumExplicit = true
|
|||
|
|
session := makeTestSession(config)
|
|||
|
|
|
|||
|
|
ep := &EnvironmentProfile{
|
|||
|
|
Net: *profile,
|
|||
|
|
System: SystemProfile{FDLimit: 4096, NumCPU: 4},
|
|||
|
|
}
|
|||
|
|
ep.TuneConfig(config, session)
|
|||
|
|
|
|||
|
|
// ThreadNum 不应被修改(fd limit 允许范围内)
|
|||
|
|
// 但 Timeout、ModuleThreadNum 等应根据探测调整
|
|||
|
|
if config.Timeout == 3*time.Second {
|
|||
|
|
t.Error("即使 -t 显式,Timeout 仍应根据探测调整")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建池
|
|||
|
|
target, ceiling := profile.RecommendConcurrency(config.ThreadNum, config.ThreadNumExplicit)
|
|||
|
|
if ceiling != 200 {
|
|||
|
|
t.Errorf("显式 -t 200 的 ceiling = %d, want 200", ceiling)
|
|||
|
|
}
|
|||
|
|
if target > 200 {
|
|||
|
|
t.Errorf("target = %d, 不应超过 ceiling 200", target)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
pool, err := NewAdaptivePool(target, ceiling, func(interface{}) {}, metrics)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建池失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer pool.Release()
|
|||
|
|
|
|||
|
|
t.Logf("显式 -t 200: Timeout=%v MT=%d Retry=%d target=%d ceiling=%d cap=%d",
|
|||
|
|
config.Timeout, config.ModuleThreadNum, config.MaxRetries, target, ceiling, pool.Cap())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 8:AdaptiveTimeout + ScanMetrics 双 RTT 追踪
|
|||
|
|
// 验证两个 RTT 追踪器独立工作不干扰
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_DualRTTTracking(t *testing.T) {
|
|||
|
|
adaptiveTO := NewAdaptiveTimeout(3 * time.Second)
|
|||
|
|
metrics := &ScanMetrics{}
|
|||
|
|
|
|||
|
|
// 喂入相同的 RTT 数据到两个追踪器
|
|||
|
|
for i := 0; i < 50; i++ {
|
|||
|
|
rtt := 10 * time.Millisecond
|
|||
|
|
adaptiveTO.Record(rtt)
|
|||
|
|
metrics.RecordConnect(rtt)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AdaptiveTimeout 用于连接超时
|
|||
|
|
toValue := adaptiveTO.Timeout()
|
|||
|
|
// ScanMetrics 用于池健康判断
|
|||
|
|
rttFast := metrics.RTTFast()
|
|||
|
|
ratio := metrics.RTTRatio()
|
|||
|
|
|
|||
|
|
if toValue > 3*time.Second {
|
|||
|
|
t.Errorf("AdaptiveTimeout 应该 < 初始值: %v", toValue)
|
|||
|
|
}
|
|||
|
|
if rttFast < 8*time.Millisecond || rttFast > 12*time.Millisecond {
|
|||
|
|
t.Errorf("ScanMetrics RTTFast 应接近 10ms: %v", rttFast)
|
|||
|
|
}
|
|||
|
|
if ratio < 0.8 || ratio > 1.2 {
|
|||
|
|
t.Errorf("稳定 RTT 的 ratio 应接近 1.0: %.2f", ratio)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t.Logf("双追踪: AdaptiveTO=%v, MetricsFast=%v, Ratio=%.2f", toValue, rttFast, ratio)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// =============================================================================
|
|||
|
|
// 集成测试 9:丢包环境下 Retry + ModuleThreadNum 联动
|
|||
|
|
// 验证高丢包同时影响重试和并发
|
|||
|
|
// =============================================================================
|
|||
|
|
|
|||
|
|
func TestIntegration_LossyNetwork_RetryAndConcurrency(t *testing.T) {
|
|||
|
|
lossRates := []float64{0.0, 0.05, 0.10, 0.20, 0.40}
|
|||
|
|
|
|||
|
|
type result struct {
|
|||
|
|
loss float64
|
|||
|
|
retry int
|
|||
|
|
mt int
|
|||
|
|
}
|
|||
|
|
var results []result
|
|||
|
|
|
|||
|
|
for _, loss := range lossRates {
|
|||
|
|
profile := &NetworkProfile{
|
|||
|
|
Env: EnvInternet,
|
|||
|
|
RTTMedian: 80 * time.Millisecond,
|
|||
|
|
RTTStddev: 20 * time.Millisecond,
|
|||
|
|
LossRate: loss,
|
|||
|
|
Samples: 20,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
config := makeDefaultConfig()
|
|||
|
|
session := makeTestSession(config)
|
|||
|
|
ep := &EnvironmentProfile{
|
|||
|
|
Net: *profile,
|
|||
|
|
System: SystemProfile{FDLimit: 65536, NumCPU: 8},
|
|||
|
|
}
|
|||
|
|
ep.TuneConfig(config, session)
|
|||
|
|
|
|||
|
|
results = append(results, result{loss, config.MaxRetries, config.ModuleThreadNum})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重试次数应随丢包率单调递增
|
|||
|
|
for i := 1; i < len(results); i++ {
|
|||
|
|
if results[i].retry < results[i-1].retry {
|
|||
|
|
t.Errorf("Retry 不递增: loss=%.2f retry=%d < loss=%.2f retry=%d",
|
|||
|
|
results[i].loss, results[i].retry, results[i-1].loss, results[i-1].retry)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 高丢包时 ModuleThreadNum 应降低
|
|||
|
|
if results[len(results)-1].mt >= results[0].mt {
|
|||
|
|
t.Errorf("40%%丢包的 MT(%d) 应 < 0%%丢包的 MT(%d)",
|
|||
|
|
results[len(results)-1].mt, results[0].mt)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, r := range results {
|
|||
|
|
t.Logf("loss=%.0f%%: Retry=%d MT=%d", r.loss*100, r.retry, r.mt)
|
|||
|
|
}
|
|||
|
|
}
|