metrics_printer.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package metricsutil
  14. import (
  15. "fmt"
  16. "io"
  17. "sort"
  18. "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/api/resource"
  20. "k8s.io/client-go/kubernetes/scheme"
  21. "k8s.io/kubernetes/pkg/kubectl/util/printers"
  22. metricsapi "k8s.io/metrics/pkg/apis/metrics"
  23. )
  24. var (
  25. MeasuredResources = []v1.ResourceName{
  26. v1.ResourceCPU,
  27. v1.ResourceMemory,
  28. }
  29. NodeColumns = []string{"NAME", "CPU(cores)", "CPU%", "MEMORY(bytes)", "MEMORY%"}
  30. PodColumns = []string{"NAME", "CPU(cores)", "MEMORY(bytes)"}
  31. NamespaceColumn = "NAMESPACE"
  32. PodColumn = "POD"
  33. )
  34. type ResourceMetricsInfo struct {
  35. Name string
  36. Metrics v1.ResourceList
  37. Available v1.ResourceList
  38. }
  39. type TopCmdPrinter struct {
  40. out io.Writer
  41. }
  42. func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
  43. return &TopCmdPrinter{out: out}
  44. }
  45. type NodeMetricsSorter struct {
  46. metrics []metricsapi.NodeMetrics
  47. sortBy string
  48. usages []v1.ResourceList
  49. }
  50. func (n *NodeMetricsSorter) Len() int {
  51. return len(n.metrics)
  52. }
  53. func (n *NodeMetricsSorter) Swap(i, j int) {
  54. n.metrics[i], n.metrics[j] = n.metrics[j], n.metrics[i]
  55. }
  56. func (n *NodeMetricsSorter) Less(i, j int) bool {
  57. switch n.sortBy {
  58. case "cpu":
  59. qi := n.usages[i][v1.ResourceCPU]
  60. qj := n.usages[j][v1.ResourceCPU]
  61. return qi.Value() > qj.Value()
  62. case "memory":
  63. qi := n.usages[i][v1.ResourceMemory]
  64. qj := n.usages[j][v1.ResourceMemory]
  65. return qi.Value() > qj.Value()
  66. default:
  67. return n.metrics[i].Name < n.metrics[j].Name
  68. }
  69. }
  70. func NewNodeMetricsSorter(metrics []metricsapi.NodeMetrics, sortBy string) (*NodeMetricsSorter, error) {
  71. var usages = make([]v1.ResourceList, len(metrics))
  72. if len(sortBy) > 0 {
  73. for i, v := range metrics {
  74. if err := scheme.Scheme.Convert(&v.Usage, &usages[i], nil); err != nil {
  75. return nil, err
  76. }
  77. }
  78. }
  79. return &NodeMetricsSorter{
  80. metrics: metrics,
  81. sortBy: sortBy,
  82. usages: usages,
  83. }, nil
  84. }
  85. type PodMetricsSorter struct {
  86. metrics []metricsapi.PodMetrics
  87. sortBy string
  88. withNamespace bool
  89. podMetrics []v1.ResourceList
  90. }
  91. func (p *PodMetricsSorter) Len() int {
  92. return len(p.metrics)
  93. }
  94. func (p *PodMetricsSorter) Swap(i, j int) {
  95. p.metrics[i], p.metrics[j] = p.metrics[j], p.metrics[i]
  96. }
  97. func (p *PodMetricsSorter) Less(i, j int) bool {
  98. switch p.sortBy {
  99. case "cpu":
  100. qi := p.podMetrics[i][v1.ResourceCPU]
  101. qj := p.podMetrics[j][v1.ResourceCPU]
  102. return qi.Value() > qj.Value()
  103. case "memory":
  104. qi := p.podMetrics[i][v1.ResourceMemory]
  105. qj := p.podMetrics[j][v1.ResourceMemory]
  106. return qi.Value() > qj.Value()
  107. default:
  108. if p.withNamespace && p.metrics[i].Namespace != p.metrics[j].Namespace {
  109. return p.metrics[i].Namespace < p.metrics[j].Namespace
  110. }
  111. return p.metrics[i].Name < p.metrics[j].Name
  112. }
  113. }
  114. func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, sortBy string) (*PodMetricsSorter, error) {
  115. var podMetrics = make([]v1.ResourceList, len(metrics))
  116. if len(sortBy) > 0 {
  117. for i, v := range metrics {
  118. podMetrics[i], _, _ = getPodMetrics(&v, printContainers)
  119. }
  120. }
  121. return &PodMetricsSorter{
  122. metrics: metrics,
  123. sortBy: sortBy,
  124. withNamespace: withNamespace,
  125. podMetrics: podMetrics,
  126. }, nil
  127. }
  128. func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
  129. if len(metrics) == 0 {
  130. return nil
  131. }
  132. w := printers.GetNewTabWriter(printer.out)
  133. defer w.Flush()
  134. n, err := NewNodeMetricsSorter(metrics, sortBy)
  135. if err != nil {
  136. return err
  137. }
  138. sort.Sort(n)
  139. if !noHeaders {
  140. printColumnNames(w, NodeColumns)
  141. }
  142. var usage v1.ResourceList
  143. for _, m := range metrics {
  144. err := scheme.Scheme.Convert(&m.Usage, &usage, nil)
  145. if err != nil {
  146. return err
  147. }
  148. printMetricsLine(w, &ResourceMetricsInfo{
  149. Name: m.Name,
  150. Metrics: usage,
  151. Available: availableResources[m.Name],
  152. })
  153. delete(availableResources, m.Name)
  154. }
  155. // print lines for nodes of which the metrics is unreachable.
  156. for nodeName := range availableResources {
  157. printMissingMetricsNodeLine(w, nodeName)
  158. }
  159. return nil
  160. }
  161. func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, printContainers bool, withNamespace bool, noHeaders bool, sortBy string) error {
  162. if len(metrics) == 0 {
  163. return nil
  164. }
  165. w := printers.GetNewTabWriter(printer.out)
  166. defer w.Flush()
  167. if !noHeaders {
  168. if withNamespace {
  169. printValue(w, NamespaceColumn)
  170. }
  171. if printContainers {
  172. printValue(w, PodColumn)
  173. }
  174. printColumnNames(w, PodColumns)
  175. }
  176. p, err := NewPodMetricsSorter(metrics, printContainers, withNamespace, sortBy)
  177. if err != nil {
  178. return err
  179. }
  180. sort.Sort(p)
  181. for _, m := range metrics {
  182. err := printSinglePodMetrics(w, &m, printContainers, withNamespace)
  183. if err != nil {
  184. return err
  185. }
  186. }
  187. return nil
  188. }
  189. func printColumnNames(out io.Writer, names []string) {
  190. for _, name := range names {
  191. printValue(out, name)
  192. }
  193. fmt.Fprint(out, "\n")
  194. }
  195. func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, printContainersOnly bool, withNamespace bool) error {
  196. podMetrics, containers, err := getPodMetrics(m, printContainersOnly)
  197. if err != nil {
  198. return err
  199. }
  200. if printContainersOnly {
  201. for contName := range containers {
  202. if withNamespace {
  203. printValue(out, m.Namespace)
  204. }
  205. printValue(out, m.Name)
  206. printMetricsLine(out, &ResourceMetricsInfo{
  207. Name: contName,
  208. Metrics: containers[contName],
  209. Available: v1.ResourceList{},
  210. })
  211. }
  212. } else {
  213. if withNamespace {
  214. printValue(out, m.Namespace)
  215. }
  216. printMetricsLine(out, &ResourceMetricsInfo{
  217. Name: m.Name,
  218. Metrics: podMetrics,
  219. Available: v1.ResourceList{},
  220. })
  221. }
  222. return nil
  223. }
  224. func getPodMetrics(m *metricsapi.PodMetrics, printContainersOnly bool) (v1.ResourceList, map[string]v1.ResourceList, error) {
  225. containers := make(map[string]v1.ResourceList)
  226. podMetrics := make(v1.ResourceList)
  227. for _, res := range MeasuredResources {
  228. podMetrics[res], _ = resource.ParseQuantity("0")
  229. }
  230. for _, c := range m.Containers {
  231. var usage v1.ResourceList
  232. if err := scheme.Scheme.Convert(&c.Usage, &usage, nil); err != nil {
  233. return nil, nil, err
  234. }
  235. containers[c.Name] = usage
  236. if !printContainersOnly {
  237. for _, res := range MeasuredResources {
  238. quantity := podMetrics[res]
  239. quantity.Add(usage[res])
  240. podMetrics[res] = quantity
  241. }
  242. }
  243. }
  244. return podMetrics, containers, nil
  245. }
  246. func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
  247. printValue(out, metrics.Name)
  248. printAllResourceUsages(out, metrics)
  249. fmt.Fprint(out, "\n")
  250. }
  251. func printMissingMetricsNodeLine(out io.Writer, nodeName string) {
  252. printValue(out, nodeName)
  253. unknownMetricsStatus := "<unknown>"
  254. for i := 0; i < len(MeasuredResources); i++ {
  255. printValue(out, unknownMetricsStatus)
  256. printValue(out, "\t")
  257. printValue(out, unknownMetricsStatus)
  258. printValue(out, "\t")
  259. }
  260. fmt.Fprint(out, "\n")
  261. }
  262. func printValue(out io.Writer, value interface{}) {
  263. fmt.Fprintf(out, "%v\t", value)
  264. }
  265. func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) {
  266. for _, res := range MeasuredResources {
  267. quantity := metrics.Metrics[res]
  268. printSingleResourceUsage(out, res, quantity)
  269. fmt.Fprint(out, "\t")
  270. if available, found := metrics.Available[res]; found {
  271. fraction := float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
  272. fmt.Fprintf(out, "%d%%\t", int64(fraction))
  273. }
  274. }
  275. }
  276. func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quantity resource.Quantity) {
  277. switch resourceType {
  278. case v1.ResourceCPU:
  279. fmt.Fprintf(out, "%vm", quantity.MilliValue())
  280. case v1.ResourceMemory:
  281. fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
  282. default:
  283. fmt.Fprintf(out, "%v", quantity.Value())
  284. }
  285. }