Feature/refresh rate (#3517)
* refactor: change refreshRate type to float64 for improved precision * test: update assertions for refreshRate to use assert.InDelta for precision * refactor: enforce minimum refresh rate and update related tests * refactor: change refresh rate type to float32 and update related logic * refactor: update refresh rate validation and logging in GetRefreshRate method * refactor: update logging keys for refresh rate validation in GetRefreshRate methodmine
parent
e5212875a4
commit
e99c735430
|
|
@ -407,7 +407,7 @@ You can now override the context portForward default address configuration by se
|
|||
bozo: bozo/gpu
|
||||
# The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info)
|
||||
screenDumpDir: /tmp/dumps
|
||||
# Represents ui poll intervals in seconds. Default 2secs
|
||||
# Represents ui poll intervals in seconds. Default 2.0 secs. Minimum value is 2.0 - values below will be capped to the minimum.
|
||||
refreshRate: 2
|
||||
# Overrides the default k8s api server requests timeout. Defaults 120s
|
||||
apiServerTimeout: 15s
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ func run(*cobra.Command, []string) error {
|
|||
app.Config.SetActiveView(app.Config.K9s.DefaultView)
|
||||
}
|
||||
|
||||
if err := app.Init(version, *k9sFlags.RefreshRate); err != nil {
|
||||
if err := app.Init(version, int(*k9sFlags.RefreshRate)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := app.Run(); err != nil {
|
||||
|
|
@ -185,11 +185,11 @@ func parseLevel(level string) slog.Level {
|
|||
|
||||
func initK9sFlags() {
|
||||
k9sFlags = config.NewFlags()
|
||||
rootCmd.Flags().IntVarP(
|
||||
rootCmd.Flags().Float32VarP(
|
||||
k9sFlags.RefreshRate,
|
||||
"refresh", "r",
|
||||
config.DefaultRefreshRate,
|
||||
"Specify the default refresh rate as an integer (sec)",
|
||||
"Specify the default refresh rate as a float (sec)",
|
||||
)
|
||||
rootCmd.Flags().StringVarP(
|
||||
k9sFlags.LogLevel,
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ func TestConfigLoad(t *testing.T) {
|
|||
cfg := mock.NewMockConfig(t)
|
||||
|
||||
require.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true))
|
||||
assert.Equal(t, 2, cfg.K9s.RefreshRate)
|
||||
assert.InDelta(t, 2.0, cfg.K9s.RefreshRate, 0.001)
|
||||
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
|
||||
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ package config
|
|||
|
||||
const (
|
||||
// DefaultRefreshRate represents the refresh interval.
|
||||
DefaultRefreshRate = 2 // secs
|
||||
DefaultRefreshRate float32 = 2.0 // secs
|
||||
|
||||
// DefaultLogLevel represents the default log level.
|
||||
DefaultLogLevel = "info"
|
||||
|
|
@ -16,7 +16,7 @@ const (
|
|||
|
||||
// Flags represents K9s configuration flags.
|
||||
type Flags struct {
|
||||
RefreshRate *int
|
||||
RefreshRate *float32
|
||||
LogLevel *string
|
||||
LogFile *string
|
||||
Headless *bool
|
||||
|
|
@ -33,7 +33,7 @@ type Flags struct {
|
|||
// NewFlags returns new configuration flags.
|
||||
func NewFlags() *Flags {
|
||||
return &Flags{
|
||||
RefreshRate: intPtr(DefaultRefreshRate),
|
||||
RefreshRate: float32Ptr(DefaultRefreshRate),
|
||||
LogLevel: strPtr(DefaultLogLevel),
|
||||
LogFile: strPtr(AppLogFile),
|
||||
Headless: boolPtr(false),
|
||||
|
|
@ -52,8 +52,8 @@ func boolPtr(b bool) *bool {
|
|||
return &b
|
||||
}
|
||||
|
||||
func intPtr(i int) *int {
|
||||
return &i
|
||||
func float32Ptr(f float32) *float32 {
|
||||
return &f
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func TestNewFlags(t *testing.T) {
|
|||
config.AppLogFile = "/tmp/k9s-test/k9s.log"
|
||||
|
||||
f := config.NewFlags()
|
||||
assert.Equal(t, 2, *f.RefreshRate)
|
||||
assert.InDelta(t, 2.0, *f.RefreshRate, 0.001)
|
||||
assert.Equal(t, "info", *f.LogLevel)
|
||||
assert.Equal(t, "/tmp/k9s-test/k9s.log", *f.LogFile)
|
||||
assert.Equal(t, config.AppDumpsDir, *f.ScreenDumpDir)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
},
|
||||
"screenDumpDir": {"type": "string"},
|
||||
"refreshRate": { "type": "integer" },
|
||||
"refreshRate": { "type": "number" },
|
||||
"apiServerTimeout": { "type": "string" },
|
||||
"maxConnRetry": { "type": "integer" },
|
||||
"readOnly": { "type": "boolean" },
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config/data"
|
||||
|
|
@ -35,7 +36,7 @@ type K9s struct {
|
|||
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
|
||||
GPUVendors gpuVendors `json:"gpuVendors" yaml:"gpuVendors"`
|
||||
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
|
||||
RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
|
||||
RefreshRate float32 `json:"refreshRate" yaml:"refreshRate"`
|
||||
APIServerTimeout string `json:"apiServerTimeout" yaml:"apiServerTimeout"`
|
||||
MaxConnRetry int32 `json:"maxConnRetry" yaml:"maxConnRetry"`
|
||||
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
|
||||
|
|
@ -49,10 +50,11 @@ type K9s struct {
|
|||
Logger Logger `json:"logger" yaml:"logger"`
|
||||
Thresholds Threshold `json:"thresholds" yaml:"thresholds"`
|
||||
DefaultView string `json:"defaultView" yaml:"defaultView"`
|
||||
manualRefreshRate int
|
||||
manualRefreshRate float32
|
||||
manualReadOnly *bool
|
||||
manualCommand *string
|
||||
manualScreenDumpDir *string
|
||||
refreshRateWarned bool
|
||||
dir *data.Dir
|
||||
activeContextName string
|
||||
activeConfig *data.Config
|
||||
|
|
@ -314,7 +316,7 @@ func (k *K9s) Reload() error {
|
|||
// Override overrides k9s config from cli args.
|
||||
func (k *K9s) Override(k9sFlags *Flags) {
|
||||
if k9sFlags.RefreshRate != nil && *k9sFlags.RefreshRate != DefaultRefreshRate {
|
||||
k.manualRefreshRate = *k9sFlags.RefreshRate
|
||||
k.manualRefreshRate = float32(*k9sFlags.RefreshRate)
|
||||
}
|
||||
|
||||
k.UI.manualHeadless = k9sFlags.Headless
|
||||
|
|
@ -369,12 +371,29 @@ func (k *K9s) IsSplashless() bool {
|
|||
}
|
||||
|
||||
// GetRefreshRate returns the current refresh rate.
|
||||
func (k *K9s) GetRefreshRate() int {
|
||||
if k.manualRefreshRate != 0 {
|
||||
return k.manualRefreshRate
|
||||
}
|
||||
func (k *K9s) GetRefreshRate() float32 {
|
||||
k.mx.Lock()
|
||||
defer k.mx.Unlock()
|
||||
|
||||
return k.RefreshRate
|
||||
rate := k.RefreshRate
|
||||
if k.manualRefreshRate != 0 {
|
||||
rate = k.manualRefreshRate
|
||||
}
|
||||
if rate < DefaultRefreshRate {
|
||||
if !k.refreshRateWarned {
|
||||
slog.Warn("Refresh rate is below minimum, capping to minimum value",
|
||||
slogs.Requested, float64(rate),
|
||||
slogs.Minimum, float64(DefaultRefreshRate))
|
||||
k.refreshRateWarned = true
|
||||
}
|
||||
return DefaultRefreshRate
|
||||
}
|
||||
return rate
|
||||
}
|
||||
|
||||
// RefreshDuration returns the refresh rate as a time.Duration.
|
||||
func (k *K9s) RefreshDuration() time.Duration {
|
||||
return time.Duration(k.GetRefreshRate() * float32(time.Second))
|
||||
}
|
||||
|
||||
// IsReadOnly returns the readonly setting.
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ func Test_k9sOverrides(t *testing.T) {
|
|||
|
||||
uu := map[string]struct {
|
||||
k *K9s
|
||||
rate int
|
||||
rate float32
|
||||
ro, hl, cl, sl, ll bool
|
||||
}{
|
||||
"plain": {
|
||||
k: &K9s{
|
||||
LiveViewAutoRefresh: false,
|
||||
ScreenDumpDir: "",
|
||||
RefreshRate: 10,
|
||||
RefreshRate: 10.0,
|
||||
MaxConnRetry: 0,
|
||||
ReadOnly: false,
|
||||
NoExitOnCtrlC: false,
|
||||
|
|
@ -34,13 +34,27 @@ func Test_k9sOverrides(t *testing.T) {
|
|||
SkipLatestRevCheck: false,
|
||||
DisablePodCounting: false,
|
||||
},
|
||||
rate: 10,
|
||||
rate: 10.0,
|
||||
},
|
||||
"sub-second": {
|
||||
k: &K9s{
|
||||
LiveViewAutoRefresh: false,
|
||||
ScreenDumpDir: "",
|
||||
RefreshRate: 0.5,
|
||||
MaxConnRetry: 0,
|
||||
ReadOnly: false,
|
||||
NoExitOnCtrlC: false,
|
||||
UI: UI{},
|
||||
SkipLatestRevCheck: false,
|
||||
DisablePodCounting: false,
|
||||
},
|
||||
rate: 2.0, // minimum enforced
|
||||
},
|
||||
"set": {
|
||||
k: &K9s{
|
||||
LiveViewAutoRefresh: false,
|
||||
ScreenDumpDir: "",
|
||||
RefreshRate: 10,
|
||||
RefreshRate: 10.0,
|
||||
MaxConnRetry: 0,
|
||||
ReadOnly: true,
|
||||
NoExitOnCtrlC: false,
|
||||
|
|
@ -53,7 +67,7 @@ func Test_k9sOverrides(t *testing.T) {
|
|||
SkipLatestRevCheck: false,
|
||||
DisablePodCounting: false,
|
||||
},
|
||||
rate: 10,
|
||||
rate: 10.0,
|
||||
ro: true,
|
||||
hl: true,
|
||||
ll: true,
|
||||
|
|
@ -64,7 +78,7 @@ func Test_k9sOverrides(t *testing.T) {
|
|||
k: &K9s{
|
||||
LiveViewAutoRefresh: false,
|
||||
ScreenDumpDir: "",
|
||||
RefreshRate: 10,
|
||||
RefreshRate: 10.0,
|
||||
MaxConnRetry: 0,
|
||||
ReadOnly: false,
|
||||
NoExitOnCtrlC: false,
|
||||
|
|
@ -79,12 +93,12 @@ func Test_k9sOverrides(t *testing.T) {
|
|||
},
|
||||
SkipLatestRevCheck: false,
|
||||
DisablePodCounting: false,
|
||||
manualRefreshRate: 100,
|
||||
manualRefreshRate: 100.0,
|
||||
manualReadOnly: &trueVal,
|
||||
manualCommand: &cmd,
|
||||
manualScreenDumpDir: &dir,
|
||||
},
|
||||
rate: 100,
|
||||
rate: 100.0,
|
||||
ro: true,
|
||||
hl: true,
|
||||
ll: true,
|
||||
|
|
@ -96,7 +110,7 @@ func Test_k9sOverrides(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.rate, u.k.GetRefreshRate())
|
||||
assert.InDelta(t, u.rate, u.k.GetRefreshRate(), 0.001)
|
||||
assert.Equal(t, u.ro, u.k.IsReadOnly())
|
||||
assert.Equal(t, u.cl, u.k.IsCrumbsless())
|
||||
assert.Equal(t, u.sl, u.k.IsSplashless())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestRefreshRateBackwardCompatibility(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
yamlContent string
|
||||
expected float32
|
||||
}{
|
||||
"integer_value": {
|
||||
yamlContent: `refreshRate: 2`,
|
||||
expected: 2.0,
|
||||
},
|
||||
"float_value": {
|
||||
yamlContent: `refreshRate: 2.5`,
|
||||
expected: 2.5,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var k K9s
|
||||
err := yaml.Unmarshal([]byte(test.yamlContent), &k)
|
||||
require.NoError(t, err)
|
||||
assert.InDelta(t, test.expected, k.RefreshRate, 0.001)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRefreshRateMinimum(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
refreshRate float32
|
||||
manualRefreshRate float32
|
||||
expected float32
|
||||
}{
|
||||
"below_minimum": {
|
||||
refreshRate: 0.5,
|
||||
expected: 2.0,
|
||||
},
|
||||
"at_minimum": {
|
||||
refreshRate: 2.0,
|
||||
expected: 2.0,
|
||||
},
|
||||
"above_minimum": {
|
||||
refreshRate: 3.5,
|
||||
expected: 3.5,
|
||||
},
|
||||
"manual_below_minimum": {
|
||||
refreshRate: 3.0,
|
||||
manualRefreshRate: 0.5,
|
||||
expected: 2.0,
|
||||
},
|
||||
"manual_above_minimum": {
|
||||
refreshRate: 2.0,
|
||||
manualRefreshRate: 4.0,
|
||||
expected: 4.0,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
k := K9s{
|
||||
RefreshRate: test.refreshRate,
|
||||
manualRefreshRate: test.manualRefreshRate,
|
||||
}
|
||||
assert.InDelta(t, test.expected, k.GetRefreshRate(), 0.001)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -221,4 +221,10 @@ const (
|
|||
|
||||
// Type tracks a type logger key.
|
||||
Type = "type"
|
||||
|
||||
// Requested tracks a requested value logger key.
|
||||
Requested = "requested"
|
||||
|
||||
// Minimum tracks a minimum value logger key.
|
||||
Minimum = "minimum"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func (b *Browser) Init(ctx context.Context) error {
|
|||
if row == 0 && b.GetRowCount() > 0 {
|
||||
b.Select(1, 0)
|
||||
}
|
||||
b.GetModel().SetRefreshRate(time.Duration(b.App().Config.K9s.GetRefreshRate()) * time.Second)
|
||||
b.GetModel().SetRefreshRate(b.App().Config.K9s.RefreshDuration())
|
||||
|
||||
b.CmdBuff().SetSuggestionFn(b.suggestFilter())
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -60,7 +59,7 @@ func (t *Table) Init(ctx context.Context) (err error) {
|
|||
}
|
||||
t.SetInputCapture(t.keyboard)
|
||||
t.bindKeys()
|
||||
t.GetModel().SetRefreshRate(time.Duration(t.app.Config.K9s.GetRefreshRate()) * time.Second)
|
||||
t.GetModel().SetRefreshRate(t.app.Config.K9s.RefreshDuration())
|
||||
t.CmdBuff().AddListener(t)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"log/slog"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -87,7 +86,7 @@ func (x *Xray) Init(ctx context.Context) error {
|
|||
x.SetGraphicsColor(x.app.Styles.Xray().GraphicColor.Color())
|
||||
x.SetTitle(fmt.Sprintf(" %s-%s ", xrayTitle, cases.Title(language.Und, cases.NoLower).String(x.gvr.R())))
|
||||
|
||||
x.model.SetRefreshRate(time.Duration(x.app.Config.K9s.GetRefreshRate()) * time.Second)
|
||||
x.model.SetRefreshRate(x.app.Config.K9s.RefreshDuration())
|
||||
x.model.SetNamespace(client.CleanseNamespace(x.app.Config.ActiveNamespace()))
|
||||
x.model.AddListener(x)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue