Fix high prio recommendations, add error / health screen
This commit is contained in:
157
health.go
Normal file
157
health.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type requestCoordinator struct {
|
||||
mu sync.Mutex
|
||||
id uint64
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (r *requestCoordinator) start(timeout time.Duration) (context.Context, context.CancelFunc, uint64) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.cancel != nil {
|
||||
r.cancel()
|
||||
}
|
||||
r.id++
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
r.cancel = cancel
|
||||
return ctx, cancel, r.id
|
||||
}
|
||||
|
||||
func (r *requestCoordinator) current(id uint64) bool {
|
||||
if id == 0 {
|
||||
return true
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.id == id
|
||||
}
|
||||
|
||||
type HealthLevel string
|
||||
|
||||
const (
|
||||
healthOK HealthLevel = "ok"
|
||||
healthInfo HealthLevel = "info"
|
||||
healthWarning HealthLevel = "warning"
|
||||
healthError HealthLevel = "error"
|
||||
healthUnknown HealthLevel = "unknown"
|
||||
)
|
||||
|
||||
type HealthComponent struct {
|
||||
Name string
|
||||
Level HealthLevel
|
||||
Summary string
|
||||
Detail string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type HealthEvent struct {
|
||||
Component string
|
||||
Level HealthLevel
|
||||
Message string
|
||||
At time.Time
|
||||
}
|
||||
|
||||
type RateLimitSnapshot struct {
|
||||
Limit int
|
||||
Remaining int
|
||||
Used int
|
||||
ResetAt time.Time
|
||||
RetryAfter time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type healthProvider interface {
|
||||
HealthReport() []HealthComponent
|
||||
RateLimit() RateLimitSnapshot
|
||||
}
|
||||
|
||||
type healthTracker struct {
|
||||
mu sync.Mutex
|
||||
components map[string]HealthComponent
|
||||
rate RateLimitSnapshot
|
||||
}
|
||||
|
||||
func (h *healthTracker) set(component HealthComponent) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.components == nil {
|
||||
h.components = make(map[string]HealthComponent)
|
||||
}
|
||||
if component.UpdatedAt.IsZero() {
|
||||
component.UpdatedAt = time.Now()
|
||||
}
|
||||
h.components[component.Name] = component
|
||||
}
|
||||
|
||||
func (h *healthTracker) setRate(rate RateLimitSnapshot) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.rate = rate
|
||||
}
|
||||
|
||||
func (h *healthTracker) report() []HealthComponent {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
components := make([]HealthComponent, 0, len(h.components))
|
||||
for _, component := range h.components {
|
||||
components = append(components, component)
|
||||
}
|
||||
sort.Slice(components, func(i, j int) bool {
|
||||
return strings.ToLower(components[i].Name) < strings.ToLower(components[j].Name)
|
||||
})
|
||||
return components
|
||||
}
|
||||
|
||||
func (h *healthTracker) rateLimit() RateLimitSnapshot {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
return h.rate
|
||||
}
|
||||
|
||||
func (m *App) recordHealth(component string, level HealthLevel, message string) {
|
||||
if strings.TrimSpace(message) == "" {
|
||||
return
|
||||
}
|
||||
event := HealthEvent{Component: component, Level: level, Message: message, At: time.Now()}
|
||||
const maximumHealthEvents = 100
|
||||
m.healthEvents = append(m.healthEvents, event)
|
||||
if len(m.healthEvents) > maximumHealthEvents {
|
||||
m.healthEvents = append([]HealthEvent(nil), m.healthEvents[len(m.healthEvents)-maximumHealthEvents:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func healthLevelLabel(level HealthLevel) string {
|
||||
switch level {
|
||||
case healthOK:
|
||||
return "OK"
|
||||
case healthInfo:
|
||||
return "INFO"
|
||||
case healthWarning:
|
||||
return "WARN"
|
||||
case healthError:
|
||||
return "ERROR"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
func healthComponentText(component HealthComponent) string {
|
||||
text := component.Summary
|
||||
if component.Detail != "" {
|
||||
text += " — " + component.Detail
|
||||
}
|
||||
if !component.UpdatedAt.IsZero() {
|
||||
text += " (" + component.UpdatedAt.Local().Format("15:04:05") + ")"
|
||||
}
|
||||
return fmt.Sprintf("%-7s %-20s %s", healthLevelLabel(component.Level), component.Name, text)
|
||||
}
|
||||
Reference in New Issue
Block a user