Skip to content

Commit f508d74

Browse files
authored
Merge pull request kubernetes#93692 from brianpursley/kubectl-901
Sort kubectl top pod output when --sort-by and --containers are used together
2 parents 28fc772 + 04266b3 commit f508d74

File tree

3 files changed

+137
-98
lines changed

3 files changed

+137
-98
lines changed

staging/src/k8s.io/kubectl/pkg/cmd/top/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ go_test(
4747
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
4848
"//staging/src/k8s.io/client-go/testing:go_default_library",
4949
"//staging/src/k8s.io/kubectl/pkg/cmd/testing:go_default_library",
50-
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
5150
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
5251
"//staging/src/k8s.io/metrics/pkg/apis/metrics/v1alpha1:go_default_library",
5352
"//staging/src/k8s.io/metrics/pkg/apis/metrics/v1beta1:go_default_library",

staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"k8s.io/client-go/rest/fake"
3535
core "k8s.io/client-go/testing"
3636
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
37-
cmdutil "k8s.io/kubectl/pkg/cmd/util"
3837
"k8s.io/kubectl/pkg/scheme"
3938
metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
4039
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
@@ -80,15 +79,16 @@ const (
8079
func TestTopPod(t *testing.T) {
8180
testNS := "testns"
8281
testCases := []struct {
83-
name string
84-
namespace string
85-
options *TopPodOptions
86-
args []string
87-
expectedQuery string
88-
expectedPods []string
89-
namespaces []string
90-
containers bool
91-
listsNamespaces bool
82+
name string
83+
namespace string
84+
options *TopPodOptions
85+
args []string
86+
expectedQuery string
87+
expectedPods []string
88+
expectedContainers []string
89+
namespaces []string
90+
containers bool
91+
listsNamespaces bool
9292
}{
9393
{
9494
name: "all namespaces",
@@ -112,24 +112,56 @@ func TestTopPod(t *testing.T) {
112112
namespaces: []string{testNS, testNS},
113113
},
114114
{
115-
name: "pod with container metrics",
116-
options: &TopPodOptions{PrintContainers: true},
117-
args: []string{"pod1"},
115+
name: "pod with container metrics",
116+
options: &TopPodOptions{PrintContainers: true},
117+
args: []string{"pod1"},
118+
expectedContainers: []string{
119+
"container1-1",
120+
"container1-2",
121+
},
118122
namespaces: []string{testNS},
119123
containers: true,
120124
},
121125
{
122-
name: "pod with label sort by cpu",
126+
name: "pod sort by cpu",
123127
options: &TopPodOptions{SortBy: "cpu"},
124128
expectedPods: []string{"pod2", "pod3", "pod1"},
125129
namespaces: []string{testNS, testNS, testNS},
126130
},
127131
{
128-
name: "pod with label sort by memory",
132+
name: "pod sort by memory",
129133
options: &TopPodOptions{SortBy: "memory"},
130134
expectedPods: []string{"pod2", "pod3", "pod1"},
131135
namespaces: []string{testNS, testNS, testNS},
132136
},
137+
{
138+
name: "container sort by cpu",
139+
options: &TopPodOptions{PrintContainers: true, SortBy: "cpu"},
140+
expectedContainers: []string{
141+
"container2-3",
142+
"container2-2",
143+
"container2-1",
144+
"container3-1",
145+
"container1-2",
146+
"container1-1",
147+
},
148+
namespaces: []string{testNS, testNS, testNS},
149+
containers: true,
150+
},
151+
{
152+
name: "container sort by memory",
153+
options: &TopPodOptions{PrintContainers: true, SortBy: "memory"},
154+
expectedContainers: []string{
155+
"container2-3",
156+
"container2-2",
157+
"container2-1",
158+
"container3-1",
159+
"container1-2",
160+
"container1-1",
161+
},
162+
namespaces: []string{testNS, testNS, testNS},
163+
containers: true,
164+
},
133165
}
134166
cmdtesting.InitTestErrorHandler(t)
135167
for _, testCase := range testCases {
@@ -234,23 +266,34 @@ func TestTopPod(t *testing.T) {
234266
t.Errorf("unexpected metrics for %s: \n%s", name, result)
235267
}
236268
}
237-
if cmdutil.GetFlagString(cmd, "sort-by") == "cpu" || cmdutil.GetFlagString(cmd, "sort-by") == "memory" {
238-
resultLines := strings.Split(result, "\n")
239-
resultPods := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
240-
241-
for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
242-
lineFirstColumn := strings.Split(line, " ")[0]
243-
resultPods[i] = lineFirstColumn
244-
}
245-
269+
if testCase.expectedPods != nil {
270+
resultPods := getResultColumnValues(result, 0)
246271
if !reflect.DeepEqual(testCase.expectedPods, resultPods) {
247-
t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", testCase.expectedPods, resultPods)
272+
t.Errorf("pods not matching:\n\texpectedPods: %v\n\tresultPods: %v\n", testCase.expectedPods, resultPods)
273+
}
274+
}
275+
if testCase.expectedContainers != nil {
276+
resultContainers := getResultColumnValues(result, 1)
277+
if !reflect.DeepEqual(testCase.expectedContainers, resultContainers) {
278+
t.Errorf("containers not matching:\n\texpectedContainers: %v\n\tresultContainers: %v\n", testCase.expectedContainers, resultContainers)
248279
}
249280
}
250281
})
251282
}
252283
}
253284

285+
func getResultColumnValues(result string, columnIndex int) []string {
286+
resultLines := strings.Split(result, "\n")
287+
values := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
288+
289+
for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
290+
value := strings.Fields(line)[columnIndex]
291+
values[i] = value
292+
}
293+
294+
return values
295+
}
296+
254297
func TestTopPodNoResourcesFound(t *testing.T) {
255298
testNS := "testns"
256299
testCases := []struct {

staging/src/k8s.io/kubectl/pkg/metricsutil/metrics_printer.go

Lines changed: 69 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
5555
type NodeMetricsSorter struct {
5656
metrics []metricsapi.NodeMetrics
5757
sortBy string
58-
usages []v1.ResourceList
5958
}
6059

6160
func (n *NodeMetricsSorter) Len() int {
@@ -64,37 +63,24 @@ func (n *NodeMetricsSorter) Len() int {
6463

6564
func (n *NodeMetricsSorter) Swap(i, j int) {
6665
n.metrics[i], n.metrics[j] = n.metrics[j], n.metrics[i]
67-
n.usages[i], n.usages[j] = n.usages[j], n.usages[i]
6866
}
6967

7068
func (n *NodeMetricsSorter) Less(i, j int) bool {
7169
switch n.sortBy {
7270
case "cpu":
73-
qi := n.usages[i][v1.ResourceCPU]
74-
qj := n.usages[j][v1.ResourceCPU]
75-
return qi.MilliValue() > qj.MilliValue()
71+
return n.metrics[i].Usage.Cpu().MilliValue() > n.metrics[j].Usage.Cpu().MilliValue()
7672
case "memory":
77-
qi := n.usages[i][v1.ResourceMemory]
78-
qj := n.usages[j][v1.ResourceMemory]
79-
return qi.Value() > qj.Value()
73+
return n.metrics[i].Usage.Memory().Value() > n.metrics[j].Usage.Memory().Value()
8074
default:
8175
return n.metrics[i].Name < n.metrics[j].Name
8276
}
8377
}
8478

85-
func NewNodeMetricsSorter(metrics []metricsapi.NodeMetrics, sortBy string) (*NodeMetricsSorter, error) {
86-
var usages = make([]v1.ResourceList, len(metrics))
87-
if len(sortBy) > 0 {
88-
for i, v := range metrics {
89-
v.Usage.DeepCopyInto(&usages[i])
90-
}
91-
}
92-
79+
func NewNodeMetricsSorter(metrics []metricsapi.NodeMetrics, sortBy string) *NodeMetricsSorter {
9380
return &NodeMetricsSorter{
9481
metrics: metrics,
9582
sortBy: sortBy,
96-
usages: usages,
97-
}, nil
83+
}
9884
}
9985

10086
type PodMetricsSorter struct {
@@ -116,13 +102,9 @@ func (p *PodMetricsSorter) Swap(i, j int) {
116102
func (p *PodMetricsSorter) Less(i, j int) bool {
117103
switch p.sortBy {
118104
case "cpu":
119-
qi := p.podMetrics[i][v1.ResourceCPU]
120-
qj := p.podMetrics[j][v1.ResourceCPU]
121-
return qi.MilliValue() > qj.MilliValue()
105+
return p.podMetrics[i].Cpu().MilliValue() > p.podMetrics[j].Cpu().MilliValue()
122106
case "memory":
123-
qi := p.podMetrics[i][v1.ResourceMemory]
124-
qj := p.podMetrics[j][v1.ResourceMemory]
125-
return qi.Value() > qj.Value()
107+
return p.podMetrics[i].Memory().Value() > p.podMetrics[j].Memory().Value()
126108
default:
127109
if p.withNamespace && p.metrics[i].Namespace != p.metrics[j].Namespace {
128110
return p.metrics[i].Namespace < p.metrics[j].Namespace
@@ -131,11 +113,11 @@ func (p *PodMetricsSorter) Less(i, j int) bool {
131113
}
132114
}
133115

134-
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, sortBy string) (*PodMetricsSorter, error) {
116+
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string) *PodMetricsSorter {
135117
var podMetrics = make([]v1.ResourceList, len(metrics))
136118
if len(sortBy) > 0 {
137119
for i, v := range metrics {
138-
podMetrics[i], _, _ = getPodMetrics(&v, printContainers)
120+
podMetrics[i] = getPodMetrics(&v)
139121
}
140122
}
141123

@@ -144,7 +126,38 @@ func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, printContainers bool,
144126
sortBy: sortBy,
145127
withNamespace: withNamespace,
146128
podMetrics: podMetrics,
147-
}, nil
129+
}
130+
}
131+
132+
type ContainerMetricsSorter struct {
133+
metrics []metricsapi.ContainerMetrics
134+
sortBy string
135+
}
136+
137+
func (s *ContainerMetricsSorter) Len() int {
138+
return len(s.metrics)
139+
}
140+
141+
func (s *ContainerMetricsSorter) Swap(i, j int) {
142+
s.metrics[i], s.metrics[j] = s.metrics[j], s.metrics[i]
143+
}
144+
145+
func (s *ContainerMetricsSorter) Less(i, j int) bool {
146+
switch s.sortBy {
147+
case "cpu":
148+
return s.metrics[i].Usage.Cpu().MilliValue() > s.metrics[j].Usage.Cpu().MilliValue()
149+
case "memory":
150+
return s.metrics[i].Usage.Memory().Value() > s.metrics[j].Usage.Memory().Value()
151+
default:
152+
return s.metrics[i].Name < s.metrics[j].Name
153+
}
154+
}
155+
156+
func NewContainerMetricsSorter(metrics []metricsapi.ContainerMetrics, sortBy string) *ContainerMetricsSorter {
157+
return &ContainerMetricsSorter{
158+
metrics: metrics,
159+
sortBy: sortBy,
160+
}
148161
}
149162

150163
func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
@@ -154,11 +167,7 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics,
154167
w := printers.GetNewTabWriter(printer.out)
155168
defer w.Flush()
156169

157-
n, err := NewNodeMetricsSorter(metrics, sortBy)
158-
if err != nil {
159-
return err
160-
}
161-
sort.Sort(n)
170+
sort.Sort(NewNodeMetricsSorter(metrics, sortBy))
162171

163172
if !noHeaders {
164173
printColumnNames(w, NodeColumns)
@@ -197,16 +206,14 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p
197206
printColumnNames(w, PodColumns)
198207
}
199208

200-
p, err := NewPodMetricsSorter(metrics, printContainers, withNamespace, sortBy)
201-
if err != nil {
202-
return err
203-
}
204-
sort.Sort(p)
209+
sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy))
205210

206211
for _, m := range metrics {
207-
err := printSinglePodMetrics(w, &m, printContainers, withNamespace)
208-
if err != nil {
209-
return err
212+
if printContainers {
213+
sort.Sort(NewContainerMetricsSorter(m.Containers, sortBy))
214+
printSinglePodContainerMetrics(w, &m, withNamespace)
215+
} else {
216+
printSinglePodMetrics(w, &m, withNamespace)
210217
}
211218
}
212219
return nil
@@ -219,56 +226,46 @@ func printColumnNames(out io.Writer, names []string) {
219226
fmt.Fprint(out, "\n")
220227
}
221228

222-
func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, printContainersOnly bool, withNamespace bool) error {
223-
podMetrics, containers, err := getPodMetrics(m, printContainersOnly)
224-
if err != nil {
225-
return err
229+
func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
230+
podMetrics := getPodMetrics(m)
231+
if withNamespace {
232+
printValue(out, m.Namespace)
226233
}
227-
if printContainersOnly {
228-
for contName := range containers {
229-
if withNamespace {
230-
printValue(out, m.Namespace)
231-
}
232-
printValue(out, m.Name)
233-
printMetricsLine(out, &ResourceMetricsInfo{
234-
Name: contName,
235-
Metrics: containers[contName],
236-
Available: v1.ResourceList{},
237-
})
238-
}
239-
} else {
234+
printMetricsLine(out, &ResourceMetricsInfo{
235+
Name: m.Name,
236+
Metrics: podMetrics,
237+
Available: v1.ResourceList{},
238+
})
239+
}
240+
241+
func printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
242+
for _, c := range m.Containers {
240243
if withNamespace {
241244
printValue(out, m.Namespace)
242245
}
246+
printValue(out, m.Name)
243247
printMetricsLine(out, &ResourceMetricsInfo{
244-
Name: m.Name,
245-
Metrics: podMetrics,
248+
Name: c.Name,
249+
Metrics: c.Usage,
246250
Available: v1.ResourceList{},
247251
})
248252
}
249-
return nil
250253
}
251254

252-
func getPodMetrics(m *metricsapi.PodMetrics, printContainersOnly bool) (v1.ResourceList, map[string]v1.ResourceList, error) {
253-
containers := make(map[string]v1.ResourceList)
255+
func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
254256
podMetrics := make(v1.ResourceList)
255257
for _, res := range MeasuredResources {
256258
podMetrics[res], _ = resource.ParseQuantity("0")
257259
}
258260

259-
var usage v1.ResourceList
260261
for _, c := range m.Containers {
261-
c.Usage.DeepCopyInto(&usage)
262-
containers[c.Name] = usage
263-
if !printContainersOnly {
264-
for _, res := range MeasuredResources {
265-
quantity := podMetrics[res]
266-
quantity.Add(usage[res])
267-
podMetrics[res] = quantity
268-
}
262+
for _, res := range MeasuredResources {
263+
quantity := podMetrics[res]
264+
quantity.Add(c.Usage[res])
265+
podMetrics[res] = quantity
269266
}
270267
}
271-
return podMetrics, containers, nil
268+
return podMetrics
272269
}
273270

274271
func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {

0 commit comments

Comments
 (0)