package types import ( "encoding/json" "fmt" "net/url" "strconv" "time" "github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/utils/strutils" ) type ( HealthStatus uint8 HealthCheckResult struct { Healthy bool `json:"healthy"` Detail string `json:"detail"` Latency time.Duration `json:"latency"` } // @name HealthCheckResult WithHealthInfo interface { Status() HealthStatus Uptime() time.Duration Latency() time.Duration Detail() string } HealthMonitor interface { task.TaskStarter task.TaskFinisher fmt.Stringer WithHealthInfo Name() string json.Marshaler } HealthChecker interface { CheckHealth() (result *HealthCheckResult, err error) URL() *url.URL Config() *HealthCheckConfig UpdateURL(url *url.URL) } HealthMonCheck interface { HealthMonitor HealthChecker } HealthJSON struct { Name string `json:"name"` Config *HealthCheckConfig `json:"config"` Started int64 `json:"started"` StartedStr string `json:"startedStr"` Status string `json:"status"` Uptime float64 `json:"uptime"` UptimeStr string `json:"uptimeStr"` Latency float64 `json:"latency"` LatencyStr string `json:"latencyStr"` LastSeen int64 `json:"lastSeen"` LastSeenStr string `json:"lastSeenStr"` Detail string `json:"detail"` URL string `json:"url"` Extra *HealthExtra `json:"extra" extensions:"x-nullable"` } // @name HealthJSON HealthJSONRepr struct { Name string Config *HealthCheckConfig Status HealthStatus Started time.Time Uptime time.Duration Latency time.Duration LastSeen time.Time Detail string URL *url.URL Extra *HealthExtra } HealthExtra struct { Config *LoadBalancerConfig `json:"config"` Pool map[string]any `json:"pool"` } // @name HealthExtra ) const ( StatusUnknown HealthStatus = 0 StatusHealthy HealthStatus = (1 << iota) StatusNapping StatusStarting StatusUnhealthy StatusError NumStatuses int = iota - 1 HealthyMask = StatusHealthy | StatusNapping | StatusStarting IdlingMask = StatusNapping | StatusStarting ) func NewHealthStatusFromString(s string) HealthStatus { switch s { case "healthy": return StatusHealthy case "unhealthy": return StatusUnhealthy case "napping": return StatusNapping case "starting": return StatusStarting case "error": return StatusError default: return StatusUnknown } } func (s HealthStatus) String() string { switch s { case StatusHealthy: return "healthy" case StatusUnhealthy: return "unhealthy" case StatusNapping: return "napping" case StatusStarting: return "starting" case StatusError: return "error" default: return "unknown" } } func (s HealthStatus) Good() bool { return s&HealthyMask != 0 } func (s HealthStatus) Bad() bool { return s&HealthyMask == 0 } func (s HealthStatus) Idling() bool { return s&IdlingMask != 0 } func (s HealthStatus) MarshalJSON() ([]byte, error) { return strconv.AppendQuote(nil, s.String()), nil } func (s *HealthStatus) UnmarshalJSON(data []byte) error { var v any if err := json.Unmarshal(data, &v); err != nil { return err } switch v := v.(type) { case string: *s = NewHealthStatusFromString(v) return nil case int: if v > 0 && v < NumStatuses { *s = HealthStatus(v) return nil } } return fmt.Errorf("invalid health status type %T of value %v", v, v) } func (jsonRepr *HealthJSONRepr) MarshalJSON() ([]byte, error) { var url string if jsonRepr.URL != nil { url = jsonRepr.URL.String() } if url == "http://:0" { url = "" } return json.Marshal(HealthJSON{ Name: jsonRepr.Name, Config: jsonRepr.Config, Started: jsonRepr.Started.Unix(), StartedStr: strutils.FormatTime(jsonRepr.Started), Status: jsonRepr.Status.String(), Uptime: jsonRepr.Uptime.Seconds(), UptimeStr: strutils.FormatDuration(jsonRepr.Uptime), Latency: jsonRepr.Latency.Seconds(), LatencyStr: strconv.Itoa(int(jsonRepr.Latency.Milliseconds())) + " ms", LastSeen: jsonRepr.LastSeen.Unix(), LastSeenStr: strutils.FormatLastSeen(jsonRepr.LastSeen), Detail: jsonRepr.Detail, URL: url, Extra: jsonRepr.Extra, }) }