Added --sum flag to kubectl top pod
Kubernetes-commit: 71acf56766c966653c3d1fe0ef6f3f036277d546
This commit is contained in:
parent
472713ca0c
commit
7254bd442d
|
@ -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())
|
||||
|
|
|
@ -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{},
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue