From a20bac35146a7c2e32f735b84ec14bf1b7f03e5f Mon Sep 17 00:00:00 2001 From: Peter Nickolov Date: Tue, 21 Sep 2021 15:33:16 -0700 Subject: [PATCH] compute QoS & display --- app/model/model.go | 6 ++++++ cmd/analysis.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ cmd/output.go | 5 +++-- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/app/model/model.go b/app/model/model.go index 5b28d5d..57db234 100644 --- a/app/model/model.go +++ b/app/model/model.go @@ -1,5 +1,11 @@ package model +const ( + QOS_GUARANTEED = "guaranteed" + QOS_BURSTABLE = "burstable" + QOS_BESTEFFORT = "besteffort" +) + type AppMetadata struct { //Cluster string // needed? //Name string diff --git a/cmd/analysis.go b/cmd/analysis.go index 275ad5e..4ca164d 100644 --- a/cmd/analysis.go +++ b/cmd/analysis.go @@ -176,6 +176,50 @@ func analyzeContainers(app *appmodel.App) { // TODO } +func computePodQoS(app *appmodel.App) string { + // following the rules at https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/ + // note: ignoring the init containers + // TODO: use other sources to get the QoS, use this one to (a) populate QoS if no QoS info found and + // (b) validate the QoS found (e.g., degrade) + + allMatch := true // assume all containers, all resources match the guaranteed requirements + oneMatch := false // track if at least one container, one resource has resources specified + for i := range app.Containers { + c := &app.Containers[i] + + if c.Cpu.Limit > 0 { + if c.Cpu.Request > 0 && c.Cpu.Request != c.Cpu.Limit { + allMatch = false + } + oneMatch = true + } else if c.Cpu.Request > 0 { + allMatch = false + oneMatch = true + } else { + allMatch = false + } + if c.Memory.Limit > 0 { + if c.Memory.Request > 0 && c.Memory.Request != c.Memory.Limit { + allMatch = false + } + oneMatch = true + } else if c.Memory.Request > 0 { + allMatch = false + oneMatch = true + } else { + allMatch = false + } + } + + if allMatch { + return appmodel.QOS_GUARANTEED + } else if oneMatch { + return appmodel.QOS_BURSTABLE + } else { + return appmodel.QOS_BESTEFFORT + } +} + // --- App-level Analysis ---------------------------------------------------- func preAnalyzeApp(app *appmodel.App) { @@ -195,6 +239,14 @@ func preAnalyzeApp(app *appmodel.App) { } } } + + computedQos := computePodQoS(app) + if app.Settings.QosClass == "" { + app.Settings.QosClass = computedQos + } else if app.Settings.QosClass != computedQos { + log.Warnf("Computed QoS class %q does not match discovered QoS class %q for app %v; assuming the latter", + computedQos, app.Settings.QosClass, app.Metadata) + } } func analyzeApp(app *appmodel.App) { diff --git a/cmd/output.go b/cmd/output.go index cf58d6c..c049f39 100644 --- a/cmd/output.go +++ b/cmd/output.go @@ -81,7 +81,7 @@ func (table *AppTable) outputTableHeader() { const RIGHT = tablewriter.ALIGN_RIGHT const LEFT = tablewriter.ALIGN_LEFT - table.t.SetHeader([]string{"Rating", "Confidence", "Namespace", "Deployment", "Container", "Instances", "CPU", "Mem", "Reason"}) + table.t.SetHeader([]string{"Rating", "Confidence", "Namespace", "Deployment", "QoS Class", "Instances", "CPU", "Mem", "Reason"}) table.t.SetColumnAlignment([]int{RIGHT, RIGHT, LEFT, LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT}) table.t.SetFooter([]string{}) table.t.SetCenterSeparator("") @@ -98,7 +98,7 @@ func (table *AppTable) outputTableApp(app *appmodel.App) { fmt.Sprintf("%d%%", app.Analysis.Confidence), app.Metadata.Namespace, app.Metadata.Workload, - app.Analysis.MainContainer, + app.Settings.QosClass, fmt.Sprintf("%.0fx%d", app.Metrics.AverageReplicas, len(app.Containers)), fmt.Sprintf("%.0f%%", app.Metrics.CpuUtilization), fmt.Sprintf("%.0f%%", app.Metrics.MemoryUtilization), @@ -135,6 +135,7 @@ func (table *AppTable) outputDetailApp(app *appmodel.App) { table.t.Rich([]string{"Deployment", app.Metadata.Workload}, nil) table.t.Rich([]string{"Kind", fmt.Sprintf("%v (%v)", app.Metadata.WorkloadKind, app.Metadata.WorkloadApiVersion)}, nil) table.t.Rich([]string{"Main Container", app.Analysis.MainContainer}, nil) + table.t.Rich([]string{"Pod QoS Class", app.Settings.QosClass}, nil) table.t.Rich([]string{"Rating", fmt.Sprintf("%4d%%", app.Analysis.Rating)}, appColors) table.t.Rich([]string{"Confidence", fmt.Sprintf("%4d%%", app.Analysis.Confidence)}, appColors)