Files
godoxy-yusing/internal/types/health.go

189 lines
4.3 KiB
Go

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,
})
}