refactor: optimize the performance of filter in proxy-group
This commit is contained in:
parent
fbec49f63d
commit
e86ab75bb0
8 changed files with 182 additions and 117 deletions
|
@ -8,18 +8,14 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type Fallback struct {
|
||||
*outbound.Base
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
filter string
|
||||
single *singledo.Single[[]C.Proxy]
|
||||
providers []provider.ProxyProvider
|
||||
failedTimes *atomic.Int32
|
||||
failedTime *atomic.Int64
|
||||
}
|
||||
|
@ -98,7 +94,7 @@ func (f *Fallback) SupportUDP() bool {
|
|||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
all := []string{}
|
||||
for _, proxy := range f.proxies(false) {
|
||||
for _, proxy := range f.GetProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
|
@ -114,16 +110,8 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
|||
return proxy
|
||||
}
|
||||
|
||||
func (f *Fallback) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := f.single.Do(func() ([]C.Proxy, error) {
|
||||
return getProvidersProxies(f.providers, touch, f.filter), nil
|
||||
})
|
||||
|
||||
return elm
|
||||
}
|
||||
|
||||
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||
proxies := f.proxies(touch)
|
||||
proxies := f.GetProxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Alive() {
|
||||
return proxy
|
||||
|
@ -135,16 +123,17 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
|||
|
||||
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
||||
return &Fallback{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.Fallback,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.Fallback,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
},
|
||||
option.Filter,
|
||||
providers,
|
||||
}),
|
||||
single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
disableUDP: option.DisableUDP,
|
||||
filter: option.Filter,
|
||||
failedTimes: atomic.NewInt32(-1),
|
||||
failedTime: atomic.NewInt64(-1),
|
||||
}
|
||||
|
|
99
adapter/outboundgroup/groupbase.go
Normal file
99
adapter/outboundgroup/groupbase.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package outboundgroup
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
type GroupBase struct {
|
||||
*outbound.Base
|
||||
filter *regexp2.Regexp
|
||||
providers []provider.ProxyProvider
|
||||
flags map[string]uint
|
||||
proxies map[string][]C.Proxy
|
||||
}
|
||||
|
||||
type GroupBaseOption struct {
|
||||
outbound.BaseOption
|
||||
filter string
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
var filter *regexp2.Regexp = nil
|
||||
if opt.filter != "" {
|
||||
filter = regexp2.MustCompile(opt.filter, 0)
|
||||
}
|
||||
return &GroupBase{
|
||||
Base: outbound.NewBase(opt.BaseOption),
|
||||
filter: filter,
|
||||
providers: opt.providers,
|
||||
flags: map[string]uint{},
|
||||
proxies: map[string][]C.Proxy{},
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
if gb.filter == nil {
|
||||
var proxies []C.Proxy
|
||||
for _, pd := range gb.providers {
|
||||
if touch {
|
||||
proxies = append(proxies, pd.ProxiesWithTouch()...)
|
||||
} else {
|
||||
proxies = append(proxies, pd.Proxies()...)
|
||||
}
|
||||
}
|
||||
if len(proxies) == 0 {
|
||||
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||
}
|
||||
return proxies
|
||||
}
|
||||
//TODO("Touch Flag 没变的")
|
||||
for _, pd := range gb.providers {
|
||||
vt := pd.VehicleType()
|
||||
if vt == types.Compatible {
|
||||
if touch {
|
||||
gb.proxies[pd.Name()] = pd.ProxiesWithTouch()
|
||||
} else {
|
||||
gb.proxies[pd.Name()] = pd.Proxies()
|
||||
}
|
||||
|
||||
gb.flags[pd.Name()] = pd.Flag()
|
||||
continue
|
||||
}
|
||||
|
||||
if flag, ok := gb.flags[pd.Name()]; !ok || flag != pd.Flag() {
|
||||
var (
|
||||
proxies []C.Proxy
|
||||
newProxies []C.Proxy
|
||||
)
|
||||
|
||||
if touch {
|
||||
proxies = pd.ProxiesWithTouch()
|
||||
} else {
|
||||
proxies = pd.Proxies()
|
||||
}
|
||||
|
||||
for _, p := range proxies {
|
||||
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
}
|
||||
|
||||
gb.proxies[pd.Name()] = newProxies
|
||||
gb.flags[pd.Name()] = pd.Flag()
|
||||
}
|
||||
}
|
||||
var proxies []C.Proxy
|
||||
for _, v := range gb.proxies {
|
||||
proxies = append(proxies, v...)
|
||||
}
|
||||
if len(proxies) == 0 {
|
||||
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||
}
|
||||
return proxies
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/murmur3"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
|
@ -20,11 +19,8 @@ import (
|
|||
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
|
||||
|
||||
type LoadBalance struct {
|
||||
*outbound.Base
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
filter string
|
||||
single *singledo.Single[[]C.Proxy]
|
||||
providers []provider.ProxyProvider
|
||||
strategyFn strategyFn
|
||||
}
|
||||
|
||||
|
@ -136,22 +132,14 @@ func strategyConsistentHashing() strategyFn {
|
|||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxies := lb.proxies(true)
|
||||
proxies := lb.GetProxies(true)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := lb.single.Do(func() ([]C.Proxy, error) {
|
||||
return getProvidersProxies(lb.providers, touch, lb.filter), nil
|
||||
})
|
||||
|
||||
return elm
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
all := []string{}
|
||||
for _, proxy := range lb.proxies(false) {
|
||||
for _, proxy := range lb.GetProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
|
@ -171,16 +159,17 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
|||
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
||||
}
|
||||
return &LoadBalance{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.LoadBalance,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.LoadBalance,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
},
|
||||
option.Filter,
|
||||
providers,
|
||||
}),
|
||||
single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
strategyFn: strategyFn,
|
||||
disableUDP: option.DisableUDP,
|
||||
filter: option.Filter,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -6,17 +6,13 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
*outbound.Base
|
||||
single *singledo.Single[[]C.Proxy]
|
||||
providers []provider.ProxyProvider
|
||||
filter string
|
||||
*GroupBase
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
|
@ -70,7 +66,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
all := []string{}
|
||||
for _, proxy := range r.rawProxies(false) {
|
||||
for _, proxy := range r.GetProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
|
@ -79,16 +75,8 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
|
|||
})
|
||||
}
|
||||
|
||||
func (r *Relay) rawProxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := r.single.Do(func() ([]C.Proxy, error) {
|
||||
return getProvidersProxies(r.providers, touch, r.filter), nil
|
||||
})
|
||||
|
||||
return elm
|
||||
}
|
||||
|
||||
func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
||||
proxies := r.rawProxies(touch)
|
||||
proxies := r.GetProxies(touch)
|
||||
|
||||
for n, proxy := range proxies {
|
||||
subproxy := proxy.Unwrap(metadata)
|
||||
|
@ -103,14 +91,15 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
|||
|
||||
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||
return &Relay{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.Relay,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.Relay,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
},
|
||||
"",
|
||||
providers,
|
||||
}),
|
||||
single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
filter: option.Filter,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,15 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type Selector struct {
|
||||
*outbound.Base
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
single *singledo.Single[C.Proxy]
|
||||
selected string
|
||||
filter string
|
||||
providers []provider.ProxyProvider
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
|
@ -51,7 +47,7 @@ func (s *Selector) SupportUDP() bool {
|
|||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
all := []string{}
|
||||
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
|
||||
for _, proxy := range s.GetProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
|
@ -67,10 +63,9 @@ func (s *Selector) Now() string {
|
|||
}
|
||||
|
||||
func (s *Selector) Set(name string) error {
|
||||
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
|
||||
for _, proxy := range s.GetProxies(false) {
|
||||
if proxy.Name() == name {
|
||||
s.selected = name
|
||||
s.single.Reset()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -84,32 +79,29 @@ func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
|
|||
}
|
||||
|
||||
func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||
elm, _, _ := s.single.Do(func() (C.Proxy, error) {
|
||||
proxies := getProvidersProxies(s.providers, touch, s.filter)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Name() == s.selected {
|
||||
return proxy, nil
|
||||
}
|
||||
proxies := s.GetProxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Name() == s.selected {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return proxies[0], nil
|
||||
})
|
||||
|
||||
return elm
|
||||
return proxies[0]
|
||||
}
|
||||
|
||||
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||
return &Selector{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.Selector,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.Selector,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
},
|
||||
option.Filter,
|
||||
providers,
|
||||
}),
|
||||
single: singledo.NewSingle[C.Proxy](defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
selected: "COMPATIBLE",
|
||||
disableUDP: option.DisableUDP,
|
||||
filter: option.Filter,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,11 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
|
|||
}
|
||||
|
||||
type URLTest struct {
|
||||
*outbound.Base
|
||||
*GroupBase
|
||||
tolerance uint16
|
||||
disableUDP bool
|
||||
fastNode C.Proxy
|
||||
filter string
|
||||
single *singledo.Single[[]C.Proxy]
|
||||
fastSingle *singledo.Single[C.Proxy]
|
||||
providers []provider.ProxyProvider
|
||||
failedTimes *atomic.Int32
|
||||
failedTime *atomic.Int64
|
||||
}
|
||||
|
@ -70,17 +67,9 @@ func (u *URLTest) Unwrap(*C.Metadata) C.Proxy {
|
|||
return u.fast(true)
|
||||
}
|
||||
|
||||
func (u *URLTest) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := u.single.Do(func() ([]C.Proxy, error) {
|
||||
return getProvidersProxies(u.providers, touch, u.filter), nil
|
||||
})
|
||||
|
||||
return elm
|
||||
}
|
||||
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
proxies := u.proxies(touch)
|
||||
proxies := u.GetProxies(touch)
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
fastNotExist := true
|
||||
|
@ -124,7 +113,7 @@ func (u *URLTest) SupportUDP() bool {
|
|||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
all := []string{}
|
||||
for _, proxy := range u.proxies(false) {
|
||||
for _, proxy := range u.GetProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
|
@ -175,17 +164,18 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
|
|||
|
||||
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
|
||||
urlTest := &URLTest{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.URLTest,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
Type: C.URLTest,
|
||||
Interface: option.Interface,
|
||||
RoutingMark: option.RoutingMark,
|
||||
},
|
||||
option.Filter,
|
||||
providers,
|
||||
}),
|
||||
single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration),
|
||||
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
||||
providers: providers,
|
||||
disableUDP: option.DisableUDP,
|
||||
filter: option.Filter,
|
||||
failedTimes: atomic.NewInt32(-1),
|
||||
failedTime: atomic.NewInt64(-1),
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/dlclark/regexp2"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
|
@ -32,6 +33,11 @@ type proxySetProvider struct {
|
|||
*fetcher
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
flag uint
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Flag() uint {
|
||||
return pp.flag
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
|
@ -117,6 +123,11 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
|||
onUpdate := func(elm any) {
|
||||
ret := elm.([]C.Proxy)
|
||||
pd.setProxies(ret)
|
||||
if pd.flag == math.MaxUint {
|
||||
pd.flag = 0
|
||||
} else {
|
||||
pd.flag++
|
||||
}
|
||||
}
|
||||
|
||||
proxiesParseAndFilter := func(buf []byte) (any, error) {
|
||||
|
@ -171,6 +182,11 @@ type compatibleProvider struct {
|
|||
name string
|
||||
healthCheck *HealthCheck
|
||||
proxies []C.Proxy
|
||||
flag uint
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Flag() uint {
|
||||
return cp.flag
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
|
|
|
@ -70,6 +70,7 @@ type ProxyProvider interface {
|
|||
// Commonly used in DialContext and DialPacketConn
|
||||
ProxiesWithTouch() []constant.Proxy
|
||||
HealthCheck()
|
||||
Flag() uint
|
||||
}
|
||||
|
||||
// Rule Type
|
||||
|
|
Loading…
Reference in a new issue