mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-24 01:08:31 +02:00
Merge branch 'main' into dev
This commit is contained in:
@@ -2956,43 +2956,6 @@
|
|||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
"HealthInfo": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"detail": {
|
|
||||||
"type": "string",
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"latency": {
|
|
||||||
"description": "latency in microseconds",
|
|
||||||
"type": "number",
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"healthy",
|
|
||||||
"unhealthy",
|
|
||||||
"napping",
|
|
||||||
"starting",
|
|
||||||
"error",
|
|
||||||
"unknown"
|
|
||||||
],
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"uptime": {
|
|
||||||
"description": "uptime in milliseconds",
|
|
||||||
"type": "number",
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"HealthInfoWithoutDetail": {
|
"HealthInfoWithoutDetail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -3047,22 +3010,14 @@
|
|||||||
"x-nullable": true
|
"x-nullable": true
|
||||||
},
|
},
|
||||||
"lastSeen": {
|
"lastSeen": {
|
||||||
|
"description": "unix timestamp in seconds",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
"lastSeenStr": {
|
|
||||||
"type": "string",
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"latency": {
|
"latency": {
|
||||||
"type": "number",
|
"description": "latency in milliseconds",
|
||||||
"x-nullable": false,
|
"type": "integer",
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"latencyStr": {
|
|
||||||
"type": "string",
|
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
@@ -3072,30 +3027,22 @@
|
|||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
"started": {
|
"started": {
|
||||||
|
"description": "unix timestamp in seconds",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
"startedStr": {
|
|
||||||
"type": "string",
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/HealthStatusString",
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
"uptime": {
|
"uptime": {
|
||||||
|
"description": "uptime in seconds",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
"uptimeStr": {
|
|
||||||
"type": "string",
|
|
||||||
"x-nullable": false,
|
|
||||||
"x-omitempty": false
|
|
||||||
},
|
|
||||||
"url": {
|
"url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
@@ -3108,11 +3055,32 @@
|
|||||||
"HealthMap": {
|
"HealthMap": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"$ref": "#/definitions/HealthInfo"
|
"$ref": "#/definitions/HealthStatusString"
|
||||||
},
|
},
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
|
"HealthStatusString": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"unknown",
|
||||||
|
"healthy",
|
||||||
|
"napping",
|
||||||
|
"starting",
|
||||||
|
"unhealthy",
|
||||||
|
"error"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"StatusUnknownStr",
|
||||||
|
"StatusHealthyStr",
|
||||||
|
"StatusNappingStr",
|
||||||
|
"StatusStartingStr",
|
||||||
|
"StatusUnhealthyStr",
|
||||||
|
"StatusErrorStr"
|
||||||
|
],
|
||||||
|
"x-nullable": false,
|
||||||
|
"x-omitempty": false
|
||||||
|
},
|
||||||
"HomepageCategory": {
|
"HomepageCategory": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -302,26 +302,6 @@ definitions:
|
|||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
HealthInfo:
|
|
||||||
properties:
|
|
||||||
detail:
|
|
||||||
type: string
|
|
||||||
latency:
|
|
||||||
description: latency in microseconds
|
|
||||||
type: number
|
|
||||||
status:
|
|
||||||
enum:
|
|
||||||
- healthy
|
|
||||||
- unhealthy
|
|
||||||
- napping
|
|
||||||
- starting
|
|
||||||
- error
|
|
||||||
- unknown
|
|
||||||
type: string
|
|
||||||
uptime:
|
|
||||||
description: uptime in milliseconds
|
|
||||||
type: number
|
|
||||||
type: object
|
|
||||||
HealthInfoWithoutDetail:
|
HealthInfoWithoutDetail:
|
||||||
properties:
|
properties:
|
||||||
latency:
|
latency:
|
||||||
@@ -351,32 +331,44 @@ definitions:
|
|||||||
- $ref: '#/definitions/HealthExtra'
|
- $ref: '#/definitions/HealthExtra'
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
lastSeen:
|
lastSeen:
|
||||||
|
description: unix timestamp in seconds
|
||||||
type: integer
|
type: integer
|
||||||
lastSeenStr:
|
|
||||||
type: string
|
|
||||||
latency:
|
latency:
|
||||||
type: number
|
description: latency in milliseconds
|
||||||
latencyStr:
|
type: integer
|
||||||
type: string
|
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
started:
|
started:
|
||||||
|
description: unix timestamp in seconds
|
||||||
type: integer
|
type: integer
|
||||||
startedStr:
|
|
||||||
type: string
|
|
||||||
status:
|
status:
|
||||||
type: string
|
$ref: '#/definitions/HealthStatusString'
|
||||||
uptime:
|
uptime:
|
||||||
|
description: uptime in seconds
|
||||||
type: number
|
type: number
|
||||||
uptimeStr:
|
|
||||||
type: string
|
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
HealthMap:
|
HealthMap:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
$ref: '#/definitions/HealthInfo'
|
$ref: '#/definitions/HealthStatusString'
|
||||||
type: object
|
type: object
|
||||||
|
HealthStatusString:
|
||||||
|
enum:
|
||||||
|
- unknown
|
||||||
|
- healthy
|
||||||
|
- napping
|
||||||
|
- starting
|
||||||
|
- unhealthy
|
||||||
|
- error
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- StatusUnknownStr
|
||||||
|
- StatusHealthyStr
|
||||||
|
- StatusNappingStr
|
||||||
|
- StatusStartingStr
|
||||||
|
- StatusUnhealthyStr
|
||||||
|
- StatusErrorStr
|
||||||
HomepageCategory:
|
HomepageCategory:
|
||||||
properties:
|
properties:
|
||||||
items:
|
items:
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import (
|
|||||||
_ "github.com/yusing/goutils/apitypes"
|
_ "github.com/yusing/goutils/apitypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HealthMap = map[string]routes.HealthInfo // @name HealthMap
|
|
||||||
|
|
||||||
// @x-id "health"
|
// @x-id "health"
|
||||||
// @BasePath /api/v1
|
// @BasePath /api/v1
|
||||||
// @Summary Get routes health info
|
// @Summary Get routes health info
|
||||||
@@ -21,16 +19,16 @@ type HealthMap = map[string]routes.HealthInfo // @name HealthMap
|
|||||||
// @Tags v1,websocket
|
// @Tags v1,websocket
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} HealthMap "Health info by route name"
|
// @Success 200 {object} routes.HealthMap "Health info by route name"
|
||||||
// @Failure 403 {object} apitypes.ErrorResponse
|
// @Failure 403 {object} apitypes.ErrorResponse
|
||||||
// @Failure 500 {object} apitypes.ErrorResponse
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
// @Router /health [get]
|
// @Router /health [get]
|
||||||
func Health(c *gin.Context) {
|
func Health(c *gin.Context) {
|
||||||
if httpheaders.IsWebsocket(c.Request.Header) {
|
if httpheaders.IsWebsocket(c.Request.Header) {
|
||||||
websocket.PeriodicWrite(c, 1*time.Second, func() (any, error) {
|
websocket.PeriodicWrite(c, 1*time.Second, func() (any, error) {
|
||||||
return routes.GetHealthInfo(), nil
|
return routes.GetHealthInfoSimple(), nil
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, routes.GetHealthInfo())
|
c.JSON(http.StatusOK, routes.GetHealthInfoSimple())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ func (state *state) initProxmox() error {
|
|||||||
|
|
||||||
errs := gperr.NewBuilder()
|
errs := gperr.NewBuilder()
|
||||||
for _, cfg := range proxmoxCfg {
|
for _, cfg := range proxmoxCfg {
|
||||||
if err := cfg.Init(); err != nil {
|
if err := cfg.Init(state.task.Context()); err != nil {
|
||||||
errs.Add(err.Subject(cfg.URL))
|
errs.Add(err.Subject(cfg.URL))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,14 +91,14 @@ func IsBlacklisted(c *types.Container) bool {
|
|||||||
return IsBlacklistedImage(c.Image) || isDatabase(c)
|
return IsBlacklistedImage(c.Image) || isDatabase(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePorts(c *types.Container) error {
|
func UpdatePorts(ctx context.Context, c *types.Container) error {
|
||||||
dockerClient, err := NewClient(c.DockerCfg)
|
dockerClient, err := NewClient(c.DockerCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer dockerClient.Close()
|
defer dockerClient.Close()
|
||||||
|
|
||||||
inspect, err := dockerClient.ContainerInspect(context.Background(), c.ContainerID, client.ContainerInspectOptions{})
|
inspect, err := dockerClient.ContainerInspect(ctx, c.ContainerID, client.ContainerInspectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ const proxmoxStateCheckInterval = 1 * time.Second
|
|||||||
|
|
||||||
var ErrNodeNotFound = gperr.New("node not found in pool")
|
var ErrNodeNotFound = gperr.New("node not found in pool")
|
||||||
|
|
||||||
func NewProxmoxProvider(nodeName string, vmid int) (idlewatcher.Provider, error) {
|
func NewProxmoxProvider(ctx context.Context, nodeName string, vmid int) (idlewatcher.Provider, error) {
|
||||||
node, ok := proxmox.Nodes.Get(nodeName)
|
node, ok := proxmox.Nodes.Get(nodeName)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound.Subject(nodeName).
|
return nil, ErrNodeNotFound.Subject(nodeName).
|
||||||
Withf("available nodes: %s", proxmox.AvailableNodeNames())
|
Withf("available nodes: %s", proxmox.AvailableNodeNames())
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
lxcName, err := node.LXCName(ctx, vmid)
|
lxcName, err := node.LXCName(ctx, vmid)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (c *Config) Client() *Client {
|
|||||||
return c.client
|
return c.client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Init() gperr.Error {
|
func (c *Config) Init(ctx context.Context) gperr.Error {
|
||||||
var tr *http.Transport
|
var tr *http.Transport
|
||||||
if c.NoTLSVerify {
|
if c.NoTLSVerify {
|
||||||
// user specified
|
// user specified
|
||||||
@@ -56,7 +56,7 @@ func (c *Config) Init() gperr.Error {
|
|||||||
}
|
}
|
||||||
c.client = NewClient(c.URL, opts...)
|
c.client = NewClient(c.URL, opts...)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := c.client.UpdateClusterInfo(ctx); err != nil {
|
if err := c.client.UpdateClusterInfo(ctx); err != nil {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if container.IsHostNetworkMode {
|
if container.IsHostNetworkMode {
|
||||||
err := docker.UpdatePorts(container)
|
err := docker.UpdatePorts(ctx, container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Add(gperr.PrependSubject(container.ContainerName, err))
|
errs.Add(gperr.PrependSubject(container.ContainerName, err))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type HealthInfoWithoutDetail struct {
|
|||||||
Latency time.Duration `json:"latency" swaggertype:"number"` // latency in microseconds
|
Latency time.Duration `json:"latency" swaggertype:"number"` // latency in microseconds
|
||||||
} // @name HealthInfoWithoutDetail
|
} // @name HealthInfoWithoutDetail
|
||||||
|
|
||||||
|
type HealthMap = map[string]types.HealthStatusString // @name HealthMap
|
||||||
|
|
||||||
// GetHealthInfo returns a map of route name to health info.
|
// GetHealthInfo returns a map of route name to health info.
|
||||||
//
|
//
|
||||||
// The health info is for all routes, including excluded routes.
|
// The health info is for all routes, including excluded routes.
|
||||||
@@ -39,6 +41,14 @@ func GetHealthInfoWithoutDetail() map[string]HealthInfoWithoutDetail {
|
|||||||
return healthMap
|
return healthMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetHealthInfoSimple() map[string]types.HealthStatus {
|
||||||
|
healthMap := make(map[string]types.HealthStatus, NumAllRoutes())
|
||||||
|
for r := range IterAll {
|
||||||
|
healthMap[r.Name()] = getHealthInfoSimple(r)
|
||||||
|
}
|
||||||
|
return healthMap
|
||||||
|
}
|
||||||
|
|
||||||
func getHealthInfo(r types.Route) HealthInfo {
|
func getHealthInfo(r types.Route) HealthInfo {
|
||||||
mon := r.HealthMonitor()
|
mon := r.HealthMonitor()
|
||||||
if mon == nil {
|
if mon == nil {
|
||||||
@@ -73,6 +83,14 @@ func getHealthInfoWithoutDetail(r types.Route) HealthInfoWithoutDetail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHealthInfoSimple(r types.Route) types.HealthStatus {
|
||||||
|
mon := r.HealthMonitor()
|
||||||
|
if mon == nil {
|
||||||
|
return types.StatusUnknown
|
||||||
|
}
|
||||||
|
return mon.Status()
|
||||||
|
}
|
||||||
|
|
||||||
// ByProvider returns a map of provider name to routes.
|
// ByProvider returns a map of provider name to routes.
|
||||||
//
|
//
|
||||||
// The routes are all routes, including excluded routes.
|
// The routes are all routes, including excluded routes.
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
strutils "github.com/yusing/goutils/strings"
|
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
HealthStatus uint8
|
HealthStatus uint8 // @name HealthStatus
|
||||||
|
HealthStatusString string // @name HealthStatusString
|
||||||
|
|
||||||
HealthCheckResult struct {
|
HealthCheckResult struct {
|
||||||
Healthy bool `json:"healthy"`
|
Healthy bool `json:"healthy"`
|
||||||
@@ -45,20 +45,16 @@ type (
|
|||||||
HealthChecker
|
HealthChecker
|
||||||
}
|
}
|
||||||
HealthJSON struct {
|
HealthJSON struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Config *HealthCheckConfig `json:"config"`
|
Config *HealthCheckConfig `json:"config"`
|
||||||
Started int64 `json:"started"`
|
Started int64 `json:"started"` // unix timestamp in seconds
|
||||||
StartedStr string `json:"startedStr"`
|
Status HealthStatusString `json:"status"`
|
||||||
Status string `json:"status"`
|
Uptime float64 `json:"uptime"` // uptime in seconds
|
||||||
Uptime float64 `json:"uptime"`
|
Latency int64 `json:"latency"` // latency in milliseconds
|
||||||
UptimeStr string `json:"uptimeStr"`
|
LastSeen int64 `json:"lastSeen"` // unix timestamp in seconds
|
||||||
Latency float64 `json:"latency"`
|
Detail string `json:"detail"`
|
||||||
LatencyStr string `json:"latencyStr"`
|
URL string `json:"url"`
|
||||||
LastSeen int64 `json:"lastSeen"`
|
Extra *HealthExtra `json:"extra,omitempty" extensions:"x-nullable"`
|
||||||
LastSeenStr string `json:"lastSeenStr"`
|
|
||||||
Detail string `json:"detail"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Extra *HealthExtra `json:"extra,omitempty" extensions:"x-nullable"`
|
|
||||||
} // @name HealthJSON
|
} // @name HealthJSON
|
||||||
|
|
||||||
HealthJSONRepr struct {
|
HealthJSONRepr struct {
|
||||||
@@ -88,12 +84,12 @@ const (
|
|||||||
StatusUnhealthy
|
StatusUnhealthy
|
||||||
StatusError
|
StatusError
|
||||||
|
|
||||||
StatusUnknownStr = "unknown"
|
StatusUnknownStr HealthStatusString = "unknown"
|
||||||
StatusHealthyStr = "healthy"
|
StatusHealthyStr HealthStatusString = "healthy"
|
||||||
StatusNappingStr = "napping"
|
StatusNappingStr HealthStatusString = "napping"
|
||||||
StatusStartingStr = "starting"
|
StatusStartingStr HealthStatusString = "starting"
|
||||||
StatusUnhealthyStr = "unhealthy"
|
StatusUnhealthyStr HealthStatusString = "unhealthy"
|
||||||
StatusErrorStr = "error"
|
StatusErrorStr HealthStatusString = "error"
|
||||||
|
|
||||||
NumStatuses int = iota - 1
|
NumStatuses int = iota - 1
|
||||||
|
|
||||||
@@ -102,15 +98,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
StatusHealthyStr2 = strconv.Itoa(int(StatusHealthy))
|
StatusHealthyStr2 HealthStatusString = HealthStatusString(strconv.Itoa(int(StatusHealthy)))
|
||||||
StatusNappingStr2 = strconv.Itoa(int(StatusNapping))
|
StatusNappingStr2 HealthStatusString = HealthStatusString(strconv.Itoa(int(StatusNapping)))
|
||||||
StatusStartingStr2 = strconv.Itoa(int(StatusStarting))
|
StatusStartingStr2 HealthStatusString = HealthStatusString(strconv.Itoa(int(StatusStarting)))
|
||||||
StatusUnhealthyStr2 = strconv.Itoa(int(StatusUnhealthy))
|
StatusUnhealthyStr2 HealthStatusString = HealthStatusString(strconv.Itoa(int(StatusUnhealthy)))
|
||||||
StatusErrorStr2 = strconv.Itoa(int(StatusError))
|
StatusErrorStr2 HealthStatusString = HealthStatusString(strconv.Itoa(int(StatusError)))
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHealthStatusFromString(s string) HealthStatus {
|
func NewHealthStatusFromString(s string) HealthStatus {
|
||||||
switch s {
|
switch HealthStatusString(s) {
|
||||||
case StatusHealthyStr, StatusHealthyStr2:
|
case StatusHealthyStr, StatusHealthyStr2:
|
||||||
return StatusHealthy
|
return StatusHealthy
|
||||||
case StatusUnhealthyStr, StatusUnhealthyStr2:
|
case StatusUnhealthyStr, StatusUnhealthyStr2:
|
||||||
@@ -126,7 +122,7 @@ func NewHealthStatusFromString(s string) HealthStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s HealthStatus) String() string {
|
func (s HealthStatus) StatusString() HealthStatusString {
|
||||||
switch s {
|
switch s {
|
||||||
case StatusHealthy:
|
case StatusHealthy:
|
||||||
return StatusHealthyStr
|
return StatusHealthyStr
|
||||||
@@ -143,6 +139,11 @@ func (s HealthStatus) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer.
|
||||||
|
func (s HealthStatus) String() string {
|
||||||
|
return string(s.StatusString())
|
||||||
|
}
|
||||||
|
|
||||||
func (s HealthStatus) Good() bool {
|
func (s HealthStatus) Good() bool {
|
||||||
return s&HealthyMask != 0
|
return s&HealthyMask != 0
|
||||||
}
|
}
|
||||||
@@ -178,19 +179,15 @@ func (jsonRepr *HealthJSONRepr) MarshalJSON() ([]byte, error) {
|
|||||||
url = ""
|
url = ""
|
||||||
}
|
}
|
||||||
return sonic.Marshal(HealthJSON{
|
return sonic.Marshal(HealthJSON{
|
||||||
Name: jsonRepr.Name,
|
Name: jsonRepr.Name,
|
||||||
Config: jsonRepr.Config,
|
Config: jsonRepr.Config,
|
||||||
Started: jsonRepr.Started.Unix(),
|
Started: jsonRepr.Started.Unix(),
|
||||||
StartedStr: strutils.FormatTime(jsonRepr.Started),
|
Status: HealthStatusString(jsonRepr.Status.String()),
|
||||||
Status: jsonRepr.Status.String(),
|
Uptime: jsonRepr.Uptime.Seconds(),
|
||||||
Uptime: jsonRepr.Uptime.Seconds(),
|
Latency: jsonRepr.Latency.Milliseconds(),
|
||||||
UptimeStr: strutils.FormatDuration(jsonRepr.Uptime),
|
LastSeen: jsonRepr.LastSeen.Unix(),
|
||||||
Latency: jsonRepr.Latency.Seconds(),
|
Detail: jsonRepr.Detail,
|
||||||
LatencyStr: strconv.Itoa(int(jsonRepr.Latency.Milliseconds())) + " ms",
|
URL: url,
|
||||||
LastSeen: jsonRepr.LastSeen.Unix(),
|
Extra: jsonRepr.Extra,
|
||||||
LastSeenStr: strutils.FormatLastSeen(jsonRepr.LastSeen),
|
|
||||||
Detail: jsonRepr.Detail,
|
|
||||||
URL: url,
|
|
||||||
Extra: jsonRepr.Extra,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeepEqual reports whether x and y are deeply equal.
|
|
||||||
// It supports numerics, strings, maps, slices, arrays, and structs (exported fields only).
|
|
||||||
// It's optimized for performance by avoiding reflection for common types and
|
|
||||||
// adaptively choosing between BFS and DFS traversal strategies.
|
|
||||||
func DeepEqual(x, y any) bool {
|
|
||||||
if x == nil || y == nil {
|
|
||||||
return x == y
|
|
||||||
}
|
|
||||||
|
|
||||||
v1 := reflect.ValueOf(x)
|
|
||||||
v2 := reflect.ValueOf(y)
|
|
||||||
|
|
||||||
if v1.Type() != v2.Type() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return deepEqual(v1, v2, make(map[visit]bool), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// visit represents a visit to a pair of values during comparison
|
|
||||||
type visit struct {
|
|
||||||
a1, a2 unsafe.Pointer
|
|
||||||
typ reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqual performs the actual deep comparison with cycle detection
|
|
||||||
func deepEqual(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
if !v1.IsValid() || !v2.IsValid() {
|
|
||||||
return v1.IsValid() == v2.IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
if v1.Type() != v2.Type() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle cycle detection for pointer-like types
|
|
||||||
if v1.CanAddr() && v2.CanAddr() {
|
|
||||||
addr1 := unsafe.Pointer(v1.UnsafeAddr())
|
|
||||||
addr2 := unsafe.Pointer(v2.UnsafeAddr())
|
|
||||||
typ := v1.Type()
|
|
||||||
v := visit{addr1, addr2, typ}
|
|
||||||
if visited[v] {
|
|
||||||
return true // already visiting, assume equal
|
|
||||||
}
|
|
||||||
visited[v] = true
|
|
||||||
defer delete(visited, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v1.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return v1.Bool() == v2.Bool()
|
|
||||||
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return v1.Int() == v2.Int()
|
|
||||||
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return v1.Uint() == v2.Uint()
|
|
||||||
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return floatEqual(v1.Float(), v2.Float())
|
|
||||||
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
c1, c2 := v1.Complex(), v2.Complex()
|
|
||||||
return floatEqual(real(c1), real(c2)) && floatEqual(imag(c1), imag(c2))
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
return v1.String() == v2.String()
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
return deepEqualArray(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
return deepEqualSlice(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
return deepEqualMap(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
return deepEqualStruct(v1, v2, visited, depth)
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
if v1.IsNil() || v2.IsNil() {
|
|
||||||
return v1.IsNil() && v2.IsNil()
|
|
||||||
}
|
|
||||||
return deepEqual(v1.Elem(), v2.Elem(), visited, depth+1)
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
if v1.IsNil() || v2.IsNil() {
|
|
||||||
return v1.IsNil() && v2.IsNil()
|
|
||||||
}
|
|
||||||
return deepEqual(v1.Elem(), v2.Elem(), visited, depth+1)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// For unsupported types (func, chan, etc.), fall back to basic equality
|
|
||||||
return v1.Interface() == v2.Interface()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// floatEqual handles NaN cases properly
|
|
||||||
func floatEqual(f1, f2 float64) bool {
|
|
||||||
return f1 == f2 || (f1 != f1 && f2 != f2) // NaN == NaN
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualArray compares arrays using DFS (since arrays have fixed size)
|
|
||||||
func deepEqualArray(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
for i := range v1.Len() {
|
|
||||||
if !deepEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualSlice compares slices, choosing strategy based on size and depth
|
|
||||||
func deepEqualSlice(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
if v1.IsNil() != v2.IsNil() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.Len() != v2.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use BFS for large slices at shallow depth to improve cache locality
|
|
||||||
// Use DFS for small slices or deep nesting to reduce memory overhead
|
|
||||||
if shouldUseBFS(v1.Len(), depth) {
|
|
||||||
return deepEqualSliceBFS(v1, v2, visited, depth)
|
|
||||||
}
|
|
||||||
return deepEqualSliceDFS(v1, v2, visited, depth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualSliceDFS uses depth-first traversal
|
|
||||||
func deepEqualSliceDFS(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
for i := range v1.Len() {
|
|
||||||
if !deepEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualSliceBFS uses breadth-first traversal for better cache locality
|
|
||||||
func deepEqualSliceBFS(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
length := v1.Len()
|
|
||||||
|
|
||||||
// First, check all direct elements
|
|
||||||
for i := range length {
|
|
||||||
elem1, elem2 := v1.Index(i), v2.Index(i)
|
|
||||||
|
|
||||||
// For simple types, compare directly
|
|
||||||
if isSimpleType(elem1.Kind()) {
|
|
||||||
if !deepEqual(elem1, elem2, visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, recursively check complex elements
|
|
||||||
for i := range length {
|
|
||||||
elem1, elem2 := v1.Index(i), v2.Index(i)
|
|
||||||
|
|
||||||
if !isSimpleType(elem1.Kind()) {
|
|
||||||
if !deepEqual(elem1, elem2, visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualMap compares maps
|
|
||||||
func deepEqualMap(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
if v1.IsNil() != v2.IsNil() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.Len() != v2.Len() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v1.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all keys and values
|
|
||||||
for _, key := range v1.MapKeys() {
|
|
||||||
val1 := v1.MapIndex(key)
|
|
||||||
val2 := v2.MapIndex(key)
|
|
||||||
|
|
||||||
if !val2.IsValid() {
|
|
||||||
return false // key doesn't exist in v2
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deepEqual(val1, val2, visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deepEqualStruct compares structs (exported fields only)
|
|
||||||
func deepEqualStruct(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
|
||||||
typ := v1.Type()
|
|
||||||
|
|
||||||
for i := range typ.NumField() {
|
|
||||||
field := typ.Field(i)
|
|
||||||
|
|
||||||
// Skip unexported fields
|
|
||||||
if !field.IsExported() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deepEqual(v1.Field(i), v2.Field(i), visited, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldUseBFS determines whether to use BFS or DFS based on slice size and depth
|
|
||||||
func shouldUseBFS(length, depth int) bool {
|
|
||||||
// Use BFS for large slices at shallow depth (better cache locality)
|
|
||||||
// Use DFS for small slices or deep nesting (lower memory overhead)
|
|
||||||
return length > 100 && depth < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSimpleType checks if a type can be compared without deep recursion
|
|
||||||
func isSimpleType(kind reflect.Kind) bool {
|
|
||||||
if kind >= reflect.Bool && kind <= reflect.Complex128 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return kind == reflect.String
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user