1
16
17 package metricsutil
18
19 import (
20 "fmt"
21 "io"
22 "sort"
23
24 "k8s.io/api/core/v1"
25 "k8s.io/apimachinery/pkg/api/resource"
26 "k8s.io/cli-runtime/pkg/printers"
27 metricsapi "k8s.io/metrics/pkg/apis/metrics"
28 )
29
30 var (
31 MeasuredResources = []v1.ResourceName{
32 v1.ResourceCPU,
33 v1.ResourceMemory,
34 }
35 NodeColumns = []string{"NAME", "CPU(cores)", "CPU%", "MEMORY(bytes)", "MEMORY%"}
36 PodColumns = []string{"NAME", "CPU(cores)", "MEMORY(bytes)"}
37 NamespaceColumn = "NAMESPACE"
38 PodColumn = "POD"
39 )
40
41 type ResourceMetricsInfo struct {
42 Name string
43 Metrics v1.ResourceList
44 Available v1.ResourceList
45 }
46
47 type TopCmdPrinter struct {
48 out io.Writer
49 }
50
51 func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
52 return &TopCmdPrinter{out: out}
53 }
54
55 func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
56 if len(metrics) == 0 {
57 return nil
58 }
59 w := printers.GetNewTabWriter(printer.out)
60 defer w.Flush()
61
62 sort.Sort(NewNodeMetricsSorter(metrics, sortBy))
63
64 if !noHeaders {
65 printColumnNames(w, NodeColumns)
66 }
67 var usage v1.ResourceList
68 for _, m := range metrics {
69 m.Usage.DeepCopyInto(&usage)
70 printMetricsLine(w, &ResourceMetricsInfo{
71 Name: m.Name,
72 Metrics: usage,
73 Available: availableResources[m.Name],
74 })
75 delete(availableResources, m.Name)
76 }
77
78
79 for nodeName := range availableResources {
80 printMissingMetricsNodeLine(w, nodeName)
81 }
82 return nil
83 }
84
85 func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, noHeaders bool, sortBy string, sum bool) error {
86 if len(metrics) == 0 {
87 return nil
88 }
89 w := printers.GetNewTabWriter(printer.out)
90 defer w.Flush()
91
92 columnWidth := len(PodColumns)
93 if !noHeaders {
94 if withNamespace {
95 printValue(w, NamespaceColumn)
96 columnWidth++
97 }
98 if printContainers {
99 printValue(w, PodColumn)
100 columnWidth++
101 }
102 printColumnNames(w, PodColumns)
103 }
104
105 sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy))
106
107 for _, m := range metrics {
108 if printContainers {
109 sort.Sort(NewContainerMetricsSorter(m.Containers, sortBy))
110 printSinglePodContainerMetrics(w, &m, withNamespace)
111 } else {
112 printSinglePodMetrics(w, &m, withNamespace)
113 }
114
115 }
116
117 if sum {
118 adder := NewResourceAdder(MeasuredResources)
119 for _, m := range metrics {
120 adder.AddPodMetrics(&m)
121 }
122 printPodResourcesSum(w, adder.total, columnWidth)
123 }
124
125 return nil
126 }
127
128 func printColumnNames(out io.Writer, names []string) {
129 for _, name := range names {
130 printValue(out, name)
131 }
132 fmt.Fprint(out, "\n")
133 }
134
135 func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
136 podMetrics := getPodMetrics(m)
137 if withNamespace {
138 printValue(out, m.Namespace)
139 }
140 printMetricsLine(out, &ResourceMetricsInfo{
141 Name: m.Name,
142 Metrics: podMetrics,
143 Available: v1.ResourceList{},
144 })
145 }
146
147 func printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
148 for _, c := range m.Containers {
149 if withNamespace {
150 printValue(out, m.Namespace)
151 }
152 printValue(out, m.Name)
153 printMetricsLine(out, &ResourceMetricsInfo{
154 Name: c.Name,
155 Metrics: c.Usage,
156 Available: v1.ResourceList{},
157 })
158 }
159 }
160
161 func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
162 podMetrics := make(v1.ResourceList)
163 for _, res := range MeasuredResources {
164 podMetrics[res], _ = resource.ParseQuantity("0")
165 }
166
167 for _, c := range m.Containers {
168 for _, res := range MeasuredResources {
169 quantity := podMetrics[res]
170 quantity.Add(c.Usage[res])
171 podMetrics[res] = quantity
172 }
173 }
174 return podMetrics
175 }
176
177 func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
178 printValue(out, metrics.Name)
179 printAllResourceUsages(out, metrics)
180 fmt.Fprint(out, "\n")
181 }
182
183 func printMissingMetricsNodeLine(out io.Writer, nodeName string) {
184 printValue(out, nodeName)
185 unknownMetricsStatus := "<unknown>"
186 for i := 0; i < len(MeasuredResources); i++ {
187 printValue(out, unknownMetricsStatus)
188 printValue(out, unknownMetricsStatus)
189 }
190 fmt.Fprint(out, "\n")
191 }
192
193 func printValue(out io.Writer, value interface{}) {
194 fmt.Fprintf(out, "%v\t", value)
195 }
196
197 func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) {
198 for _, res := range MeasuredResources {
199 quantity := metrics.Metrics[res]
200 printSingleResourceUsage(out, res, quantity)
201 fmt.Fprint(out, "\t")
202 if available, found := metrics.Available[res]; found {
203 fraction := float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
204 fmt.Fprintf(out, "%d%%\t", int64(fraction))
205 }
206 }
207 }
208
209 func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quantity resource.Quantity) {
210 switch resourceType {
211 case v1.ResourceCPU:
212 fmt.Fprintf(out, "%vm", quantity.MilliValue())
213 case v1.ResourceMemory:
214 fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
215 default:
216 fmt.Fprintf(out, "%v", quantity.Value())
217 }
218 }
219
220 func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int) {
221 for i := 0; i < columnWidth-2; i++ {
222 printValue(out, "")
223 }
224 printValue(out, "________")
225 printValue(out, "________")
226 fmt.Fprintf(out, "\n")
227 for i := 0; i < columnWidth-3; i++ {
228 printValue(out, "")
229 }
230 printMetricsLine(out, &ResourceMetricsInfo{
231 Name: "",
232 Metrics: total,
233 Available: v1.ResourceList{},
234 })
235
236 }
237
View as plain text