refactor + cleanup

mine
derailed 2019-06-18 23:52:33 -06:00
parent 81ef0ae0ec
commit 5005d8dc85
6 changed files with 290 additions and 251 deletions

View File

@ -235,6 +235,52 @@ func (v *benchView) hydrate() resource.TableData {
return data
}
func (v *benchView) getTV() *tableView {
if vu, ok := v.GetPrimitive("table").(*tableView); ok {
return vu
}
return nil
}
func (v *benchView) getDetails() *detailsView {
if vu, ok := v.GetPrimitive("details").(*detailsView); ok {
return vu
}
return nil
}
func (v *benchView) resetTitle() {
v.SetTitle(fmt.Sprintf(benchTitleFmt, benchTitle, v.getTV().GetRowCount()-1))
}
func (v *benchView) watchBenchDir(ctx context.Context) error {
w, err := fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
for {
select {
case evt := <-w.Events:
log.Debug().Msgf("Bench event %#v", evt)
v.app.QueueUpdateDraw(func() {
v.refresh()
})
case err := <-w.Errors:
log.Info().Err(err).Msg("Dir Watcher failed")
return
case <-ctx.Done():
log.Debug().Msg("!!!! FS WATCHER DONE!!")
w.Close()
return
}
}
}()
return w.Add(v.benchDir())
}
// ----------------------------------------------------------------------------
// Helpers...
@ -302,49 +348,3 @@ func augmentRow(fields resource.Row, data string) {
fields[col] = asNum(sum)
}
}
func (v *benchView) resetTitle() {
v.SetTitle(fmt.Sprintf(benchTitleFmt, benchTitle, v.getTV().GetRowCount()-1))
}
func (v *benchView) watchBenchDir(ctx context.Context) error {
w, err := fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
for {
select {
case evt := <-w.Events:
log.Debug().Msgf("Bench event %#v", evt)
v.app.QueueUpdateDraw(func() {
v.refresh()
})
case err := <-w.Errors:
log.Info().Err(err).Msg("Dir Watcher failed")
return
case <-ctx.Done():
log.Debug().Msg("!!!! FS WATCHER DONE!!")
w.Close()
return
}
}
}()
return w.Add(filepath.Join(K9sBenchDir, v.app.config.K9s.CurrentCluster))
}
func (v *benchView) getTV() *tableView {
if vu, ok := v.GetPrimitive("table").(*tableView); ok {
return vu
}
return nil
}
func (v *benchView) getDetails() *detailsView {
if vu, ok := v.GetPrimitive("details").(*detailsView); ok {
return vu
}
return nil
}

View File

@ -18,53 +18,56 @@ import (
const noSelection = ""
type updatable interface {
restartUpdates()
stopUpdates()
update(context.Context)
}
type (
updatable interface {
restartUpdates()
stopUpdates()
update(context.Context)
}
type resourceView struct {
*tview.Pages
masterDetail struct {
*tview.Pages
app *appView
title string
selectedItem string
selectedRow int
namespaces map[int]string
selectedNS string
list resource.List
enterFn enterFn
extraActionsFn func(keyActions)
selectedFn func() string
decorateFn decorateFn
colorerFn colorerFn
actions keyActions
nsListAccess bool
path *string
cancelFn context.CancelFunc
parentCtx context.Context
}
app *appView
actions keyActions
title string
selectedItem string
selectedRow int
selectedNS string
path *string
}
func (v *resourceView) filterResource(sel string) {
v.list.SetLabelSelector(sel)
v.refresh()
}
resourceView struct {
*masterDetail
namespaces map[int]string
list resource.List
enterFn enterFn
extraActionsFn func(keyActions)
selectedFn func() string
decorateFn decorateFn
colorerFn colorerFn
nsListAccess bool
cancelFn context.CancelFunc
parentCtx context.Context
}
)
func newResourceView(title string, app *appView, list resource.List) resourceViewer {
v := resourceView{
app: app,
title: title,
actions: make(keyActions),
list: list,
selectedNS: list.GetNamespace(),
Pages: tview.NewPages(),
masterDetail: &masterDetail{
Pages: tview.NewPages(),
app: app,
title: title,
actions: make(keyActions),
selectedNS: list.GetNamespace(),
},
list: list,
}
tv := newTableView(app, v.title)
tv.SetSelectionChangedFunc(v.selChanged)
tv.filterChanged(v.filterResource)
v.AddPage(v.list.GetName(), tv, true, true)
details := newDetailsView(app, v.backCmd)
@ -73,22 +76,6 @@ func newResourceView(title string, app *appView, list resource.List) resourceVie
return &v
}
func (v *resourceView) stopUpdates() {
if v.cancelFn != nil {
v.cancelFn()
}
}
func (v *resourceView) restartUpdates() {
if v.cancelFn != nil {
v.cancelFn()
}
var vctx context.Context
vctx, v.cancelFn = context.WithCancel(v.parentCtx)
v.update(vctx)
}
// Init watches all running pods in given namespace
func (v *resourceView) init(ctx context.Context, ns string) {
v.parentCtx = ctx
@ -128,6 +115,27 @@ func (v *resourceView) init(ctx context.Context, ns string) {
}
}
func (v *resourceView) filterResource(sel string) {
v.list.SetLabelSelector(sel)
v.refresh()
}
func (v *resourceView) stopUpdates() {
if v.cancelFn != nil {
v.cancelFn()
}
}
func (v *resourceView) restartUpdates() {
if v.cancelFn != nil {
v.cancelFn()
}
var vctx context.Context
vctx, v.cancelFn = context.WithCancel(v.parentCtx)
v.update(vctx)
}
func (v *resourceView) update(ctx context.Context) {
go func(ctx context.Context) {
for {

View File

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
@ -102,6 +103,51 @@ func (v *svcView) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
func trimCell(tv *tableView, row, col int) (string, error) {
c := tv.GetCell(row, tv.nameColIndex()+col)
if c == nil {
return "", fmt.Errorf("No cell at location [%d:%d]", row, col)
}
return strings.TrimSpace(c.Text), nil
}
func (v *svcView) checkSvc(row int) error {
svcType, err := trimCell(v.getTV(), row, 1)
if err != nil {
return err
}
if svcType != "NodePort" && svcType != "LoadBalancer" {
return errors.New("You must select a reachable service")
}
return nil
}
func (v *svcView) getExternalPort(row int) (string, error) {
ports, err := trimCell(v.getTV(), row, 5)
if err != nil {
return "", err
}
pp := strings.Split(ports, " ")
if len(pp) == 0 {
return "", errors.New("No ports found")
}
// Grap the first port pair for now...
tokens := strings.Split(pp[0], "►")
if len(tokens) < 2 {
return "", errors.New("No ports pair found")
}
return tokens[1], nil
}
func (v *svcView) reloadBenchCfg() error {
// BOZO!! Poorman Reload bench to make sure we pick up updates if any.
path := benchConfig(v.app.config.K9s.CurrentCluster)
return v.app.bench.Reload(path)
}
func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
@ -111,56 +157,45 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
v.app.flash().err(errors.New("Only one benchmark allowed at a time"))
return nil
}
sel := v.getSelectedItem()
tv := v.getTV()
r, _ := tv.GetSelection()
// BOZO!! Poorman Reload bench to make sure we pick up updates if any.
path := benchConfig(v.app.config.K9s.CurrentCluster)
if err := v.app.bench.Reload(path); err != nil {
if err := v.reloadBenchCfg(); err != nil {
log.Error().Err(err).Msg("Bench config reload")
v.app.flash().err(err)
return nil
}
sel := v.getSelectedItem()
cfg, ok := v.app.bench.Benchmarks.Services[sel]
if !ok {
v.app.flash().errf("No bench config found for service %s", sel)
return nil
}
svcType := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+1).Text)
if svcType != "NodePort" && svcType != "LoadBalancer" {
v.app.flash().err(errors.New("You must select a reachable service"))
return nil
}
ports := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+5).Text)
pp := strings.Split(ports, " ")
if len(pp) == 0 {
v.app.flash().err(errors.New("No ports found"))
return nil
}
// Grap the first port pair for now...
tokens := strings.Split(pp[0], "►")
if len(tokens) < 2 {
v.app.flash().err(errors.New("No ports pair found"))
return nil
}
// Found external nodeport
port := tokens[1]
cfg.Name = sel
log.Debug().Msgf(">>>>> BENCHCONFIG %#v", cfg)
base := "http://" + cfg.Host + ":" + port + cfg.Path
var err error
if v.bench, err = newBenchmark(base, cfg); err != nil {
log.Error().Err(err).Msg("Bench failed!")
v.app.flash().errf("Bench failed %v", err)
row, _ := v.getTV().GetSelection()
if err := v.checkSvc(row); err != nil {
v.app.flash().err(err)
return nil
}
port, err := v.getExternalPort(row)
if err != nil {
v.app.flash().err(err)
return nil
}
if err := v.runBenchmark(port, cfg); err != nil {
log.Error().Err(err).Msg("Benchmark failed!")
v.app.flash().errf("Benchmark failed %v", err)
v.app.statusReset()
v.bench = nil
return nil
}
return nil
}
func (v *svcView) runBenchmark(port string, cfg config.BenchConfig) error {
var err error
base := "http://" + cfg.Host + ":" + port + cfg.Path
if v.bench, err = newBenchmark(base, cfg); err != nil {
return err
}
v.app.status(flashWarn, "Benchmark in progress...")
@ -185,7 +220,6 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
})
return nil
}
func (v *svcView) showSvcPods(ns string, sel map[string]string, b actionHandler) {

View File

@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
@ -43,7 +42,7 @@ type (
asc bool
}
tableView struct {
resTable struct {
*tview.Table
app *appView
@ -51,6 +50,11 @@ type (
currentNS string
data resource.TableData
actions keyActions
}
tableView struct {
*resTable
cmdBuff *cmdBuff
colorerFn colorerFn
sortFn sortFn
@ -62,12 +66,14 @@ type (
func newTableView(app *appView, title string) *tableView {
v := tableView{
app: app,
Table: tview.NewTable(),
sortCol: sortColumn{0, 0, true},
actions: make(keyActions),
baseTitle: title,
cmdBuff: newCmdBuff('/'),
resTable: &resTable{
Table: tview.NewTable(),
app: app,
actions: make(keyActions),
baseTitle: title,
},
sortCol: sortColumn{0, 0, true},
cmdBuff: newCmdBuff('/'),
}
v.SetFixed(1, 0)
v.SetBorder(true)
@ -121,7 +127,7 @@ func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
v.cmdBuff.add(evt.Rune())
v.clearSelection()
v.doUpdate(v.filtered())
v.setSelection()
v.selectFirstRow()
return nil
}
key = tcell.Key(evt.Rune())
@ -138,7 +144,7 @@ func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
func (v *tableView) setSelection() {
func (v *tableView) selectFirstRow() {
if v.GetRowCount() > 0 {
v.Select(1, 0)
}
@ -350,18 +356,6 @@ func (v *tableView) filtered() resource.TableData {
return filtered
}
func (v *tableView) sortIndicator(index int, name string) string {
if v.sortCol.index != index {
return name
}
order := descIndicator
if v.sortCol.asc {
order = ascIndicator
}
return fmt.Sprintf("%s[%s::]%s[::]", name, v.app.styles.Style.Table.Header.SorterColor, order)
}
func (v *tableView) doUpdate(data resource.TableData) {
v.currentNS = data.Namespace
if v.currentNS == resource.AllNamespaces && v.currentNS != "*" {
@ -390,15 +384,18 @@ func (v *tableView) doUpdate(data resource.TableData) {
fg := config.AsColor(v.app.styles.Style.Table.Header.FgColor)
bg := config.AsColor(v.app.styles.Style.Table.Header.BgColor)
for col, h := range data.Header {
v.addHeaderCell(data.NumCols, col, h, fg, bg)
v.addHeaderCell(data.NumCols[h], col, h)
c := v.GetCell(0, col)
c.SetBackgroundColor(bg)
c.SetTextColor(fg)
}
row++
sortFn := v.defaultSort
sortFn := defaultSort
if v.sortFn != nil {
sortFn = v.sortFn
}
prim, sec := v.sortAllRows(data.Rows, sortFn)
prim, sec := sortAllRows(v.sortCol, data.Rows, sortFn)
fgColor := config.AsColor(v.app.styles.Style.Table.FgColor)
for _, pk := range prim {
for _, sk := range sec[pk] {
@ -421,36 +418,11 @@ func (v *tableView) doUpdate(data resource.TableData) {
}
}
func (v *tableView) sortAllRows(rows resource.RowEvents, sortFn sortFn) (resource.Row, map[string]resource.Row) {
keys := make([]string, len(rows))
v.sortRows(rows, sortFn, v.sortCol, keys)
sec := make(map[string]resource.Row, len(rows))
for _, k := range keys {
grp := rows[k].Fields[v.sortCol.index]
sec[grp] = append(sec[grp], k)
}
// Performs secondary to sort by name for each groups.
prim := make(resource.Row, 0, len(sec))
for k, v := range sec {
sort.Strings(v)
prim = append(prim, k)
}
sort.Sort(groupSorter{prim, v.sortCol.asc})
return prim, sec
}
func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string, fg, bg tcell.Color) {
c := tview.NewTableCell(v.sortIndicator(col, name))
{
c.SetExpansion(1)
if numCols[name] || cpuRX.MatchString(name) || memRX.MatchString(name) {
c.SetAlign(tview.AlignRight)
}
c.SetTextColor(fg)
c.SetBackgroundColor(bg)
func (v *tableView) addHeaderCell(numerical bool, col int, name string) {
c := tview.NewTableCell(sortIndicator(v.sortCol, v.app.styles.Style, col, name))
c.SetExpansion(1)
if numerical || cpuRX.MatchString(name) || memRX.MatchString(name) {
c.SetAlign(tview.AlignRight)
}
v.SetCell(0, col, c)
}
@ -475,27 +447,6 @@ func (v *tableView) formatCell(numerical bool, header, field string, padding int
return field, align
}
func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) {
t := rowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc}
sort.Sort(t)
}
func (*tableView) sortRows(evts resource.RowEvents, sortFn sortFn, sortCol sortColumn, keys []string) {
rows := make(resource.Rows, 0, len(evts))
for k, r := range evts {
rows = append(rows, append(r.Fields, k))
}
sortFn(rows, sortCol)
for i, r := range rows {
keys[i] = r[len(r)-1]
}
}
func (*tableView) defaultColCleanse(s string) string {
return strings.TrimSpace(s)
}
func (v *tableView) resetTitle() {
var title string
@ -523,39 +474,3 @@ func (v *tableView) resetTitle() {
}
v.SetTitle(title)
}
// ----------------------------------------------------------------------------
// Event listeners...
func skinTitle(fmat string, style *config.Style) string {
fmat = strings.Replace(fmat, "[fg:bg", "["+style.Title.FgColor+":"+style.Title.BgColor, -1)
fmat = strings.Replace(fmat, "[hilite", "["+style.Title.HighlightColor, 1)
fmat = strings.Replace(fmat, "[key", "["+style.Menu.NumKeyColor, 1)
fmat = strings.Replace(fmat, "[filter", "["+style.Title.FilterColor, 1)
fmat = strings.Replace(fmat, "[count", "["+style.Title.CounterColor, 1)
fmat = strings.Replace(fmat, ":bg:", ":"+style.Title.BgColor+":", -1)
return fmat
}
func (v *tableView) changed(s string) {}
func (v *tableView) active(b bool) {
if b {
v.SetBorderColor(tcell.ColorRed)
return
}
v.SetBorderColor(tcell.ColorDodgerBlue)
}
var labelCmd = regexp.MustCompile(`\A\-l`)
func isLabelSelector(s string) bool {
if s == "" {
return false
}
return labelCmd.MatchString(s)
}
func trimLabelSelector(s string) string {
return strings.TrimSpace(s[2:])
}

View File

@ -0,0 +1,84 @@
package views
import (
"fmt"
"regexp"
"sort"
"strings"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource"
)
var labelCmd = regexp.MustCompile(`\A\-l`)
func isLabelSelector(s string) bool {
if s == "" {
return false
}
return labelCmd.MatchString(s)
}
func trimLabelSelector(s string) string {
return strings.TrimSpace(s[2:])
}
func skinTitle(fmat string, style *config.Style) string {
fmat = strings.Replace(fmat, "[fg:bg", "["+style.Title.FgColor+":"+style.Title.BgColor, -1)
fmat = strings.Replace(fmat, "[hilite", "["+style.Title.HighlightColor, 1)
fmat = strings.Replace(fmat, "[key", "["+style.Menu.NumKeyColor, 1)
fmat = strings.Replace(fmat, "[filter", "["+style.Title.FilterColor, 1)
fmat = strings.Replace(fmat, "[count", "["+style.Title.CounterColor, 1)
fmat = strings.Replace(fmat, ":bg:", ":"+style.Title.BgColor+":", -1)
return fmat
}
func sortRows(evts resource.RowEvents, sortFn sortFn, sortCol sortColumn, keys []string) {
rows := make(resource.Rows, 0, len(evts))
for k, r := range evts {
rows = append(rows, append(r.Fields, k))
}
sortFn(rows, sortCol)
for i, r := range rows {
keys[i] = r[len(r)-1]
}
}
func defaultSort(rows resource.Rows, sortCol sortColumn) {
t := rowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc}
sort.Sort(t)
}
func sortAllRows(col sortColumn, rows resource.RowEvents, sortFn sortFn) (resource.Row, map[string]resource.Row) {
keys := make([]string, len(rows))
sortRows(rows, sortFn, col, keys)
sec := make(map[string]resource.Row, len(rows))
for _, k := range keys {
grp := rows[k].Fields[col.index]
sec[grp] = append(sec[grp], k)
}
// Performs secondary to sort by name for each groups.
prim := make(resource.Row, 0, len(sec))
for k, v := range sec {
sort.Strings(v)
prim = append(prim, k)
}
sort.Sort(groupSorter{prim, col.asc})
return prim, sec
}
func sortIndicator(col sortColumn, style *config.Style, index int, name string) string {
if col.index != index {
return name
}
order := descIndicator
if col.asc {
order = ascIndicator
}
return fmt.Sprintf("%s[%s::]%s[::]", name, style.Table.Header.SorterColor, order)
}

View File

@ -195,10 +195,9 @@ func TestTVSortRows(t *testing.T) {
},
}
var v *tableView
for _, u := range uu {
keys := make([]string, len(u.rows))
v.sortRows(u.rows, v.defaultSort, sortColumn{u.col, len(u.rows), u.asc}, keys)
sortRows(u.rows, defaultSort, sortColumn{u.col, len(u.rows), u.asc}, keys)
assert.Equal(t, u.e, keys)
assert.Equal(t, u.first, u.rows[u.e[0]].Fields)
}
@ -210,12 +209,11 @@ func BenchmarkTVSortRows(b *testing.B) {
"row2": {Fields: resource.Row{"a", "b"}},
}
sc := sortColumn{0, 2, true}
var v *tableView
keys := make([]string, len(evts))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
v.sortRows(evts, v.defaultSort, sc, keys)
sortRows(evts, defaultSort, sc, keys)
}
}