Skip to content

Commit

Permalink
adding sorting, updating docs
Browse files Browse the repository at this point in the history
  • Loading branch information
robscott committed Apr 3, 2019
1 parent a9ddac7 commit dabbedf
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 126 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ example-node-2 tiller tiller-deploy 140m (14%) 180m (18%)

It's worth noting that utilization numbers from pods will likely not add up to the total node utilization numbers. Unlike request and limit numbers where node and cluster level numbers represent a sum of pod values, node metrics come directly from metrics-server and will likely include other forms of resource utilization.

### Sorting
To highlight the nodes, pods, and containers with the highest metrics, you can sort by a variety of columns:

```
kube-capacity --util --sort cpu.util
NODE CPU REQUESTS CPU LIMITS CPU UTIL MEMORY REQUESTS MEMORY LIMITS MEMORY UTIL
* 560m (28%) 130m (7%) 40m (2%) 572Mi (9%) 770Mi (13%) 470Mi (8%)
example-node-2 340m (34%) 120m (12%) 30m (3%) 380Mi (13%) 410Mi (14%) 260Mi (9%)
example-node-1 220m (22%) 10m (1%) 10m (1%) 192Mi (6%) 360Mi (12%) 210Mi (7%)
```

### Filtering By Labels
For more advanced usage, kube-capacity also supports filtering by pod, namespace, and/or node labels. The following examples show how to use these filters:

Expand All @@ -91,17 +103,31 @@ kube-capacity --namespace-labels team=api
kube-capacity --node-labels kubernetes.io/role=node
```

### JSON and YAML Output
By default, kube-capacity will provide output in a table format. To view this data in JSON or YAML format, the output flag can be used. Here are some sample commands:
```
kube-capacity --pods --output json
kube-capacity --pods --containers --util --output yaml
```

## Prerequisites
Any commands requesting cluster utilization are dependent on [metrics-server](https://github.com/kubernetes-incubator/metrics-server) running on your cluster. If it's not already installed, you can install it with the official [helm chart](https://github.com/helm/charts/tree/master/stable/metrics-server).

## Flags Supported
```
-c, --containers includes containers in output
--context string context to use for Kubernetes config
-h, --help help for kube-capacity
-n, --namespace-labels string labels to filter namespaces with
--node-labels string labels to filter nodes with
-o, --output string output format for information
(supports: [table json yaml])
(default "table")
-l, --pod-labels string labels to filter pods with
-p, --pods includes pods in output
--sort string attribute to sort results be (supports:
[cpu.util cpu.request cpu.limit mem.util mem.request mem.limit name])
(default "name")
-u, --util includes resource utilization in output
```

Expand Down
43 changes: 2 additions & 41 deletions pkg/capacity/capacity.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
)

// FetchAndPrint gathers cluster resource data and outputs it
func FetchAndPrint(showContainers, showPods, showUtil bool, podLabels, nodeLabels, namespaceLabels, kubeContext string, output string) {
func FetchAndPrint(showContainers, showPods, showUtil bool, podLabels, nodeLabels, namespaceLabels, kubeContext, output, sortBy string) {
clientset, err := kube.NewClientSet(kubeContext)
if err != nil {
fmt.Printf("Error connecting to Kubernetes: %v\n", err)
Expand All @@ -48,7 +48,7 @@ func FetchAndPrint(showContainers, showPods, showUtil bool, podLabels, nodeLabel
}

cm := buildClusterMetric(podList, pmList, nodeList)
printList(&cm, showContainers, showPods, showUtil, output)
printList(&cm, showContainers, showPods, showUtil, output, sortBy)
}

func getPodsAndNodes(clientset kubernetes.Interface, podLabels, nodeLabels, namespaceLabels string) (*corev1.PodList, *corev1.NodeList) {
Expand Down Expand Up @@ -125,42 +125,3 @@ func getMetrics(mClientset *metrics.Clientset) *v1beta1.PodMetricsList {

return pmList
}

func buildClusterMetric(podList *corev1.PodList, pmList *v1beta1.PodMetricsList, nodeList *corev1.NodeList) clusterMetric {
cm := clusterMetric{
cpu: &resourceMetric{resourceType: "cpu"},
memory: &resourceMetric{resourceType: "memory"},
nodeMetrics: map[string]*nodeMetric{},
podMetrics: map[string]*podMetric{},
}

for _, node := range nodeList.Items {
cm.nodeMetrics[node.Name] = &nodeMetric{
cpu: &resourceMetric{
resourceType: "cpu",
allocatable: node.Status.Allocatable["cpu"],
},
memory: &resourceMetric{
resourceType: "memory",
allocatable: node.Status.Allocatable["memory"],
},
podMetrics: map[string]*podMetric{},
}

cm.cpu.allocatable.Add(node.Status.Allocatable["cpu"])
cm.memory.allocatable.Add(node.Status.Allocatable["memory"])
}

podMetrics := map[string]v1beta1.PodMetrics{}
for _, pm := range pmList.Items {
podMetrics[fmt.Sprintf("%s-%s", pm.GetNamespace(), pm.GetName())] = pm
}

for _, pod := range podList.Items {
if pod.Status.Phase != corev1.PodSucceeded && pod.Status.Phase != corev1.PodFailed {
cm.addPodMetric(&pod, podMetrics[fmt.Sprintf("%s-%s", pod.GetNamespace(), pod.GetName())])
}
}

return cm
}
31 changes: 15 additions & 16 deletions pkg/capacity/capacity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ func TestBuildClusterMetricEmpty(t *testing.T) {
utilization: resource.Quantity{},
},
nodeMetrics: map[string]*nodeMetric{},
podMetrics: map[string]*podMetric{},
}

assert.EqualValues(t, cm, expected)
Expand Down Expand Up @@ -149,8 +148,6 @@ func TestBuildClusterMetricFull(t *testing.T) {
utilization: resource.MustParse("299Mi"),
}

assert.Len(t, cm.podMetrics, 1)

assert.NotNil(t, cm.cpu)
ensureEqualResourceMetric(t, cm.cpu, cpuExpected)
assert.NotNil(t, cm.memory)
Expand All @@ -162,15 +159,18 @@ func TestBuildClusterMetricFull(t *testing.T) {
assert.NotNil(t, cm.nodeMetrics["example-node-1"].memory)
ensureEqualResourceMetric(t, cm.nodeMetrics["example-node-1"].memory, memoryExpected)

assert.Len(t, cm.nodeMetrics["example-node-1"].podMetrics, 1)

pm := cm.nodeMetrics["example-node-1"].podMetrics
// Change to pod specific util numbers
cpuExpected.utilization = resource.MustParse("23m")
memoryExpected.utilization = resource.MustParse("299Mi")

assert.NotNil(t, cm.podMetrics["default-example-pod"])
assert.NotNil(t, cm.podMetrics["default-example-pod"].cpu)
ensureEqualResourceMetric(t, cm.podMetrics["default-example-pod"].cpu, cpuExpected)
assert.NotNil(t, cm.podMetrics["default-example-pod"].memory)
ensureEqualResourceMetric(t, cm.podMetrics["default-example-pod"].memory, memoryExpected)
assert.NotNil(t, pm["default-example-pod"])
assert.NotNil(t, pm["default-example-pod"].cpu)
ensureEqualResourceMetric(t, pm["default-example-pod"].cpu, cpuExpected)
assert.NotNil(t, pm["default-example-pod"].memory)
ensureEqualResourceMetric(t, pm["default-example-pod"].memory, memoryExpected)
}

func ensureEqualResourceMetric(t *testing.T, actual *resourceMetric, expected *resourceMetric) {
Expand Down Expand Up @@ -203,11 +203,11 @@ func listPods(p *corev1.PodList) []string {
func node(name string, labels map[string]string) *corev1.Node {
return &corev1.Node{
TypeMeta: metav1.TypeMeta{
Kind: "Node",
Kind: "Node",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: name,
Labels: labels,
},
}
Expand All @@ -216,11 +216,11 @@ func node(name string, labels map[string]string) *corev1.Node {
func namespace(name string, labels map[string]string) *corev1.Namespace {
return &corev1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: name,
Labels: labels,
},
}
Expand All @@ -229,13 +229,13 @@ func namespace(name string, labels map[string]string) *corev1.Namespace {
func pod(node, namespace, name string, labels map[string]string) *corev1.Pod {
return &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: name,
Namespace: namespace,
Labels: labels,
Labels: labels,
},
Spec: corev1.PodSpec{
NodeName: node,
Expand Down Expand Up @@ -286,7 +286,6 @@ func TestGetPodsAndNodes(t *testing.T) {
"default/mypod",
}, listPods(podList))


podList, nodeList = getPodsAndNodes(clientset, "a=test,b!=test", "", "app=true")
assert.Equal(t, []string{"mynode", "mynode2"}, listNodes(nodeList))
assert.Equal(t, []string{
Expand Down
9 changes: 5 additions & 4 deletions pkg/capacity/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type listPrinter struct {
showPods bool
showContainers bool
showUtil bool
sortBy string
}

func (lp listPrinter) Print(outputType string) {
Expand Down Expand Up @@ -101,22 +102,22 @@ func (lp *listPrinter) buildListClusterMetrics() listClusterMetrics {
Memory: lp.buildListResourceOutput(lp.cm.memory),
}

for key, nodeMetric := range lp.cm.nodeMetrics {
for _, nodeMetric := range lp.cm.getSortedNodeMetrics(lp.sortBy) {
var node listNodeMetric
node.Name = key
node.Name = nodeMetric.name
node.CPU = lp.buildListResourceOutput(nodeMetric.cpu)
node.Memory = lp.buildListResourceOutput(nodeMetric.memory)

if lp.showPods || lp.showContainers {
for _, podMetric := range nodeMetric.podMetrics {
for _, podMetric := range nodeMetric.getSortedPodMetrics(lp.sortBy) {
var pod listPod
pod.Name = podMetric.name
pod.Namespace = podMetric.namespace
pod.CPU = lp.buildListResourceOutput(podMetric.cpu)
pod.Memory = lp.buildListResourceOutput(podMetric.memory)

if lp.showContainers {
for _, containerMetric := range podMetric.containers {
for _, containerMetric := range podMetric.getSortedContainerMetrics(lp.sortBy) {
pod.Containers = append(pod.Containers, listContainer{
Name: containerMetric.name,
Memory: lp.buildListResourceOutput(containerMetric.memory),
Expand Down
4 changes: 3 additions & 1 deletion pkg/capacity/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ func SupportedOutputs() []string {
}
}

func printList(cm *clusterMetric, showContainers bool, showPods bool, showUtil bool, output string) {
func printList(cm *clusterMetric, showContainers, showPods, showUtil bool, output, sortBy string) {
if output == JSONOutput || output == YAMLOutput {
lp := &listPrinter{
cm: cm,
showPods: showPods,
showUtil: showUtil,
showContainers: showContainers,
sortBy: sortBy,
}
lp.Print(output)
} else if output == TableOutput {
Expand All @@ -53,6 +54,7 @@ func printList(cm *clusterMetric, showContainers bool, showPods bool, showUtil b
showPods: showPods,
showUtil: showUtil,
showContainers: showContainers,
sortBy: sortBy,
w: new(tabwriter.Writer),
}
tp.Print()
Expand Down

0 comments on commit dabbedf

Please sign in to comment.