Added --sum flag to kubectl top pod

Kubernetes-commit: 71acf56766c966653c3d1fe0ef6f3f036277d546
This commit is contained in:
Chok Yip Lau 2021-09-16 09:28:30 -04:00 committed by Kubernetes Publisher
parent 472713ca0c
commit 7254bd442d
5 changed files with 332 additions and 112 deletions

View File

@ -22,7 +22,7 @@ import (
"fmt"
"time"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
@ -52,6 +52,7 @@ type TopPodOptions struct {
PrintContainers bool
NoHeaders bool
UseProtocolBuffers bool
Sum bool
PodClient corev1client.PodsGetter
Printer *metricsutil.TopCmdPrinter
@ -115,6 +116,7 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericclioptions
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers.")
cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.")
cmd.Flags().BoolVar(&o.Sum, "sum", o.Sum, "Print the sum of the resource usage")
return cmd
}
@ -215,7 +217,7 @@ func (o TopPodOptions) RunTopPod() error {
}
}
return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders, o.SortBy)
return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders, o.SortBy, o.Sum)
}
func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, labelSelector labels.Selector, fieldSelector fields.Selector) (*metricsapi.PodMetricsList, error) {
@ -274,7 +276,7 @@ func verifyEmptyMetrics(o TopPodOptions, labelSelector labels.Selector, fieldSel
return errors.New("metrics not available yet")
}
func checkPodAge(pod *v1.Pod) error {
func checkPodAge(pod *corev1.Pod) error {
age := time.Since(pod.CreationTimestamp.Time)
if age > metricsCreationDelay {
message := fmt.Sprintf("Metrics not available for pod %s/%s, age: %s", pod.Namespace, pod.Name, age.String())

View File

@ -52,114 +52,6 @@ func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
return &TopCmdPrinter{out: out}
}
type NodeMetricsSorter struct {
metrics []metricsapi.NodeMetrics
sortBy string
}
func (n *NodeMetricsSorter) Len() int {
return len(n.metrics)
}
func (n *NodeMetricsSorter) Swap(i, j int) {
n.metrics[i], n.metrics[j] = n.metrics[j], n.metrics[i]
}
func (n *NodeMetricsSorter) Less(i, j int) bool {
switch n.sortBy {
case "cpu":
return n.metrics[i].Usage.Cpu().MilliValue() > n.metrics[j].Usage.Cpu().MilliValue()
case "memory":
return n.metrics[i].Usage.Memory().Value() > n.metrics[j].Usage.Memory().Value()
default:
return n.metrics[i].Name < n.metrics[j].Name
}
}
func NewNodeMetricsSorter(metrics []metricsapi.NodeMetrics, sortBy string) *NodeMetricsSorter {
return &NodeMetricsSorter{
metrics: metrics,
sortBy: sortBy,
}
}
type PodMetricsSorter struct {
metrics []metricsapi.PodMetrics
sortBy string
withNamespace bool
podMetrics []v1.ResourceList
}
func (p *PodMetricsSorter) Len() int {
return len(p.metrics)
}
func (p *PodMetricsSorter) Swap(i, j int) {
p.metrics[i], p.metrics[j] = p.metrics[j], p.metrics[i]
p.podMetrics[i], p.podMetrics[j] = p.podMetrics[j], p.podMetrics[i]
}
func (p *PodMetricsSorter) Less(i, j int) bool {
switch p.sortBy {
case "cpu":
return p.podMetrics[i].Cpu().MilliValue() > p.podMetrics[j].Cpu().MilliValue()
case "memory":
return p.podMetrics[i].Memory().Value() > p.podMetrics[j].Memory().Value()
default:
if p.withNamespace && p.metrics[i].Namespace != p.metrics[j].Namespace {
return p.metrics[i].Namespace < p.metrics[j].Namespace
}
return p.metrics[i].Name < p.metrics[j].Name
}
}
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string) *PodMetricsSorter {
var podMetrics = make([]v1.ResourceList, len(metrics))
if len(sortBy) > 0 {
for i, v := range metrics {
podMetrics[i] = getPodMetrics(&v)
}
}
return &PodMetricsSorter{
metrics: metrics,
sortBy: sortBy,
withNamespace: withNamespace,
podMetrics: podMetrics,
}
}
type ContainerMetricsSorter struct {
metrics []metricsapi.ContainerMetrics
sortBy string
}
func (s *ContainerMetricsSorter) Len() int {
return len(s.metrics)
}
func (s *ContainerMetricsSorter) Swap(i, j int) {
s.metrics[i], s.metrics[j] = s.metrics[j], s.metrics[i]
}
func (s *ContainerMetricsSorter) Less(i, j int) bool {
switch s.sortBy {
case "cpu":
return s.metrics[i].Usage.Cpu().MilliValue() > s.metrics[j].Usage.Cpu().MilliValue()
case "memory":
return s.metrics[i].Usage.Memory().Value() > s.metrics[j].Usage.Memory().Value()
default:
return s.metrics[i].Name < s.metrics[j].Name
}
}
func NewContainerMetricsSorter(metrics []metricsapi.ContainerMetrics, sortBy string) *ContainerMetricsSorter {
return &ContainerMetricsSorter{
metrics: metrics,
sortBy: sortBy,
}
}
func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
if len(metrics) == 0 {
return nil
@ -190,18 +82,22 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics,
return nil
}
func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, noHeaders bool, sortBy string) error {
func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, noHeaders bool, sortBy string, sum bool) error {
if len(metrics) == 0 {
return nil
}
w := printers.GetNewTabWriter(printer.out)
defer w.Flush()
columnWidth := len(PodColumns)
if !noHeaders {
if withNamespace {
printValue(w, NamespaceColumn)
columnWidth++
}
if printContainers {
printValue(w, PodColumn)
columnWidth++
}
printColumnNames(w, PodColumns)
}
@ -215,7 +111,17 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p
} else {
printSinglePodMetrics(w, &m, withNamespace)
}
}
if sum {
adder := NewResourceAdder(MeasuredResources)
for _, m := range metrics {
adder.AddPodMetrics(&m)
}
printPodResourcesSum(w, adder.total, columnWidth)
}
return nil
}
@ -310,3 +216,21 @@ func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quant
fmt.Fprintf(out, "%v", quantity.Value())
}
}
func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int) {
for i := 0; i < columnWidth-2; i++ {
printValue(out, "")
}
printValue(out, "________")
printValue(out, "________")
fmt.Fprintf(out, "\n")
for i := 0; i < columnWidth-3; i++ {
printValue(out, "")
}
printMetricsLine(out, &ResourceMetricsInfo{
Name: "",
Metrics: total,
Available: v1.ResourceList{},
})
}

View File

@ -0,0 +1,45 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metricsutil
import (
corev1 "k8s.io/api/core/v1"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
)
type ResourceAdder struct {
resources []corev1.ResourceName
total corev1.ResourceList
}
func NewResourceAdder(resources []corev1.ResourceName) *ResourceAdder {
return &ResourceAdder{
resources: resources,
total: make(corev1.ResourceList),
}
}
// AddPodMetrics adds each pod metric to the total
func (adder *ResourceAdder) AddPodMetrics(m *metricsapi.PodMetrics) {
for _, c := range m.Containers {
for _, res := range adder.resources {
total := adder.total[res]
total.Add(c.Usage[res])
adder.total[res] = total
}
}
}

View File

@ -0,0 +1,119 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metricsutil
import (
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/metrics/pkg/apis/metrics"
)
func getResourceQuantity(t *testing.T, quantityStr string) resource.Quantity {
t.Helper()
var err error
quantity, err := resource.ParseQuantity("0")
if err != nil {
t.Errorf("failed when parsing 0 into resource.Quantity")
}
if quantityStr != "" {
quantity, err = resource.ParseQuantity(quantityStr)
if err != nil {
t.Errorf("%s is not a valid resource value", quantityStr)
}
}
return quantity
}
func addContainerMetricsToPodMetrics(t *testing.T, podMetrics *metrics.PodMetrics, cpuUsage, memUsage string) {
t.Helper()
containerMetrics := metrics.ContainerMetrics{
Usage: corev1.ResourceList{},
}
containerMetrics.Usage["cpu"] = getResourceQuantity(t, cpuUsage)
containerMetrics.Usage["memory"] = getResourceQuantity(t, memUsage)
podMetrics.Containers = append(podMetrics.Containers, containerMetrics)
}
func initResourceAdder() *ResourceAdder {
resources := []corev1.ResourceName{
corev1.ResourceCPU,
corev1.ResourceMemory,
}
return NewResourceAdder(resources)
}
func TestAddPodMetrics(t *testing.T) {
resourceAdder := initResourceAdder()
tests := []struct {
name string
cpuUsage string
memUsage string
expectedCpuUsage resource.Quantity
expectedMemUsage resource.Quantity
}{
{
name: "initial value",
cpuUsage: "0",
memUsage: "0",
expectedCpuUsage: getResourceQuantity(t, "0"),
expectedMemUsage: getResourceQuantity(t, "0"),
},
{
name: "add first container metric",
cpuUsage: "1m",
memUsage: "10Mi",
expectedCpuUsage: getResourceQuantity(t, "1m"),
expectedMemUsage: getResourceQuantity(t, "10Mi"),
},
{
name: "add second container metric",
cpuUsage: "5m",
memUsage: "25Mi",
expectedCpuUsage: getResourceQuantity(t, "6m"),
expectedMemUsage: getResourceQuantity(t, "35Mi"),
},
{
name: "add third container zero metric",
cpuUsage: "0m",
memUsage: "0Mi",
expectedCpuUsage: getResourceQuantity(t, "6m"),
expectedMemUsage: getResourceQuantity(t, "35Mi"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
podMetrics := metrics.PodMetrics{}
addContainerMetricsToPodMetrics(t, &podMetrics, test.cpuUsage, test.memUsage)
resourceAdder.AddPodMetrics(&podMetrics)
cpuUsage := resourceAdder.total["cpu"]
memUsage := resourceAdder.total["memory"]
if !test.expectedCpuUsage.Equal(cpuUsage) {
t.Errorf("expecting cpu usage %s but getting %s", test.expectedCpuUsage.String(), cpuUsage.String())
}
if !test.expectedMemUsage.Equal(memUsage) {
t.Errorf("expecting memeory usage %s but getting %s", test.expectedMemUsage.String(), memUsage.String())
}
})
}
}

View File

@ -0,0 +1,130 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metricsutil
import (
"k8s.io/api/core/v1"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
)
type NodeMetricsSorter struct {
metrics []metricsapi.NodeMetrics
sortBy string
}
func (n *NodeMetricsSorter) Len() int {
return len(n.metrics)
}
func (n *NodeMetricsSorter) Swap(i, j int) {
n.metrics[i], n.metrics[j] = n.metrics[j], n.metrics[i]
}
func (n *NodeMetricsSorter) Less(i, j int) bool {
switch n.sortBy {
case "cpu":
return n.metrics[i].Usage.Cpu().MilliValue() > n.metrics[j].Usage.Cpu().MilliValue()
case "memory":
return n.metrics[i].Usage.Memory().Value() > n.metrics[j].Usage.Memory().Value()
default:
return n.metrics[i].Name < n.metrics[j].Name
}
}
func NewNodeMetricsSorter(metrics []metricsapi.NodeMetrics, sortBy string) *NodeMetricsSorter {
return &NodeMetricsSorter{
metrics: metrics,
sortBy: sortBy,
}
}
type PodMetricsSorter struct {
metrics []metricsapi.PodMetrics
sortBy string
withNamespace bool
podMetrics []v1.ResourceList
}
func (p *PodMetricsSorter) Len() int {
return len(p.metrics)
}
func (p *PodMetricsSorter) Swap(i, j int) {
p.metrics[i], p.metrics[j] = p.metrics[j], p.metrics[i]
p.podMetrics[i], p.podMetrics[j] = p.podMetrics[j], p.podMetrics[i]
}
func (p *PodMetricsSorter) Less(i, j int) bool {
switch p.sortBy {
case "cpu":
return p.podMetrics[i].Cpu().MilliValue() > p.podMetrics[j].Cpu().MilliValue()
case "memory":
return p.podMetrics[i].Memory().Value() > p.podMetrics[j].Memory().Value()
default:
if p.withNamespace && p.metrics[i].Namespace != p.metrics[j].Namespace {
return p.metrics[i].Namespace < p.metrics[j].Namespace
}
return p.metrics[i].Name < p.metrics[j].Name
}
}
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string) *PodMetricsSorter {
var podMetrics = make([]v1.ResourceList, len(metrics))
if len(sortBy) > 0 {
for i, v := range metrics {
podMetrics[i] = getPodMetrics(&v)
}
}
return &PodMetricsSorter{
metrics: metrics,
sortBy: sortBy,
withNamespace: withNamespace,
podMetrics: podMetrics,
}
}
type ContainerMetricsSorter struct {
metrics []metricsapi.ContainerMetrics
sortBy string
}
func (s *ContainerMetricsSorter) Len() int {
return len(s.metrics)
}
func (s *ContainerMetricsSorter) Swap(i, j int) {
s.metrics[i], s.metrics[j] = s.metrics[j], s.metrics[i]
}
func (s *ContainerMetricsSorter) Less(i, j int) bool {
switch s.sortBy {
case "cpu":
return s.metrics[i].Usage.Cpu().MilliValue() > s.metrics[j].Usage.Cpu().MilliValue()
case "memory":
return s.metrics[i].Usage.Memory().Value() > s.metrics[j].Usage.Memory().Value()
default:
return s.metrics[i].Name < s.metrics[j].Name
}
}
func NewContainerMetricsSorter(metrics []metricsapi.ContainerMetrics, sortBy string) *ContainerMetricsSorter {
return &ContainerMetricsSorter{
metrics: metrics,
sortBy: sortBy,
}
}