perf pass on no/po/co
parent
a6f950b2a2
commit
ea9f6abd08
|
|
@ -25,7 +25,7 @@ const (
|
|||
var _ config.KubeSettings = (*client.Config)(nil)
|
||||
|
||||
var (
|
||||
version, commit, date = "dev", "dev", client.NA
|
||||
version, commit, date = "0.23.8", "dev", client.NA
|
||||
k9sFlags *config.Flags
|
||||
k8sFlags *genericclioptions.ConfigFlags
|
||||
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -855,6 +855,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
helm.sh/helm v1.2.1 h1:Jrn7kKQqQ/hnFWZEX+9pMFvYqFexkzrBnGqYBmIph7c=
|
||||
helm.sh/helm v2.17.0+incompatible h1:cSe3FaQOpRWLDXvTObQNj0P7WI98IG5yloU6tQVls2k=
|
||||
helm.sh/helm/v3 v3.2.0 h1:V12EGAmr2DJ/fWrPo2fPdXWSIXvlXm51vGkQIXMeymE=
|
||||
helm.sh/helm/v3 v3.2.0/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
|||
|
|
@ -59,32 +59,26 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL
|
|||
nodeMetrics := make(NodesMetrics, len(nos.Items))
|
||||
for _, no := range nos.Items {
|
||||
nodeMetrics[no.Name] = NodeMetrics{
|
||||
AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AllocatableMEM: no.Status.Allocatable.Memory().Value(),
|
||||
AllocatableEphemeral: no.Status.Allocatable.StorageEphemeral().Value(),
|
||||
AllocatableCPU: no.Status.Allocatable.Cpu().MilliValue(),
|
||||
AllocatableMEM: no.Status.Allocatable.Memory().Value(),
|
||||
}
|
||||
}
|
||||
for _, mx := range nmx.Items {
|
||||
if node, ok := nodeMetrics[mx.Name]; ok {
|
||||
node.CurrentCPU = mx.Usage.Cpu().MilliValue()
|
||||
node.CurrentMEM = mx.Usage.Memory().Value()
|
||||
node.CurrentEphemeral = mx.Usage.StorageEphemeral().Value()
|
||||
nodeMetrics[mx.Name] = node
|
||||
}
|
||||
}
|
||||
|
||||
var ccpu, cmem, ceph, tcpu, tmem, teph int64
|
||||
var ccpu, cmem, tcpu, tmem int64
|
||||
for _, mx := range nodeMetrics {
|
||||
ccpu += mx.CurrentCPU
|
||||
cmem += mx.CurrentMEM
|
||||
ceph += mx.CurrentEphemeral
|
||||
tcpu += mx.AllocatableCPU
|
||||
tmem += mx.AllocatableMEM
|
||||
teph += mx.AllocatableEphemeral
|
||||
}
|
||||
mx.PercCPU = ToPercentage(ccpu, tcpu)
|
||||
mx.PercMEM = ToPercentage(cmem, tmem)
|
||||
mx.PercEphemeral = ToPercentage(ceph, teph)
|
||||
mx.PercCPU, mx.PercMEM = ToPercentage(ccpu, tcpu), ToPercentage(cmem, tmem)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -131,6 +125,22 @@ func (m *MetricsServer) NodesMetrics(nodes *v1.NodeList, metrics *mv1beta1.NodeM
|
|||
}
|
||||
}
|
||||
|
||||
// FetchNodesMetricsMap fetch node metrics as a map.
|
||||
func (m *MetricsServer) FetchNodesMetricsMap(ctx context.Context) (NodesMetricsMap, error) {
|
||||
mm, err := m.FetchNodesMetrics(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hh := make(NodesMetricsMap, len(mm.Items))
|
||||
for i := range mm.Items {
|
||||
mx := mm.Items[i]
|
||||
hh[mx.Name] = &mx
|
||||
}
|
||||
|
||||
return hh, nil
|
||||
}
|
||||
|
||||
// FetchNodesMetrics return all metrics for nodes.
|
||||
func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMetricsList, error) {
|
||||
const msg = "user is not authorized to list node metrics"
|
||||
|
|
@ -162,6 +172,43 @@ func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMe
|
|||
return mxList, nil
|
||||
}
|
||||
|
||||
// FetchNodesMetrics return all metrics for nodes.
|
||||
func (m *MetricsServer) FetchNodeMetrics(ctx context.Context, n string) (*mv1beta1.NodeMetrics, error) {
|
||||
const msg = "user is not authorized to list node metrics"
|
||||
|
||||
mx := new(mv1beta1.NodeMetrics)
|
||||
if err := m.checkAccess(ClusterScope, "metrics.k8s.io/v1beta1/nodes", msg); err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
mmx, err := m.FetchNodesMetricsMap(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mx, ok := mmx[n]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unable to retrieve node metrics for %q", n)
|
||||
}
|
||||
return mx, nil
|
||||
}
|
||||
|
||||
// FetchPodsMetricsMap fetch pods metrics as a map.
|
||||
func (m *MetricsServer) FetchPodsMetricsMap(ctx context.Context, ns string) (PodsMetricsMap, error) {
|
||||
mm, err := m.FetchPodsMetrics(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hh := make(PodsMetricsMap, len(mm.Items))
|
||||
for i := range mm.Items {
|
||||
mx := mm.Items[i]
|
||||
hh[FQN(mx.Namespace, mx.Name)] = &mx
|
||||
}
|
||||
|
||||
return hh, nil
|
||||
}
|
||||
|
||||
// FetchPodsMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1beta1.PodMetricsList, error) {
|
||||
mx := new(mv1beta1.PodMetricsList)
|
||||
|
|
@ -196,12 +243,27 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be
|
|||
return mxList, err
|
||||
}
|
||||
|
||||
func (m *MetricsServer) FetchContainersMetrics(ctx context.Context, fqn string) (ContainersMetrics, error) {
|
||||
mm, err := m.FetchPodMetrics(ctx, fqn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmx := make(ContainersMetrics, len(mm.Containers))
|
||||
for i := range mm.Containers {
|
||||
c := mm.Containers[i]
|
||||
cmx[c.Name] = &c
|
||||
}
|
||||
|
||||
return cmx, nil
|
||||
}
|
||||
|
||||
// FetchPodMetrics return all metrics for pods in a given namespace.
|
||||
func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1beta1.PodMetrics, error) {
|
||||
var mx *mv1beta1.PodMetrics
|
||||
const msg = "user is not authorized to list pod metrics"
|
||||
|
||||
ns, n := Namespaced(fqn)
|
||||
ns, _ := Namespaced(fqn)
|
||||
if ns == NamespaceAll {
|
||||
ns = AllNamespaces
|
||||
}
|
||||
|
|
@ -209,25 +271,16 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be
|
|||
return mx, err
|
||||
}
|
||||
|
||||
if entry, ok := m.cache.Get(fqn); ok {
|
||||
pmx, ok := entry.(*mv1beta1.PodMetrics)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expecting podmetrics but got %T", entry)
|
||||
}
|
||||
return pmx, nil
|
||||
mmx, err := m.FetchPodsMetricsMap(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pmx, ok := mmx[fqn]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unable to locate pod metrics for pod %q", fqn)
|
||||
}
|
||||
|
||||
client, err := m.MXDial()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
mx, err = client.MetricsV1beta1().PodMetricses(ns).Get(ctx, n, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
m.cache.Add(fqn, mx, mxCacheExpiry)
|
||||
|
||||
return mx, nil
|
||||
return pmx, nil
|
||||
}
|
||||
|
||||
// PodsMetrics retrieves metrics for all pods in a given namespace.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
|
|
@ -59,6 +60,15 @@ var (
|
|||
ReadAllAccess = []string{GetVerb, ListVerb, WatchVerb}
|
||||
)
|
||||
|
||||
// ContainersMetrics tracks containers metrics.
|
||||
type ContainersMetrics map[string]*mv1beta1.ContainerMetrics
|
||||
|
||||
// NodesMetrics tracks node metrics.
|
||||
type NodesMetricsMap map[string]*mv1beta1.NodeMetrics
|
||||
|
||||
// PodsMetrics tracks pod metrics.
|
||||
type PodsMetricsMap map[string]*mv1beta1.PodMetrics
|
||||
|
||||
// Authorizer checks what a user can or cannot do to a resource.
|
||||
type Authorizer interface {
|
||||
// CanI returns true if the user can use these actions for a given resource.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
|
@ -33,13 +32,11 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
}
|
||||
|
||||
var (
|
||||
pmx *mv1beta1.PodMetrics
|
||||
cmx client.ContainersMetrics
|
||||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
if pmx, err = client.DialMetrics(c.Client()).FetchPodMetrics(ctx, fqn); err != nil {
|
||||
log.Warn().Err(err).Msgf("No metrics found for pod %q", fqn)
|
||||
}
|
||||
cmx, _ = client.DialMetrics(c.Client()).FetchContainersMetrics(ctx, fqn)
|
||||
}
|
||||
|
||||
po, err := c.fetchPod(fqn)
|
||||
|
|
@ -48,10 +45,10 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
|
|||
}
|
||||
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers))
|
||||
for _, co := range po.Spec.InitContainers {
|
||||
res = append(res, makeContainerRes(co, po, pmx, true))
|
||||
res = append(res, makeContainerRes(co, po, cmx[co.Name], true))
|
||||
}
|
||||
for _, co := range po.Spec.Containers {
|
||||
res = append(res, makeContainerRes(co, po, pmx, false))
|
||||
res = append(res, makeContainerRes(co, po, cmx[co.Name], false))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
|
@ -68,12 +65,7 @@ func (c *Container) TailLogs(ctx context.Context, logChan LogChan, opts LogOptio
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func makeContainerRes(co v1.Container, po *v1.Pod, pmx *mv1beta1.PodMetrics, isInit bool) render.ContainerRes {
|
||||
cmx, err := containerMetrics(co.Name, pmx)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No container metrics found for %s::%s", po.Name, co.Name)
|
||||
}
|
||||
|
||||
func makeContainerRes(co v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics, isInit bool) render.ContainerRes {
|
||||
return render.ContainerRes{
|
||||
Container: &co,
|
||||
Status: getContainerStatus(co.Name, po.Status),
|
||||
|
|
@ -83,18 +75,6 @@ func makeContainerRes(co v1.Container, po *v1.Pod, pmx *mv1beta1.PodMetrics, isI
|
|||
}
|
||||
}
|
||||
|
||||
func containerMetrics(n string, pmx *mv1beta1.PodMetrics) (*mv1beta1.ContainerMetrics, error) {
|
||||
if pmx == nil {
|
||||
return nil, fmt.Errorf("no metrics for container %s", n)
|
||||
}
|
||||
for _, m := range pmx.Containers {
|
||||
if m.Name == n {
|
||||
return &m, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus {
|
||||
for _, c := range status.ContainerStatuses {
|
||||
if c.Name == co {
|
||||
|
|
@ -117,9 +97,5 @@ func (c *Container) fetchPod(fqn string) (*v1.Pod, error) {
|
|||
}
|
||||
var po v1.Pod
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &po, nil
|
||||
return &po, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
@ -110,66 +109,58 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error {
|
|||
|
||||
// Get returns a node resource.
|
||||
func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
var (
|
||||
nmx *mv1beta1.NodeMetricsList
|
||||
err error
|
||||
)
|
||||
o, err := n.Resource.Get(ctx, path)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
|
||||
u, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
|
||||
}
|
||||
|
||||
var nmx *mv1beta1.NodeMetrics
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
nmx, _ = client.DialMetrics(n.Client()).FetchNodesMetrics(ctx)
|
||||
nmx, _ = client.DialMetrics(n.Client()).FetchNodeMetrics(ctx, path)
|
||||
}
|
||||
|
||||
no, err := FetchNode(ctx, n.Factory, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &render.NodeWithMetrics{
|
||||
Raw: &unstructured.Unstructured{Object: o},
|
||||
MX: nodeMetricsFor(MetaFQN(no.ObjectMeta), nmx),
|
||||
}, nil
|
||||
return &render.NodeWithMetrics{Raw: u, MX: nmx}, nil
|
||||
}
|
||||
|
||||
// List returns a collection of node resources.
|
||||
func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
var (
|
||||
nmx *mv1beta1.NodeMetricsList
|
||||
err error
|
||||
)
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
nmx, _ = client.DialMetrics(n.Client()).FetchNodesMetrics(ctx)
|
||||
}
|
||||
|
||||
labels, _ := ctx.Value(internal.KeyLabels).(string)
|
||||
nn, err := FetchNodes(ctx, n.Factory, labels)
|
||||
oo, err := n.Resource.List(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oo := make([]runtime.Object, len(nn.Items))
|
||||
for i, no := range nn.Items {
|
||||
o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&nn.Items[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta, ok := o["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expecting interface map but got `%T", o)
|
||||
}
|
||||
pods, err := n.GetPods(meta["name"].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oo[i] = &render.NodeWithMetrics{
|
||||
Raw: &unstructured.Unstructured{Object: o},
|
||||
MX: nodeMetricsFor(MetaFQN(no.ObjectMeta), nmx),
|
||||
Pods: pods,
|
||||
}
|
||||
return oo, err
|
||||
}
|
||||
|
||||
return oo, nil
|
||||
var nmx client.NodesMetricsMap
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
nmx, _ = client.DialMetrics(n.Client()).FetchNodesMetricsMap(ctx)
|
||||
}
|
||||
|
||||
res := make([]runtime.Object, 0, len(oo))
|
||||
for _, o := range oo {
|
||||
u, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
|
||||
}
|
||||
|
||||
fqn := extractFQN(o)
|
||||
_, name := client.Namespaced(fqn)
|
||||
podCount, err := n.CountPods(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, &render.NodeWithMetrics{
|
||||
Raw: u,
|
||||
MX: nmx[name],
|
||||
PodCount: podCount,
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CountPods counts the pods scheduled on a given node.
|
||||
|
|
@ -189,7 +180,7 @@ func (n *Node) CountPods(nodeName string) (int, error) {
|
|||
if !ok {
|
||||
return count, fmt.Errorf("expecting interface map but got `%T", o)
|
||||
}
|
||||
if spec["nodeName"] == nodeName {
|
||||
if node, ok := spec["nodeName"]; ok && node == nodeName {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
|
@ -231,11 +222,18 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
|
|||
return nil, fmt.Errorf("user is not authorized to list nodes")
|
||||
}
|
||||
|
||||
dial, err := f.Client().Dial()
|
||||
o, err := f.Get("v1/nodes", path, false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dial.CoreV1().Nodes().Get(ctx, path, metav1.GetOptions{})
|
||||
|
||||
var node v1.Node
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &node, nil
|
||||
}
|
||||
|
||||
// FetchNodes retrieves all nodes.
|
||||
|
|
@ -248,23 +246,19 @@ func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList,
|
|||
return nil, fmt.Errorf("user is not authorized to list nodes")
|
||||
}
|
||||
|
||||
dial, err := f.Client().Dial()
|
||||
oo, err := f.List("v1/nodes", "", false, labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dial.CoreV1().Nodes().List(ctx, metav1.ListOptions{
|
||||
LabelSelector: labelsSel,
|
||||
})
|
||||
}
|
||||
|
||||
func nodeMetricsFor(fqn string, mmx *mv1beta1.NodeMetricsList) *mv1beta1.NodeMetrics {
|
||||
if mmx == nil {
|
||||
return nil
|
||||
}
|
||||
for _, mx := range mmx.Items {
|
||||
if MetaFQN(mx.ObjectMeta) == fqn {
|
||||
return &mx
|
||||
nn := make([]v1.Node, 0, len(oo))
|
||||
for _, o := range oo {
|
||||
var node v1.Node
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nn = append(nn, node)
|
||||
}
|
||||
return nil
|
||||
|
||||
return &v1.NodeList{Items: nn}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,15 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) {
|
|||
|
||||
// List returns a collection of nodes.
|
||||
func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
oo, err := p.Resource.List(ctx, ns)
|
||||
if err != nil {
|
||||
return oo, err
|
||||
}
|
||||
|
||||
var pmx client.PodsMetricsMap
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
pmx, _ = client.DialMetrics(p.Client()).FetchPodsMetricsMap(ctx, ns)
|
||||
}
|
||||
sel, _ := ctx.Value(internal.KeyFields).(string)
|
||||
fsel, err := labels.ConvertSelectorToLabelsMap(sel)
|
||||
if err != nil {
|
||||
|
|
@ -80,24 +89,15 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
}
|
||||
nodeName := fsel["spec.nodeName"]
|
||||
|
||||
oo, err := p.Resource.List(ctx, ns)
|
||||
if err != nil {
|
||||
return oo, err
|
||||
}
|
||||
|
||||
var pmx *mv1beta1.PodMetricsList
|
||||
if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok {
|
||||
pmx, _ = client.DialMetrics(p.Client()).FetchPodsMetrics(ctx, ns)
|
||||
}
|
||||
|
||||
res := make([]runtime.Object, 0, len(oo))
|
||||
for _, o := range oo {
|
||||
u, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o)
|
||||
}
|
||||
fqn := extractFQN(o)
|
||||
if nodeName == "" {
|
||||
res = append(res, &render.PodWithMetrics{Raw: u, MX: podMetricsFor(o, pmx)})
|
||||
res = append(res, &render.PodWithMetrics{Raw: u, MX: pmx[fqn]})
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
|||
return res, fmt.Errorf("expecting interface map but got `%T", o)
|
||||
}
|
||||
if spec["nodeName"] == nodeName {
|
||||
res = append(res, &render.PodWithMetrics{Raw: u, MX: podMetricsFor(o, pmx)})
|
||||
res = append(res, &render.PodWithMetrics{Raw: u, MX: pmx[fqn]})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,7 +370,7 @@ func readLogs(stream io.ReadCloser, c LogChan, opts LogOptions) {
|
|||
for {
|
||||
bytes, err := r.ReadBytes('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if errors.Is(err, io.EOF) {
|
||||
log.Warn().Err(err).Msgf("Stream closed for %s", opts.Info())
|
||||
c <- opts.DecorateLog([]byte("\nlog stream closed\n"))
|
||||
return
|
||||
|
|
@ -383,19 +383,6 @@ func readLogs(stream io.ReadCloser, c LogChan, opts LogOptions) {
|
|||
}
|
||||
}
|
||||
|
||||
func podMetricsFor(o runtime.Object, mmx *mv1beta1.PodMetricsList) *mv1beta1.PodMetrics {
|
||||
if mmx == nil {
|
||||
return nil
|
||||
}
|
||||
fqn := extractFQN(o)
|
||||
for _, mx := range mmx.Items {
|
||||
if MetaFQN(mx.ObjectMeta) == fqn {
|
||||
return &mx
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MetaFQN returns a fully qualified resource name.
|
||||
func MetaFQN(m metav1.ObjectMeta) string {
|
||||
if m.Namespace == "" {
|
||||
|
|
|
|||
|
|
@ -90,23 +90,22 @@ func (c *Cluster) UserName() string {
|
|||
|
||||
// Metrics gathers node level metrics and compute utilization percentages.
|
||||
func (c *Cluster) Metrics(ctx context.Context, mx *client.ClusterMetrics) error {
|
||||
var nn *v1.NodeList
|
||||
if n, ok := c.cache.Get(clusterNodesKey); ok {
|
||||
if nodes, ok := n.(*v1.NodeList); ok {
|
||||
nn = nodes
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if nn == nil {
|
||||
nn, err = dao.FetchNodes(ctx, c.factory, "")
|
||||
if err != nil {
|
||||
var (
|
||||
nn *v1.NodeList
|
||||
err error
|
||||
)
|
||||
if v, ok := c.cache.Get(clusterNodesKey); ok {
|
||||
nn = v.(*v1.NodeList)
|
||||
} else {
|
||||
if nn, err = dao.FetchNodes(ctx, c.factory, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.cache.Add(clusterNodesKey, nn, clusterCacheExpiry)
|
||||
nmx, err := c.mx.FetchNodesMetrics(ctx)
|
||||
if err != nil {
|
||||
if len(nn.Items) > 0 {
|
||||
c.cache.Add(clusterNodesKey, nn, clusterCacheExpiry)
|
||||
}
|
||||
var nmx *mv1beta1.NodeMetricsList
|
||||
if nmx, err = c.mx.FetchNodesMetrics(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
"vbom.ml/util/sortorder"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -101,7 +100,7 @@ func (c *ClusterInfo) fetchK9sLatestRev() string {
|
|||
return rev.(string)
|
||||
}
|
||||
|
||||
latestRev, err := checkLastestRev()
|
||||
latestRev, err := fetchLastestRev()
|
||||
if err != nil {
|
||||
log.Error().Msgf("k9s latest rev fetch failed")
|
||||
} else {
|
||||
|
|
@ -123,8 +122,9 @@ func (c *ClusterInfo) Refresh() {
|
|||
data.Context = c.cluster.ContextName()
|
||||
data.Cluster = c.cluster.ClusterName()
|
||||
data.User = c.cluster.UserName()
|
||||
data.K9sVer, data.K9sLatest = c.version, c.fetchK9sLatestRev()
|
||||
if !sortorder.NaturalLess(data.K9sVer, data.K9sLatest) {
|
||||
v1, v2 := NewSemVer(data.K9sVer), NewSemVer(c.fetchK9sLatestRev())
|
||||
data.K9sVer, data.K9sLatest = v1.String(), v2.String()
|
||||
if v1.IsCurrent(v2) {
|
||||
data.K9sLatest = ""
|
||||
}
|
||||
data.K8sVer = c.cluster.Version()
|
||||
|
|
@ -134,6 +134,8 @@ func (c *ClusterInfo) Refresh() {
|
|||
var mx client.ClusterMetrics
|
||||
if err := c.cluster.Metrics(ctx, &mx); err == nil {
|
||||
data.Cpu, data.Mem, data.Ephemeral = mx.PercCPU, mx.PercMEM, mx.PercEphemeral
|
||||
} else {
|
||||
log.Error().Err(err).Msgf("Cluster metrics failed")
|
||||
}
|
||||
|
||||
if c.data.Deltas(data) {
|
||||
|
|
@ -178,7 +180,7 @@ func (c *ClusterInfo) fireNoMetaChanged(data ClusterMeta) {
|
|||
|
||||
// Helpers...
|
||||
|
||||
func checkLastestRev() (string, error) {
|
||||
func fetchLastestRev() (string, error) {
|
||||
log.Debug().Msgf("Fetching latest k9s rev...")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
|
|
|||
|
|
@ -6,41 +6,12 @@ import (
|
|||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"vbom.ml/util/sortorder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||
}
|
||||
|
||||
func TestVersionCheck(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
current, latest string
|
||||
e bool
|
||||
}{
|
||||
"same": {
|
||||
current: "v0.11.1",
|
||||
latest: "v0.11.1",
|
||||
},
|
||||
"updated": {
|
||||
current: "v0.11.1",
|
||||
latest: "v0.12.1",
|
||||
e: true,
|
||||
},
|
||||
"current": {
|
||||
current: "v0.11.1",
|
||||
latest: "v0.09.2",
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, sortorder.NaturalLess(u.current, u.latest))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterMetaDelta(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
o, n model.ClusterMeta
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var versionRX = regexp.MustCompile(`\Av(\d+)\.(\d+)\.(\d+)\z`)
|
||||
|
||||
// SemVer represents a semantic version.
|
||||
type SemVer struct {
|
||||
Major, Minor, Patch int
|
||||
}
|
||||
|
||||
// NewSemVer returns a new semantic version.
|
||||
func NewSemVer(version string) *SemVer {
|
||||
var v SemVer
|
||||
v.Major, v.Minor, v.Patch = v.parse(NormalizeVersion(version))
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
// String returns version as a string.
|
||||
func (v *SemVer) String() string {
|
||||
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
func (*SemVer) parse(version string) (major, minor, patch int) {
|
||||
mm := versionRX.FindStringSubmatch(version)
|
||||
if len(mm) < 4 {
|
||||
return
|
||||
}
|
||||
major, _ = strconv.Atoi(mm[1])
|
||||
minor, _ = strconv.Atoi(mm[2])
|
||||
patch, _ = strconv.Atoi(mm[3])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Normalize ensures the version starts with a v.
|
||||
func NormalizeVersion(version string) string {
|
||||
if version[0] == 'v' {
|
||||
return version
|
||||
}
|
||||
return "v" + version
|
||||
}
|
||||
|
||||
// IsCurrent asserts if at latest release.
|
||||
func (v *SemVer) IsCurrent(latest *SemVer) bool {
|
||||
return v.Major >= latest.Major && v.Minor >= latest.Minor && v.Patch >= latest.Patch
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package model_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewSemVer(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
version string
|
||||
major, minor, patch int
|
||||
}{
|
||||
"plain": {
|
||||
version: "0.11.1",
|
||||
major: 0,
|
||||
minor: 11,
|
||||
patch: 1,
|
||||
},
|
||||
"normalized": {
|
||||
version: "v10.11.12",
|
||||
major: 10,
|
||||
minor: 11,
|
||||
patch: 12,
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
v := model.NewSemVer(u.version)
|
||||
assert.Equal(t, u.major, v.Major)
|
||||
assert.Equal(t, u.minor, v.Minor)
|
||||
assert.Equal(t, u.patch, v.Patch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemVerIsCurrent(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
current, latest string
|
||||
e bool
|
||||
}{
|
||||
"same": {
|
||||
current: "0.11.1",
|
||||
latest: "0.11.1",
|
||||
e: true,
|
||||
},
|
||||
"older": {
|
||||
current: "v10.11.12",
|
||||
latest: "v10.11.13",
|
||||
},
|
||||
"newer": {
|
||||
current: "10.11.13",
|
||||
latest: "10.11.12",
|
||||
e: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
v1, v2 := model.NewSemVer(u.current), model.NewSemVer(u.latest)
|
||||
assert.Equal(t, u.e, v1.IsCurrent(v2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ func (n Node) Render(o interface{}, ns string, r *Row) error {
|
|||
no.Status.NodeInfo.KernelVersion,
|
||||
iIP,
|
||||
eIP,
|
||||
strconv.Itoa(len(oo.Pods)),
|
||||
strconv.Itoa(oo.PodCount),
|
||||
toMc(c.cpu),
|
||||
toMi(c.mem),
|
||||
strconv.Itoa(p.rCPU()),
|
||||
|
|
@ -134,9 +134,9 @@ func (Node) diagnose(ss []string) error {
|
|||
|
||||
// NodeWithMetrics represents a node with its associated metrics.
|
||||
type NodeWithMetrics struct {
|
||||
Raw *unstructured.Unstructured
|
||||
MX *mv1beta1.NodeMetrics
|
||||
Pods []*v1.Pod
|
||||
Raw *unstructured.Unstructured
|
||||
MX *mv1beta1.NodeMetrics
|
||||
PodCount int
|
||||
}
|
||||
|
||||
// GetObjectKind returns a schema object.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func (a *App) ConOK() bool {
|
|||
|
||||
// Init initializes the application.
|
||||
func (a *App) Init(version string, rate int) error {
|
||||
a.version = version
|
||||
a.version = model.NormalizeVersion(version)
|
||||
|
||||
ctx := context.WithValue(context.Background(), internal.KeyApp, a)
|
||||
if err := a.Content.Init(ctx); err != nil {
|
||||
|
|
@ -103,7 +103,7 @@ func (a *App) Init(version string, rate int) error {
|
|||
}
|
||||
a.initFactory(ns)
|
||||
|
||||
a.clusterModel = model.NewClusterInfo(a.factory, version)
|
||||
a.clusterModel = model.NewClusterInfo(a.factory, a.version)
|
||||
a.clusterModel.AddListener(a.clusterInfo())
|
||||
a.clusterModel.AddListener(a.statusIndicator())
|
||||
a.clusterModel.Refresh()
|
||||
|
|
@ -115,13 +115,13 @@ func (a *App) Init(version string, rate int) error {
|
|||
}
|
||||
a.CmdBuff().SetSuggestionFn(a.suggestCommand())
|
||||
|
||||
a.layout(ctx, version)
|
||||
a.layout(ctx)
|
||||
a.initSignals()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) layout(ctx context.Context, version string) {
|
||||
func (a *App) layout(ctx context.Context) {
|
||||
flash := ui.NewFlash(a.App)
|
||||
go flash.Watch(ctx, a.Flash().Channel())
|
||||
|
||||
|
|
@ -134,9 +134,8 @@ func (a *App) layout(ctx context.Context, version string) {
|
|||
main.AddItem(flash, 1, 1, false)
|
||||
|
||||
a.Main.AddPage("main", main, true, false)
|
||||
a.Main.AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
|
||||
a.Main.AddPage("splash", ui.NewSplash(a.Styles, a.version), true, true)
|
||||
a.toggleHeader(!a.Config.K9s.IsHeadless())
|
||||
// a.toggleCrumbs(!a.Config.K9s.GetCrumbsless())
|
||||
}
|
||||
|
||||
func (a *App) initSignals() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue