parent
f0ae348b02
commit
43d62f9528
2
go.mod
2
go.mod
|
|
@ -2,6 +2,8 @@ module github.com/derailed/k9s
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
|
replace github.com/derailed/tview => /Users/fernand/go_wk/derailed/src/github.com/derailed/tview
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/docker => github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf
|
github.com/docker/docker => github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf
|
||||||
k8s.io/api => k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
|
k8s.io/api => k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,11 @@ func join(a []string, sep string) string {
|
||||||
return buff.String()
|
return buff.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToPerc prints a number as percentage.
|
||||||
|
func ToPerc(f float64) string {
|
||||||
|
return AsPerc(f) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
// AsPerc prints a number as a percentage.
|
// AsPerc prints a number as a percentage.
|
||||||
func AsPerc(f float64) string {
|
func AsPerc(f float64) string {
|
||||||
return strconv.Itoa(int(f))
|
return strconv.Itoa(int(f))
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -61,9 +60,7 @@ func (c *Component) InputHandler() func(event *tcell.EventKey, setFocus func(p t
|
||||||
return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||||
switch key := event.Key(); key {
|
switch key := event.Key(); key {
|
||||||
case tcell.KeyEnter:
|
case tcell.KeyEnter:
|
||||||
log.Debug().Msgf("YO %s ENTER!!", c.id)
|
|
||||||
case tcell.KeyBacktab, tcell.KeyTab:
|
case tcell.KeyBacktab, tcell.KeyTab:
|
||||||
log.Debug().Msgf("YO %s TAB!!", c.id)
|
|
||||||
if c.blur != nil {
|
if c.blur != nil {
|
||||||
c.blur(key)
|
c.blur(key)
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +110,8 @@ func (c *Component) GetSeriesColorNames() []string {
|
||||||
func (c *Component) colorForSeries() (tcell.Color, tcell.Color) {
|
func (c *Component) colorForSeries() (tcell.Color, tcell.Color) {
|
||||||
c.mx.RLock()
|
c.mx.RLock()
|
||||||
defer c.mx.RUnlock()
|
defer c.mx.RUnlock()
|
||||||
if len(c.seriesColors) > 1 {
|
|
||||||
|
if len(c.seriesColors) == 2 {
|
||||||
return c.seriesColors[0], c.seriesColors[1]
|
return c.seriesColors[0], c.seriesColors[1]
|
||||||
}
|
}
|
||||||
return okColor, faultColor
|
return okColor, faultColor
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package tchart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/tview"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComponentAsRect(t *testing.T) {
|
||||||
|
c := NewComponent("fred")
|
||||||
|
r := image.Rectangle{Min: image.Point{X: 0, Y: 0}, Max: image.Point{X: 15, Y: 10}}
|
||||||
|
|
||||||
|
assert.Equal(t, r, c.asRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComponentColorForSeries(t *testing.T) {
|
||||||
|
c := NewComponent("fred")
|
||||||
|
okC, errC := c.colorForSeries()
|
||||||
|
|
||||||
|
assert.Equal(t, tview.Styles.PrimaryTextColor, okC)
|
||||||
|
assert.Equal(t, tview.Styles.FocusColor, errC)
|
||||||
|
assert.Equal(t, []string{"white", "green"}, c.GetSeriesColorNames())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package tchart_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/tchart"
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCoSeriesColorNames(t *testing.T) {
|
||||||
|
c := tchart.NewComponent("fred")
|
||||||
|
|
||||||
|
c.SetSeriesColors(tcell.ColorGreen, tcell.ColorBlue)
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"green", "blue"}, c.GetSeriesColorNames())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComponentAsRect(t *testing.T) {
|
||||||
|
c := tchart.NewComponent("fred")
|
||||||
|
|
||||||
|
c.SetSeriesColors(tcell.ColorGreen, tcell.ColorBlue)
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"green", "blue"}, c.GetSeriesColorNames())
|
||||||
|
}
|
||||||
|
|
@ -38,12 +38,12 @@ func TestSegmentFor(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDial(t *testing.T) {
|
func TestDial5x3(t *testing.T) {
|
||||||
d := tchart.NewDotMatrix(5, 3)
|
d := tchart.NewDotMatrix(5, 3)
|
||||||
for n := 0; n <= 9; n++ {
|
for n := 0; n <= 9; n++ {
|
||||||
i := n
|
i := n
|
||||||
t.Run(strconv.Itoa(n), func(t *testing.T) {
|
t.Run(strconv.Itoa(n), func(t *testing.T) {
|
||||||
assert.Equal(t, numbers[i], d.Print(i))
|
assert.Equal(t, numbers3x5[i], d.Print(i))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ func TestDial(t *testing.T) {
|
||||||
|
|
||||||
const hChar, vChar = '▤', '▥'
|
const hChar, vChar = '▤', '▥'
|
||||||
|
|
||||||
var numbers = []tchart.Matrix{
|
var numbers3x5 = []tchart.Matrix{
|
||||||
[][]rune{
|
[][]rune{
|
||||||
{hChar, hChar, hChar},
|
{hChar, hChar, hChar},
|
||||||
{vChar, ' ', vChar},
|
{vChar, ' ', vChar},
|
||||||
|
|
|
||||||
|
|
@ -52,33 +52,6 @@ func (g *Gauge) Add(m Metric) {
|
||||||
g.data = m
|
g.data = m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int, dn delta, ns string, style tcell.Style) {
|
|
||||||
c1, _ := g.colorForSeries()
|
|
||||||
if ok {
|
|
||||||
o.X -= 1
|
|
||||||
style = style.Foreground(c1)
|
|
||||||
printDelta(sc, dn, o, style)
|
|
||||||
o.X += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
dm, sig := NewDotMatrix(5, 3), n == 0
|
|
||||||
if n == 0 {
|
|
||||||
style = g.dimmed
|
|
||||||
}
|
|
||||||
for i := 0; i < len(ns); i++ {
|
|
||||||
if ns[i] == '0' && !sig {
|
|
||||||
g.drawDial(sc, dm.Print(int(ns[i]-48)), o, g.dimmed)
|
|
||||||
} else {
|
|
||||||
sig = true
|
|
||||||
g.drawDial(sc, dm.Print(int(ns[i]-48)), o, style)
|
|
||||||
}
|
|
||||||
o.X += 5
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
printDelta(sc, dn, o, style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw draws the primitive.
|
// Draw draws the primitive.
|
||||||
func (g *Gauge) Draw(sc tcell.Screen) {
|
func (g *Gauge) Draw(sc tcell.Screen) {
|
||||||
g.Component.Draw(sc)
|
g.Component.Draw(sc)
|
||||||
|
|
@ -87,7 +60,7 @@ func (g *Gauge) Draw(sc tcell.Screen) {
|
||||||
defer g.mx.RUnlock()
|
defer g.mx.RUnlock()
|
||||||
|
|
||||||
rect := g.asRect()
|
rect := g.asRect()
|
||||||
mid := image.Point{X: rect.Min.X + rect.Dx()/2 - 2, Y: rect.Min.Y + rect.Dy()/2 - 2}
|
mid := image.Point{X: rect.Min.X + rect.Dx()/2 - 1, Y: rect.Min.Y + rect.Dy()/2 - 2}
|
||||||
|
|
||||||
style := tcell.StyleDefault.Background(g.bgColor)
|
style := tcell.StyleDefault.Background(g.bgColor)
|
||||||
style = style.Foreground(tcell.ColorYellow)
|
style = style.Foreground(tcell.ColorYellow)
|
||||||
|
|
@ -129,6 +102,33 @@ func (g *Gauge) drawDial(sc tcell.Screen, m Matrix, o image.Point, style tcell.S
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int, dn delta, ns string, style tcell.Style) {
|
||||||
|
c1, _ := g.colorForSeries()
|
||||||
|
if ok {
|
||||||
|
o.X -= 1
|
||||||
|
style = style.Foreground(c1)
|
||||||
|
printDelta(sc, dn, o, style)
|
||||||
|
o.X += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
dm, sig := NewDotMatrix(5, 3), n == 0
|
||||||
|
if n == 0 {
|
||||||
|
style = g.dimmed
|
||||||
|
}
|
||||||
|
for i := 0; i < len(ns); i++ {
|
||||||
|
if ns[i] == '0' && !sig {
|
||||||
|
g.drawDial(sc, dm.Print(int(ns[i]-48)), o, g.dimmed)
|
||||||
|
} else {
|
||||||
|
sig = true
|
||||||
|
g.drawDial(sc, dm.Print(int(ns[i]-48)), o, style)
|
||||||
|
}
|
||||||
|
o.X += 5
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
printDelta(sc, dn, o, style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package tchart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComputeDeltas(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
d1, d2 int
|
||||||
|
e delta
|
||||||
|
}{
|
||||||
|
"same": {
|
||||||
|
e: DeltaSame,
|
||||||
|
},
|
||||||
|
"more": {
|
||||||
|
d1: 10,
|
||||||
|
d2: 20,
|
||||||
|
e: DeltaMore,
|
||||||
|
},
|
||||||
|
"less": {
|
||||||
|
d1: 20,
|
||||||
|
d2: 10,
|
||||||
|
e: DeltaLess,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, computeDelta(u.d1, u.d2))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package tchart_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/tchart"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricsMaxDigits(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
m tchart.Metric
|
||||||
|
e int
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
e: 1,
|
||||||
|
},
|
||||||
|
"oks": {
|
||||||
|
m: tchart.Metric{OK: 100, Fault: 10},
|
||||||
|
e: 3,
|
||||||
|
},
|
||||||
|
"errs": {
|
||||||
|
m: tchart.Metric{OK: 10, Fault: 1000},
|
||||||
|
e: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, u.m.MaxDigits())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetricsMax(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
m tchart.Metric
|
||||||
|
e int
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
e: 0,
|
||||||
|
},
|
||||||
|
"max_ok": {
|
||||||
|
m: tchart.Metric{OK: 100, Fault: 10},
|
||||||
|
e: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, u.m.Max())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package tchart
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
|
|
@ -24,13 +25,18 @@ type Metric struct {
|
||||||
OK, Fault int
|
OK, Fault int
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxDigits returns the max of the metric.
|
// MaxDigits returns the max series number of digits.
|
||||||
func (m Metric) MaxDigits() int {
|
func (m Metric) MaxDigits() int {
|
||||||
max := int(math.Max(float64(m.OK), float64(m.Fault)))
|
|
||||||
s := fmt.Sprintf("%d", max)
|
s := fmt.Sprintf("%d", m.Max())
|
||||||
return len(s)
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
// Sum returns the sum of the metrics.
|
// Sum returns the sum of the metrics.
|
||||||
func (m Metric) Sum() int {
|
func (m Metric) Sum() int {
|
||||||
return m.OK + m.Fault
|
return m.OK + m.Fault
|
||||||
|
|
@ -41,15 +47,22 @@ type SparkLine struct {
|
||||||
*Component
|
*Component
|
||||||
|
|
||||||
data []Metric
|
data []Metric
|
||||||
|
multiSeries bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSparkLine returns a new graph.
|
// NewSparkLine returns a new graph.
|
||||||
func NewSparkLine(id string) *SparkLine {
|
func NewSparkLine(id string) *SparkLine {
|
||||||
return &SparkLine{
|
return &SparkLine{
|
||||||
Component: NewComponent(id),
|
Component: NewComponent(id),
|
||||||
|
multiSeries: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSingleSeries indicates multi series are in effect or not.
|
||||||
|
func (s *SparkLine) SetMultiSeries(b bool) {
|
||||||
|
s.multiSeries = b
|
||||||
|
}
|
||||||
|
|
||||||
// Add adds a metric.
|
// Add adds a metric.
|
||||||
func (s *SparkLine) Add(m Metric) {
|
func (s *SparkLine) Add(m Metric) {
|
||||||
s.mx.Lock()
|
s.mx.Lock()
|
||||||
|
|
@ -84,13 +97,17 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
|
||||||
idx = len(s.data) - rect.Dx()/2
|
idx = len(s.data) - rect.Dx()/2
|
||||||
}
|
}
|
||||||
|
|
||||||
scale := float64(len(sparks)) * float64((rect.Dy() - pad)) / float64(max)
|
factor := 2
|
||||||
|
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
|
||||||
s.drawBlock(screen, cX, cY, b.oks, c1)
|
cY = s.drawBlock(rect, screen, cX, cY, b.oks, c1)
|
||||||
s.drawBlock(screen, cX, cY, b.errs, c2)
|
_ = s.drawBlock(rect, screen, cX, cY, b.errs, c2)
|
||||||
cX += 2
|
cX += 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,18 +120,27 @@ func (s *SparkLine) Draw(screen tcell.Screen) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SparkLine) drawBlock(screen tcell.Screen, x, y int, b block, c tcell.Color) {
|
func (s *SparkLine) drawBlock(r image.Rectangle, screen tcell.Screen, x, y int, b block, c tcell.Color) int {
|
||||||
style := tcell.StyleDefault.Foreground(c).Background(s.bgColor)
|
style := tcell.StyleDefault.Foreground(c).Background(s.bgColor)
|
||||||
|
|
||||||
|
zeroY := r.Max.Y - r.Dy()
|
||||||
for i := 0; i < b.full; i++ {
|
for i := 0; i < b.full; i++ {
|
||||||
screen.SetContent(x, y, sparks[len(sparks)-1], nil, style)
|
screen.SetContent(x, y, sparks[len(sparks)-1], nil, style)
|
||||||
y--
|
y--
|
||||||
|
if y <= zeroY {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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) {
|
||||||
if width <= 0 || len(s.data) == 0 {
|
if width <= 0 || len(s.data) == 0 {
|
||||||
return
|
return
|
||||||
|
|
@ -128,8 +154,9 @@ func (s *SparkLine) cutSet(width int) {
|
||||||
func (s *SparkLine) computeMax() int {
|
func (s *SparkLine) computeMax() int {
|
||||||
var max int
|
var max int
|
||||||
for _, d := range s.data {
|
for _, d := range s.data {
|
||||||
if max < d.OK {
|
m := d.Max()
|
||||||
max = d.OK
|
if max < m {
|
||||||
|
max = m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,18 +167,17 @@ 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, false, scale), errs: makeBlocks(m.Fault, true, scale)}
|
return blocks{oks: makeBlocks(m.OK, scale), errs: makeBlocks(m.Fault, scale)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeBlocks(v int, isErr bool, scale float64) block {
|
func makeBlocks(v int, scale float64) block {
|
||||||
scaled := int(math.Round(float64(v) * scale))
|
scaled := int(math.Round(float64(v) * scale))
|
||||||
part, b := scaled%len(sparks), block{full: scaled / len(sparks)}
|
p, b := scaled%len(sparks), block{full: scaled / len(sparks)}
|
||||||
// Err might get scaled way down if so nudge.
|
if b.full == 0 && v > 0 && p == 0 {
|
||||||
if v > 0 && isErr && scaled == 0 {
|
p = 4
|
||||||
part = 1
|
|
||||||
}
|
}
|
||||||
if part > 0 {
|
if v > 0 && p >= 0 && p < len(sparks) {
|
||||||
b.partial = sparks[part-1]
|
b.partial = sparks[p]
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
package tchart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCutSet(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
mm []Metric
|
||||||
|
w, e int
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
w: 10,
|
||||||
|
e: 0,
|
||||||
|
},
|
||||||
|
"at": {
|
||||||
|
mm: make([]Metric, 10),
|
||||||
|
w: 10,
|
||||||
|
e: 10,
|
||||||
|
},
|
||||||
|
"under": {
|
||||||
|
mm: make([]Metric, 5),
|
||||||
|
w: 10,
|
||||||
|
e: 5,
|
||||||
|
},
|
||||||
|
"over": {
|
||||||
|
mm: make([]Metric, 10),
|
||||||
|
w: 5,
|
||||||
|
e: 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
s := NewSparkLine("s")
|
||||||
|
assert.False(t, s.IsDial())
|
||||||
|
|
||||||
|
for _, m := range u.mm {
|
||||||
|
s.Add(m)
|
||||||
|
}
|
||||||
|
s.cutSet(u.w)
|
||||||
|
assert.Equal(t, u.e, len(s.data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToBlocks(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
m Metric
|
||||||
|
s float64
|
||||||
|
e blocks
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
e: blocks{},
|
||||||
|
},
|
||||||
|
"max_ok": {
|
||||||
|
m: Metric{OK: 100, Fault: 10},
|
||||||
|
s: 0.5,
|
||||||
|
e: blocks{
|
||||||
|
oks: block{full: 6, partial: sparks[2]},
|
||||||
|
errs: block{full: 0, partial: sparks[5]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"max_fault": {
|
||||||
|
m: Metric{OK: 10, Fault: 100},
|
||||||
|
s: 0.5,
|
||||||
|
e: blocks{
|
||||||
|
oks: block{full: 0, partial: sparks[5]},
|
||||||
|
errs: block{full: 6, partial: sparks[2]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"over": {
|
||||||
|
m: Metric{OK: 22, Fault: 999},
|
||||||
|
s: float64(8*20) / float64(999),
|
||||||
|
e: blocks{
|
||||||
|
oks: block{full: 0, partial: sparks[4]},
|
||||||
|
errs: block{full: 20, partial: sparks[0]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, toBlocks(u.m, u.s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeMax(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
mm []Metric
|
||||||
|
e int
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
e: 0,
|
||||||
|
},
|
||||||
|
"max_ok": {
|
||||||
|
mm: []Metric{{OK: 100, Fault: 10}},
|
||||||
|
e: 100,
|
||||||
|
},
|
||||||
|
"max_fault": {
|
||||||
|
mm: []Metric{{OK: 100, Fault: 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},
|
||||||
|
},
|
||||||
|
e: 1040,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
s := NewSparkLine("s")
|
||||||
|
for _, m := range u.mm {
|
||||||
|
s.Add(m)
|
||||||
|
}
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, s.computeMax())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ func (s *StatusIndicator) StylesChanged(styles *config.Styles) {
|
||||||
s.SetTextColor(styles.FgColor())
|
s.SetTextColor(styles.FgColor())
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusIndicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%"
|
const statusIndicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s[white::]::[darkturquoise::]%s"
|
||||||
|
|
||||||
// ClusterInfoUpdated notifies the cluster meta was updated.
|
// ClusterInfoUpdated notifies the cluster meta was updated.
|
||||||
func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) {
|
func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) {
|
||||||
|
|
@ -56,8 +56,8 @@ func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) {
|
||||||
data.Cluster,
|
data.Cluster,
|
||||||
data.User,
|
data.User,
|
||||||
data.K8sVer,
|
data.K8sVer,
|
||||||
render.AsPerc(data.Cpu),
|
render.ToPerc(data.Cpu),
|
||||||
render.AsPerc(data.Mem),
|
render.ToPerc(data.Mem),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ type SelectTable struct {
|
||||||
*tview.Table
|
*tview.Table
|
||||||
|
|
||||||
model Tabular
|
model Tabular
|
||||||
selectedRow int
|
|
||||||
selectedFn func(string) string
|
selectedFn func(string) string
|
||||||
marks map[string]struct{}
|
marks map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +68,8 @@ func (s *SelectTable) GetSelectedItem() string {
|
||||||
|
|
||||||
// GetSelectedCell returns the content of a cell for the currently selected row.
|
// GetSelectedCell returns the content of a cell for the currently selected row.
|
||||||
func (s *SelectTable) GetSelectedCell(col int) string {
|
func (s *SelectTable) GetSelectedCell(col int) string {
|
||||||
return TrimCell(s, s.selectedRow, col)
|
r, _ := s.GetSelection()
|
||||||
|
return TrimCell(s, r, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSelectedFn defines a function that cleanse the current selection.
|
// SetSelectedFn defines a function that cleanse the current selection.
|
||||||
|
|
@ -79,7 +79,8 @@ func (s *SelectTable) SetSelectedFn(f func(string) string) {
|
||||||
|
|
||||||
// GetSelectedRowIndex fetch the currently selected row index.
|
// GetSelectedRowIndex fetch the currently selected row index.
|
||||||
func (s *SelectTable) GetSelectedRowIndex() int {
|
func (s *SelectTable) GetSelectedRowIndex() int {
|
||||||
return s.selectedRow
|
r, _ := s.GetSelection()
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectRow select a given row by index.
|
// SelectRow select a given row by index.
|
||||||
|
|
@ -93,14 +94,14 @@ func (s *SelectTable) SelectRow(r int, broadcast bool) {
|
||||||
|
|
||||||
// UpdateSelection refresh selected row.
|
// UpdateSelection refresh selected row.
|
||||||
func (s *SelectTable) updateSelection(broadcast bool) {
|
func (s *SelectTable) updateSelection(broadcast bool) {
|
||||||
s.SelectRow(s.selectedRow, broadcast)
|
r, _ := s.GetSelection()
|
||||||
|
s.SelectRow(r, broadcast)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SelectTable) selectionChanged(r, c int) {
|
func (s *SelectTable) selectionChanged(r, c int) {
|
||||||
if r < 0 {
|
if r < 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.selectedRow = r
|
|
||||||
cell := s.GetCell(r, c)
|
cell := s.GetCell(r, c)
|
||||||
s.SetSelectedStyle(tcell.ColorBlack, cell.Color, tcell.AttrBold)
|
s.SetSelectedStyle(tcell.ColorBlack, cell.Color, tcell.AttrBold)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ func NewTable(gvr client.GVR) *Table {
|
||||||
SelectTable: &SelectTable{
|
SelectTable: &SelectTable{
|
||||||
Table: tview.NewTable(),
|
Table: tview.NewTable(),
|
||||||
model: model.NewTable(gvr),
|
model: model.NewTable(gvr),
|
||||||
selectedRow: 1,
|
|
||||||
marks: make(map[string]struct{}),
|
marks: make(map[string]struct{}),
|
||||||
},
|
},
|
||||||
gvr: gvr,
|
gvr: gvr,
|
||||||
|
|
@ -71,7 +70,7 @@ func (t *Table) Init(ctx context.Context) {
|
||||||
t.SetSelectable(true, false)
|
t.SetSelectable(true, false)
|
||||||
t.SetSelectionChangedFunc(t.selectionChanged)
|
t.SetSelectionChangedFunc(t.selectionChanged)
|
||||||
t.SetBackgroundColor(tcell.ColorDefault)
|
t.SetBackgroundColor(tcell.ColorDefault)
|
||||||
|
t.Select(1, 0)
|
||||||
t.hasMetrics = false
|
t.hasMetrics = false
|
||||||
if mx, ok := ctx.Value(internal.KeyHasMetrics).(bool); ok {
|
if mx, ok := ctx.Value(internal.KeyHasMetrics).(bool); ok {
|
||||||
t.hasMetrics = mx
|
t.hasMetrics = mx
|
||||||
|
|
|
||||||
|
|
@ -159,10 +159,10 @@ func (h *Help) showNav() model.MenuHints {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-b",
|
Mnemonic: "Ctrl-b",
|
||||||
Description: "Page Down"},
|
Description: "Page Up"},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-f",
|
Mnemonic: "Ctrl-f",
|
||||||
Description: "Page Up",
|
Description: "Page Down",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "h",
|
Mnemonic: "h",
|
||||||
|
|
|
||||||
|
|
@ -80,25 +80,26 @@ func (p *Pulse) Init(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
p.charts = []Grapheable{
|
p.charts = []Grapheable{
|
||||||
p.makeGA(image.Point{X: 0, Y: 0}, image.Point{X: 4, Y: 2}, "apps/v1/deployments"),
|
p.makeGA(image.Point{X: 0, Y: 0}, image.Point{X: 3, Y: 2}, "apps/v1/deployments"),
|
||||||
p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 4, Y: 2}, "apps/v1/replicasets"),
|
p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 3, Y: 2}, "apps/v1/replicasets"),
|
||||||
p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 4, Y: 2}, "apps/v1/statefulsets"),
|
p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 3, Y: 2}, "apps/v1/statefulsets"),
|
||||||
p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 4, Y: 2}, "apps/v1/daemonsets"),
|
p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 3, Y: 2}, "apps/v1/daemonsets"),
|
||||||
p.makeSP(image.Point{X: 4, Y: 0}, image.Point{X: 3, Y: 4}, "v1/pods"),
|
p.makeSP(true, image.Point{X: 3, Y: 0}, image.Point{X: 3, Y: 4}, "v1/pods"),
|
||||||
p.makeSP(image.Point{X: 4, Y: 4}, image.Point{X: 3, Y: 4}, "v1/events"),
|
p.makeSP(true, image.Point{X: 3, Y: 4}, image.Point{X: 3, Y: 4}, "v1/events"),
|
||||||
p.makeSP(image.Point{X: 7, Y: 0}, image.Point{X: 3, Y: 4}, "batch/v1/jobs"),
|
p.makeSP(true, image.Point{X: 6, Y: 0}, image.Point{X: 3, Y: 4}, "batch/v1/jobs"),
|
||||||
p.makeSP(image.Point{X: 7, Y: 4}, image.Point{X: 3, Y: 4}, "v1/persistentvolumes"),
|
p.makeSP(true, image.Point{X: 6, Y: 4}, image.Point{X: 3, Y: 4}, "v1/persistentvolumes"),
|
||||||
}
|
}
|
||||||
if p.app.Conn().HasMetrics() {
|
if p.app.Conn().HasMetrics() {
|
||||||
p.charts = append(p.charts,
|
p.charts = append(p.charts,
|
||||||
p.makeSP(image.Point{X: 10, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"),
|
p.makeSP(false, image.Point{X: 9, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"),
|
||||||
p.makeSP(image.Point{X: 10, Y: 4}, image.Point{X: 2, Y: 4}, "mem"),
|
p.makeSP(false, image.Point{X: 9, Y: 4}, image.Point{X: 2, Y: 4}, "mem"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
p.bindKeys()
|
p.bindKeys()
|
||||||
p.model.AddListener(p)
|
p.model.AddListener(p)
|
||||||
p.app.SetFocus(p.charts[0])
|
p.app.SetFocus(p.charts[0])
|
||||||
p.app.Styles.AddListener(p)
|
p.app.Styles.AddListener(p)
|
||||||
|
p.StylesChanged(p.app.Styles)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -135,9 +136,9 @@ func (p *Pulse) PulseChanged(c *health.Check) {
|
||||||
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)))
|
v.SetLegend(fmt.Sprintf(" %s(%dm)", strings.Title(gvr.R()), c.Tally(health.OK)))
|
||||||
case "mem":
|
case "mem":
|
||||||
v.SetLegend(fmt.Sprintf(" %s - %dMi", strings.Title(gvr.R()), c.Tally(health.OK)))
|
v.SetLegend(fmt.Sprintf(" %s(%dMi)", strings.Title(gvr.R()), c.Tally(health.OK)))
|
||||||
default:
|
default:
|
||||||
nn := v.GetSeriesColorNames()
|
nn := v.GetSeriesColorNames()
|
||||||
if c.Tally(health.OK) == 0 {
|
if c.Tally(health.OK) == 0 {
|
||||||
|
|
@ -146,7 +147,7 @@ func (p *Pulse) PulseChanged(c *health.Check) {
|
||||||
if c.Tally(health.Toast) == 0 {
|
if c.Tally(health.Toast) == 0 {
|
||||||
nn[1] = "gray"
|
nn[1] = "gray"
|
||||||
}
|
}
|
||||||
v.SetLegend(fmt.Sprintf(" %s - [%s::]%d/[%s::b]%d[-::]",
|
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.OK),
|
||||||
|
|
@ -296,7 +297,7 @@ func (p *Pulse) nextFocusCmd(direction int) func(evt *tcell.EventKey) *tcell.Eve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.SparkLine {
|
func (p *Pulse) makeSP(multi bool, 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)
|
||||||
|
|
@ -307,6 +308,9 @@ func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.Sp
|
||||||
}
|
}
|
||||||
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(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
|
||||||
|
|
@ -323,7 +327,7 @@ func (p *Pulse) makeGA(loc image.Point, span image.Point, gvr string) *tchart.Ga
|
||||||
}
|
}
|
||||||
g.SetLegend(fmt.Sprintf(" %s ", strings.Title(client.NewGVR(gvr).R())))
|
g.SetLegend(fmt.Sprintf(" %s ", strings.Title(client.NewGVR(gvr).R())))
|
||||||
g.SetInputCapture(p.keyboard)
|
g.SetInputCapture(p.keyboard)
|
||||||
p.AddItem(g, loc.X, loc.Y, span.X, span.Y, 0, 0, true)
|
p.AddItem(g, loc.X, loc.Y, span.X, span.Y, span.X, span.Y, true)
|
||||||
|
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue