parent
e213ba645e
commit
5fcfecb38e
|
|
@ -48,29 +48,6 @@ func NewMetricsServer(c Connection) *MetricsServer {
|
|||
}
|
||||
}
|
||||
|
||||
// NodesMetrics retrieves metrics for a given set of nodes.
|
||||
func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
|
||||
if nodes == nil || metrics == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, no := range nodes.Items {
|
||||
mmx[no.Name] = NodeMetrics{
|
||||
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
|
||||
TotalCPU: no.Status.Capacity.Cpu().MilliValue(),
|
||||
TotalMEM: ToMB(no.Status.Capacity.Memory().Value()),
|
||||
}
|
||||
}
|
||||
for _, c := range metrics.Items {
|
||||
if mx, ok := mmx[c.Name]; ok {
|
||||
mx.CurrentCPU = c.Usage.Cpu().MilliValue()
|
||||
mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
|
||||
mmx[c.Name] = mx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ClusterLoad retrieves all cluster nodes metrics.
|
||||
func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsList, mx *ClusterMetrics) error {
|
||||
if nos == nil || nmx == nil {
|
||||
|
|
@ -79,26 +56,32 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
|
|||
nodeMetrics := make(NodesMetrics, len(nos.Items))
|
||||
for _, no := range nos.Items {
|
||||
nodeMetrics[no.Name] = NodeMetrics{
|
||||
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
|
||||
AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AllocatableMEM: no.Status.Allocatable.Memory().Value(),
|
||||
AllocatableEphemeral: no.Status.Allocatable.StorageEphemeral().Value(),
|
||||
}
|
||||
}
|
||||
for _, mx := range nmx.Items {
|
||||
if m, ok := nodeMetrics[mx.Name]; ok {
|
||||
m.CurrentCPU = mx.Usage.Cpu().MilliValue()
|
||||
m.CurrentMEM = ToMB(mx.Usage.Memory().Value())
|
||||
nodeMetrics[mx.Name] = m
|
||||
if node, ok := nodeMetrics[mx.Name]; ok {
|
||||
node.CurrentCPU = mx.Usage.Cpu().MilliValue()
|
||||
node.CurrentMEM = mx.Usage.Memory().Value()
|
||||
node.CurrentEphemeral = mx.Usage.StorageEphemeral().Value()
|
||||
nodeMetrics[mx.Name] = node
|
||||
}
|
||||
}
|
||||
|
||||
var cpu, tcpu, mem, tmem float64
|
||||
var ccpu, cmem, ceph, tcpu, tmem, teph int64
|
||||
for _, mx := range nodeMetrics {
|
||||
cpu += float64(mx.CurrentCPU)
|
||||
tcpu += float64(mx.AvailCPU)
|
||||
mem += mx.CurrentMEM
|
||||
tmem += mx.AvailMEM
|
||||
ccpu += mx.CurrentCPU
|
||||
cmem += mx.CurrentMEM
|
||||
ceph += mx.CurrentEphemeral
|
||||
tcpu += mx.AllocatableCPU
|
||||
tmem += mx.AllocatableMEM
|
||||
teph += mx.AllocatableEphemeral
|
||||
}
|
||||
mx.PercCPU, mx.PercMEM = toPerc(cpu, tcpu), toPerc(mem, tmem)
|
||||
mx.PercCPU, mx.PercMEM, mx.PercEphemeral = ToPercentage(ccpu, tcpu),
|
||||
ToPercentage(cmem, tmem),
|
||||
ToPercentage(ceph, teph)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -118,6 +101,33 @@ func (m *MetricsServer) checkAccess(ns, gvr, msg string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NodesMetrics retrieves metrics for a given set of nodes.
|
||||
func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
|
||||
if nodes == nil || metrics == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, no := range nodes.Items {
|
||||
mmx[no.Name] = NodeMetrics{
|
||||
AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AllocatableMEM: ToMB(no.Status.Allocatable.Memory().Value()),
|
||||
AllocatableEphemeral: ToMB(no.Status.Allocatable.StorageEphemeral().Value()),
|
||||
TotalCPU: no.Status.Capacity.Cpu().MilliValue(),
|
||||
TotalMEM: ToMB(no.Status.Capacity.Memory().Value()),
|
||||
TotalEphemeral: ToMB(no.Status.Capacity.StorageEphemeral().Value()),
|
||||
}
|
||||
}
|
||||
for _, c := range metrics.Items {
|
||||
if mx, ok := mmx[c.Name]; ok {
|
||||
mx.CurrentCPU = c.Usage.Cpu().MilliValue()
|
||||
mx.CurrentMEM = ToMB(c.Usage.Memory().Value())
|
||||
mx.AvailableCPU = mx.AllocatableCPU - mx.CurrentCPU
|
||||
mx.AvailableMEM = mx.AllocatableMEM - mx.CurrentMEM
|
||||
mmx[c.Name] = mx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FetchNodesMetrics return all metrics for nodes.
|
||||
func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) {
|
||||
const msg = "user is not authorized to list node metrics"
|
||||
|
|
@ -237,19 +247,20 @@ func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetri
|
|||
}
|
||||
}
|
||||
|
||||
// 0---------------------------------------------------------------------------
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
const megaByte = 1024 * 1024
|
||||
|
||||
// ToMB converts bytes to megabytes.
|
||||
func ToMB(v int64) float64 {
|
||||
return float64(v) / megaByte
|
||||
func ToMB(v int64) int64 {
|
||||
return v / megaByte
|
||||
}
|
||||
|
||||
func toPerc(v1, v2 float64) float64 {
|
||||
// ToPercentageentage computes percentage.
|
||||
func ToPercentage(v1, v2 int64) int {
|
||||
if v2 == 0 {
|
||||
return 0
|
||||
}
|
||||
return math.Round((v1 / v2) * 100)
|
||||
return int(math.Floor((float64(v1) / float64(v2)) * 100))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,38 @@ import (
|
|||
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
func TestToPercentage(t *testing.T) {
|
||||
uu := []struct {
|
||||
v1, v2 int64
|
||||
e int
|
||||
}{
|
||||
{0, 0, 0},
|
||||
{100, 200, 50},
|
||||
{200, 100, 200},
|
||||
{224, 4000, 5},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, client.ToPercentage(u.v1, u.v2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToMB(t *testing.T) {
|
||||
const mb = 1024 * 1024
|
||||
uu := []struct {
|
||||
v int64
|
||||
e int64
|
||||
}{
|
||||
{0, 0},
|
||||
{2 * mb, 2},
|
||||
{10 * mb, 10},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, client.ToMB(u.v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodsMetrics(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
metrics *mv1beta1.PodMetricsList
|
||||
|
|
@ -32,8 +64,8 @@ func TestPodsMetrics(t *testing.T) {
|
|||
eSize: 2,
|
||||
e: client.PodsMetrics{
|
||||
"default/p1": client.PodMetrics{
|
||||
CurrentCPU: int64(3000),
|
||||
CurrentMEM: float64(12288),
|
||||
CurrentCPU: 3000,
|
||||
CurrentMEM: 12288,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -107,8 +139,8 @@ func TestNodesMetrics(t *testing.T) {
|
|||
"ok": {
|
||||
nodes: &v1.NodeList{
|
||||
Items: []v1.Node{
|
||||
makeNode("n1", "32", "128Gi", "50m", "2Mi"),
|
||||
makeNode("n2", "8", "4Gi", "50m", "10Mi"),
|
||||
makeNode("n1", "32", "128Gi", "32", "128Gi"),
|
||||
makeNode("n2", "8", "4Gi", "8", "4Gi"),
|
||||
},
|
||||
},
|
||||
metrics: &v1beta1.NodeMetricsList{
|
||||
|
|
@ -120,13 +152,15 @@ func TestNodesMetrics(t *testing.T) {
|
|||
eSize: 2,
|
||||
e: client.NodesMetrics{
|
||||
"n1": client.NodeMetrics{
|
||||
TotalCPU: int64(32000),
|
||||
TotalMEM: float64(131072),
|
||||
AvailCPU: int64(50),
|
||||
AvailMEM: float64(2),
|
||||
TotalCPU: 32000,
|
||||
TotalMEM: 131072,
|
||||
AllocatableCPU: 32000,
|
||||
AllocatableMEM: 131072,
|
||||
AvailableCPU: 22000,
|
||||
AvailableMEM: 122880,
|
||||
CurrentMetrics: client.CurrentMetrics{
|
||||
CurrentCPU: int64(10000),
|
||||
CurrentMEM: float64(8192),
|
||||
CurrentCPU: 10000,
|
||||
CurrentMEM: 8192,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -105,8 +105,7 @@ type Connection interface {
|
|||
|
||||
// CurrentMetrics tracks current cpu/mem.
|
||||
type CurrentMetrics struct {
|
||||
CurrentCPU int64
|
||||
CurrentMEM float64
|
||||
CurrentCPU, CurrentMEM, CurrentEphemeral int64
|
||||
}
|
||||
|
||||
// PodMetrics represent an aggregation of all pod containers metrics.
|
||||
|
|
@ -115,16 +114,15 @@ type PodMetrics CurrentMetrics
|
|||
// NodeMetrics describes raw node metrics.
|
||||
type NodeMetrics struct {
|
||||
CurrentMetrics
|
||||
AvailCPU int64
|
||||
AvailMEM float64
|
||||
TotalCPU int64
|
||||
TotalMEM float64
|
||||
|
||||
AllocatableCPU, AllocatableMEM, AllocatableEphemeral int64
|
||||
AvailableCPU, AvailableMEM, AvailableEphemeral int64
|
||||
TotalCPU, TotalMEM, TotalEphemeral int64
|
||||
}
|
||||
|
||||
// ClusterMetrics summarizes total node metrics as percentages.
|
||||
type ClusterMetrics struct {
|
||||
PercCPU float64
|
||||
PercMEM float64
|
||||
PercCPU, PercMEM, PercEphemeral int
|
||||
}
|
||||
|
||||
// NodesMetrics tracks usage metrics per nodes.
|
||||
|
|
|
|||
|
|
@ -297,6 +297,10 @@ var expectedConfig = `k9s:
|
|||
- kube-system
|
||||
view:
|
||||
active: ctx
|
||||
thresholds:
|
||||
cpu: 80
|
||||
memory: 80
|
||||
disk: 80
|
||||
`
|
||||
|
||||
var resetConfig = `k9s:
|
||||
|
|
@ -316,4 +320,8 @@ var resetConfig = `k9s:
|
|||
- default
|
||||
view:
|
||||
active: po
|
||||
thresholds:
|
||||
cpu: 80
|
||||
memory: 80
|
||||
disk: 80
|
||||
`
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type K9s struct {
|
|||
CurrentCluster string `yaml:"currentCluster"`
|
||||
FullScreenLogs bool `yaml:"fullScreenLogs"`
|
||||
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
|
||||
Thresholds *Threshold `yaml:"thresholds"`
|
||||
manualRefreshRate int
|
||||
manualHeadless *bool
|
||||
manualReadOnly *bool
|
||||
|
|
@ -34,6 +35,7 @@ func NewK9s() *K9s {
|
|||
LogBufferSize: defaultLogBufferSize,
|
||||
LogRequestSize: defaultLogRequestSize,
|
||||
Clusters: make(map[string]*Cluster),
|
||||
Thresholds: newThreshold(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,12 +135,16 @@ func (k *K9s) checkClusters(ks KubeSettings) {
|
|||
// Validate the current configuration.
|
||||
func (k *K9s) Validate(c client.Connection, ks KubeSettings) {
|
||||
k.validateDefaults()
|
||||
|
||||
if k.Clusters == nil {
|
||||
k.Clusters = map[string]*Cluster{}
|
||||
}
|
||||
k.checkClusters(ks)
|
||||
|
||||
if k.Thresholds == nil {
|
||||
k.Thresholds = newThreshold()
|
||||
}
|
||||
k.Thresholds.Validate(c, ks)
|
||||
|
||||
if ctx, err := ks.CurrentContextName(); err == nil && len(k.CurrentContext) == 0 {
|
||||
k.CurrentContext = ctx
|
||||
k.CurrentCluster = ""
|
||||
|
|
|
|||
|
|
@ -213,6 +213,10 @@ func newCharts() Charts {
|
|||
ChartBgColor: "default",
|
||||
DefaultDialColors: Colors{Color("palegreen"), Color("orangered")},
|
||||
DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
|
||||
ResourceColors: map[string]Colors{
|
||||
"cpu": Colors{Color("dodgerblue"), Color("darkslateblue")},
|
||||
"mem": Colors{Color("yellow"), Color("goldenrod")},
|
||||
},
|
||||
}
|
||||
}
|
||||
func newViews() Views {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCPU = 80
|
||||
defaultMEM = 80
|
||||
defaultDisk = 70
|
||||
)
|
||||
|
||||
// Threshold tracks threshold to alert user when excided.
|
||||
type Threshold struct {
|
||||
CPU int `yaml:"cpu"`
|
||||
Memory int `yaml:"memory"`
|
||||
Disk int `yaml:"disk"`
|
||||
}
|
||||
|
||||
func newThreshold() *Threshold {
|
||||
return &Threshold{
|
||||
CPU: defaultCPU,
|
||||
Memory: defaultMEM,
|
||||
Disk: defaultMEM,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a namespace is setup correctly
|
||||
func (t *Threshold) Validate(c client.Connection, ks KubeSettings) {
|
||||
if t.CPU == 0 || t.CPU > 100 {
|
||||
t.CPU = defaultCPU
|
||||
}
|
||||
if t.Memory == 0 || t.Memory > 100 {
|
||||
t.Memory = defaultMEM
|
||||
}
|
||||
if t.Disk == 0 || t.Disk > 100 {
|
||||
t.Disk = defaultDisk
|
||||
}
|
||||
}
|
||||
|
||||
// ExceedsCPUPerc returns true if current metrics exceeds threshold or false otherwise.
|
||||
func (t *Threshold) ExceedsCPUPerc(p int) bool {
|
||||
return p >= t.CPU
|
||||
}
|
||||
|
||||
// ExceedsMemoryPerc returns true if current metrics exceeds threshold or false otherwise.
|
||||
func (t *Threshold) ExceedsMemoryPerc(p int) bool {
|
||||
return p >= t.Memory
|
||||
}
|
||||
|
||||
// ExceedsDiskPerc returns true if current metrics exceeds threshold or false otherwise.
|
||||
func (t *Threshold) ExceedsDiskPerc(p int) bool {
|
||||
return p >= t.Disk
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ func NewCheck(gvr string) *Check {
|
|||
}
|
||||
|
||||
// Set sets a health metric.
|
||||
func (c *Check) Set(l Level, v int) {
|
||||
func (c *Check) Set(l Level, v int64) {
|
||||
c.Counts[l] = v
|
||||
}
|
||||
|
||||
|
|
@ -34,12 +34,12 @@ func (c *Check) Inc(l Level) {
|
|||
}
|
||||
|
||||
// Total stores a metric total.
|
||||
func (c *Check) Total(n int) {
|
||||
func (c *Check) Total(n int64) {
|
||||
c.Counts[Corpus] = n
|
||||
}
|
||||
|
||||
// Tally retrieves a given health metric.
|
||||
func (c *Check) Tally(l Level) int {
|
||||
func (c *Check) Tally(l Level) int64 {
|
||||
return c.Counts[l]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ func TestCheck(t *testing.T) {
|
|||
c := health.NewCheck("test")
|
||||
n := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Inc(health.OK)
|
||||
c.Inc(health.S1)
|
||||
cc = append(cc, c)
|
||||
n++
|
||||
}
|
||||
c.Total(n)
|
||||
c.Total(int64(n))
|
||||
|
||||
assert.Equal(t, 10, len(cc))
|
||||
assert.Equal(t, 10, c.Tally(health.Corpus))
|
||||
assert.Equal(t, 10, c.Tally(health.OK))
|
||||
assert.Equal(t, 0, c.Tally(health.Toast))
|
||||
assert.Equal(t, int64(10), c.Tally(health.Corpus))
|
||||
assert.Equal(t, int64(10), c.Tally(health.S1))
|
||||
assert.Equal(t, int64(0), c.Tally(health.S2))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ const (
|
|||
// Corpus tracks total health.
|
||||
Corpus
|
||||
|
||||
// OK tracks healhy.
|
||||
OK
|
||||
// S1 tracks series 1.
|
||||
S1
|
||||
|
||||
// Warn tracks health warnings.
|
||||
Warn
|
||||
// S2 tracks series 2.
|
||||
S2
|
||||
|
||||
// Toast tracks unhealties.
|
||||
Toast
|
||||
// S3 tracks series 3.
|
||||
S3
|
||||
)
|
||||
|
||||
// Message represents a health message.
|
||||
|
|
@ -32,7 +32,7 @@ type Message struct {
|
|||
type Messages []Message
|
||||
|
||||
// Counts tracks health counts by category.
|
||||
type Counts map[Level]int
|
||||
type Counts map[Level]int64
|
||||
|
||||
// Vital tracks a resource vitals.
|
||||
type Vital struct {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package model
|
|||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
)
|
||||
|
||||
// ClusterInfoListener registers a listener for model changes.
|
||||
|
|
@ -20,31 +19,35 @@ const NA = "n/a"
|
|||
|
||||
// ClusterMeta represents cluster meta data.
|
||||
type ClusterMeta struct {
|
||||
Context, Cluster string
|
||||
User string
|
||||
K9sVer, K8sVer string
|
||||
Cpu, Mem float64
|
||||
Context, Cluster string
|
||||
User string
|
||||
K9sVer, K8sVer string
|
||||
Cpu, Mem, Ephemeral int
|
||||
}
|
||||
|
||||
// NewClusterMeta returns a new instance.
|
||||
func NewClusterMeta() ClusterMeta {
|
||||
return ClusterMeta{
|
||||
Context: NA,
|
||||
Cluster: NA,
|
||||
User: NA,
|
||||
K9sVer: NA,
|
||||
K8sVer: NA,
|
||||
Cpu: 0,
|
||||
Mem: 0,
|
||||
Context: NA,
|
||||
Cluster: NA,
|
||||
User: NA,
|
||||
K9sVer: NA,
|
||||
K8sVer: NA,
|
||||
Cpu: 0,
|
||||
Mem: 0,
|
||||
Ephemeral: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Deltas diffs cluster meta return true if different, false otherwise.
|
||||
func (c ClusterMeta) Deltas(n ClusterMeta) bool {
|
||||
if render.AsPerc(c.Cpu) != render.AsPerc(n.Cpu) {
|
||||
if c.Cpu != n.Cpu {
|
||||
return true
|
||||
}
|
||||
if render.AsPerc(c.Mem) != render.AsPerc(n.Mem) {
|
||||
if c.Mem != n.Mem {
|
||||
return true
|
||||
}
|
||||
if c.Ephemeral != n.Ephemeral {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +92,7 @@ func (c *ClusterInfo) Refresh() {
|
|||
|
||||
var mx client.ClusterMetrics
|
||||
if err := c.cluster.Metrics(&mx); err == nil {
|
||||
data.Cpu, data.Mem = mx.PercCPU, mx.PercMEM
|
||||
data.Cpu, data.Mem, data.Ephemeral = mx.PercCPU, mx.PercMEM, mx.PercEphemeral
|
||||
}
|
||||
|
||||
if c.data.Deltas(data) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package model
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -65,21 +64,38 @@ func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, er
|
|||
|
||||
func (h *PulseHealth) checkMetrics() (health.Checks, error) {
|
||||
dial := client.DialMetrics(h.factory.Client())
|
||||
|
||||
nn, err := dao.FetchNodes(h.factory, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nmx, err := dial.FetchNodesMetrics()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching metrics")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cpu, mem float64
|
||||
for _, mx := range nmx.Items {
|
||||
cpu += float64(mx.Usage.Cpu().MilliValue())
|
||||
mem += client.ToMB(mx.Usage.Memory().Value())
|
||||
mx := make(client.NodesMetrics, len(nn.Items))
|
||||
dial.NodesMetrics(nn, nmx, mx)
|
||||
|
||||
var ccpu, cmem, acpu, amem, tcpu, tmem int64
|
||||
for _, m := range mx {
|
||||
ccpu += m.CurrentCPU
|
||||
cmem += m.CurrentMEM
|
||||
acpu += m.AllocatableCPU
|
||||
amem += m.AllocatableMEM
|
||||
tcpu += m.TotalCPU
|
||||
tmem += m.TotalMEM
|
||||
}
|
||||
c1 := health.NewCheck("cpu")
|
||||
c1.Set(health.OK, int(math.Round(cpu)))
|
||||
c1.Set(health.S1, ccpu)
|
||||
c1.Set(health.S2, acpu)
|
||||
c1.Set(health.S3, tcpu)
|
||||
c2 := health.NewCheck("mem")
|
||||
c2.Set(health.OK, int(math.Round(mem)))
|
||||
c2.Set(health.S1, cmem)
|
||||
c2.Set(health.S2, amem)
|
||||
c2.Set(health.S3, tmem)
|
||||
|
||||
return health.Checks{c1, c2}, nil
|
||||
}
|
||||
|
|
@ -100,16 +116,16 @@ func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check,
|
|||
}
|
||||
|
||||
c := health.NewCheck(gvr)
|
||||
c.Total(len(oo))
|
||||
c.Total(int64(len(oo)))
|
||||
rr, re := make(render.Rows, len(oo)), meta.Renderer
|
||||
for i, o := range oo {
|
||||
if err := re.Render(o, ns, &rr[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !render.Happy(ns, re.Header(ns), rr[i]) {
|
||||
c.Inc(health.Toast)
|
||||
c.Inc(health.S2)
|
||||
} else {
|
||||
c.Inc(health.OK)
|
||||
c.Inc(health.S1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ import (
|
|||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
|
@ -163,13 +161,7 @@ func (Benchmark) countReq(rr [][]string) string {
|
|||
sum += m
|
||||
}
|
||||
}
|
||||
return asNum(sum)
|
||||
}
|
||||
|
||||
// AsNumb prints a number with thousand separator.
|
||||
func asNum(n int) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%d", n)
|
||||
return AsThousands(int64(sum))
|
||||
}
|
||||
|
||||
// BenchInfo represents benchmark run info.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
|
@ -142,7 +143,7 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l met
|
|||
}
|
||||
|
||||
cpu := mx.Usage.Cpu().MilliValue()
|
||||
mem := ToMB(mx.Usage.Memory().Value())
|
||||
mem := client.ToMB(mx.Usage.Memory().Value())
|
||||
c = metric{
|
||||
cpu: ToMillicore(cpu),
|
||||
mem: ToMi(mem),
|
||||
|
|
@ -150,18 +151,18 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l met
|
|||
|
||||
rcpu, rmem := containerResources(*co)
|
||||
if rcpu != nil {
|
||||
p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue())))
|
||||
p.cpu = IntToStr(client.ToPercentage(cpu, rcpu.MilliValue()))
|
||||
}
|
||||
if rmem != nil {
|
||||
p.mem = AsPerc(toPerc(mem, ToMB(rmem.Value())))
|
||||
p.mem = IntToStr(client.ToPercentage(mem, client.ToMB(rmem.Value())))
|
||||
}
|
||||
|
||||
lcpu, lmem := containerLimits(*co)
|
||||
if lcpu != nil {
|
||||
l.cpu = AsPerc(toPerc(float64(cpu), float64(lcpu.MilliValue())))
|
||||
l.cpu = IntToStr(client.ToPercentage(cpu, lcpu.MilliValue()))
|
||||
}
|
||||
if lmem != nil {
|
||||
l.mem = AsPerc(toPerc(mem, ToMB(lmem.Value())))
|
||||
l.mem = IntToStr(client.ToPercentage(mem, client.ToMB(lmem.Value())))
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -24,10 +24,9 @@ func (Deployment) Header(ns string) Header {
|
|||
return Header{
|
||||
HeaderColumn{Name: "NAMESPACE"},
|
||||
HeaderColumn{Name: "NAME"},
|
||||
HeaderColumn{Name: "READY"},
|
||||
HeaderColumn{Name: "READY", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "READY", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "LABELS", Wide: true},
|
||||
HeaderColumn{Name: "VALID", Wide: true},
|
||||
HeaderColumn{Name: "AGE", Time: true, Decorator: AgeDecorator},
|
||||
|
|
@ -54,7 +53,6 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error {
|
|||
strconv.Itoa(int(dp.Status.AvailableReplicas)) + "/" + strconv.Itoa(int(dp.Status.Replicas)),
|
||||
strconv.Itoa(int(dp.Status.UpdatedReplicas)),
|
||||
strconv.Itoa(int(dp.Status.AvailableReplicas)),
|
||||
strconv.Itoa(int(dp.Status.ReadyReplicas)),
|
||||
mapToStr(dp.Labels),
|
||||
asStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)),
|
||||
toAge(dp.ObjectMeta.CreationTimestamp),
|
||||
|
|
|
|||
|
|
@ -9,10 +9,18 @@ import (
|
|||
"github.com/derailed/tview"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
// AsThousands prints a number with thousand separator.
|
||||
func AsThousands(n int64) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%d", n)
|
||||
}
|
||||
|
||||
// Happy returns true if resoure is happy, false otherwise
|
||||
func Happy(ns string, h Header, r Row) bool {
|
||||
if len(r.Fields) == 0 {
|
||||
|
|
@ -25,12 +33,12 @@ func Happy(ns string, h Header, r Row) bool {
|
|||
return strings.TrimSpace(r.Fields[validCol]) == ""
|
||||
}
|
||||
|
||||
const megaByte = 1024 * 1024
|
||||
// const megaByte = 1024 * 1024
|
||||
|
||||
// ToMB converts bytes to megabytes.
|
||||
func ToMB(v int64) float64 {
|
||||
return float64(v) / megaByte
|
||||
}
|
||||
// // ToMB converts bytes to megabytes.
|
||||
// func ToMB(v int64) float64 {
|
||||
// return float64(v) / megaByte
|
||||
// }
|
||||
|
||||
func asStatus(err error) string {
|
||||
if err == nil {
|
||||
|
|
@ -112,22 +120,14 @@ func join(a []string, sep string) string {
|
|||
return buff.String()
|
||||
}
|
||||
|
||||
// ToPerc prints a number as percentage.
|
||||
func ToPerc(f float64) string {
|
||||
return AsPerc(f) + "%"
|
||||
// PrintPerc prints a number as percentage.
|
||||
func PrintPerc(p int) string {
|
||||
return strconv.Itoa(p) + "%"
|
||||
}
|
||||
|
||||
// AsPerc prints a number as a percentage.
|
||||
func AsPerc(f float64) string {
|
||||
return strconv.Itoa(int(f))
|
||||
}
|
||||
|
||||
// ToPerc computes the ratio of two numbers as a percentage.
|
||||
func toPerc(v1, v2 float64) float64 {
|
||||
if v2 == 0 {
|
||||
return 0
|
||||
}
|
||||
return (v1 / v2) * 100
|
||||
// IntToStr converts an int to a string.
|
||||
func IntToStr(p int) string {
|
||||
return strconv.Itoa(int(p))
|
||||
}
|
||||
|
||||
func missing(s string) string {
|
||||
|
|
@ -233,7 +233,7 @@ func ToMillicore(v int64) string {
|
|||
}
|
||||
|
||||
// ToMi shows mem reading for human.
|
||||
func ToMi(v float64) string {
|
||||
func ToMi(v int64) string {
|
||||
return strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,35 +9,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestToMB(t *testing.T) {
|
||||
uu := []struct {
|
||||
v int64
|
||||
e float64
|
||||
}{
|
||||
{0, 0},
|
||||
{2 * megaByte, 2},
|
||||
{10 * megaByte, 10},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, ToMB(u.v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPerc(t *testing.T) {
|
||||
uu := []struct {
|
||||
v1, v2, e float64
|
||||
}{
|
||||
{0, 0, 0},
|
||||
{100, 200, 50},
|
||||
{200, 100, 200},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, toPerc(u.v1, u.v2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToAge(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
t time.Time
|
||||
|
|
@ -343,7 +314,7 @@ func TestToMillicore(t *testing.T) {
|
|||
|
||||
func TestToMi(t *testing.T) {
|
||||
uu := []struct {
|
||||
v float64
|
||||
v int64
|
||||
e string
|
||||
}{
|
||||
{0, "0"},
|
||||
|
|
@ -356,27 +327,25 @@ func TestToMi(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAsPerc(t *testing.T) {
|
||||
func TestIntToStr(t *testing.T) {
|
||||
uu := []struct {
|
||||
v float64
|
||||
v int
|
||||
e string
|
||||
}{
|
||||
{0, "0"},
|
||||
{10.5, "10"},
|
||||
{10, "10"},
|
||||
{0.05, "0"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, AsPerc(u.v))
|
||||
assert.Equal(t, u.e, IntToStr(u.v))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAsPerc(b *testing.B) {
|
||||
v := 10.5
|
||||
func BenchmarkIntToStr(b *testing.B) {
|
||||
v := 10
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
AsPerc(v)
|
||||
IntToStr(v)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,12 +259,12 @@ func resourceMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []a
|
|||
}
|
||||
|
||||
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.Current.AverageUtilization != nil {
|
||||
current = AsPerc(float64(*statuses[i].Resource.Current.AverageUtilization))
|
||||
current = IntToStr(int(*statuses[i].Resource.Current.AverageUtilization))
|
||||
}
|
||||
|
||||
target := "<auto>"
|
||||
if spec.Resource.Target.AverageUtilization != nil {
|
||||
target = AsPerc(float64(*spec.Resource.Target.AverageUtilization))
|
||||
target = IntToStr(int(*spec.Resource.Target.AverageUtilization))
|
||||
}
|
||||
|
||||
return current + "/" + target
|
||||
|
|
@ -293,12 +293,12 @@ func resourceMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []a
|
|||
}
|
||||
|
||||
if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil {
|
||||
current = AsPerc(float64(*statuses[i].Resource.CurrentAverageUtilization))
|
||||
current = IntToStr(int(*statuses[i].Resource.CurrentAverageUtilization))
|
||||
}
|
||||
|
||||
target := "<auto>"
|
||||
if spec.Resource.TargetAverageUtilization != nil {
|
||||
target = AsPerc(float64(*spec.Resource.TargetAverageUtilization))
|
||||
target = IntToStr(int(*spec.Resource.TargetAverageUtilization))
|
||||
}
|
||||
|
||||
return current + "/" + target
|
||||
|
|
|
|||
|
|
@ -141,23 +141,21 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p
|
|||
return
|
||||
}
|
||||
|
||||
cpu := mx.Usage.Cpu().MilliValue()
|
||||
mem := ToMB(mx.Usage.Memory().Value())
|
||||
cpu, mem := mx.Usage.Cpu().MilliValue(), client.ToMB(mx.Usage.Memory().Value())
|
||||
c = metric{
|
||||
cpu: ToMillicore(cpu),
|
||||
mem: ToMi(mem),
|
||||
}
|
||||
|
||||
acpu := no.Status.Allocatable.Cpu().MilliValue()
|
||||
amem := ToMB(no.Status.Allocatable.Memory().Value())
|
||||
acpu, amem := no.Status.Allocatable.Cpu().MilliValue(), client.ToMB(no.Status.Allocatable.Memory().Value())
|
||||
a = metric{
|
||||
cpu: ToMillicore(acpu),
|
||||
mem: ToMi(amem),
|
||||
}
|
||||
|
||||
p = metric{
|
||||
cpu: AsPerc(toPerc(float64(cpu), float64(acpu))),
|
||||
mem: AsPerc(toPerc(mem, amem)),
|
||||
cpu: IntToStr(client.ToPercentage(cpu, acpu)),
|
||||
mem: IntToStr(client.ToPercentage(mem, amem)),
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -156,16 +156,16 @@ func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric) {
|
|||
cpu, mem := currentRes(mx)
|
||||
c = metric{
|
||||
cpu: ToMillicore(cpu.MilliValue()),
|
||||
mem: ToMi(ToMB(mem.Value())),
|
||||
mem: ToMi(client.ToMB(mem.Value())),
|
||||
}
|
||||
|
||||
rc, rm := requestedRes(pod.Spec.Containers)
|
||||
lc, lm := resourceLimits(pod.Spec.Containers)
|
||||
p = metric{
|
||||
cpu: AsPerc(toPerc(float64(cpu.MilliValue()), float64(rc.MilliValue()))),
|
||||
mem: AsPerc(toPerc(ToMB(mem.Value()), ToMB(rm.Value()))),
|
||||
cpuLim: AsPerc(toPerc(float64(cpu.MilliValue()), float64(lc.MilliValue()))),
|
||||
memLim: AsPerc(toPerc(ToMB(mem.Value()), ToMB(lm.Value()))),
|
||||
cpu: IntToStr(client.ToPercentage(cpu.MilliValue(), rc.MilliValue())),
|
||||
mem: IntToStr(client.ToPercentage(client.ToMB(mem.Value()), client.ToMB(rm.Value()))),
|
||||
cpuLim: IntToStr(client.ToPercentage(cpu.MilliValue(), lc.MilliValue())),
|
||||
memLim: IntToStr(client.ToPercentage(client.ToMB(mem.Value()), client.ToMB(lm.Value()))),
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ func (f PortForward) Render(o interface{}, gvr string, r *Row) error {
|
|||
pf.Container(),
|
||||
strings.Join(pf.Ports(), ","),
|
||||
UrlFor(pf.Config.Host, pf.Config.Path, ports[0]),
|
||||
asNum(pf.Config.C),
|
||||
asNum(pf.Config.N),
|
||||
AsThousands(int64(pf.Config.C)),
|
||||
AsThousands(int64(pf.Config.N)),
|
||||
"",
|
||||
pf.Age(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func (c *Component) GetSeriesColorNames() []string {
|
|||
c.mx.RLock()
|
||||
defer c.mx.RUnlock()
|
||||
|
||||
var nn []string
|
||||
nn := make([]string, 0, len(c.seriesColors))
|
||||
for _, color := range c.seriesColors {
|
||||
for name, co := range tcell.ColorNames {
|
||||
if co == color {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package tchart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/tview"
|
||||
)
|
||||
|
||||
|
|
@ -24,12 +22,6 @@ const (
|
|||
lv = '\u257b'
|
||||
)
|
||||
|
||||
// Segment represents a dial segment.
|
||||
type Segment []int
|
||||
|
||||
// Segments represents a collection of segments.
|
||||
type Segments []Segment
|
||||
|
||||
// Matrix represents a number dial.
|
||||
type Matrix [][]rune
|
||||
|
||||
|
|
@ -42,94 +34,16 @@ type DotMatrix struct {
|
|||
}
|
||||
|
||||
// NewDotMatrix returns a new matrix.
|
||||
func NewDotMatrix(row, col int) DotMatrix {
|
||||
func NewDotMatrix() DotMatrix {
|
||||
return DotMatrix{
|
||||
row: row,
|
||||
col: col,
|
||||
row: 3,
|
||||
col: 3,
|
||||
}
|
||||
}
|
||||
|
||||
// Print prints the matrix.
|
||||
func (d DotMatrix) Print(n int) Matrix {
|
||||
if d.row == d.col {
|
||||
return To3x3Char(n)
|
||||
}
|
||||
m := make(Matrix, d.row)
|
||||
segs := asSegments(n)
|
||||
for row := 0; row < d.row; row++ {
|
||||
for col := 0; col < d.col; col++ {
|
||||
m[row] = append(m[row], segs.CharFor(row, col))
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func asSegments(n int) Segment {
|
||||
switch n {
|
||||
case 0:
|
||||
return Segment{1, 1, 1, 0, 1, 1, 1}
|
||||
case 1:
|
||||
return Segment{0, 0, 1, 0, 0, 1, 0}
|
||||
case 2:
|
||||
return Segment{1, 0, 1, 1, 1, 0, 1}
|
||||
case 3:
|
||||
return Segment{1, 0, 1, 1, 0, 1, 1}
|
||||
case 4:
|
||||
return Segment{0, 1, 0, 1, 0, 1, 0}
|
||||
case 5:
|
||||
return Segment{1, 1, 0, 1, 0, 1, 1}
|
||||
case 6:
|
||||
return Segment{0, 1, 0, 1, 1, 1, 1}
|
||||
case 7:
|
||||
return Segment{1, 0, 1, 0, 0, 1, 0}
|
||||
case 8:
|
||||
return Segment{1, 1, 1, 1, 1, 1, 1}
|
||||
case 9:
|
||||
return Segment{1, 1, 1, 1, 0, 1, 0}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("NYI %d", n))
|
||||
}
|
||||
}
|
||||
|
||||
// CharFor returns a char based on row/col.
|
||||
func (s Segment) CharFor(row, col int) rune {
|
||||
c := dots[0]
|
||||
segs := ToSegments(row, col)
|
||||
if segs == nil {
|
||||
return c
|
||||
}
|
||||
for _, seg := range segs {
|
||||
if s[seg] == 1 {
|
||||
c = charForSeg(seg, row, col)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func charForSeg(seg, row, col int) rune {
|
||||
switch seg {
|
||||
case 0, 3, 6:
|
||||
return dots[2]
|
||||
}
|
||||
if row == 0 && (col == 0 || col == 2) {
|
||||
return dots[2]
|
||||
}
|
||||
|
||||
return dots[3]
|
||||
}
|
||||
|
||||
var segs = map[int][][]int{
|
||||
0: {{1, 0}, {0}, {2, 0}},
|
||||
1: {{1}, nil, {2}},
|
||||
2: {{1, 3}, {3}, {2, 3}},
|
||||
3: {{4}, nil, {5}},
|
||||
4: {{4, 6}, {6}, {5, 6}},
|
||||
}
|
||||
|
||||
// ToSegments return path segments.
|
||||
func ToSegments(row, col int) []int {
|
||||
return segs[row][col]
|
||||
return To3x3Char(n)
|
||||
}
|
||||
|
||||
// To3x3Char returns 3x3 number matrix
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package tchart_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
|
|
@ -9,130 +8,12 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSegmentFor(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
r, c int
|
||||
e []int
|
||||
}{
|
||||
"0x0": {r: 0, c: 0, e: []int{1, 0}},
|
||||
"0x1": {r: 0, c: 1, e: []int{0}},
|
||||
"0x2": {r: 0, c: 2, e: []int{2, 0}},
|
||||
"1x0": {r: 1, c: 0, e: []int{1}},
|
||||
"1x1": {r: 1, c: 1, e: nil},
|
||||
"1x2": {r: 1, c: 2, e: []int{2}},
|
||||
"2x0": {r: 2, c: 0, e: []int{1, 3}},
|
||||
"2x1": {r: 2, c: 1, e: []int{3}},
|
||||
"2x2": {r: 2, c: 2, e: []int{2, 3}},
|
||||
"3x0": {r: 3, c: 0, e: []int{4}},
|
||||
"3x1": {r: 3, c: 1, e: nil},
|
||||
"3x2": {r: 3, c: 2, e: []int{5}},
|
||||
"4x0": {r: 4, c: 0, e: []int{4, 6}},
|
||||
"4x1": {r: 4, c: 1, e: []int{6}},
|
||||
"4x2": {r: 4, c: 2, e: []int{5, 6}},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, tchart.ToSegments(u.r, u.c))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDial5x3(t *testing.T) {
|
||||
d := tchart.NewDotMatrix(5, 3)
|
||||
for n := 0; n <= 9; n++ {
|
||||
i := n
|
||||
t.Run(strconv.Itoa(n), func(t *testing.T) {
|
||||
assert.Equal(t, numbers3x5[i], d.Print(i))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDial3x3(t *testing.T) {
|
||||
d := tchart.NewDotMatrix(3, 3)
|
||||
d := tchart.NewDotMatrix()
|
||||
for n := 0; n <= 2; n++ {
|
||||
i := n
|
||||
t.Run(strconv.Itoa(n), func(t *testing.T) {
|
||||
fmt.Println(tchart.To3x3Char(i))
|
||||
assert.Equal(t, tchart.To3x3Char(i), d.Print(i))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
const hChar, vChar = '▤', '▥'
|
||||
|
||||
var numbers3x5 = []tchart.Matrix{
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', vChar},
|
||||
{vChar, ' ', vChar},
|
||||
{vChar, ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
},
|
||||
[][]rune{
|
||||
{' ', ' ', hChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', ' '},
|
||||
{hChar, hChar, hChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, ' ', ' '},
|
||||
{vChar, ' ', ' '},
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', ' '},
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, ' ', ' '},
|
||||
{vChar, ' ', ' '},
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
},
|
||||
[][]rune{
|
||||
{hChar, hChar, hChar},
|
||||
{vChar, ' ', vChar},
|
||||
{hChar, hChar, hChar},
|
||||
{' ', ' ', vChar},
|
||||
{' ', ' ', vChar},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ type delta int
|
|||
type Gauge struct {
|
||||
*Component
|
||||
|
||||
data Metric
|
||||
resolution int
|
||||
deltaOk, deltaFault delta
|
||||
data Metric
|
||||
resolution int
|
||||
deltaOk, deltaS2 delta
|
||||
}
|
||||
|
||||
// NewGauge returns a new gauge.
|
||||
|
|
@ -53,7 +53,7 @@ func (g *Gauge) Add(m Metric) {
|
|||
g.mx.Lock()
|
||||
defer g.mx.Unlock()
|
||||
|
||||
g.deltaOk, g.deltaFault = computeDelta(g.data.OK, m.OK), computeDelta(g.data.Fault, m.Fault)
|
||||
g.deltaOk, g.deltaS2 = computeDelta(g.data.S1, m.S1), computeDelta(g.data.S2, m.S2)
|
||||
g.data = m
|
||||
}
|
||||
|
||||
|
|
@ -80,12 +80,12 @@ func (g *Gauge) Draw(sc tcell.Screen) {
|
|||
)
|
||||
|
||||
s1C, s2C := g.colorForSeries()
|
||||
d1, d2 := fmt.Sprintf(fmat, g.data.OK), fmt.Sprintf(fmat, g.data.Fault)
|
||||
d1, d2 := fmt.Sprintf(fmat, g.data.S1), fmt.Sprintf(fmat, g.data.S2)
|
||||
o.X -= len(d1) * 3
|
||||
g.drawNum(sc, true, o, g.data.OK, g.deltaOk, d1, style.Foreground(s1C).Dim(false))
|
||||
g.drawNum(sc, true, o, g.data.S1, g.deltaOk, d1, style.Foreground(s1C).Dim(false))
|
||||
|
||||
o.X = mid.X + 1
|
||||
g.drawNum(sc, false, o, g.data.Fault, g.deltaFault, d2, style.Foreground(s2C).Dim(false))
|
||||
g.drawNum(sc, false, o, g.data.S2, g.deltaS2, d2, style.Foreground(s2C).Dim(false))
|
||||
|
||||
if rect.Dx() > 0 && rect.Dy() > 0 && g.legend != "" {
|
||||
legend := g.legend
|
||||
|
|
@ -96,14 +96,14 @@ func (g *Gauge) Draw(sc tcell.Screen) {
|
|||
}
|
||||
}
|
||||
|
||||
func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int, dn delta, ns string, style tcell.Style) {
|
||||
func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int64, dn delta, ns string, style tcell.Style) {
|
||||
c1, _ := g.colorForSeries()
|
||||
if ok {
|
||||
style = style.Foreground(c1)
|
||||
printDelta(sc, dn, o, style)
|
||||
}
|
||||
|
||||
dm, significant := NewDotMatrix(3, 3), n == 0
|
||||
dm, significant := NewDotMatrix(), n == 0
|
||||
if n == 0 {
|
||||
style = g.dimmed
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ func (g *Gauge) drawDial(sc tcell.Screen, m Matrix, o image.Point, style tcell.S
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func computeDelta(d1, d2 int) delta {
|
||||
func computeDelta(d1, d2 int64) delta {
|
||||
if d2 == 0 {
|
||||
return DeltaSame
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
func TestComputeDeltas(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
d1, d2 int
|
||||
d1, d2 int64
|
||||
e delta
|
||||
}{
|
||||
"same": {
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ func TestMetricsMaxDigits(t *testing.T) {
|
|||
e: 1,
|
||||
},
|
||||
"oks": {
|
||||
m: tchart.Metric{OK: 100, Fault: 10},
|
||||
m: tchart.Metric{S1: 100, S2: 10},
|
||||
e: 3,
|
||||
},
|
||||
"errs": {
|
||||
m: tchart.Metric{OK: 10, Fault: 1000},
|
||||
m: tchart.Metric{S1: 10, S2: 1000},
|
||||
e: 4,
|
||||
},
|
||||
}
|
||||
|
|
@ -36,13 +36,13 @@ func TestMetricsMaxDigits(t *testing.T) {
|
|||
func TestMetricsMax(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
m tchart.Metric
|
||||
e int
|
||||
e int64
|
||||
}{
|
||||
"empty": {
|
||||
e: 0,
|
||||
},
|
||||
"max_ok": {
|
||||
m: tchart.Metric{OK: 100, Fault: 10},
|
||||
m: tchart.Metric{S1: 100, S2: 10},
|
||||
e: 100,
|
||||
},
|
||||
}
|
||||
|
|
@ -54,35 +54,3 @@ func TestMetricsMax(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
mm []tchart.Metric
|
||||
e int
|
||||
}{
|
||||
"empty": {
|
||||
e: 1,
|
||||
},
|
||||
"oks": {
|
||||
mm: []tchart.Metric{{OK: 100, Fault: 10}},
|
||||
e: 3,
|
||||
},
|
||||
"errs": {
|
||||
mm: []tchart.Metric{{OK: 10, Fault: 1000}},
|
||||
e: 4,
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
g := tchart.NewGauge("fred")
|
||||
assert.True(t, g.IsDial())
|
||||
for _, m := range u.mm {
|
||||
g.Add(m)
|
||||
}
|
||||
t.Run(k, func(t *testing.T) {
|
||||
// assert.Equal(t, u.e, u.m.MaxDigits())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,29 +17,29 @@ type block struct {
|
|||
}
|
||||
|
||||
type blocks struct {
|
||||
oks, errs block
|
||||
s1, s2 block
|
||||
}
|
||||
|
||||
// Metric tracks a good and error rates.
|
||||
// Metric tracks two series.
|
||||
type Metric struct {
|
||||
OK, Fault int
|
||||
S1, S2 int64
|
||||
}
|
||||
|
||||
// MaxDigits returns the max series number of digits.
|
||||
func (m Metric) MaxDigits() int {
|
||||
|
||||
s := fmt.Sprintf("%d", m.Max())
|
||||
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Max returns the max of the series.
|
||||
func (m Metric) Max() int {
|
||||
return int(math.Max(float64(m.OK), float64(m.Fault)))
|
||||
func (m Metric) Max() int64 {
|
||||
return int64(math.Max(float64(m.S1), float64(m.S2)))
|
||||
}
|
||||
|
||||
// Sum returns the sum of the metrics.
|
||||
func (m Metric) Sum() int {
|
||||
return m.OK + m.Fault
|
||||
// Sum returns the sum of series.
|
||||
func (m Metric) Sum() int64 {
|
||||
return m.S1 + m.S2
|
||||
}
|
||||
|
||||
// SparkLine represents a sparkline component.
|
||||
|
|
@ -81,7 +81,7 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
|
|||
return
|
||||
}
|
||||
|
||||
pad := 1
|
||||
pad := 0
|
||||
if s.legend != "" {
|
||||
pad++
|
||||
}
|
||||
|
|
@ -97,18 +97,15 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
|
|||
idx = len(s.data) - rect.Dx()/2
|
||||
}
|
||||
|
||||
factor := 2
|
||||
if !s.multiSeries {
|
||||
factor = 1
|
||||
}
|
||||
scale := float64(len(sparks)*(rect.Dy()-pad)/factor) / float64(max)
|
||||
scale := float64(len(sparks)*(rect.Dy()-pad)) / float64(max)
|
||||
c1, c2 := s.colorForSeries()
|
||||
for _, d := range s.data[idx:] {
|
||||
b := toBlocks(d, scale)
|
||||
cY := rect.Max.Y - pad
|
||||
cY = s.drawBlock(rect, screen, cX, cY, b.oks, c1)
|
||||
_ = s.drawBlock(rect, screen, cX, cY, b.errs, c2)
|
||||
cX += 2
|
||||
s.drawBlock(rect, screen, cX, cY, b.s1, c1)
|
||||
cX++
|
||||
s.drawBlock(rect, screen, cX, cY, b.s2, c2)
|
||||
cX++
|
||||
}
|
||||
|
||||
if rect.Dx() > 0 && rect.Dy() > 0 && s.legend != "" {
|
||||
|
|
@ -120,7 +117,7 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *SparkLine) drawBlock(r image.Rectangle, screen tcell.Screen, x, y int, b block, c tcell.Color) int {
|
||||
func (s *SparkLine) drawBlock(r image.Rectangle, screen tcell.Screen, x, y int, b block, c tcell.Color) {
|
||||
style := tcell.StyleDefault.Foreground(c).Background(s.bgColor)
|
||||
|
||||
zeroY := r.Max.Y - r.Dy()
|
||||
|
|
@ -133,12 +130,7 @@ func (s *SparkLine) drawBlock(r image.Rectangle, screen tcell.Screen, x, y int,
|
|||
}
|
||||
if b.partial != 0 {
|
||||
screen.SetContent(x, y, b.partial, nil, style)
|
||||
if b.full == 0 {
|
||||
y--
|
||||
}
|
||||
}
|
||||
|
||||
return y
|
||||
}
|
||||
|
||||
func (s *SparkLine) cutSet(width int) {
|
||||
|
|
@ -151,8 +143,8 @@ func (s *SparkLine) cutSet(width int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *SparkLine) computeMax() int {
|
||||
var max int
|
||||
func (s *SparkLine) computeMax() int64 {
|
||||
var max int64
|
||||
for _, d := range s.data {
|
||||
m := d.Max()
|
||||
if max < m {
|
||||
|
|
@ -167,10 +159,10 @@ func toBlocks(m Metric, scale float64) blocks {
|
|||
if m.Sum() <= 0 {
|
||||
return blocks{}
|
||||
}
|
||||
return blocks{oks: makeBlocks(m.OK, scale), errs: makeBlocks(m.Fault, scale)}
|
||||
return blocks{s1: makeBlocks(m.S1, scale), s2: makeBlocks(m.S2, scale)}
|
||||
}
|
||||
|
||||
func makeBlocks(v int, scale float64) block {
|
||||
func makeBlocks(v int64, scale float64) block {
|
||||
scaled := int(math.Round(float64(v) * scale))
|
||||
p, b := scaled%len(sparks), block{full: scaled / len(sparks)}
|
||||
if b.full == 0 && v > 0 && p == 0 {
|
||||
|
|
|
|||
|
|
@ -57,27 +57,27 @@ func TestToBlocks(t *testing.T) {
|
|||
e: blocks{},
|
||||
},
|
||||
"max_ok": {
|
||||
m: Metric{OK: 100, Fault: 10},
|
||||
m: Metric{S1: 100, S2: 10},
|
||||
s: 0.5,
|
||||
e: blocks{
|
||||
oks: block{full: 6, partial: sparks[2]},
|
||||
errs: block{full: 0, partial: sparks[5]},
|
||||
s1: block{full: 6, partial: sparks[2]},
|
||||
s2: block{full: 0, partial: sparks[5]},
|
||||
},
|
||||
},
|
||||
"max_fault": {
|
||||
m: Metric{OK: 10, Fault: 100},
|
||||
m: Metric{S1: 10, S2: 100},
|
||||
s: 0.5,
|
||||
e: blocks{
|
||||
oks: block{full: 0, partial: sparks[5]},
|
||||
errs: block{full: 6, partial: sparks[2]},
|
||||
s1: block{full: 0, partial: sparks[5]},
|
||||
s2: block{full: 6, partial: sparks[2]},
|
||||
},
|
||||
},
|
||||
"over": {
|
||||
m: Metric{OK: 22, Fault: 999},
|
||||
m: Metric{S1: 22, S2: 999},
|
||||
s: float64(8*20) / float64(999),
|
||||
e: blocks{
|
||||
oks: block{full: 0, partial: sparks[4]},
|
||||
errs: block{full: 20, partial: sparks[0]},
|
||||
s1: block{full: 0, partial: sparks[4]},
|
||||
s2: block{full: 20, partial: sparks[0]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -93,26 +93,26 @@ func TestToBlocks(t *testing.T) {
|
|||
func TestComputeMax(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
mm []Metric
|
||||
e int
|
||||
e int64
|
||||
}{
|
||||
"empty": {
|
||||
e: 0,
|
||||
},
|
||||
"max_ok": {
|
||||
mm: []Metric{{OK: 100, Fault: 10}},
|
||||
mm: []Metric{{S1: 100, S2: 10}},
|
||||
e: 100,
|
||||
},
|
||||
"max_fault": {
|
||||
mm: []Metric{{OK: 100, Fault: 1000}},
|
||||
mm: []Metric{{S1: 100, S2: 1000}},
|
||||
e: 1000,
|
||||
},
|
||||
"many": {
|
||||
mm: []Metric{
|
||||
{OK: 100, Fault: 1000},
|
||||
{OK: 110, Fault: 1010},
|
||||
{OK: 120, Fault: 1020},
|
||||
{OK: 130, Fault: 1030},
|
||||
{OK: 140, Fault: 1040},
|
||||
{S1: 100, S2: 1000},
|
||||
{S1: 110, S2: 1010},
|
||||
{S1: 120, S2: 1020},
|
||||
{S1: 130, S2: 1030},
|
||||
{S1: 140, S2: 1040},
|
||||
},
|
||||
e: 1040,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) {
|
|||
data.Cluster,
|
||||
data.User,
|
||||
data.K8sVer,
|
||||
render.ToPerc(data.Cpu),
|
||||
render.ToPerc(data.Mem),
|
||||
render.PrintPerc(data.Cpu),
|
||||
render.PrintPerc(data.Mem),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
@ -131,8 +131,8 @@ func (s *StatusIndicator) setText(msg string) {
|
|||
// Helpers...
|
||||
|
||||
// AsPercDelta represents a percentage with a delta indicator.
|
||||
func AsPercDelta(ov, nv float64) string {
|
||||
prev, cur := render.AsPerc(ov), render.AsPerc(nv)
|
||||
func AsPercDelta(ov, nv int) string {
|
||||
prev, cur := render.IntToStr(ov), render.IntToStr(nv)
|
||||
if cur == "0" {
|
||||
return render.NAValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ func NewLogo(styles *config.Styles) *Logo {
|
|||
styles: styles,
|
||||
}
|
||||
l.SetDirection(tview.FlexRow)
|
||||
l.AddItem(l.logo, 0, 6, false)
|
||||
l.AddItem(l.status, 1, 0, false)
|
||||
l.AddItem(l.logo, 6, 1, false)
|
||||
l.AddItem(l.status, 1, 1, false)
|
||||
l.refreshLogo(styles.Body().LogoColor)
|
||||
l.SetBackgroundColor(styles.BgColor())
|
||||
styles.AddListener(&l)
|
||||
|
|
|
|||
|
|
@ -271,8 +271,9 @@ func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads M
|
|||
cell := tview.NewTableCell(field)
|
||||
cell.SetExpansion(1)
|
||||
cell.SetAlign(h[c].Align)
|
||||
cell.SetTextColor(color(t.GetModel().GetNamespace(), t.header, ore))
|
||||
if marked {
|
||||
fgColor := color(t.GetModel().GetNamespace(), t.header, ore)
|
||||
cell.SetTextColor(fgColor)
|
||||
if marked && fgColor != render.ErrColor {
|
||||
cell.SetTextColor(t.styles.Table().MarkColor.Color())
|
||||
}
|
||||
if col == 0 {
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ func (a *App) switchCtx(name string, loadPods bool) error {
|
|||
a.Flash().Infof("Switching context to %s", name)
|
||||
a.ReloadStyles(name)
|
||||
v := a.Config.ActiveView()
|
||||
if v == "" {
|
||||
if v == "" || v == "ctx" || v == "context" {
|
||||
v = "pod"
|
||||
}
|
||||
if err := a.gotoResource(v, ns, true); loadPods && err != nil {
|
||||
|
|
@ -342,8 +342,11 @@ func (a *App) Run() error {
|
|||
func (a *App) Status(l model.FlashLevel, msg string) {
|
||||
a.QueueUpdateDraw(func() {
|
||||
a.Flash().SetMessage(l, msg)
|
||||
a.setIndicator(l, msg)
|
||||
a.setLogo(l, msg)
|
||||
if a.showHeader {
|
||||
a.setLogo(l, msg)
|
||||
} else {
|
||||
a.setIndicator(l, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,18 @@ func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) {
|
|||
if c.app.Conn().HasMetrics() {
|
||||
row = c.setCell(row, ui.AsPercDelta(prev.Cpu, curr.Cpu))
|
||||
_ = c.setCell(row, ui.AsPercDelta(prev.Mem, curr.Mem))
|
||||
var set bool
|
||||
if c.app.Config.K9s.Thresholds.ExceedsCPUPerc(curr.Cpu) {
|
||||
c.app.Status(model.FlashErr, "CPU on fire!")
|
||||
set = true
|
||||
}
|
||||
if c.app.Config.K9s.Thresholds.ExceedsMemoryPerc(curr.Mem) {
|
||||
c.app.Status(model.FlashErr, "Memory on fire!")
|
||||
set = true
|
||||
}
|
||||
if !set {
|
||||
c.app.ClearStatus(true)
|
||||
}
|
||||
}
|
||||
c.updateStyle()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/health"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/tchart"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
|
|
@ -87,15 +88,15 @@ func (p *Pulse) Init(ctx context.Context) error {
|
|||
p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 2, Y: 2}, "apps/v1/replicasets"),
|
||||
p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 2, Y: 2}, "apps/v1/statefulsets"),
|
||||
p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 2, Y: 2}, "apps/v1/daemonsets"),
|
||||
p.makeSP(true, image.Point{X: 2, Y: 0}, image.Point{X: 3, Y: 2}, "v1/pods"),
|
||||
p.makeSP(true, image.Point{X: 2, Y: 2}, image.Point{X: 3, Y: 2}, "v1/events"),
|
||||
p.makeSP(true, image.Point{X: 2, Y: 4}, image.Point{X: 3, Y: 2}, "batch/v1/jobs"),
|
||||
p.makeSP(true, image.Point{X: 2, Y: 6}, image.Point{X: 3, Y: 2}, "v1/persistentvolumes"),
|
||||
p.makeSP(image.Point{X: 2, Y: 0}, image.Point{X: 3, Y: 2}, "v1/pods"),
|
||||
p.makeSP(image.Point{X: 2, Y: 2}, image.Point{X: 3, Y: 2}, "v1/events"),
|
||||
p.makeSP(image.Point{X: 2, Y: 4}, image.Point{X: 3, Y: 2}, "batch/v1/jobs"),
|
||||
p.makeSP(image.Point{X: 2, Y: 6}, image.Point{X: 3, Y: 2}, "v1/persistentvolumes"),
|
||||
}
|
||||
if p.app.Conn().HasMetrics() {
|
||||
p.charts = append(p.charts,
|
||||
p.makeSP(false, image.Point{X: 5, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"),
|
||||
p.makeSP(false, image.Point{X: 5, Y: 4}, image.Point{X: 2, Y: 4}, "mem"),
|
||||
p.makeSP(image.Point{X: 5, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"),
|
||||
p.makeSP(image.Point{X: 5, Y: 4}, image.Point{X: 2, Y: 4}, "mem"),
|
||||
)
|
||||
}
|
||||
p.bindKeys()
|
||||
|
|
@ -126,6 +127,14 @@ func (p *Pulse) StylesChanged(s *config.Styles) {
|
|||
p.app.Draw()
|
||||
}
|
||||
|
||||
const (
|
||||
genFmat = " %s([%s::]%d[white::]:[%s::b]%d[-::])"
|
||||
cpuFmt = " %s [%s::b]%s[white::-]::[gray::]%s ([%s::]%sm[white::]/[%s::]%sm[-::])"
|
||||
memFmt = " %s [%s::b]%s[white::-]::[gray::]%s ([%s::]%sMi[white::]/[%s::]%sMi[-::])"
|
||||
okColor = "palegreen"
|
||||
errColor = "orangered"
|
||||
)
|
||||
|
||||
// PulseChanged notifies the model data changed.
|
||||
func (p *Pulse) PulseChanged(c *health.Check) {
|
||||
index, ok := findIndexGVR(p.charts, c.GVR)
|
||||
|
|
@ -137,29 +146,59 @@ func (p *Pulse) PulseChanged(c *health.Check) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
nn := v.GetSeriesColorNames()
|
||||
if c.Tally(health.S1) == 0 {
|
||||
nn[0] = "gray"
|
||||
}
|
||||
if c.Tally(health.S2) == 0 {
|
||||
nn[1] = "gray"
|
||||
}
|
||||
|
||||
gvr := client.NewGVR(c.GVR)
|
||||
switch c.GVR {
|
||||
case "cpu":
|
||||
v.SetLegend(fmt.Sprintf(" %s(%dm)", strings.Title(gvr.R()), c.Tally(health.OK)))
|
||||
perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2))
|
||||
color := okColor
|
||||
if p.app.Config.K9s.Thresholds.ExceedsCPUPerc(perc) {
|
||||
color = errColor
|
||||
}
|
||||
v.SetLegend(fmt.Sprintf(cpuFmt,
|
||||
strings.Title(gvr.R()),
|
||||
color,
|
||||
render.PrintPerc(perc),
|
||||
render.PrintPerc(p.app.Config.K9s.Thresholds.CPU),
|
||||
nn[0],
|
||||
render.AsThousands(c.Tally(health.S1)),
|
||||
nn[1],
|
||||
render.AsThousands(c.Tally(health.S2)),
|
||||
))
|
||||
case "mem":
|
||||
v.SetLegend(fmt.Sprintf(" %s(%dMi)", strings.Title(gvr.R()), c.Tally(health.OK)))
|
||||
perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2))
|
||||
color := okColor
|
||||
if p.app.Config.K9s.Thresholds.ExceedsMemoryPerc(perc) {
|
||||
color = errColor
|
||||
}
|
||||
v.SetLegend(fmt.Sprintf(memFmt,
|
||||
strings.Title(gvr.R()),
|
||||
color,
|
||||
render.PrintPerc(perc),
|
||||
render.PrintPerc(p.app.Config.K9s.Thresholds.Memory),
|
||||
nn[0],
|
||||
render.AsThousands(c.Tally(health.S1)),
|
||||
nn[1],
|
||||
render.AsThousands(c.Tally(health.S2)),
|
||||
))
|
||||
default:
|
||||
nn := v.GetSeriesColorNames()
|
||||
if c.Tally(health.OK) == 0 {
|
||||
nn[0] = "gray"
|
||||
}
|
||||
if c.Tally(health.Toast) == 0 {
|
||||
nn[1] = "gray"
|
||||
}
|
||||
v.SetLegend(fmt.Sprintf(" %s([%s::]%d[white::]:[%s::b]%d[-::])",
|
||||
v.SetLegend(fmt.Sprintf(genFmat,
|
||||
strings.Title(gvr.R()),
|
||||
nn[0],
|
||||
c.Tally(health.OK),
|
||||
c.Tally(health.S1),
|
||||
nn[1],
|
||||
c.Tally(health.Toast),
|
||||
c.Tally(health.S2),
|
||||
))
|
||||
}
|
||||
v.Add(tchart.Metric{OK: c.Tally(health.OK), Fault: c.Tally(health.Toast)})
|
||||
v.Add(tchart.Metric{S1: c.Tally(health.S1), S2: c.Tally(health.S2)})
|
||||
}
|
||||
|
||||
// PulseFailed notifies the load failed.
|
||||
|
|
@ -301,7 +340,7 @@ func (p *Pulse) nextFocusCmd(direction int) func(evt *tcell.EventKey) *tcell.Eve
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) makeSP(multi bool, loc image.Point, span image.Point, gvr string) *tchart.SparkLine {
|
||||
func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.SparkLine {
|
||||
s := tchart.NewSparkLine(gvr)
|
||||
s.SetBackgroundColor(p.app.Styles.Charts().BgColor.Color())
|
||||
s.SetBorderPadding(0, 1, 0, 1)
|
||||
|
|
@ -312,9 +351,7 @@ func (p *Pulse) makeSP(multi bool, loc image.Point, span image.Point, gvr string
|
|||
}
|
||||
s.SetLegend(fmt.Sprintf(" %s ", strings.Title(client.NewGVR(gvr).R())))
|
||||
s.SetInputCapture(p.keyboard)
|
||||
if !multi {
|
||||
s.SetMultiSeries(multi)
|
||||
}
|
||||
s.SetMultiSeries(true)
|
||||
p.AddItem(s, loc.X, loc.Y, span.X, span.Y, 0, 0, true)
|
||||
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package view
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
|
|
@ -30,19 +31,24 @@ func (r *RestartExtender) bindKeys(aa ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := r.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
paths := r.GetTable().GetSelectedItems()
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.Stop()
|
||||
defer r.Start()
|
||||
msg := "Please confirm rollout restart for " + path
|
||||
msg := fmt.Sprintf("Restart deployment %s?" + paths[0])
|
||||
if len(paths) > 1 {
|
||||
msg = fmt.Sprintf("Restart %d deployments?", len(paths))
|
||||
}
|
||||
dialog.ShowConfirm(r.App().Content.Pages, "<Confirm Restart>", msg, func() {
|
||||
if err := r.restartRollout(path); err != nil {
|
||||
r.App().Flash().Err(err)
|
||||
} else {
|
||||
r.App().Flash().Infof("Rollout restart in progress for `%s...", path)
|
||||
for _, path := range paths {
|
||||
if err := r.restartRollout(path); err != nil {
|
||||
r.App().Flash().Err(err)
|
||||
} else {
|
||||
r.App().Flash().Infof("Rollout restart in progress for `%s...", path)
|
||||
}
|
||||
}
|
||||
}, func() {})
|
||||
|
||||
|
|
@ -54,7 +60,6 @@ func (r *RestartExtender) restartRollout(path string) error {
|
|||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s, ok := res.(dao.Restartable)
|
||||
if !ok {
|
||||
return errors.New("resource is not restartable")
|
||||
|
|
|
|||
Loading…
Reference in New Issue