Skip to content

Allow metrics cache expiry to be configured #3165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ You can now override the context portForward default address configuration by se
screenDumpDir: /tmp/dumps
# Represents ui poll intervals. Default 2secs
refreshRate: 2
# How often metrics data will be cached for e.g. CPU and memory usage. Default 60s
metricsCacheExpiry: 60
# Number of retries once the connection to the api-server is lost. Default 15.
maxConnRetry: 5
# Indicates whether modification commands like delete/kill/edit are disabled. Default is false
Expand Down Expand Up @@ -966,6 +968,7 @@ k9s:
liveViewAutoRefresh: false
screenDumpDir: /tmp/dumps
refreshRate: 2
metricsCacheExpiry: 60
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
Expand Down
5 changes: 3 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ func loadConfiguration() (*config.Config, error) {
errs = errors.Join(errs, err)
}

conn, err := client.InitConnection(k8sCfg)
options := client.NewOptions(k9sCfg.K9s.MetricsCacheExpiry)
conn, err := client.InitConnection(k8sCfg, options)

if err != nil {
errs = errors.Join(errs, err)
Expand Down Expand Up @@ -376,7 +377,7 @@ func initK8sFlagCompletion() {

_ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, s string) ([]string, cobra.ShellCompDirective) {
conn := client.NewConfig(k8sFlags)
if c, err := client.InitConnection(conn); err == nil {
if c, err := client.InitConnection(conn, client.Options{}); err == nil {
if nss, err := c.ValidNamespaceNames(); err == nil {
return filterFlagCompletions(nss, s)
}
Expand Down
40 changes: 35 additions & 5 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ type APIClient struct {
nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset
cachedClient *disk.CachedDiscoveryClient
metricsServer *MetricsServer
config *Config
mx sync.RWMutex
cache *cache.LRUExpireCache
connOK bool

options Options
}

// NewTestAPIClient for testing ONLY!!
Expand All @@ -62,11 +65,12 @@ func NewTestAPIClient() *APIClient {

// InitConnection initialize connection from command line args.
// Checks for connectivity with the api server.
func InitConnection(config *Config) (*APIClient, error) {
func InitConnection(config *Config, options Options) (*APIClient, error) {
a := APIClient{
config: config,
cache: cache.NewLRUExpireCache(cacheSize),
connOK: true,
config: config,
cache: cache.NewLRUExpireCache(cacheSize),
connOK: true,
options: options,
}
err := a.supportsMetricsResources()
if err != nil {
Expand Down Expand Up @@ -394,6 +398,20 @@ func (a *APIClient) getLogClient() kubernetes.Interface {
return a.logClient
}

func (a *APIClient) getMetricsServer() *MetricsServer {
a.mx.RLock()
defer a.mx.RUnlock()

return a.metricsServer
}

func (a *APIClient) setMetricsServer(ms *MetricsServer) {
a.mx.Lock()
defer a.mx.Unlock()

a.metricsServer = ms
}

func (a *APIClient) setClient(k kubernetes.Interface) {
a.mx.Lock()
defer a.mx.Unlock()
Expand Down Expand Up @@ -453,6 +471,18 @@ func (a *APIClient) Dial() (kubernetes.Interface, error) {
return a.getClient(), nil
}

func (a *APIClient) DialMetrics() *MetricsServer {
if ms := a.getMetricsServer(); ms != nil {
return ms
}

cacheExpiry := time.Duration(a.options.MetricsCacheExpiry) * time.Second
ms := NewMetricsServer(a, cacheExpiry)
a.setMetricsServer(ms)

return a.getMetricsServer()
}

// RestConfig returns a rest api client.
func (a *APIClient) RestConfig() (*restclient.Config, error) {
return a.config.RESTConfig()
Expand Down Expand Up @@ -552,7 +582,6 @@ func (a *APIClient) SwitchContext(name string) error {
return err
}
a.reset()
ResetMetrics()

// Need reload to pick up any kubeconfig changes.
a.config = NewConfig(a.config.flags)
Expand All @@ -570,6 +599,7 @@ func (a *APIClient) reset() {
a.setCachedClient(nil)
a.setClient(nil)
a.setLogClient(nil)
a.setMetricsServer(nil)
a.setConnOK(true)
}

Expand Down
38 changes: 11 additions & 27 deletions internal/client/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,25 @@ import (
)

const (
mxCacheSize = 100
mxCacheExpiry = 1 * time.Minute
podMXGVR = "metrics.k8s.io/v1beta1/pods"
nodeMXGVR = "metrics.k8s.io/v1beta1/nodes"
mxCacheSize = 100
podMXGVR = "metrics.k8s.io/v1beta1/pods"
nodeMXGVR = "metrics.k8s.io/v1beta1/nodes"
)

// MetricsDial tracks global metric server handle.
var MetricsDial *MetricsServer

// DialMetrics dials the metrics server.
func DialMetrics(c Connection) *MetricsServer {
if MetricsDial == nil {
MetricsDial = NewMetricsServer(c)
}

return MetricsDial
}

// ResetMetrics resets the metric server handle.
func ResetMetrics() {
MetricsDial = nil
}

// MetricsServer serves cluster metrics for nodes and pods.
type MetricsServer struct {
Connection

cache *cache.LRUExpireCache
cache *cache.LRUExpireCache
cacheExpiry time.Duration
}

// NewMetricsServer return a metric server instance.
func NewMetricsServer(c Connection) *MetricsServer {
func NewMetricsServer(c Connection, cacheExpiry time.Duration) *MetricsServer {
return &MetricsServer{
Connection: c,
cache: cache.NewLRUExpireCache(mxCacheSize),
Connection: c,
cache: cache.NewLRUExpireCache(mxCacheSize),
cacheExpiry: cacheExpiry,
}
}

Expand Down Expand Up @@ -172,7 +156,7 @@ func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMe
if err != nil {
return mx, err
}
m.cache.Add(key, mxList, mxCacheExpiry)
m.cache.Add(key, mxList, m.cacheExpiry)

return mxList, nil
}
Expand Down Expand Up @@ -243,7 +227,7 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be
if err != nil {
return mx, err
}
m.cache.Add(key, mxList, mxCacheExpiry)
m.cache.Add(key, mxList, m.cacheExpiry)

return mxList, err
}
Expand Down
17 changes: 11 additions & 6 deletions internal/client/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package client_test

import (
"testing"
"time"

"github.com/derailed/k9s/internal/client"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -72,7 +73,7 @@ func TestPodsMetrics(t *testing.T) {
},
}

m := client.NewMetricsServer(nil)
m := makeMetricsServer()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
Expand All @@ -91,7 +92,7 @@ func TestPodsMetrics(t *testing.T) {
}

func BenchmarkPodsMetrics(b *testing.B) {
m := client.NewMetricsServer(nil)
m := makeMetricsServer()

metrics := v1beta1.PodMetricsList{
Items: []v1beta1.PodMetrics{
Expand Down Expand Up @@ -168,7 +169,7 @@ func TestNodesMetrics(t *testing.T) {
},
}

m := client.NewMetricsServer(nil)
m := makeMetricsServer()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
Expand Down Expand Up @@ -201,7 +202,7 @@ func BenchmarkNodesMetrics(b *testing.B) {
},
}

m := client.NewMetricsServer(nil)
m := makeMetricsServer()
mmx := make(client.NodesMetrics)

b.ResetTimer()
Expand Down Expand Up @@ -261,7 +262,7 @@ func TestClusterLoad(t *testing.T) {
},
}

m := client.NewMetricsServer(nil)
m := makeMetricsServer()
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
Expand All @@ -287,7 +288,7 @@ func BenchmarkClusterLoad(b *testing.B) {
},
}

m := client.NewMetricsServer(nil)
m := makeMetricsServer()
var mx client.ClusterMetrics
b.ResetTimer()
b.ReportAllocs()
Expand All @@ -299,6 +300,10 @@ func BenchmarkClusterLoad(b *testing.B) {
// ----------------------------------------------------------------------------
// Helpers...

func makeMetricsServer() *client.MetricsServer {
return client.NewMetricsServer(nil, 1*time.Minute)
}

func makeMxPod(name, cpu, mem string) *v1beta1.PodMetrics {
return &v1beta1.PodMetrics{
ObjectMeta: metav1.ObjectMeta{
Expand Down
12 changes: 12 additions & 0 deletions internal/client/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package client

type Options struct {
MetricsCacheExpiry int
}

func NewOptions(metricsCacheExpiry int) Options {
return Options{MetricsCacheExpiry: metricsCacheExpiry}
}
3 changes: 3 additions & 0 deletions internal/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ type Connection interface {
// DialLogs connects to api server for logs.
DialLogs() (kubernetes.Interface, error)

// DialMetrics connects to metrics server.
DialMetrics() *MetricsServer

// SwitchContext switches cluster based on context.
SwitchContext(ctx string) error

Expand Down
2 changes: 2 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ func TestConfigLoad(t *testing.T) {

assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))
assert.Equal(t, 2, cfg.K9s.RefreshRate)
assert.Equal(t, 60, cfg.K9s.MetricsCacheExpiry)
assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount)
assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize)
}
Expand All @@ -553,6 +554,7 @@ func TestConfigSaveFile(t *testing.T) {
assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true))

cfg.K9s.RefreshRate = 100
cfg.K9s.MetricsCacheExpiry = 100
cfg.K9s.ReadOnly = true
cfg.K9s.Logger.TailCount = 500
cfg.K9s.Logger.BufferSize = 800
Expand Down
1 change: 1 addition & 0 deletions internal/config/json/schemas/k9s.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"liveViewAutoRefresh": { "type": "boolean" },
"screenDumpDir": {"type": "string"},
"refreshRate": { "type": "integer" },
"metricsCacheExpiry": { "type": "integer" },
"maxConnRetry": { "type": "integer" },
"readOnly": { "type": "boolean" },
"noExitOnCtrlC": { "type": "boolean" },
Expand Down
26 changes: 16 additions & 10 deletions internal/config/k9s.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type K9s struct {
LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"`
ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"`
RefreshRate int `json:"refreshRate" yaml:"refreshRate"`
MetricsCacheExpiry int `json:"metricsCacheExpiry" yaml:"metricsCacheExpiry"`
MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"`
ReadOnly bool `json:"readOnly" yaml:"readOnly"`
NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"`
Expand Down Expand Up @@ -51,16 +52,17 @@ type K9s struct {
// NewK9s create a new K9s configuration.
func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
return &K9s{
RefreshRate: defaultRefreshRate,
MaxConnRetry: defaultMaxConnRetry,
ScreenDumpDir: AppDumpsDir,
Logger: NewLogger(),
Thresholds: NewThreshold(),
ShellPod: NewShellPod(),
ImageScans: NewImageScans(),
dir: data.NewDir(AppContextsDir),
conn: conn,
ks: ks,
RefreshRate: defaultRefreshRate,
MetricsCacheExpiry: defaultMetricsCacheExpiry,
MaxConnRetry: defaultMaxConnRetry,
ScreenDumpDir: AppDumpsDir,
Logger: NewLogger(),
Thresholds: NewThreshold(),
ShellPod: NewShellPod(),
ImageScans: NewImageScans(),
dir: data.NewDir(AppContextsDir),
conn: conn,
ks: ks,
}
}

Expand Down Expand Up @@ -98,6 +100,7 @@ func (k *K9s) Merge(k1 *K9s) {
k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh
k.ScreenDumpDir = k1.ScreenDumpDir
k.RefreshRate = k1.RefreshRate
k.MetricsCacheExpiry = k1.MetricsCacheExpiry
k.MaxConnRetry = k1.MaxConnRetry
k.ReadOnly = k1.ReadOnly
k.NoExitOnCtrlC = k1.NoExitOnCtrlC
Expand Down Expand Up @@ -344,6 +347,9 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) {
if k.RefreshRate <= 0 {
k.RefreshRate = defaultRefreshRate
}
if k.MetricsCacheExpiry <= 0 {
k.MetricsCacheExpiry = defaultMetricsCacheExpiry
}
if k.MaxConnRetry <= 0 {
k.MaxConnRetry = defaultMaxConnRetry
}
Expand Down
3 changes: 3 additions & 0 deletions internal/config/k9s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func TestK9sMerge(t *testing.T) {
LiveViewAutoRefresh: false,
ScreenDumpDir: "",
RefreshRate: 0,
MetricsCacheExpiry: 0,
MaxConnRetry: 0,
ReadOnly: false,
NoExitOnCtrlC: false,
Expand All @@ -102,12 +103,14 @@ func TestK9sMerge(t *testing.T) {
k2: &config.K9s{
LiveViewAutoRefresh: true,
MaxConnRetry: 100,
MetricsCacheExpiry: 100,
ShellPod: config.NewShellPod(),
},
ek: &config.K9s{
LiveViewAutoRefresh: true,
ScreenDumpDir: "",
RefreshRate: 0,
MetricsCacheExpiry: 100,
MaxConnRetry: 100,
ReadOnly: false,
NoExitOnCtrlC: false,
Expand Down
3 changes: 3 additions & 0 deletions internal/config/mock/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ func (m mockConnection) Dial() (kubernetes.Interface, error) {
func (m mockConnection) DialLogs() (kubernetes.Interface, error) {
return nil, nil
}
func (m mockConnection) DialMetrics() *client.MetricsServer {
return nil
}
func (m mockConnection) SwitchContext(ctx string) error {
return nil
}
Expand Down
Loading
Loading