150 lines
3.8 KiB
Go
150 lines
3.8 KiB
Go
package provider
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"proxy-pool/internal/domain/upstream"
|
|
)
|
|
|
|
var ErrInvalidStatsRecorder = errors.New("invalid Provider stats recorder")
|
|
|
|
type Stats struct {
|
|
UpstreamID string
|
|
ConsecutiveEmptyFetch int64
|
|
FetchErrorCount int64
|
|
}
|
|
|
|
type StatsReader interface {
|
|
ReadProviderStats([]string) []Stats
|
|
}
|
|
|
|
type StatsRetainer interface {
|
|
RetainProviderStats([]string)
|
|
}
|
|
|
|
type StatsRecorder struct {
|
|
mu sync.Mutex
|
|
maximum int
|
|
byID map[string]Stats
|
|
observers []ResultObserver
|
|
}
|
|
|
|
func NewStatsRecorder(maximum int) (*StatsRecorder, error) {
|
|
if maximum <= 0 {
|
|
return nil, ErrInvalidStatsRecorder
|
|
}
|
|
return &StatsRecorder{maximum: maximum, byID: make(map[string]Stats)}, nil
|
|
}
|
|
|
|
func (recorder *StatsRecorder) Record(result Result) {
|
|
if recorder == nil || result.UpstreamID == "" || !validResultClass(result.Class) {
|
|
return
|
|
}
|
|
recorder.mu.Lock()
|
|
observers := append([]ResultObserver(nil), recorder.observers...)
|
|
stats, exists := recorder.byID[result.UpstreamID]
|
|
if !exists {
|
|
if len(recorder.byID) >= recorder.maximum {
|
|
recorder.mu.Unlock()
|
|
recorder.observe(observers, result)
|
|
return
|
|
}
|
|
stats.UpstreamID = result.UpstreamID
|
|
}
|
|
switch result.Class {
|
|
case upstream.FetchEmpty:
|
|
if stats.ConsecutiveEmptyFetch < math.MaxInt64 {
|
|
stats.ConsecutiveEmptyFetch++
|
|
}
|
|
case upstream.FetchValid, upstream.FetchDuplicateOnly:
|
|
stats.ConsecutiveEmptyFetch = 0
|
|
case upstream.FetchError:
|
|
if stats.FetchErrorCount < math.MaxInt64 {
|
|
stats.FetchErrorCount++
|
|
}
|
|
}
|
|
recorder.byID[result.UpstreamID] = stats
|
|
recorder.mu.Unlock()
|
|
recorder.observe(observers, result)
|
|
}
|
|
|
|
// AddResultObserver appends one process-local observer. Duplicate instances
|
|
// are ignored so repeated bootstrap assembly cannot double-count metrics.
|
|
func (recorder *StatsRecorder) AddResultObserver(observer ResultObserver) {
|
|
if recorder == nil || observer == nil {
|
|
return
|
|
}
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
for _, existing := range recorder.observers {
|
|
if sameResultObserver(existing, observer) {
|
|
return
|
|
}
|
|
}
|
|
recorder.observers = append(recorder.observers, observer)
|
|
}
|
|
|
|
func (recorder *StatsRecorder) observe(observers []ResultObserver, result Result) {
|
|
for _, observer := range observers {
|
|
observer.ObserveProviderResult(result)
|
|
}
|
|
}
|
|
|
|
func validResultClass(class upstream.FetchClass) bool {
|
|
switch class {
|
|
case upstream.FetchValid, upstream.FetchEmpty, upstream.FetchDuplicateOnly, upstream.FetchError:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func sameResultObserver(left, right ResultObserver) bool {
|
|
leftValue := reflect.ValueOf(left)
|
|
rightValue := reflect.ValueOf(right)
|
|
if !leftValue.IsValid() || !rightValue.IsValid() || leftValue.Type() != rightValue.Type() || !leftValue.Type().Comparable() {
|
|
return false
|
|
}
|
|
return leftValue.Interface() == rightValue.Interface()
|
|
}
|
|
|
|
func (recorder *StatsRecorder) ReadProviderStats(upstreamIDs []string) []Stats {
|
|
result := make([]Stats, len(upstreamIDs))
|
|
if recorder == nil {
|
|
return result
|
|
}
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
for index, upstreamID := range upstreamIDs {
|
|
result[index] = recorder.byID[upstreamID]
|
|
result[index].UpstreamID = upstreamID
|
|
}
|
|
return result
|
|
}
|
|
|
|
// RetainProviderStats removes observations for upstreams no longer present in
|
|
// the complete configuration. Disabled but configured upstreams must be kept.
|
|
func (recorder *StatsRecorder) RetainProviderStats(upstreamIDs []string) {
|
|
if recorder == nil {
|
|
return
|
|
}
|
|
retained := make(map[string]struct{}, len(upstreamIDs))
|
|
for _, upstreamID := range upstreamIDs {
|
|
if upstreamID != "" {
|
|
retained[upstreamID] = struct{}{}
|
|
}
|
|
}
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
for upstreamID := range recorder.byID {
|
|
if _, keep := retained[upstreamID]; !keep {
|
|
delete(recorder.byID, upstreamID)
|
|
}
|
|
}
|
|
}
|
|
|
|
var _ ResultObserverRegistrar = (*StatsRecorder)(nil)
|