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