From 3b33ff88e880659164c6414c7ca1495c552537b5 Mon Sep 17 00:00:00 2001 From: jebbs Date: Tue, 11 Oct 2022 09:27:53 +0800 Subject: [PATCH] HealthCheck options code optimize --- balancer/healthcheck.go | 73 ++++++++++++++++------------------------- balancer/leastload.go | 4 +-- balancer/leastping.go | 4 +-- option/balancer.go | 32 ++++++++---------- 4 files changed, 46 insertions(+), 67 deletions(-) diff --git a/balancer/healthcheck.go b/balancer/healthcheck.go index ea13570d..f4a84ab5 100644 --- a/balancer/healthcheck.go +++ b/balancer/healthcheck.go @@ -3,7 +3,6 @@ package balancer import ( "fmt" "math/rand" - "strings" "sync" "time" @@ -12,15 +11,6 @@ import ( 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 type HealthCheck struct { sync.Mutex @@ -29,44 +19,37 @@ type HealthCheck struct { nodes []*Node logger log.Logger - Settings *HealthPingSettings - Results map[string]*HealthCheckRTTS + options *option.HealthCheckSettings + Results map[string]*HealthCheckRTTS } // NewHealthCheck creates a new HealthPing with settings func NewHealthCheck(outbounds []*Node, logger log.Logger, config *option.HealthCheckSettings) *HealthCheck { - settings := &HealthPingSettings{} - if config != nil { - 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 config == nil { + config = &option.HealthCheckSettings{} } - if settings.Destination == "" { - settings.Destination = "http://www.gstatic.com/generate_204" + if config.Destination == "" { + config.Destination = "http://www.gstatic.com/generate_204" } - if settings.Interval == 0 { - settings.Interval = time.Duration(1) * time.Minute - } else if settings.Interval < 10 { + if config.Interval == 0 { + config.Interval = option.Duration(time.Minute) + } else if config.Interval < 10 { 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 { - settings.SamplingCount = 10 + if config.SamplingCount <= 0 { + config.SamplingCount = 10 } - if settings.Timeout <= 0 { + if config.Timeout <= 0 { // results are saved after all health pings finish, // 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{ - nodes: outbounds, - Settings: settings, - Results: nil, - logger: logger, + nodes: outbounds, + options: config, + Results: nil, + logger: logger, } } @@ -75,7 +58,7 @@ func (h *HealthCheck) Start() error { if h.ticker != 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) h.ticker = ticker go func() { @@ -85,7 +68,7 @@ func (h *HealthCheck) Start() error { if !ok { break } - h.doCheck(interval, h.Settings.SamplingCount) + h.doCheck(interval, h.options.SamplingCount) } }() return nil @@ -124,8 +107,8 @@ func (h *HealthCheck) doCheck(duration time.Duration, rounds int) { tag, detour := node.Outbound.Tag(), node.Outbound client := newPingClient( detour, - h.Settings.Destination, - h.Settings.Timeout, + h.options.Destination, + time.Duration(h.options.Timeout), ) for i := 0; i < rounds; i++ { delay := time.Duration(0) @@ -153,7 +136,7 @@ func (h *HealthCheck) doCheck(duration time.Duration, rounds int) { h.logger.Debug( E.Cause( err, - fmt.Sprintf("ping %s via %s", h.Settings.Destination, tag), + fmt.Sprintf("ping %s via %s", h.options.Destination, tag), ), ) ch <- &rtt{ @@ -186,8 +169,8 @@ func (h *HealthCheck) PutResult(tag string, rtt time.Duration) { // distributed in the time line randomly, in extreme cases, // previous checks are distributed on the left, and latters // on the right - validity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2 - r = NewHealthPingResult(h.Settings.SamplingCount, validity) + validity := time.Duration(h.options.Interval) * time.Duration(h.options.SamplingCount) * 2 + r = NewHealthPingResult(h.options.SamplingCount, validity) h.Results[tag] = r } r.Put(rtt) @@ -196,12 +179,12 @@ func (h *HealthCheck) PutResult(tag string, rtt time.Duration) { // checkConnectivity checks the network connectivity, it returns // true if network is good or "connectivity check url" not set func (h *HealthCheck) checkConnectivity() bool { - if h.Settings.Connectivity == "" { + if h.options.Connectivity == "" { return true } tester := newDirectPingClient( - h.Settings.Connectivity, - h.Settings.Timeout, + h.options.Connectivity, + time.Duration(h.options.Timeout), ) if _, err := tester.MeasureDelay(); err != nil { return false diff --git a/balancer/leastload.go b/balancer/leastload.go index 6e0c91ca..6136fcac 100644 --- a/balancer/leastload.go +++ b/balancer/leastload.go @@ -121,10 +121,10 @@ func (s *LeastLoad) getNodes() ([]*Node, []*Node) { case node.All == 0: node.applied = rttUntested 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 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 if node.All-node.Fail == 0 { // no good, put them after has-good nodes diff --git a/balancer/leastping.go b/balancer/leastping.go index ceedcb01..ba7043bc 100644 --- a/balancer/leastping.go +++ b/balancer/leastping.go @@ -55,10 +55,10 @@ func (s *LeastPing) getNodes() ([]*Node, []*Node) { case node.All == 0: node.applied = rttUntested 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 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 if node.All-node.Fail == 0 { // no good, put them after has-good nodes diff --git a/option/balancer.go b/option/balancer.go index 3f52a889..bc59839d 100644 --- a/option/balancer.go +++ b/option/balancer.go @@ -1,31 +1,17 @@ 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 type LeastPingOutboundOptions struct { BalancerOutboundOptions - HealthCheckOptions + // health check settings + HealthCheck HealthCheckSettings `json:"health_check,omitempty"` } // LeastLoadOutboundOptions is the options for leastload outbound type LeastLoadOutboundOptions struct { BalancerOutboundOptions - HealthCheckOptions + // health check settings + HealthCheck HealthCheckSettings `json:"health_check,omitempty"` // expected nodes count to select Expected int32 `json:"expected,omitempty"` // ping rtt baselines (ms) @@ -34,6 +20,12 @@ type LeastLoadOutboundOptions struct { 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 type HealthCheckSettings struct { Destination string `json:"destination"` @@ -41,6 +33,10 @@ type HealthCheckSettings struct { Interval Duration `json:"interval"` SamplingCount int `json:"sampling"` 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