Skip to content

Commit

Permalink
Initial Attempt at JSON Support
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick Huanca committed Mar 29, 2019
1 parent 0a5a460 commit 8ad6db2
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 137 deletions.
101 changes: 101 additions & 0 deletions pkg/capacity/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Package capacity - json.go contains all the messy details for the json printer implementation
package capacity

import (
"encoding/json"
"fmt"
)

type jsonNodeMetric struct {
Name string `json:"name"`
CPU *jsonResourceOutput `json:"cpu,omitempty"`
Memory *jsonResourceOutput `json:"memory,omitempty"`
Pods []*jsonPod `json:"pods,omitempty"`
}

type jsonPod struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
CPU *jsonResourceOutput `json:"cpu"`
Memory *jsonResourceOutput `json:"memory"`
}

type jsonResourceOutput struct {
Requests string `json:"requests"`
RequestsPct string `json:"requests_pct"`
Limits string `json:"limits"`
LimitsPct string `json:"limits_pct"`
Utilization string `json:"utilization,omitempty"`
UtilizationPct string `json:"utilization_pct,omitempty"`
}

type jsonClusterMetrics struct {
Nodes []*jsonNodeMetric `json:"nodes"`
Cluster struct {
CPU *jsonResourceOutput `json:"cpu"`
Memory *jsonResourceOutput `json:"memory"`
} `json:"cluster"`
}

type jsonPrinter struct {
cm *clusterMetric
showPods bool
showUtil bool
}

func (jp jsonPrinter) Print() {
jsonOutput := jp.buildJSONClusterMetrics()

jsonRaw, err := json.MarshalIndent(jsonOutput, "", " ")
if err != nil {
fmt.Println("Error Marshalling JSON")
fmt.Println(err)
}

fmt.Printf("%s", jsonRaw)
}

func (jp *jsonPrinter) buildJSONClusterMetrics() jsonClusterMetrics {
var response jsonClusterMetrics

response.Cluster.CPU = jp.buildJSONResourceOutput(jp.cm.cpu)
response.Cluster.Memory = jp.buildJSONResourceOutput(jp.cm.memory)

for key, val := range jp.cm.nodeMetrics {
var node jsonNodeMetric
node.Name = key
node.CPU = jp.buildJSONResourceOutput(val.cpu)
node.Memory = jp.buildJSONResourceOutput(val.memory)
if jp.showPods {
for _, val := range val.podMetrics {
var newNode jsonPod
newNode.Name = val.name
newNode.Namespace = val.namespace
newNode.CPU = jp.buildJSONResourceOutput(val.cpu)
newNode.Memory = jp.buildJSONResourceOutput(val.memory)
node.Pods = append(node.Pods, &newNode)
}
}
response.Nodes = append(response.Nodes, &node)
}

return response
}

func (jp *jsonPrinter) buildJSONResourceOutput(item *resourceMetric) *jsonResourceOutput {
valueCalculator := item.valueFunction()
percentCalculator := item.percentFunction()

out := jsonResourceOutput{
Requests: valueCalculator(item.request),
RequestsPct: percentCalculator(item.request),
Limits: valueCalculator(item.limit),
LimitsPct: percentCalculator(item.limit),
}

if jp.showUtil {
out.Utilization = valueCalculator(item.utilization)
out.UtilizationPct = percentCalculator(item.utilization)
}
return &out
}
4 changes: 2 additions & 2 deletions pkg/capacity/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
)

// List gathers cluster resource data and outputs it
func List(showPods, showUtil bool, podLabels, nodeLabels, namespaceLabels, kubeContext string) {
func List(showPods, showUtil bool, podLabels, nodeLabels, namespaceLabels, kubeContext string, output string) {
clientset, err := kube.NewClientSet(kubeContext)
if err != nil {
fmt.Printf("Error connecting to Kubernetes: %v\n", err)
Expand All @@ -47,7 +47,7 @@ func List(showPods, showUtil bool, podLabels, nodeLabels, namespaceLabels, kubeC
pmList = getMetrics(mClientset)
}
cm := buildClusterMetric(podList, pmList, nodeList)
printList(&cm, showPods, showUtil)
printList(&cm, showPods, showUtil, output)
}

func getPodsAndNodes(clientset kubernetes.Interface, podLabels, nodeLabels, namespaceLabels string) (*corev1.PodList, *corev1.NodeList) {
Expand Down
171 changes: 38 additions & 133 deletions pkg/capacity/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,151 +17,56 @@ package capacity
import (
"fmt"
"os"
"sort"
"text/tabwriter"
)

func printList(cm *clusterMetric, showPods bool, showUtil bool) {
names := make([]string, len(cm.nodeMetrics))

i := 0
for name := range cm.nodeMetrics {
names[i] = name
i++
}
sort.Strings(names)

w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 8, 2, ' ', 0)

printHeaders(w, cm, showPods, showUtil)
const (
//TextOutput is the constant value for output type text
TextOutput string = "text"
//JSONOutput is the constant value for output type text
JSONOutput string = "json"
)

for _, name := range names {
printNode(w, name, cm.nodeMetrics[name], showPods, showUtil)
// SupportedOutputs returns a string list of output formats supposed by this package
func SupportedOutputs() []string {
return []string{
TextOutput,
JSONOutput,
}

w.Flush()
}

func printHeaders(w *tabwriter.Writer, cm *clusterMetric, showPods bool, showUtil bool) {
if showPods && showUtil {
fmt.Fprintln(w, "NODE\t NAMESPACE\t POD\t CPU REQUESTS \t CPU LIMITS \t CPU UTIL \t MEMORY REQUESTS \t MEMORY LIMITS \t MEMORY UTIL")

if len(cm.nodeMetrics) > 1 {
fmt.Fprintf(w, "* \t *\t *\t %s \t %s \t %s \t %s \t %s \t %s \n",
cm.cpu.requestString(),
cm.cpu.limitString(),
cm.cpu.utilString(),
cm.memory.requestString(),
cm.memory.limitString(),
cm.memory.utilString())

fmt.Fprintln(w, "\t\t\t\t\t\t\t\t")
}
} else if showPods {
fmt.Fprintln(w, "NODE\t NAMESPACE\t POD\t CPU REQUESTS \t CPU LIMITS \t MEMORY REQUESTS \t MEMORY LIMITS")

fmt.Fprintf(w, "* \t *\t *\t %s \t %s \t %s \t %s \n",
cm.cpu.requestString(),
cm.cpu.limitString(),
cm.memory.requestString(),
cm.memory.limitString())

fmt.Fprintln(w, "\t\t\t\t\t\t")

} else if showUtil {
fmt.Fprintln(w, "NODE\t CPU REQUESTS \t CPU LIMITS \t CPU UTIL \t MEMORY REQUESTS \t MEMORY LIMITS \t MEMORY UTIL")

fmt.Fprintf(w, "* \t %s \t %s \t %s \t %s \t %s \t %s \n",
cm.cpu.requestString(),
cm.cpu.limitString(),
cm.cpu.utilString(),
cm.memory.requestString(),
cm.memory.limitString(),
cm.memory.utilString())

} else {
fmt.Fprintln(w, "NODE\t CPU REQUESTS \t CPU LIMITS \t MEMORY REQUESTS \t MEMORY LIMITS")

if len(cm.nodeMetrics) > 1 {
fmt.Fprintf(w, "* \t %s \t %s \t %s \t %s \n",
cm.cpu.requestString(), cm.cpu.limitString(),
cm.memory.requestString(), cm.memory.limitString())
}
}
type printer interface {
Print()
}

func printNode(w *tabwriter.Writer, name string, nm *nodeMetric, showPods bool, showUtil bool) {
podNames := make([]string, len(nm.podMetrics))

i := 0
for name := range nm.podMetrics {
podNames[i] = name
i++
func printList(cm *clusterMetric, showPods bool, showUtil bool, output string) {
p, err := printerFactory(cm, showPods, showUtil, output)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
sort.Strings(podNames)

if showPods && showUtil {
fmt.Fprintf(w, "%s \t *\t *\t %s \t %s \t %s \t %s \t %s \t %s \n",
name,
nm.cpu.requestString(),
nm.cpu.limitString(),
nm.cpu.utilString(),
nm.memory.requestString(),
nm.memory.limitString(),
nm.memory.utilString())
p.Print()
}

for _, podName := range podNames {
pm := nm.podMetrics[podName]
fmt.Fprintf(w, "%s \t %s \t %s \t %s \t %s \t %s \t %s \t %s \t %s \n",
name,
pm.namespace,
pm.name,
pm.cpu.requestString(),
pm.cpu.limitString(),
pm.cpu.utilString(),
pm.memory.requestString(),
pm.memory.limitString(),
pm.memory.utilString())
func printerFactory(cm *clusterMetric, showPods bool, showUtil bool, outputType string) (printer, error) {
var response printer
switch outputType {
case JSONOutput:
response = jsonPrinter{
cm: cm,
showPods: showPods,
showUtil: showUtil,
}

fmt.Fprintln(w, "\t\t\t\t\t\t\t\t")

} else if showPods {
fmt.Fprintf(w, "%s \t *\t *\t %s \t %s \t %s \t %s \n",
name,
nm.cpu.requestString(),
nm.cpu.limitString(),
nm.memory.requestString(),
nm.memory.limitString())

for _, podName := range podNames {
pm := nm.podMetrics[podName]
fmt.Fprintf(w, "%s \t %s \t %s \t %s \t %s \t %s \t %s \n",
name,
pm.namespace,
pm.name,
pm.cpu.requestString(),
pm.cpu.limitString(),
pm.memory.requestString(),
pm.memory.limitString())
return response, nil
case TextOutput:
response = textPrinter{
cm: cm,
showPods: showPods,
showUtil: showUtil,
w: new(tabwriter.Writer),
}

fmt.Fprintln(w, "\t\t\t\t\t\t")

} else if showUtil {
fmt.Fprintf(w, "%s \t %s \t %s \t %s \t %s \t %s \t %s \n",
name,
nm.cpu.requestString(),
nm.cpu.limitString(),
nm.cpu.utilString(),
nm.memory.requestString(),
nm.memory.limitString(),
nm.memory.utilString())

} else {
fmt.Fprintf(w, "%s \t %s \t %s \t %s \t %s \n", name,
nm.cpu.requestString(), nm.cpu.limitString(),
nm.memory.requestString(), nm.memory.limitString())
return response, nil
default:
return response, fmt.Errorf("Called with an unsupported output type: %s", outputType)
}
}
25 changes: 24 additions & 1 deletion pkg/capacity/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import (
"fmt"

corev1 "k8s.io/api/core/v1"
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
"k8s.io/apimachinery/pkg/api/resource"
resourcehelper "k8s.io/kubernetes/pkg/kubectl/util/resource"
v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)

type resourceMetric struct {
Expand Down Expand Up @@ -138,3 +138,26 @@ func resourceString(actual, allocatable resource.Quantity, resourceType string)
}
return fmt.Sprintf("%dMi (%d%%)", actual.Value()/1048576, int64(utilPercent))
}

// NOTE: This might not be a great place for closures due to the cyclical nature of how resourceType works. Perhaps better implemented another way.
func (rm resourceMetric) valueFunction() (f func(r resource.Quantity) string) {
switch rm.resourceType {
case "cpu":
f = func(r resource.Quantity) string {
return fmt.Sprintf("%dm", r.MilliValue())
}
case "memory":
f = func(r resource.Quantity) string {
return fmt.Sprintf("%dMi", r.Value()/1048576)
}
}
return f
}

// NOTE: This might not be a great place for closures due to the cyclical nature of how resourceType works. Perhaps better implemented another way.
func (rm resourceMetric) percentFunction() (f func(r resource.Quantity) string) {
f = func(r resource.Quantity) string {
return fmt.Sprintf("%v%%", int64(float64(r.MilliValue())/float64(rm.allocatable.MilliValue())*100))
}
return f
}

0 comments on commit 8ad6db2

Please sign in to comment.