chore: 准备发布 v1.0.0 版本

This commit is contained in:
xiamuceer-j
2026-03-02 13:13:33 +08:00
parent f57ae3c712
commit 8163e4c193
58 changed files with 19545 additions and 1 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Easy Proxies</title>
<script type="module" crossorigin src="/assets/index-BkdNNtGR.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CZdhzSdd.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+21
View File
@@ -0,0 +1,21 @@
package monitor
import "context"
type contextKey string
const managerKey contextKey = "easy_proxies.monitor"
// ContextWith attaches the manager into context so downstream components can reuse it.
func ContextWith(ctx context.Context, mgr *Manager) context.Context {
if mgr == nil {
return ctx
}
return context.WithValue(ctx, managerKey, mgr)
}
// FromContext extracts a manager if present.
func FromContext(ctx context.Context) *Manager {
mgr, _ := ctx.Value(managerKey).(*Manager)
return mgr
}
+868
View File
@@ -0,0 +1,868 @@
package monitor
import (
"context"
"errors"
"fmt"
"net"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
M "github.com/sagernet/sing/common/metadata"
)
// Config mirrors user settings needed by the monitoring server.
type Config struct {
Enabled bool
Listen string
ProbeTarget string
Password string
ProxyUsername string // 代理池的用户名(用于导出)
ProxyPassword string // 代理池的密码(用于导出)
ExternalIP string // 外部 IP 地址,用于导出时替换 0.0.0.0
SkipCertVerify bool // 全局跳过 SSL 证书验证
}
// NodeInfo is static metadata about a proxy entry.
type NodeInfo struct {
Tag string `json:"tag"`
Name string `json:"name"`
URI string `json:"uri"`
Mode string `json:"mode"`
ListenAddress string `json:"listen_address,omitempty"`
Port uint16 `json:"port,omitempty"`
Region string `json:"region,omitempty"` // GeoIP region code: "jp", "kr", "us", "hk", "tw", "other"
Country string `json:"country,omitempty"` // Full country name from GeoIP
}
// TimelineEvent represents a single usage event for debug tracking.
type TimelineEvent struct {
Time time.Time `json:"time"`
Success bool `json:"success"`
LatencyMs int64 `json:"latency_ms"`
Error string `json:"error,omitempty"`
Destination string `json:"destination,omitempty"`
}
const maxTimelineSize = 20
// Snapshot is a runtime view of a proxy node.
type Snapshot struct {
NodeInfo
FailureCount int `json:"failure_count"`
SuccessCount int64 `json:"success_count"`
Blacklisted bool `json:"blacklisted"`
BlacklistedUntil time.Time `json:"blacklisted_until"`
ActiveConnections int32 `json:"active_connections"`
LastError string `json:"last_error,omitempty"`
LastFailure time.Time `json:"last_failure,omitempty"`
LastSuccess time.Time `json:"last_success,omitempty"`
LastProbeLatency time.Duration `json:"last_probe_latency,omitempty"`
LastLatencyMs int64 `json:"last_latency_ms"`
Available bool `json:"available"`
InitialCheckDone bool `json:"initial_check_done"`
TotalUpload int64 `json:"total_upload"`
TotalDownload int64 `json:"total_download"`
UploadSpeed int64 `json:"upload_speed"` // bytes/sec
DownloadSpeed int64 `json:"download_speed"` // bytes/sec
Timeline []TimelineEvent `json:"timeline,omitempty"`
}
type NodeTrafficSpeed struct {
Tag string `json:"tag"`
UploadSpeed int64 `json:"upload_speed"` // bytes/sec
DownloadSpeed int64 `json:"download_speed"` // bytes/sec
TotalUpload int64 `json:"total_upload"`
TotalDownload int64 `json:"total_download"`
}
type TrafficSummary struct {
NodeCount int `json:"node_count"`
TotalUpload int64 `json:"total_upload"`
TotalDownload int64 `json:"total_download"`
UploadSpeed int64 `json:"upload_speed"` // bytes/sec
DownloadSpeed int64 `json:"download_speed"` // bytes/sec
Nodes []NodeTrafficSpeed `json:"nodes,omitempty"`
SampledAt time.Time `json:"sampled_at"`
}
type probeFunc func(ctx context.Context) (time.Duration, error)
type releaseFunc func()
type EntryHandle struct {
ref *entry
}
type entry struct {
info NodeInfo
failure int
success int64
timeline []TimelineEvent
blacklist bool
until time.Time
lastError string
lastFail time.Time
lastOK time.Time
lastProbe time.Duration
active atomic.Int32
totalUpload atomic.Int64
totalDownload atomic.Int64
uploadSpeed int64
downloadSpeed int64
lastSpeedUpload int64
lastSpeedDown int64
lastSpeedAt time.Time
probe probeFunc
release releaseFunc
initialCheckDone bool
available bool
reloadGen uint64 // generation counter to track active registrations
mu sync.RWMutex
}
// Manager aggregates all node states for the UI/API.
type Manager struct {
cfg Config
reloadGen uint64 // current reload generation
probeDst M.Socksaddr
probeReady bool
mu sync.RWMutex
nodes map[string]*entry
ctx context.Context
cancel context.CancelFunc
logger Logger
}
// Logger interface for logging
type Logger interface {
Info(args ...any)
Warn(args ...any)
}
// NewManager constructs a manager and pre-validates the probe target.
func NewManager(cfg Config) (*Manager, error) {
ctx, cancel := context.WithCancel(context.Background())
m := &Manager{
cfg: cfg,
nodes: make(map[string]*entry),
ctx: ctx,
cancel: cancel,
}
if cfg.ProbeTarget != "" {
target := cfg.ProbeTarget
// Strip URL scheme if present (e.g., "https://www.google.com:443" -> "www.google.com:443")
if strings.HasPrefix(target, "https://") {
target = strings.TrimPrefix(target, "https://")
} else if strings.HasPrefix(target, "http://") {
target = strings.TrimPrefix(target, "http://")
}
// Remove trailing path if present
if idx := strings.Index(target, "/"); idx != -1 {
target = target[:idx]
}
host, port, err := net.SplitHostPort(target)
if err != nil {
// If no port specified, use default based on original scheme
if strings.HasPrefix(cfg.ProbeTarget, "https://") {
host = target
port = "443"
} else {
host = target
port = "80"
}
}
parsed := M.ParseSocksaddrHostPort(host, parsePort(port))
m.probeDst = parsed
m.probeReady = true
}
go m.startTrafficSpeedSampler()
return m, nil
}
// SetLogger sets the logger for the manager.
func (m *Manager) SetLogger(logger Logger) {
m.logger = logger
}
// StartPeriodicHealthCheck starts a background goroutine that periodically checks all nodes.
// interval: how often to check (e.g., 30 * time.Second)
// timeout: timeout for each probe (e.g., 10 * time.Second)
func (m *Manager) StartPeriodicHealthCheck(interval, timeout time.Duration) {
if !m.probeReady {
if m.logger != nil {
m.logger.Warn("probe target not configured, periodic health check disabled")
}
return
}
go func() {
// 启动后立即进行一次检查
m.probeAllNodes(timeout)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-m.ctx.Done():
return
case <-ticker.C:
m.probeAllNodes(timeout)
}
}
}()
if m.logger != nil {
m.logger.Info("periodic health check started, interval: ", interval)
}
}
// probeAllNodes checks all registered nodes concurrently.
func (m *Manager) probeAllNodes(timeout time.Duration) {
m.mu.RLock()
entries := make([]*entry, 0, len(m.nodes))
for _, e := range m.nodes {
entries = append(entries, e)
}
m.mu.RUnlock()
if len(entries) == 0 {
return
}
if m.logger != nil {
m.logger.Info("starting health check for ", len(entries), " nodes")
}
workerLimit := runtime.NumCPU() * 2
if workerLimit < 8 {
workerLimit = 8
}
sem := make(chan struct{}, workerLimit)
var wg sync.WaitGroup
var availableCount atomic.Int32
var failedCount atomic.Int32
for _, e := range entries {
e.mu.RLock()
probeFn := e.probe
tag := e.info.Tag
e.mu.RUnlock()
if probeFn == nil {
continue
}
sem <- struct{}{}
wg.Add(1)
go func(entry *entry, probe probeFunc, tag string) {
defer wg.Done()
defer func() { <-sem }()
ctx, cancel := context.WithTimeout(m.ctx, timeout)
latency, err := probe(ctx)
cancel()
entry.mu.Lock()
if err != nil {
failedCount.Add(1)
entry.lastError = err.Error()
entry.lastFail = time.Now()
entry.lastProbe = 0
entry.available = false
entry.initialCheckDone = true
} else {
availableCount.Add(1)
entry.lastOK = time.Now()
entry.lastProbe = latency
entry.available = true
entry.initialCheckDone = true
}
entry.mu.Unlock()
if err != nil && m.logger != nil {
m.logger.Warn("probe failed for ", tag, ": ", err)
}
}(e, probeFn, tag)
}
wg.Wait()
if m.logger != nil {
m.logger.Info("health check completed: ", availableCount.Load(), " available, ", failedCount.Load(), " failed")
}
}
// Stop stops the periodic health check.
func (m *Manager) Stop() {
if m.cancel != nil {
m.cancel()
}
}
func (m *Manager) startTrafficSpeedSampler() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-m.ctx.Done():
return
case now := <-ticker.C:
m.sampleTrafficSpeeds(now)
}
}
}
func (m *Manager) sampleTrafficSpeeds(now time.Time) {
m.mu.RLock()
entries := make([]*entry, 0, len(m.nodes))
for _, e := range m.nodes {
entries = append(entries, e)
}
m.mu.RUnlock()
for _, e := range entries {
e.updateTrafficSpeed(now)
}
}
func parsePort(value string) uint16 {
p, err := strconv.Atoi(value)
if err != nil || p <= 0 || p > 65535 {
return 80
}
return uint16(p)
}
// BeginReload bumps the generation counter. Nodes registered after this call
// will be marked with the new generation. Call SweepStaleNodes after reload
// to remove nodes that were not re-registered (disabled/deleted nodes).
func (m *Manager) BeginReload() {
m.mu.Lock()
defer m.mu.Unlock()
m.reloadGen++
}
// SweepStaleNodes removes nodes that were not re-registered during the current
// reload cycle. This preserves monitoring data (latency, success/failure counts)
// for nodes that are still active, while cleaning up disabled/removed nodes.
func (m *Manager) SweepStaleNodes() {
m.mu.Lock()
defer m.mu.Unlock()
for tag, e := range m.nodes {
if e.reloadGen != m.reloadGen {
delete(m.nodes, tag)
}
}
}
// ClearNodes removes all registered nodes. Use BeginReload + SweepStaleNodes
// for reload scenarios to preserve data for active nodes.
func (m *Manager) ClearNodes() {
m.mu.Lock()
defer m.mu.Unlock()
m.nodes = make(map[string]*entry)
}
// Register ensures a node is tracked and returns its entry.
// If the node already exists, its info is updated but monitoring stats
// (latency, success/failure counts, etc.) are preserved.
func (m *Manager) Register(info NodeInfo) *EntryHandle {
m.mu.Lock()
defer m.mu.Unlock()
e, ok := m.nodes[info.Tag]
if !ok {
e = &entry{
info: info,
timeline: make([]TimelineEvent, 0, maxTimelineSize),
reloadGen: m.reloadGen,
}
m.nodes[info.Tag] = e
} else {
e.info = info
e.reloadGen = m.reloadGen
}
return &EntryHandle{ref: e}
}
// DestinationForProbe exposes the configured destination for health checks.
func (m *Manager) DestinationForProbe() (M.Socksaddr, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
if !m.probeReady {
return M.Socksaddr{}, false
}
return m.probeDst, true
}
// UpdateProbeTarget dynamically updates the probe destination at runtime.
func (m *Manager) UpdateProbeTarget(target string) error {
if target == "" {
return nil
}
// Strip URL scheme if present
if strings.HasPrefix(target, "https://") {
target = strings.TrimPrefix(target, "https://")
} else if strings.HasPrefix(target, "http://") {
target = strings.TrimPrefix(target, "http://")
}
// Remove trailing path if present
if idx := strings.Index(target, "/"); idx != -1 {
target = target[:idx]
}
host, port, err := net.SplitHostPort(target)
if err != nil {
// If no port specified, use default port 80
host = target
port = "80"
}
parsed := M.ParseSocksaddrHostPort(host, parsePort(port))
m.mu.Lock()
m.probeDst = parsed
m.probeReady = true
m.cfg.ProbeTarget = target
m.mu.Unlock()
return nil
}
// Snapshot returns a sorted copy of current node states.
// If onlyAvailable is true, only returns nodes that passed initial health check.
func (m *Manager) Snapshot() []Snapshot {
return m.SnapshotFiltered(false)
}
// SnapshotFiltered returns a sorted copy of current node states.
// If onlyAvailable is true, only returns nodes that passed initial health check.
// Nodes that haven't been checked yet are also included (they will be checked on first use).
func (m *Manager) SnapshotFiltered(onlyAvailable bool) []Snapshot {
m.mu.RLock()
list := make([]*entry, 0, len(m.nodes))
for _, e := range m.nodes {
list = append(list, e)
}
m.mu.RUnlock()
snapshots := make([]Snapshot, 0, len(list))
for _, e := range list {
snap := e.snapshot()
// 如果只要可用节点:
// - 跳过已完成检查但不可用的节点
// - 保留未完成检查的节点(它们会在首次使用时被检查)
if onlyAvailable && snap.InitialCheckDone && !snap.Available {
continue
}
snapshots = append(snapshots, snap)
}
// 按延迟排序(延迟小的在前面,未测试的排在最后)
sort.Slice(snapshots, func(i, j int) bool {
latencyI := snapshots[i].LastLatencyMs
latencyJ := snapshots[j].LastLatencyMs
// -1 表示未测试,排在最后
if latencyI < 0 && latencyJ < 0 {
return snapshots[i].Name < snapshots[j].Name // 都未测试时按名称排序
}
if latencyI < 0 {
return false // i 未测试,排在后面
}
if latencyJ < 0 {
return true // j 未测试,i 排在前面
}
if latencyI == latencyJ {
return snapshots[i].Name < snapshots[j].Name // 延迟相同时按名称排序
}
return latencyI < latencyJ
})
return snapshots
}
// TrafficSummary returns aggregated traffic totals/speeds and per-node speeds.
// includeNodes controls whether per-node details are returned.
func (m *Manager) TrafficSummary(includeNodes bool) TrafficSummary {
m.mu.RLock()
list := make([]*entry, 0, len(m.nodes))
for _, e := range m.nodes {
list = append(list, e)
}
m.mu.RUnlock()
summary := TrafficSummary{
NodeCount: len(list),
SampledAt: time.Now(),
}
if includeNodes {
summary.Nodes = make([]NodeTrafficSpeed, 0, len(list))
}
for _, e := range list {
totalUp := e.totalUpload.Load()
totalDown := e.totalDownload.Load()
e.mu.RLock()
upSpeed := e.uploadSpeed
downSpeed := e.downloadSpeed
tag := e.info.Tag
e.mu.RUnlock()
summary.TotalUpload += totalUp
summary.TotalDownload += totalDown
summary.UploadSpeed += upSpeed
summary.DownloadSpeed += downSpeed
if includeNodes {
summary.Nodes = append(summary.Nodes, NodeTrafficSpeed{
Tag: tag,
UploadSpeed: upSpeed,
DownloadSpeed: downSpeed,
TotalUpload: totalUp,
TotalDownload: totalDown,
})
}
}
return summary
}
// Probe triggers a manual health check.
func (m *Manager) Probe(ctx context.Context, tag string) (time.Duration, error) {
e, err := m.entry(tag)
if err != nil {
return 0, err
}
if e.probe == nil {
return 0, errors.New("probe not available for this node")
}
latency, err := e.probe(ctx)
if err != nil {
// 探测失败:标记节点为不可用,清除旧延迟数据
e.mu.Lock()
e.available = false
e.initialCheckDone = true
e.lastProbe = 0 // 清除延迟,前端显示"未测试"
e.lastError = err.Error()
e.lastFail = time.Now()
e.mu.Unlock()
return 0, err
}
// 探测成功:recordSuccessWithLatency 已在 probe 函数内更新 available=true
e.recordProbeLatency(latency)
return latency, nil
}
// Release clears blacklist state for the given node.
func (m *Manager) Release(tag string) error {
e, err := m.entry(tag)
if err != nil {
return err
}
if e.release == nil {
return errors.New("release not available for this node")
}
e.release()
return nil
}
func (m *Manager) entry(tag string) (*entry, error) {
m.mu.RLock()
e, ok := m.nodes[tag]
m.mu.RUnlock()
if !ok {
return nil, fmt.Errorf("node %s not found", tag)
}
return e, nil
}
func (e *entry) snapshot() Snapshot {
e.mu.RLock()
defer e.mu.RUnlock()
latencyMs := int64(-1)
if e.lastProbe > 0 {
latencyMs = e.lastProbe.Milliseconds()
if latencyMs == 0 {
latencyMs = 1
}
}
var timelineCopy []TimelineEvent
if len(e.timeline) > 0 {
timelineCopy = make([]TimelineEvent, len(e.timeline))
copy(timelineCopy, e.timeline)
}
return Snapshot{
NodeInfo: e.info,
FailureCount: e.failure,
SuccessCount: e.success,
Blacklisted: e.blacklist,
BlacklistedUntil: e.until,
ActiveConnections: e.active.Load(),
LastError: e.lastError,
LastFailure: e.lastFail,
LastSuccess: e.lastOK,
LastProbeLatency: e.lastProbe,
LastLatencyMs: latencyMs,
Available: e.available,
InitialCheckDone: e.initialCheckDone,
TotalUpload: e.totalUpload.Load(),
TotalDownload: e.totalDownload.Load(),
UploadSpeed: e.uploadSpeed,
DownloadSpeed: e.downloadSpeed,
Timeline: timelineCopy,
}
}
func (e *entry) recordFailure(err error, destination string) {
e.mu.Lock()
defer e.mu.Unlock()
errStr := err.Error()
e.failure++
e.lastError = errStr
e.lastFail = time.Now()
// 注意:不修改 available/initialCheckDone
// 流量失败不代表节点不可用(可能是目标网站的问题)
// available 只由探测操作控制
e.appendTimelineLocked(false, 0, errStr, destination)
}
func (e *entry) recordSuccess(destination string) {
e.mu.Lock()
defer e.mu.Unlock()
e.success++
e.lastOK = time.Now()
// 注意:不修改 available/initialCheckDone
// 流量成功不代表需要更新探测状态
// available 只由探测操作控制
e.appendTimelineLocked(true, 0, "", destination)
}
func (e *entry) recordSuccessWithLatency(latency time.Duration) {
e.mu.Lock()
defer e.mu.Unlock()
e.success++
e.lastOK = time.Now()
e.lastProbe = latency
e.available = true
e.initialCheckDone = true
latencyMs := latency.Milliseconds()
if latencyMs == 0 && latency > 0 {
latencyMs = 1
}
e.appendTimelineLocked(true, latencyMs, "", "")
}
func (e *entry) appendTimelineLocked(success bool, latencyMs int64, errStr string, destination string) {
evt := TimelineEvent{
Time: time.Now(),
Success: success,
LatencyMs: latencyMs,
Error: errStr,
Destination: destination,
}
if len(e.timeline) >= maxTimelineSize {
copy(e.timeline, e.timeline[1:])
e.timeline[len(e.timeline)-1] = evt
} else {
e.timeline = append(e.timeline, evt)
}
}
func (e *entry) blacklistUntil(until time.Time) {
e.mu.Lock()
e.blacklist = true
e.until = until
e.mu.Unlock()
}
func (e *entry) clearBlacklist() {
e.mu.Lock()
e.blacklist = false
e.until = time.Time{}
e.mu.Unlock()
}
func (e *entry) incActive() {
e.active.Add(1)
}
func (e *entry) decActive() {
e.active.Add(-1)
}
func (e *entry) setProbe(fn probeFunc) {
e.mu.Lock()
defer e.mu.Unlock()
e.probe = fn
}
func (e *entry) setRelease(fn releaseFunc) {
e.mu.Lock()
defer e.mu.Unlock()
e.release = fn
}
func (e *entry) recordProbeLatency(d time.Duration) {
e.mu.Lock()
e.lastProbe = d
e.mu.Unlock()
}
func (e *entry) updateTrafficSpeed(now time.Time) {
curUp := e.totalUpload.Load()
curDown := e.totalDownload.Load()
e.mu.Lock()
defer e.mu.Unlock()
if e.lastSpeedAt.IsZero() {
e.lastSpeedAt = now
e.lastSpeedUpload = curUp
e.lastSpeedDown = curDown
e.uploadSpeed = 0
e.downloadSpeed = 0
return
}
elapsed := now.Sub(e.lastSpeedAt).Seconds()
if elapsed <= 0 {
return
}
deltaUp := curUp - e.lastSpeedUpload
deltaDown := curDown - e.lastSpeedDown
if deltaUp < 0 {
deltaUp = 0
}
if deltaDown < 0 {
deltaDown = 0
}
e.uploadSpeed = int64(float64(deltaUp) / elapsed)
e.downloadSpeed = int64(float64(deltaDown) / elapsed)
e.lastSpeedUpload = curUp
e.lastSpeedDown = curDown
e.lastSpeedAt = now
}
// RecordFailure updates failure counters with destination info.
func (h *EntryHandle) RecordFailure(err error, destination string) {
if h == nil || h.ref == nil {
return
}
h.ref.recordFailure(err, destination)
}
// RecordSuccess updates the last success timestamp with destination info.
func (h *EntryHandle) RecordSuccess(destination string) {
if h == nil || h.ref == nil {
return
}
h.ref.recordSuccess(destination)
}
// RecordSuccessWithLatency updates the last success timestamp and latency.
func (h *EntryHandle) RecordSuccessWithLatency(latency time.Duration) {
if h == nil || h.ref == nil {
return
}
h.ref.recordSuccessWithLatency(latency)
}
// Blacklist marks the node unavailable until the given deadline.
func (h *EntryHandle) Blacklist(until time.Time) {
if h == nil || h.ref == nil {
return
}
h.ref.blacklistUntil(until)
}
// ClearBlacklist removes the blacklist flag.
func (h *EntryHandle) ClearBlacklist() {
if h == nil || h.ref == nil {
return
}
h.ref.clearBlacklist()
}
// IncActive increments the active connection counter.
func (h *EntryHandle) IncActive() {
if h == nil || h.ref == nil {
return
}
h.ref.incActive()
}
// DecActive decrements the active connection counter.
func (h *EntryHandle) DecActive() {
if h == nil || h.ref == nil {
return
}
h.ref.decActive()
}
// SetProbe assigns a probe function.
func (h *EntryHandle) SetProbe(fn func(ctx context.Context) (time.Duration, error)) {
if h == nil || h.ref == nil {
return
}
h.ref.setProbe(fn)
}
// SetRelease assigns a release function.
func (h *EntryHandle) SetRelease(fn func()) {
if h == nil || h.ref == nil {
return
}
h.ref.setRelease(fn)
}
// MarkInitialCheckDone marks the initial health check as completed.
func (h *EntryHandle) MarkInitialCheckDone(available bool) {
if h == nil || h.ref == nil {
return
}
h.ref.mu.Lock()
h.ref.initialCheckDone = true
h.ref.available = available
h.ref.mu.Unlock()
}
// MarkAvailable updates the availability status.
func (h *EntryHandle) MarkAvailable(available bool) {
if h == nil || h.ref == nil {
return
}
h.ref.mu.Lock()
h.ref.available = available
h.ref.mu.Unlock()
}
// AddTraffic adds upload and download byte counts to the node's traffic counters.
func (h *EntryHandle) AddTraffic(upload, download int64) {
if h == nil || h.ref == nil {
return
}
if upload > 0 {
h.ref.totalUpload.Add(upload)
}
if download > 0 {
h.ref.totalDownload.Add(download)
}
}
// SetTraffic sets the traffic counters to specific values (used for restoring from store).
func (h *EntryHandle) SetTraffic(upload, download int64) {
if h == nil || h.ref == nil {
return
}
h.ref.totalUpload.Store(upload)
h.ref.totalDownload.Store(download)
}
File diff suppressed because it is too large Load Diff