From c996ab314b3f229857cf6ddb37d130d4c76f9c4b Mon Sep 17 00:00:00 2001 From: derailed Date: Tue, 3 Nov 2020 08:30:55 -0700 Subject: [PATCH] fix perf issue with resource metrics --- internal/render/container.go | 57 ++++++++++++------ internal/render/container_test.go | 19 ++++++ internal/render/helpers.go | 9 --- internal/render/node.go | 26 ++++----- internal/render/pod.go | 96 +++++++++---------------------- 5 files changed, 95 insertions(+), 112 deletions(-) diff --git a/internal/render/container.go b/internal/render/container.go index d91f21fb..ba7f47c5 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" v1 "k8s.io/api/core/v1" @@ -112,10 +113,10 @@ func (c Container) Render(o interface{}, name string, r *Row) error { boolToStr(co.IsInit), restarts, probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe), - toMc(cur.rCPU().MilliValue()), - toMi(cur.rMEM().Value()), - toMc(res[requestCPU].MilliValue()) + ":" + toMc(res[limitCPU].MilliValue()), - toMi(res[requestMEM].Value()) + ":" + toMi(res[limitMEM].Value()), + toMc(cur.cpu), + toMi(cur.mem), + toMc(res.cpu) + ":" + toMc(res.lcpu), + toMi(res.mem) + ":" + toMi(res.lmem), strconv.Itoa(perc.rCPU()), strconv.Itoa(perc.lCPU()), strconv.Itoa(perc.rMEM()), @@ -143,26 +144,39 @@ func (Container) diagnose(state, ready string) error { // ---------------------------------------------------------------------------- // Helpers... -func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (resources, percentages, resources) { +func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (metric, percentages, metric) { rList, lList := containerRequests(co), co.Resources.Limits - c, p, r := newResources(nil, nil), newPercentages(), newResources(rList, lList) + var c metric + p := newPercentages() + + var r metric + if rList.Cpu() != nil { + r.cpu = rList.Cpu().MilliValue() + } + if lList.Cpu() != nil { + r.lcpu = lList.Cpu().MilliValue() + } + if rList.Memory() != nil { + r.mem = rList.Memory().Value() + } + if lList.Memory() != nil { + r.lmem = lList.Memory().Value() + } + if mx == nil { return c, p, r } - c[requestCPU], c[requestMEM] = mx.Usage.Cpu(), mx.Usage.Memory() - if rList.Cpu() != nil { - p[requestCPU] = percentMc(c.rCPU(), rList.Cpu()) + if mx.Usage.Cpu() != nil { + c.cpu = mx.Usage.Cpu().MilliValue() } - if lList.Cpu() != nil { - p[limitCPU] = percentMc(c.rCPU(), lList.Cpu()) - } - if rList.Memory() != nil { - p[requestMEM] = percentMi(c.rMEM(), rList.Memory()) - } - if lList.Memory() != nil { - p[limitMEM] = percentMi(c.rMEM(), lList.Memory()) + if mx.Usage.Memory() != nil { + c.mem = mx.Usage.Memory().Value() } + p[requestCPU] = client.ToPercentage(c.cpu, r.cpu) + p[limitCPU] = client.ToPercentage(c.cpu, r.lcpu) + p[requestMEM] = client.ToPercentage(c.mem, r.mem) + p[limitMEM] = client.ToPercentage(c.mem, r.lmem) return c, p, r } @@ -204,11 +218,16 @@ func ToContainerState(s v1.ContainerState) string { } } +const ( + on string = "on" + off = "off" +) + func probe(p *v1.Probe) string { if p == nil { - return "off" + return off } - return "on" + return on } // ContainerRes represents a container and its metrics. diff --git a/internal/render/container_test.go b/internal/render/container_test.go index e75768c4..fe1bdcd3 100644 --- a/internal/render/container_test.go +++ b/internal/render/container_test.go @@ -50,6 +50,25 @@ func TestContainer(t *testing.T) { ) } +func BenchmarkContainerRender(b *testing.B) { + var c render.Container + + cres := render.ContainerRes{ + Container: makeContainer(), + Status: makeContainerStatus(), + MX: makeContainerMetrics(), + IsInit: false, + Age: makeAge(), + } + var r render.Row + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + c.Render(cres, "blee", &r) + } +} + // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 1d6b7926..41204afb 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -13,7 +13,6 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/text/language" "golang.org/x/text/message" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/duration" ) @@ -82,14 +81,6 @@ func asSelector(s *metav1.LabelSelector) string { return sel.String() } -func percentMc(v1, v2 *resource.Quantity) int { - return client.ToPercentage(v1.MilliValue(), v2.MilliValue()) -} - -func percentMi(v1, v2 *resource.Quantity) int { - return client.ToPercentage(client.ToMB(v1.Value()), client.ToMB(v2.Value())) -} - // ToSelector flattens a map selector to a string selector. func toSelector(m map[string]string) string { s := make([]string, 0, len(m)) diff --git a/internal/render/node.go b/internal/render/node.go index e0df15d4..4c1d5419 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -80,20 +80,13 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { c, p, a := gatherNodeMX(&no, oo.MX) trc, trm, tlc, tlm := new(resource.Quantity), new(resource.Quantity), new(resource.Quantity), new(resource.Quantity) for _, p := range oo.Pods { - rList := podRequests(p.Spec) - if rList.Cpu() != nil { - trc.Add(*rList.Cpu()) - } - if rList.Memory() != nil { - trm.Add(*rList.Memory()) - } - lList := podLimits(p.Spec) - if lList.Cpu() != nil { - tlc.Add(*lList.Cpu()) - } - if lList.Memory() != nil { - tlm.Add(*lList.Memory()) - } + rcpu, rmem := podRequests(p.Spec) + trc.Add(rcpu) + trm.Add(rmem) + + lcpu, lmem := podLimits(p.Spec) + tlc.Add(lcpu) + tlm.Add(lmem) } statuses := make(sort.StringSlice, 10) status(no.Status.Conditions, no.Spec.Unschedulable, statuses) @@ -176,11 +169,12 @@ func (n *NodeWithMetrics) DeepCopyObject() runtime.Object { } type metric struct { - cpu, mem int64 + cpu, mem int64 + lcpu, lmem int64 } func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (metric, percentages, metric) { - c := metric{cpu: 0, mem: 0} + var c metric p := newPercentages() a := metric{ cpu: no.Status.Allocatable.Cpu().MilliValue(), diff --git a/internal/render/pod.go b/internal/render/pod.go index 7f466142..7546ba88 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -25,7 +25,6 @@ const ( type ( qualifiedResource string - resources map[qualifiedResource]*resource.Quantity percentages map[qualifiedResource]int ) @@ -45,31 +44,6 @@ func (p percentages) lMEM() int { return p[limitMEM] } -func newResources(req, lim v1.ResourceList) resources { - if lim == nil { - lim = v1.ResourceList{} - } - return resources{ - requestCPU: req.Cpu(), - requestMEM: req.Memory(), - limitCPU: lim.Cpu(), - limitMEM: lim.Memory(), - } -} - -func (r resources) rCPU() *resource.Quantity { - return r[requestCPU] -} -func (r resources) rMEM() *resource.Quantity { - return r[requestMEM] -} -func (r resources) lCPU() *resource.Quantity { - return r[limitCPU] -} -func (r resources) lMEM() *resource.Quantity { - return r[limitMEM] -} - // Pod renders a K8s Pod to screen. type Pod struct{} @@ -158,10 +132,10 @@ func (p Pod) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss)), strconv.Itoa(rc), phase, - toMc(c.rCPU().MilliValue()), - toMi(c.rMEM().Value()), - toMc(res[requestCPU].MilliValue()) + ":" + toMc(res[limitCPU].MilliValue()), - toMi(res[requestMEM].Value()) + ":" + toMi(res[limitMEM].Value()), + toMc(c.cpu), + toMi(c.mem), + toMc(res.cpu) + ":" + toMc(res.lcpu), + toMi(res.mem) + ":" + toMi(res.lmem), strconv.Itoa(perc.rCPU()), strconv.Itoa(perc.lCPU()), strconv.Itoa(perc.rMEM()), @@ -207,26 +181,25 @@ func (p *PodWithMetrics) DeepCopyObject() runtime.Object { return p } -func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (resources, percentages, resources) { - rList, lList := podRequests(pod.Spec), podLimits(pod.Spec) - r, p := newResources(rList, lList), newPercentages() +func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (metric, percentages, metric) { + var c, r metric + p := newPercentages() + + rcpu, rmem := podRequests(pod.Spec) + lcpu, lmem := podLimits(pod.Spec) + r.cpu, r.lcpu = rcpu.MilliValue(), lcpu.MilliValue() + r.mem, r.lmem = rmem.Value(), lmem.Value() + if mx == nil { - return newResources(nil, nil), p, r + return c, p, r } - c := newResources(currentRes(mx), nil) - if rList.Cpu() != nil { - p[requestCPU] = percentMc(c.rCPU(), rList.Cpu()) - } - if lList.Cpu() != nil { - p[limitCPU] = percentMc(c.rCPU(), lList.Cpu()) - } - if rList.Memory() != nil { - p[requestMEM] = percentMi(c.rMEM(), rList.Memory()) - } - if lList.Memory() != nil { - p[limitMEM] = percentMi(c.rMEM(), lList.Memory()) - } + ccpu, cmem := currentRes(mx) + c.cpu = ccpu.MilliValue() + c.mem = cmem.Value() + + p[requestCPU], p[limitCPU] = client.ToPercentage(c.cpu, r.cpu), client.ToPercentage(c.cpu, r.lcpu) + p[requestMEM], p[limitMEM] = client.ToPercentage(c.mem, r.mem), client.ToPercentage(c.mem, r.lmem) return c, p, r } @@ -241,10 +214,10 @@ func containerRequests(co *v1.Container) v1.ResourceList { return lim } - return newResourceList(nil, nil) + return nil } -func podLimits(spec v1.PodSpec) v1.ResourceList { +func podLimits(spec v1.PodSpec) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) for _, co := range spec.Containers { limit := co.Resources.Limits @@ -255,23 +228,10 @@ func podLimits(spec v1.PodSpec) v1.ResourceList { mem.Add(*limit.Memory()) } } - return newResourceList(cpu, mem) + return *cpu, *mem } -func newResourceList(cpu, mem *resource.Quantity) v1.ResourceList { - if cpu == nil { - cpu = new(resource.Quantity) - } - if mem == nil { - mem = new(resource.Quantity) - } - return v1.ResourceList{ - v1.ResourceCPU: *cpu, - v1.ResourceMemory: *mem, - } -} - -func podRequests(spec v1.PodSpec) v1.ResourceList { +func podRequests(spec v1.PodSpec) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) for i := range spec.Containers { rl := containerRequests(&spec.Containers[i]) @@ -282,13 +242,13 @@ func podRequests(spec v1.PodSpec) v1.ResourceList { mem.Add(*rl.Memory()) } } - return newResourceList(cpu, mem) + return *cpu, *mem } -func currentRes(mx *mv1beta1.PodMetrics) v1.ResourceList { +func currentRes(mx *mv1beta1.PodMetrics) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) if mx == nil { - return newResourceList(nil, nil) + return *cpu, *mem } for _, co := range mx.Containers { c, m := co.Usage.Cpu(), co.Usage.Memory() @@ -296,7 +256,7 @@ func currentRes(mx *mv1beta1.PodMetrics) v1.ResourceList { mem.Add(*m) } - return newResourceList(cpu, mem) + return *cpu, *mem } func (*Pod) mapQOS(class v1.PodQOSClass) string {