fix perf issue with resource metrics

mine
derailed 2020-11-03 08:30:55 -07:00
parent 2d2c6b06b6
commit c996ab314b
5 changed files with 95 additions and 112 deletions

View File

@ -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.

View File

@ -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...

View File

@ -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))

View File

@ -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(),

View File

@ -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 {