mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
HealthCheck options code optimize
This commit is contained in:
parent
8460c2c431
commit
3b33ff88e8
@ -3,7 +3,6 @@ package balancer
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -12,15 +11,6 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthPingSettings holds settings for health Checker
|
|
||||||
type HealthPingSettings struct {
|
|
||||||
Destination string `json:"destination"`
|
|
||||||
Connectivity string `json:"connectivity"`
|
|
||||||
Interval time.Duration `json:"interval"`
|
|
||||||
SamplingCount int `json:"sampling"`
|
|
||||||
Timeout time.Duration `json:"timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheck is the health checker for balancers
|
// HealthCheck is the health checker for balancers
|
||||||
type HealthCheck struct {
|
type HealthCheck struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
@ -29,44 +19,37 @@ type HealthCheck struct {
|
|||||||
nodes []*Node
|
nodes []*Node
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
|
||||||
Settings *HealthPingSettings
|
options *option.HealthCheckSettings
|
||||||
Results map[string]*HealthCheckRTTS
|
Results map[string]*HealthCheckRTTS
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHealthCheck creates a new HealthPing with settings
|
// NewHealthCheck creates a new HealthPing with settings
|
||||||
func NewHealthCheck(outbounds []*Node, logger log.Logger, config *option.HealthCheckSettings) *HealthCheck {
|
func NewHealthCheck(outbounds []*Node, logger log.Logger, config *option.HealthCheckSettings) *HealthCheck {
|
||||||
settings := &HealthPingSettings{}
|
if config == nil {
|
||||||
if config != nil {
|
config = &option.HealthCheckSettings{}
|
||||||
settings = &HealthPingSettings{
|
|
||||||
Connectivity: strings.TrimSpace(config.Connectivity),
|
|
||||||
Destination: strings.TrimSpace(config.Destination),
|
|
||||||
Interval: time.Duration(config.Interval),
|
|
||||||
SamplingCount: int(config.SamplingCount),
|
|
||||||
Timeout: time.Duration(config.Timeout),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if settings.Destination == "" {
|
if config.Destination == "" {
|
||||||
settings.Destination = "http://www.gstatic.com/generate_204"
|
config.Destination = "http://www.gstatic.com/generate_204"
|
||||||
}
|
}
|
||||||
if settings.Interval == 0 {
|
if config.Interval == 0 {
|
||||||
settings.Interval = time.Duration(1) * time.Minute
|
config.Interval = option.Duration(time.Minute)
|
||||||
} else if settings.Interval < 10 {
|
} else if config.Interval < 10 {
|
||||||
logger.Warn("health check interval is too small, 10s is applied")
|
logger.Warn("health check interval is too small, 10s is applied")
|
||||||
settings.Interval = time.Duration(10) * time.Second
|
config.Interval = option.Duration(10 * time.Second)
|
||||||
}
|
}
|
||||||
if settings.SamplingCount <= 0 {
|
if config.SamplingCount <= 0 {
|
||||||
settings.SamplingCount = 10
|
config.SamplingCount = 10
|
||||||
}
|
}
|
||||||
if settings.Timeout <= 0 {
|
if config.Timeout <= 0 {
|
||||||
// results are saved after all health pings finish,
|
// results are saved after all health pings finish,
|
||||||
// a larger timeout could possibly makes checks run longer
|
// a larger timeout could possibly makes checks run longer
|
||||||
settings.Timeout = time.Duration(5) * time.Second
|
config.Timeout = option.Duration(5 * time.Second)
|
||||||
}
|
}
|
||||||
return &HealthCheck{
|
return &HealthCheck{
|
||||||
nodes: outbounds,
|
nodes: outbounds,
|
||||||
Settings: settings,
|
options: config,
|
||||||
Results: nil,
|
Results: nil,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +58,7 @@ func (h *HealthCheck) Start() error {
|
|||||||
if h.ticker != nil {
|
if h.ticker != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
|
interval := time.Duration(h.options.Interval) * time.Duration(h.options.SamplingCount)
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
h.ticker = ticker
|
h.ticker = ticker
|
||||||
go func() {
|
go func() {
|
||||||
@ -85,7 +68,7 @@ func (h *HealthCheck) Start() error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
h.doCheck(interval, h.Settings.SamplingCount)
|
h.doCheck(interval, h.options.SamplingCount)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
@ -124,8 +107,8 @@ func (h *HealthCheck) doCheck(duration time.Duration, rounds int) {
|
|||||||
tag, detour := node.Outbound.Tag(), node.Outbound
|
tag, detour := node.Outbound.Tag(), node.Outbound
|
||||||
client := newPingClient(
|
client := newPingClient(
|
||||||
detour,
|
detour,
|
||||||
h.Settings.Destination,
|
h.options.Destination,
|
||||||
h.Settings.Timeout,
|
time.Duration(h.options.Timeout),
|
||||||
)
|
)
|
||||||
for i := 0; i < rounds; i++ {
|
for i := 0; i < rounds; i++ {
|
||||||
delay := time.Duration(0)
|
delay := time.Duration(0)
|
||||||
@ -153,7 +136,7 @@ func (h *HealthCheck) doCheck(duration time.Duration, rounds int) {
|
|||||||
h.logger.Debug(
|
h.logger.Debug(
|
||||||
E.Cause(
|
E.Cause(
|
||||||
err,
|
err,
|
||||||
fmt.Sprintf("ping %s via %s", h.Settings.Destination, tag),
|
fmt.Sprintf("ping %s via %s", h.options.Destination, tag),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ch <- &rtt{
|
ch <- &rtt{
|
||||||
@ -186,8 +169,8 @@ func (h *HealthCheck) PutResult(tag string, rtt time.Duration) {
|
|||||||
// distributed in the time line randomly, in extreme cases,
|
// distributed in the time line randomly, in extreme cases,
|
||||||
// previous checks are distributed on the left, and latters
|
// previous checks are distributed on the left, and latters
|
||||||
// on the right
|
// on the right
|
||||||
validity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2
|
validity := time.Duration(h.options.Interval) * time.Duration(h.options.SamplingCount) * 2
|
||||||
r = NewHealthPingResult(h.Settings.SamplingCount, validity)
|
r = NewHealthPingResult(h.options.SamplingCount, validity)
|
||||||
h.Results[tag] = r
|
h.Results[tag] = r
|
||||||
}
|
}
|
||||||
r.Put(rtt)
|
r.Put(rtt)
|
||||||
@ -196,12 +179,12 @@ func (h *HealthCheck) PutResult(tag string, rtt time.Duration) {
|
|||||||
// checkConnectivity checks the network connectivity, it returns
|
// checkConnectivity checks the network connectivity, it returns
|
||||||
// true if network is good or "connectivity check url" not set
|
// true if network is good or "connectivity check url" not set
|
||||||
func (h *HealthCheck) checkConnectivity() bool {
|
func (h *HealthCheck) checkConnectivity() bool {
|
||||||
if h.Settings.Connectivity == "" {
|
if h.options.Connectivity == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
tester := newDirectPingClient(
|
tester := newDirectPingClient(
|
||||||
h.Settings.Connectivity,
|
h.options.Connectivity,
|
||||||
h.Settings.Timeout,
|
time.Duration(h.options.Timeout),
|
||||||
)
|
)
|
||||||
if _, err := tester.MeasureDelay(); err != nil {
|
if _, err := tester.MeasureDelay(); err != nil {
|
||||||
return false
|
return false
|
||||||
|
@ -121,10 +121,10 @@ func (s *LeastLoad) getNodes() ([]*Node, []*Node) {
|
|||||||
case node.All == 0:
|
case node.All == 0:
|
||||||
node.applied = rttUntested
|
node.applied = rttUntested
|
||||||
untested = append(untested, node)
|
untested = append(untested, node)
|
||||||
case s.options.MaxRTT > 0 && node.Average > time.Duration(s.options.MaxRTT):
|
case s.options.HealthCheck.MaxRTT > 0 && node.Average > time.Duration(s.options.HealthCheck.MaxRTT):
|
||||||
node.applied = rttUnqualified
|
node.applied = rttUnqualified
|
||||||
unqualified = append(unqualified, node)
|
unqualified = append(unqualified, node)
|
||||||
case float64(node.Fail)/float64(node.All) > float64(s.options.Tolerance):
|
case float64(node.Fail)/float64(node.All) > float64(s.options.HealthCheck.Tolerance):
|
||||||
node.applied = rttFailed
|
node.applied = rttFailed
|
||||||
if node.All-node.Fail == 0 {
|
if node.All-node.Fail == 0 {
|
||||||
// no good, put them after has-good nodes
|
// no good, put them after has-good nodes
|
||||||
|
@ -55,10 +55,10 @@ func (s *LeastPing) getNodes() ([]*Node, []*Node) {
|
|||||||
case node.All == 0:
|
case node.All == 0:
|
||||||
node.applied = rttUntested
|
node.applied = rttUntested
|
||||||
untested = append(untested, node)
|
untested = append(untested, node)
|
||||||
case s.options.MaxRTT > 0 && node.Average > time.Duration(s.options.MaxRTT):
|
case s.options.HealthCheck.MaxRTT > 0 && node.Average > time.Duration(s.options.HealthCheck.MaxRTT):
|
||||||
node.applied = rttUnqualified
|
node.applied = rttUnqualified
|
||||||
unqualified = append(unqualified, node)
|
unqualified = append(unqualified, node)
|
||||||
case float64(node.Fail)/float64(node.All) > float64(s.options.Tolerance):
|
case float64(node.Fail)/float64(node.All) > float64(s.options.HealthCheck.Tolerance):
|
||||||
node.applied = rttFailed
|
node.applied = rttFailed
|
||||||
if node.All-node.Fail == 0 {
|
if node.All-node.Fail == 0 {
|
||||||
// no good, put them after has-good nodes
|
// no good, put them after has-good nodes
|
||||||
|
@ -1,31 +1,17 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
// BalancerOutboundOptions is the options for balancer outbound
|
|
||||||
type BalancerOutboundOptions struct {
|
|
||||||
Outbounds []string `json:"outbounds"`
|
|
||||||
Fallback string `json:"fallback,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheckOptions is the options for health check
|
|
||||||
type HealthCheckOptions struct {
|
|
||||||
// health check settings
|
|
||||||
HealthCheck HealthCheckSettings `json:"healthCheck,omitempty"`
|
|
||||||
// max acceptable rtt (ms), filter away high delay nodes. defalut 0
|
|
||||||
MaxRTT Duration `json:"maxRTT,omitempty"`
|
|
||||||
// acceptable failure rate
|
|
||||||
Tolerance float64 `json:"tolerance,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeastPingOutboundOptions is the options for leastping outbound
|
// LeastPingOutboundOptions is the options for leastping outbound
|
||||||
type LeastPingOutboundOptions struct {
|
type LeastPingOutboundOptions struct {
|
||||||
BalancerOutboundOptions
|
BalancerOutboundOptions
|
||||||
HealthCheckOptions
|
// health check settings
|
||||||
|
HealthCheck HealthCheckSettings `json:"health_check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeastLoadOutboundOptions is the options for leastload outbound
|
// LeastLoadOutboundOptions is the options for leastload outbound
|
||||||
type LeastLoadOutboundOptions struct {
|
type LeastLoadOutboundOptions struct {
|
||||||
BalancerOutboundOptions
|
BalancerOutboundOptions
|
||||||
HealthCheckOptions
|
// health check settings
|
||||||
|
HealthCheck HealthCheckSettings `json:"health_check,omitempty"`
|
||||||
// expected nodes count to select
|
// expected nodes count to select
|
||||||
Expected int32 `json:"expected,omitempty"`
|
Expected int32 `json:"expected,omitempty"`
|
||||||
// ping rtt baselines (ms)
|
// ping rtt baselines (ms)
|
||||||
@ -34,6 +20,12 @@ type LeastLoadOutboundOptions struct {
|
|||||||
Costs []*StrategyWeight `json:"costs,omitempty"`
|
Costs []*StrategyWeight `json:"costs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BalancerOutboundOptions is the options for balancer outbound
|
||||||
|
type BalancerOutboundOptions struct {
|
||||||
|
Outbounds []string `json:"outbounds"`
|
||||||
|
Fallback string `json:"fallback,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// HealthCheckSettings is the settings for health check
|
// HealthCheckSettings is the settings for health check
|
||||||
type HealthCheckSettings struct {
|
type HealthCheckSettings struct {
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
@ -41,6 +33,10 @@ type HealthCheckSettings struct {
|
|||||||
Interval Duration `json:"interval"`
|
Interval Duration `json:"interval"`
|
||||||
SamplingCount int `json:"sampling"`
|
SamplingCount int `json:"sampling"`
|
||||||
Timeout Duration `json:"timeout"`
|
Timeout Duration `json:"timeout"`
|
||||||
|
// max acceptable rtt (ms), filter away high delay nodes. defalut 0
|
||||||
|
MaxRTT Duration `json:"max_rtt,omitempty"`
|
||||||
|
// acceptable failure rate
|
||||||
|
Tolerance float64 `json:"tolerance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrategyWeight is the weight for a balancing strategy
|
// StrategyWeight is the weight for a balancing strategy
|
||||||
|
Loading…
x
Reference in New Issue
Block a user