Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Attempt at JSON Support #12

Merged
merged 4 commits into from
Mar 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"`
ClusterTotals struct {
CPU *jsonResourceOutput `json:"cpu"`
Memory *jsonResourceOutput `json:"memory"`
} `json:"cluster_totals"`
}

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 {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might not need to be a pointer ref method

var response jsonClusterMetrics

response.ClusterTotals.CPU = jp.buildJSONResourceOutput(jp.cm.cpu)
response.ClusterTotals.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 {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might not need to be a pointer ref method of jsonPrinter

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 (
//TableOutput is the constant value for output type text
TableOutput string = "table"
//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{
TableOutput,
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 TableOutput:
response = tablePrinter{
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
}
Loading