k9s/internal/model/pulse_health.go

158 lines
2.9 KiB
Go

package model
import (
"context"
"log/slog"
"time"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
const pulseRate = 10 * time.Second
type HealthPoint struct {
GVR *client.GVR
Total, Faults int
}
type GVRs []*client.GVR
var PulseGVRs = client.GVRs{
client.NodeGVR,
client.NsGVR,
client.SvcGVR,
client.EvGVR,
client.PodGVR,
client.DpGVR,
client.StsGVR,
client.DsGVR,
client.JobGVR,
client.CjGVR,
client.PvGVR,
client.PvcGVR,
client.HpaGVR,
client.IngGVR,
client.NpGVR,
client.SaGVR,
}
func (g GVRs) First() *client.GVR {
return g[0]
}
func (g GVRs) Last() *client.GVR {
return g[len(g)-1]
}
func (g GVRs) Index(gvr *client.GVR) int {
for i := range g {
if g[i] == gvr {
return i
}
}
return -1
}
// PulseHealth tracks resources health.
type PulseHealth struct {
factory dao.Factory
}
// NewPulseHealth returns a new instance.
func NewPulseHealth(f dao.Factory) *PulseHealth {
return &PulseHealth{factory: f}
}
func (h *PulseHealth) Watch(ctx context.Context, ns string) HealthChan {
c := make(HealthChan, 2)
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
go func(ctx context.Context, ns string, c HealthChan) {
if err := h.checkPulse(ctx, ns, c); err != nil {
slog.Error("Pulse check failed", slogs.Error, err)
}
for {
select {
case <-ctx.Done():
close(c)
return
case <-time.After(pulseRate):
if err := h.checkPulse(ctx, ns, c); err != nil {
slog.Error("Pulse check failed", slogs.Error, err)
}
}
}
}(ctx, ns, c)
return c
}
func (h *PulseHealth) checkPulse(ctx context.Context, ns string, c HealthChan) error {
slog.Debug("Checking pulses...")
for _, gvr := range PulseGVRs {
check, err := h.check(ctx, ns, gvr)
if err != nil {
return err
}
c <- check
}
return nil
}
func (h *PulseHealth) check(ctx context.Context, ns string, gvr *client.GVR) (HealthPoint, error) {
meta, ok := Registry[gvr]
if !ok {
meta = ResourceMeta{
DAO: new(dao.Table),
Renderer: new(render.Table),
}
}
if meta.DAO == nil {
meta.DAO = &dao.Resource{}
}
meta.DAO.Init(h.factory, gvr)
oo, err := meta.DAO.List(ctx, ns)
if err != nil {
return HealthPoint{}, err
}
c := HealthPoint{GVR: gvr, Total: len(oo)}
if isTable(oo) {
ta := oo[0].(*metav1.Table)
c.Total = len(ta.Rows)
for _, row := range ta.Rows {
if err := meta.Renderer.Healthy(ctx, row); err != nil {
c.Faults++
}
}
} else {
for _, o := range oo {
if err := meta.Renderer.Healthy(ctx, o); err != nil {
c.Faults++
}
}
}
slog.Debug("Checked", slogs.GVR, gvr, slogs.Config, c)
return c, nil
}
func isTable(oo []runtime.Object) bool {
if len(oo) == 0 || len(oo) > 1 {
return false
}
_, ok := oo[0].(*metav1.Table)
return ok
}