rework thresholds. Fix #604 #601 #598 #593

mine
derailed 2020-03-06 10:03:47 -07:00
parent 9427f2a3db
commit 391eed9ea4
11 changed files with 152 additions and 141 deletions

View File

@ -0,0 +1,47 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.17.5
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please consider sponsoring 👆us or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/story/this_is_fine_300.png" align="center" width="500" height="auto"/>
## Thresholds Reloaded!
In the previous k9s release, we've introduced the notion of thresholds to provide with an alert mechanism when either the cpu or memory goes high on your clusters. Looking at the current solution, we felt we needed a bit more granularity in the severity levels thanks to [Eldad Assis](https://github.com/eldada) feedback on this one! So here is the new configuration for cluster thresholds. Please keep in mind this feature is still in flux!
```yaml
# $HOME/.k9s/config.yml
k9s:
refreshRate: 2
headless: false
...
# Specify resources thresholds in percent - defaults: critical=90, warn=70
thresholds:
cpu:
critical: 85
warn: 75
memory:
critical: 80
warn: 70
...
```
## Resolved Bugs/Features/PRs
- [Issue #604](https://github.com/derailed/k9s/issues/604)
- [Issue #601](https://github.com/derailed/k9s/issues/601) Thank you [Christian Vent](https://github.com/christian-vent)
- [Issue #598](https://github.com/derailed/k9s/issues/598) `Ctrl-l` will now trigger the benchmarking toggle!
- [Issue #593](https://github.com/derailed/k9s/issues/593)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -305,6 +305,7 @@ func (a *APIClient) reset() {
a.cache = cache.NewLRUExpireCache(cacheSize) a.cache = cache.NewLRUExpireCache(cacheSize)
a.client, a.dClient, a.nsClient, a.mxsClient = nil, nil, nil, nil a.client, a.dClient, a.nsClient, a.mxsClient = nil, nil, nil, nil
a.cachedClient = nil
} }
func (a *APIClient) supportsMetricsResources() (supported bool) { func (a *APIClient) supportsMetricsResources() (supported bool) {

View File

@ -299,17 +299,11 @@ var expectedConfig = `k9s:
active: ctx active: ctx
thresholds: thresholds:
cpu: cpu:
defcon: critical: 90
- 90 warn: 70
- 80
- 75
- 70
memory: memory:
defcon: critical: 90
- 90 warn: 70
- 80
- 75
- 70
` `
var resetConfig = `k9s: var resetConfig = `k9s:
@ -331,15 +325,9 @@ var resetConfig = `k9s:
active: po active: po
thresholds: thresholds:
cpu: cpu:
defcon: critical: 90
- 90 warn: 70
- 80
- 75
- 70
memory: memory:
defcon: critical: 90
- 90 warn: 70
- 80
- 75
- 70
` `

View File

@ -1,81 +1,63 @@
package config package config
import ( import (
"strings"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
) )
const ( const (
// DefCon1 tracks high severity. // SeverityLow tracks low severity.
DefCon1 DefConLevel = iota + 1 SeverityLow SeverityLevel = iota
// DefCon2 tracks warn level. // SeverityMedium tracks medium severity level.
DefCon2 SeverityMedium
// DefCon3 tracks medium level. // SeverityHigh tracks high severity level.
DefCon3 SeverityHigh
// DefCon4 tracks low level.
DefCon4
// DefCon5 tracks all cool.
DefCon5
) )
// DefConLevel tracks defcon severity. // SeverityLevel tracks severity levels.
type DefConLevel int type SeverityLevel int
// DefCon tracks a resource alert level. // Severity tracks a resource severity levels.
type DefCon struct { type Severity struct {
Levels []int `yaml:"defcon,omitempty"` Critical int `yaml:"critical"`
Warn int `yaml:"warn"`
} }
// NewDefCon returns a new instance. // NewSeverity returns a new instance.
func NewDefCon() *DefCon { func NewSeverity() *Severity {
return &DefCon{Levels: []int{90, 80, 75, 70}} return &Severity{
Critical: 90,
Warn: 70,
}
} }
// Validate checks all thresholds and make sure we're cool. If not use defaults. // Validate checks all thresholds and make sure we're cool. If not use defaults.
func (d *DefCon) Validate() { func (s *Severity) Validate() {
norm := NewDefCon() norm := NewSeverity()
if len(d.Levels) < 4 { if !validateRange(s.Warn) {
d.Levels = norm.Levels s.Warn = norm.Warn
return
}
for i, level := range d.Levels {
if !d.isValidRange(level) {
d.Levels[i] = norm.Levels[i]
} }
if !validateRange(s.Critical) {
s.Critical = norm.Critical
} }
} }
// String returns defcon settings a string. func validateRange(v int) bool {
func (d *DefCon) String() string { if v <= 0 || v > 100 {
ss := make([]string, len(d.Levels))
for i := 0; i < len(d.Levels); i++ {
ss[i] = render.PrintPerc(d.Levels[i])
}
return strings.Join(ss, "|")
}
func (d *DefCon) isValidRange(v int) bool {
if v < 0 || v > 100 {
return false return false
} }
return true return true
} }
// Threshold tracks threshold to alert user when excided. // Threshold tracks threshold to alert user when excided.
type Threshold map[string]*DefCon type Threshold map[string]*Severity
// NewThreshold returns a new threshold. // NewThreshold returns a new threshold.
func NewThreshold() Threshold { func NewThreshold() Threshold {
return Threshold{ return Threshold{
"cpu": NewDefCon(), "cpu": NewSeverity(),
"memory": NewDefCon(), "memory": NewSeverity(),
} }
} }
@ -84,7 +66,7 @@ func (t Threshold) Validate(c client.Connection, ks KubeSettings) {
for _, k := range []string{"cpu", "memory"} { for _, k := range []string{"cpu", "memory"} {
v, ok := t[k] v, ok := t[k]
if !ok { if !ok {
t[k] = NewDefCon() t[k] = NewSeverity()
} else { } else {
v.Validate() v.Validate()
} }
@ -92,48 +74,29 @@ func (t Threshold) Validate(c client.Connection, ks KubeSettings) {
} }
// DefConFor returns a defcon level for the current state. // DefConFor returns a defcon level for the current state.
func (t Threshold) DefConFor(k string, v int) DefConLevel { func (t Threshold) LevelFor(k string, v int) SeverityLevel {
dc, ok := t[k] s, ok := t[k]
if !ok || v < 0 || v > 100 { if !ok || v < 0 || v > 100 {
return DefCon5 return SeverityLow
} }
for i, l := range dc.Levels { if v >= s.Critical {
if v >= l { return SeverityHigh
return dcLevelFor(i)
} }
if v >= s.Warn {
return SeverityMedium
} }
return DefCon5 return SeverityLow
} }
// DefConColorFor returns an defcon level associated level. // DefConColorFor returns an defcon level associated level.
func (t *Threshold) DefConColorFor(k string, v int) string { func (t *Threshold) SeverityColor(k string, v int) string {
switch t.DefConFor(k, v) { switch t.LevelFor(k, v) {
case DefCon1: case SeverityHigh:
return "red" return "red"
case DefCon2: case SeverityMedium:
return "orangered" return "orangered"
case DefCon3:
return "orange"
default: default:
return "green" return "green"
} }
} }
// ----------------------------------------------------------------------------
// Helpers...
func dcLevelFor(l int) DefConLevel {
switch l {
case 0:
return DefCon1
case 1:
return DefCon2
case 2:
return DefCon3
case 3:
return DefCon4
default:
return DefCon5
}
}

View File

@ -7,25 +7,25 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestDefConValidate(t *testing.T) { func TestSeverityValidate(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
d, e *config.DefCon d, e *config.Severity
}{ }{
"default": { "default": {
d: config.NewDefCon(), d: config.NewSeverity(),
e: config.NewDefCon(), e: config.NewSeverity(),
}, },
"toast": { "toast": {
d: &config.DefCon{Levels: []int{10}}, d: &config.Severity{Warn: 10},
e: config.NewDefCon(), e: &config.Severity{Warn: 10, Critical: 90},
}, },
"negative": { "negative": {
d: &config.DefCon{Levels: []int{-1, 10, 10, 10}}, d: &config.Severity{Warn: -1},
e: &config.DefCon{Levels: []int{90, 10, 10, 10}}, e: config.NewSeverity(),
}, },
"out-of-range": { "out-of-range": {
d: &config.DefCon{Levels: []int{150, 200, 10, 300}}, d: &config.Severity{Warn: 150},
e: &config.DefCon{Levels: []int{90, 80, 10, 70}}, e: config.NewSeverity(),
}, },
} }
@ -38,41 +38,41 @@ func TestDefConValidate(t *testing.T) {
} }
} }
func TestDefConFor(t *testing.T) { func TestLevelFor(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
k string k string
v int v int
e config.DefConLevel e config.SeverityLevel
}{ }{
"normal": { "normal": {
k: "cpu", k: "cpu",
v: 0, v: 0,
e: config.DefCon5, e: config.SeverityLow,
}, },
"4": { "4": {
k: "cpu", k: "cpu",
v: 71, v: 71,
e: config.DefCon4, e: config.SeverityMedium,
}, },
"3": { "3": {
k: "cpu", k: "cpu",
v: 75, v: 75,
e: config.DefCon3, e: config.SeverityMedium,
}, },
"2": { "2": {
k: "cpu", k: "cpu",
v: 80, v: 80,
e: config.DefCon2, e: config.SeverityMedium,
}, },
"1": { "1": {
k: "cpu", k: "cpu",
v: 100, v: 100,
e: config.DefCon1, e: config.SeverityHigh,
}, },
"over": { "over": {
k: "cpu", k: "cpu",
v: 150, v: 150,
e: config.DefCon5, e: config.SeverityLow,
}, },
} }
@ -80,7 +80,7 @@ func TestDefConFor(t *testing.T) {
for k := range uu { for k := range uu {
u := uu[k] u := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, o.DefConFor(u.k, u.v)) assert.Equal(t, u.e, o.LevelFor(u.k, u.v))
}) })
} }
} }

View File

@ -94,7 +94,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
} }
} }
var res []runtime.Object res := make([]runtime.Object, 0, len(oo))
for _, o := range oo { for _, o := range oo {
u, ok := o.(*unstructured.Unstructured) u, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {

View File

@ -284,7 +284,7 @@ func (b *Browser) editCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
ns, n := client.Namespaced(path) ns, n := client.Namespaced(path)
if ok, err := b.app.Conn().CanI(ns, b.GVR().String(), []string{"edit"}); !ok || err != nil { if ok, err := b.app.Conn().CanI(ns, b.GVR().String(), []string{"patch"}); !ok || err != nil {
b.App().Flash().Err(fmt.Errorf("Current user can't edit resource %s", b.GVR())) b.App().Flash().Err(fmt.Errorf("Current user can't edit resource %s", b.GVR()))
return nil return nil
} }

View File

@ -72,16 +72,16 @@ func (c *ClusterInfo) infoCell(t string) *tview.TableCell {
return cell return cell
} }
// ClusterInfoUpdated notifies the cluster meta was updated.
func (c *ClusterInfo) ClusterInfoUpdated(data model.ClusterMeta) {
c.ClusterInfoChanged(data, data)
}
func (c *ClusterInfo) setCell(row int, s string) int { func (c *ClusterInfo) setCell(row int, s string) int {
c.GetCell(row, 1).SetText(s) c.GetCell(row, 1).SetText(s)
return row + 1 return row + 1
} }
// ClusterInfoUpdated notifies the cluster meta was updated.
func (c *ClusterInfo) ClusterInfoUpdated(data model.ClusterMeta) {
c.ClusterInfoChanged(data, data)
}
// ClusterInfoChanged notifies the cluster meta was changed. // ClusterInfoChanged notifies the cluster meta was changed.
func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) { func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) {
c.app.QueueUpdateDraw(func() { c.app.QueueUpdateDraw(func() {
@ -101,20 +101,18 @@ func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) {
}) })
} }
const defconFmt = "Cluster <%s> at DEFCON %d" const defconFmt = "%s %s level!"
func (c *ClusterInfo) setDefCon(cpu, mem int) { func (c *ClusterInfo) setDefCon(cpu, mem int) {
var set bool var set bool
dc := c.app.Config.K9s.Thresholds.DefConFor("cpu", cpu) l := c.app.Config.K9s.Thresholds.LevelFor("cpu", cpu)
if dc < config.DefCon5 { if l > config.SeverityLow {
l := flashFromDefCon(dc) c.app.Status(flashLevel(l), fmt.Sprintf(defconFmt, flashMessage(l), "CPU"))
c.app.Status(l, fmt.Sprintf(defconFmt, "cpu", int(dc)))
set = true set = true
} }
dc = c.app.Config.K9s.Thresholds.DefConFor("memory", mem) l = c.app.Config.K9s.Thresholds.LevelFor("memory", mem)
if dc < config.DefCon5 { if l > config.SeverityLow {
l := flashFromDefCon(dc) c.app.Status(flashLevel(l), fmt.Sprintf(defconFmt, flashMessage(l), "Memory"))
c.app.Status(l, fmt.Sprintf(defconFmt, "mem", int(dc)))
set = true set = true
} }
if !set { if !set {
@ -131,13 +129,27 @@ func (c *ClusterInfo) updateStyle() {
} }
} }
func flashFromDefCon(l config.DefConLevel) model.FlashLevel { // ----------------------------------------------------------------------------
// Helpers...
func flashLevel(l config.SeverityLevel) model.FlashLevel {
switch l { switch l {
case config.DefCon1: case config.SeverityHigh:
return model.FlashErr return model.FlashErr
case config.DefCon2, config.DefCon3: case config.SeverityMedium:
return model.FlashWarn return model.FlashWarn
default: default:
return model.FlashInfo return model.FlashInfo
} }
} }
func flashMessage(l config.SeverityLevel) string {
switch l {
case config.SeverityHigh:
return "Critical"
case config.SeverityMedium:
return "Warning"
default:
return "OK"
}
}

View File

@ -48,7 +48,7 @@ func (p *PortForward) portForwardContext(ctx context.Context) context.Context {
func (p *PortForward) bindKeys(aa ui.KeyActions) { func (p *PortForward) bindKeys(aa ui.KeyActions) {
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("View Benchmarks", p.showBenchCmd, true), tcell.KeyEnter: ui.NewKeyAction("View Benchmarks", p.showBenchCmd, true),
tcell.KeyCtrlB: ui.NewKeyAction("Bench Run/Stop", p.toggleBenchCmd, true), tcell.KeyCtrlL: ui.NewKeyAction("Benchmark Run/Stop", p.toggleBenchCmd, true),
tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true), tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true),
ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.GetTable().SortColCmd("PORTS", true), false), ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.GetTable().SortColCmd("PORTS", true), false),
ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.GetTable().SortColCmd("URL", true), false), ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.GetTable().SortColCmd("URL", true), false),

View File

@ -159,7 +159,7 @@ func (p *Pulse) PulseChanged(c *health.Check) {
perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2)) perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2))
v.SetLegend(fmt.Sprintf(cpuFmt, v.SetLegend(fmt.Sprintf(cpuFmt,
strings.Title(gvr.R()), strings.Title(gvr.R()),
p.app.Config.K9s.Thresholds.DefConColorFor("cpu", perc), p.app.Config.K9s.Thresholds.SeverityColor("cpu", perc),
render.PrintPerc(perc), render.PrintPerc(perc),
nn[0], nn[0],
render.AsThousands(c.Tally(health.S1)), render.AsThousands(c.Tally(health.S1)),
@ -170,7 +170,7 @@ func (p *Pulse) PulseChanged(c *health.Check) {
perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2)) perc := client.ToPercentage(c.Tally(health.S1), c.Tally(health.S2))
v.SetLegend(fmt.Sprintf(memFmt, v.SetLegend(fmt.Sprintf(memFmt,
strings.Title(gvr.R()), strings.Title(gvr.R()),
p.app.Config.K9s.Thresholds.DefConColorFor("memory", perc), p.app.Config.K9s.Thresholds.SeverityColor("memory", perc),
render.PrintPerc(perc), render.PrintPerc(perc),
nn[0], nn[0],
render.AsThousands(c.Tally(health.S1)), render.AsThousands(c.Tally(health.S1)),