@@ -63,6 +63,11 @@ type ServerInfo struct {
63
63
Proto stamps.StampProtoType
64
64
useGet bool
65
65
odohTargetConfigs []ODoHTargetConfig
66
+
67
+ // WP2 strategy fields
68
+ totalQueries uint64 // Total queries sent to this server
69
+ failedQueries uint64 // Failed queries count
70
+ lastUpdateTime time.Time // Last time metrics were updated
66
71
}
67
72
68
73
type LBStrategy interface {
@@ -120,7 +125,33 @@ func (LBStrategyRandom) getActiveCount(serversCount int) int {
120
125
return serversCount
121
126
}
122
127
123
- var DefaultLBStrategy = LBStrategyP2 {}
128
+ type LBStrategyWP2 struct {}
129
+
130
+ func (LBStrategyWP2 ) getCandidate (serversCount int ) int {
131
+ if serversCount <= 1 {
132
+ return 0
133
+ }
134
+ if serversCount == 2 {
135
+ return rand .Intn (2 )
136
+ }
137
+
138
+ // Select two random servers
139
+ first := rand .Intn (serversCount )
140
+ second := rand .Intn (serversCount )
141
+
142
+ // Ensure we have two different servers
143
+ for second == first {
144
+ second = rand .Intn (serversCount )
145
+ }
146
+
147
+ return first // Will be refined in getWeightedCandidate
148
+ }
149
+
150
+ func (LBStrategyWP2 ) getActiveCount (serversCount int ) int {
151
+ return serversCount // All servers are considered active for WP2
152
+ }
153
+
154
+ var DefaultLBStrategy = LBStrategyWP2 {}
124
155
125
156
type DNSCryptRelay struct {
126
157
RelayUDPAddr * net.UDPAddr
@@ -324,17 +355,136 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
324
355
serversInfo .Unlock ()
325
356
return nil
326
357
}
327
- candidate := serversInfo .lbStrategy .getCandidate (serversCount )
328
- if serversInfo .lbEstimator {
329
- serversInfo .estimatorUpdate (candidate )
358
+
359
+ var candidate int
360
+
361
+ // Check if using WP2 strategy
362
+ if _ , isWP2 := serversInfo .lbStrategy .(LBStrategyWP2 ); isWP2 {
363
+ candidate = serversInfo .getWeightedCandidate (serversCount )
364
+ } else {
365
+ candidate = serversInfo .lbStrategy .getCandidate (serversCount )
366
+ if serversInfo .lbEstimator {
367
+ serversInfo .estimatorUpdate (candidate )
368
+ }
330
369
}
370
+
331
371
serverInfo := serversInfo .inner [candidate ]
332
- dlog .Debugf ("Using candidate [%s] RTT: %d" , serverInfo .Name , int (serverInfo .rtt .Value ()))
372
+ dlog .Debugf ("Using candidate [%s] RTT: %d Score: %.3f" ,
373
+ serverInfo .Name ,
374
+ int (serverInfo .rtt .Value ()),
375
+ serversInfo .calculateServerScore (serverInfo ))
333
376
serversInfo .Unlock ()
334
377
335
378
return serverInfo
336
379
}
337
380
381
+ // getWeightedCandidate implements the WP2 algorithm
382
+ func (serversInfo * ServersInfo ) getWeightedCandidate (serversCount int ) int {
383
+ if serversCount <= 1 {
384
+ return 0
385
+ }
386
+
387
+ // Select two random servers
388
+ first := rand .Intn (serversCount )
389
+ second := rand .Intn (serversCount )
390
+
391
+ // Ensure we have two different servers
392
+ for second == first {
393
+ second = rand .Intn (serversCount )
394
+ }
395
+
396
+ server1 := serversInfo .inner [first ]
397
+ server2 := serversInfo .inner [second ]
398
+
399
+ // Calculate weighted scores
400
+ score1 := serversInfo .calculateServerScore (server1 )
401
+ score2 := serversInfo .calculateServerScore (server2 )
402
+
403
+ // Select the better performing server with small randomization
404
+ if score1 > score2 {
405
+ return first
406
+ } else if score2 > score1 {
407
+ return second
408
+ } else {
409
+ // Tie-breaker: random selection
410
+ if rand .Float64 () < 0.5 {
411
+ return first
412
+ }
413
+ return second
414
+ }
415
+ }
416
+
417
+ // calculateServerScore computes a performance score for server selection
418
+ func (serversInfo * ServersInfo ) calculateServerScore (server * ServerInfo ) float64 {
419
+ // Base score from RTT (lower RTT = higher score)
420
+ rtt := server .rtt .Value ()
421
+ if rtt <= 0 {
422
+ rtt = 1000 // Default high RTT for servers without data
423
+ }
424
+
425
+ // Normalize RTT to a 0-1 scale (1000ms max)
426
+ rttScore := 1.0 - (rtt / 1000.0 )
427
+ if rttScore < 0.0 {
428
+ rttScore = 0.0
429
+ }
430
+
431
+ // Success rate score
432
+ successRate := 1.0 // Default to perfect success rate
433
+ if server .totalQueries > 0 {
434
+ successRate = float64 (server .totalQueries - server .failedQueries ) / float64 (server .totalQueries )
435
+ }
436
+
437
+ // Combine scores (RTT weighted 70%, success rate 30%)
438
+ finalScore := (rttScore * 0.7 ) + (successRate * 0.3 )
439
+
440
+ return finalScore
441
+ }
442
+
443
+ // updateServerStats updates server statistics after each query
444
+ func (serversInfo * ServersInfo ) updateServerStats (serverName string , success bool ) {
445
+ serversInfo .Lock ()
446
+ defer serversInfo .Unlock ()
447
+
448
+ for _ , server := range serversInfo .inner {
449
+ if server .Name == serverName {
450
+ server .totalQueries ++
451
+ if ! success {
452
+ server .failedQueries ++
453
+ }
454
+ server .lastUpdateTime = time .Now ()
455
+
456
+ // Reset counters periodically to prevent overflow and adapt to changes
457
+ if server .totalQueries > 10000 {
458
+ server .totalQueries = server .totalQueries / 2
459
+ server .failedQueries = server .failedQueries / 2
460
+ }
461
+ break
462
+ }
463
+ }
464
+ }
465
+
466
+ // logWP2Stats logs WP2 performance statistics for debugging
467
+ func (serversInfo * ServersInfo ) logWP2Stats () {
468
+ if _ , isWP2 := serversInfo .lbStrategy .(LBStrategyWP2 ); ! isWP2 {
469
+ return
470
+ }
471
+
472
+ serversInfo .RLock ()
473
+ defer serversInfo .RUnlock ()
474
+
475
+ dlog .Debug ("WP2 Strategy Server Statistics:" )
476
+ for i , server := range serversInfo .inner {
477
+ score := serversInfo .calculateServerScore (server )
478
+ successRate := 1.0
479
+ if server .totalQueries > 0 {
480
+ successRate = float64 (server .totalQueries - server .failedQueries ) / float64 (server .totalQueries )
481
+ }
482
+
483
+ dlog .Debugf ("[%d] %s: RTT=%dms, Score=%.3f, Success=%.2f%%, Queries=%d" ,
484
+ i , server .Name , int (server .rtt .Value ()), score , successRate * 100 , server .totalQueries )
485
+ }
486
+ }
487
+
338
488
func fetchServerInfo (proxy * Proxy , name string , stamp stamps.ServerStamp , isNew bool ) (ServerInfo , error ) {
339
489
if stamp .Proto == stamps .StampProtoTypeDNSCrypt {
340
490
return fetchDNSCryptServerInfo (proxy , name , stamp , isNew )
0 commit comments