k9s/internal/view/bench.go

235 lines
5.1 KiB
Go

package view
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/perf"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"github.com/fsnotify/fsnotify"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
const (
benchTitle = "Benchmarks"
resultTitle = "Benchmark Results"
)
// Bench represents a service benchmark results view.
type Bench struct {
*Table
details *Details
}
// NewBench returns a new viewer.
func NewBench(title, _ string, _ resource.List) ResourceViewer {
return &Bench{
Table: NewTable(benchTitle),
details: NewDetails(resultTitle),
}
}
func (*Bench) SetContextFn(ContextFunc) {}
// Init initializes the viewer.
func (b *Bench) Init(ctx context.Context) error {
log.Debug().Msgf(">>> Bench INIT")
if err := b.Table.Init(ctx); err != nil {
return err
}
b.SetBorderFocusColor(tcell.ColorSeaGreen)
b.SetSelectedStyle(tcell.ColorWhite, tcell.ColorSeaGreen, tcell.AttrNone)
b.SetColorerFn(render.Bench{}.ColorerFunc())
b.bindKeys()
b.details.SetTextColor(tcell.ColorSeaGreen)
if err := b.details.Init(ctx); err != nil {
return nil
}
b.Start()
b.refresh()
b.SetSortCol(b.NameColIndex()+7, 0, true)
b.Refresh()
b.Select(1, 0)
return nil
}
// GVR returns a resource descriptor.
func (b *Bench) GVR() string {
return "n/a"
}
// SetEnvFn sets k9s env vars.
func (b *Bench) SetEnvFn(EnvFunc) {}
// GetTable returns the table view.
func (b *Bench) GetTable() *Table { return b.Table }
// SetPath sets parent selector.
func (b *Bench) SetPath(s string) {}
// Start runs the refresh loop
func (b *Bench) Start() {
log.Debug().Msgf(">>>> Bench START")
var ctx context.Context
ctx, b.cancelFn = context.WithCancel(context.Background())
if err := b.watchBenchDir(ctx); err != nil {
b.app.Flash().Errf("Unable to watch benchmarks directory %s", err)
}
}
// List returns a resource list.
func (b *Bench) List() resource.List {
return nil
}
func (b *Bench) refresh() {
b.Update(b.hydrate())
b.UpdateTitle()
}
func (b *Bench) bindKeys() {
b.Actions().Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Enter", b.enterCmd, false),
tcell.KeyCtrlD: ui.NewKeyAction("Delete", b.deleteCmd, false),
})
}
func (b *Bench) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
if b.SearchBuff().IsActive() {
return b.filterCmd(evt)
}
if !b.RowSelected() {
return nil
}
data, err := readBenchFile(b.app.Config, b.benchFile())
if err != nil {
b.app.Flash().Errf("Unable to load bench file %s", err)
return nil
}
b.details.SetText(data)
b.details.SetSubject(b.GetSelectedItem())
b.app.inject(b.details)
return nil
}
func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
if !b.RowSelected() {
return nil
}
sel, file := b.GetSelectedItem(), b.benchFile()
dir := filepath.Join(perf.K9sBenchDir, b.app.Config.K9s.CurrentCluster)
showModal(b.app.Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() {
if err := os.Remove(filepath.Join(dir, file)); err != nil {
b.app.Flash().Errf("Unable to delete file %s", err)
return
}
b.app.Flash().Infof("Benchmark %s deleted!", sel)
})
return nil
}
func (b *Bench) benchFile() string {
r := b.GetSelectedRowIndex()
return ui.TrimCell(b.SelectTable, r, 7)
}
func (b *Bench) hydrate() render.TableData {
ff, err := loadBenchDir(b.app.Config)
if err != nil {
b.app.Flash().Errf("Unable to read bench directory %s", err)
}
var re render.Bench
data := render.TableData{
Header: re.Header(render.AllNamespaces),
RowEvents: make(render.RowEvents, 0, 10),
Namespace: render.AllNamespaces,
}
for _, f := range ff {
bench := render.BenchInfo{
File: f,
Path: filepath.Join(benchDir(b.app.Config), f.Name()),
}
var row render.Row
if err := re.Render(bench, render.AllNamespaces, &row); err != nil {
log.Error().Err(err).Msg("Bench render failed")
continue
}
data.RowEvents = append(data.RowEvents, render.RowEvent{
Kind: render.EventAdd,
Row: row,
})
}
return data
}
func (b *Bench) 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)
b.app.QueueUpdateDraw(func() {
b.refresh()
})
case err := <-w.Errors:
log.Info().Err(err).Msg("Dir Watcher failed")
return
case <-ctx.Done():
log.Debug().Msg("!!!! FS WATCHER DONE!!")
if err := w.Close(); err != nil {
log.Error().Err(err).Msg("Closing bench watched")
}
return
}
}
}()
return w.Add(benchDir(b.app.Config))
}
// ----------------------------------------------------------------------------
// Helpers...
func benchDir(cfg *config.Config) string {
return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster)
}
func loadBenchDir(cfg *config.Config) ([]os.FileInfo, error) {
return ioutil.ReadDir(benchDir(cfg))
}
func readBenchFile(cfg *config.Config, n string) (string, error) {
data, err := ioutil.ReadFile(filepath.Join(benchDir(cfg), n))
if err != nil {
return "", err
}
return string(data), nil
}