235 lines
5.1 KiB
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
|
|
}
|