HealthCheck options code optimize

This commit is contained in:
jebbs 2022-10-11 09:27:53 +08:00
parent 8460c2c431
commit 3b33ff88e8
4 changed files with 46 additions and 67 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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