Skip to content
Open
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
12 changes: 10 additions & 2 deletions cmd/optiqor/golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,19 @@ func TestCmd_Golden_Stable(t *testing.T) {
// normalize strips the test's cwd and the repo root from filepath.Abs
// output so goldens stay bit-identical across laptops and CI runners.
func normalize(s string) string {
s = strings.ReplaceAll(s, `\`, `/`)
s = strings.ReplaceAll(s, `//`, `/`)
s = strings.ReplaceAll(s, `https:/`, `https://`)
s = strings.ReplaceAll(s, `http:/`, `http://`)

if cwd, err := os.Getwd(); err == nil {
s = strings.ReplaceAll(s, cwd, "<CWD>")
cwdNorm := strings.ReplaceAll(cwd, `\`, `/`)

if repo, err := filepath.Abs(filepath.Join(cwd, "..", "..")); err == nil {
s = strings.ReplaceAll(s, repo, "<REPO>")
repoNorm := strings.ReplaceAll(repo, `\`, `/`)
s = strings.ReplaceAll(s, repoNorm, "<REPO>")
}
s = strings.ReplaceAll(s, cwdNorm, "<CWD>")
}
return s
}
8 changes: 8 additions & 0 deletions pkg/rules/excessive_replicas.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,13 @@ func (excessiveReplicaCount) Run(w parser.Workload) []Finding {
Detail: fmt.Sprintf("Replicas set to %d. Past ~20 replicas the cost grows linearly while the marginal availability gain approaches zero — most cloud zones can't fit that many across enough fault domains. Cap the HPA's maxReplicas or split the workload across multiple deployments.", w.Replicas),
Severity: SeverityMed,
Confidence: ConfidenceMed,
Signal: &Signal{
Label: "replicas",
Have: float64(excessiveReplicaThreshold),
Want: float64(w.Replicas),
HaveDisplay: fmt.Sprintf("%d", excessiveReplicaThreshold),
WantDisplay: fmt.Sprintf("%d", w.Replicas),
Note: fmt.Sprintf("%d replicas", w.Replicas),
},
}}
}
16 changes: 16 additions & 0 deletions pkg/rules/incomplete_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ func (cpuWithoutMemoryRequest) Run(w parser.Workload) []Finding {
Detail: "requests.cpu is set but requests.memory is not. The scheduler can't reserve memory accurately, so the pod is BestEffort for memory — first to evict under pressure. Add a memory request matching observed P95.",
Severity: SeverityLow,
Confidence: ConfidenceHigh,
Signal: &Signal{
Label: "memory",
Have: 0,
Want: 0,
HaveDisplay: "unset",
WantDisplay: "required",
Note: "memory request missing",
},
}}
}

Expand All @@ -54,5 +62,13 @@ func (memoryWithoutCPURequest) Run(w parser.Workload) []Finding {
Detail: "requests.memory is set but requests.cpu is not. The scheduler can't reserve CPU, so bin-packing assumes zero — pods can stack onto a single node and starve each other. Add a CPU request matching observed P95.",
Severity: SeverityLow,
Confidence: ConfidenceHigh,
Signal: &Signal{
Label: "CPU",
Have: 0,
Want: 0,
HaveDisplay: "unset",
WantDisplay: "required",
Note: "CPU request missing",
},
}}
}
16 changes: 16 additions & 0 deletions pkg/rules/oversized_limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ func (oversizedCPULimit) Run(w parser.Workload) []Finding {
Detail: fmt.Sprintf("CPU limit is %s. Above 4 vCPU, the pod can only land on large nodes; smaller / Spot instance types are excluded from bin-packing. Either split the workload or confirm the high limit is justified by P99.", w.Limits.CPU),
Severity: SeverityMed,
Confidence: ConfidenceMed,
Signal: &Signal{
Label: "CPU",
Have: float64(w.Requests.CPU.Value),
Want: float64(w.Limits.CPU.Value),
HaveDisplay: w.Requests.CPU.String(),
WantDisplay: w.Limits.CPU.String(),
Note: "limit > 4 vCPU",
},
}}
}

Expand All @@ -59,5 +67,13 @@ func (oversizedMemoryLimit) Run(w parser.Workload) []Finding {
Detail: fmt.Sprintf("Memory limit is %s. Above 16 GiB, the pod can only land on memory-class nodes; balanced / Spot bin-packing is excluded. Confirm the workload genuinely uses this much, or split the workload.", w.Limits.Memory),
Severity: SeverityMed,
Confidence: ConfidenceMed,
Signal: &Signal{
Label: "memory",
Have: float64(w.Requests.Memory.Value),
Want: float64(w.Limits.Memory.Value),
HaveDisplay: w.Requests.Memory.String(),
WantDisplay: w.Limits.Memory.String(),
Note: "limit > 16 GiB",
},
}}
}
16 changes: 16 additions & 0 deletions pkg/rules/tiny_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ func (tinyCPURequest) Run(w parser.Workload) []Finding {
Detail: fmt.Sprintf("requests.cpu is %s — below the 10m threshold most charts use as a sentinel. Probably a placeholder from a Helm scaffold. Set it to your observed P95 (or remove the limit-without-request asymmetry the scheduler is currently dealing with).", w.Requests.CPU),
Severity: SeverityLow,
Confidence: ConfidenceHigh,
Signal: &Signal{
Label: "CPU",
Have: float64(w.Requests.CPU.Value),
Want: float64(tinyCPUMillicores),
HaveDisplay: w.Requests.CPU.String(),
WantDisplay: "10m",
Note: "below 10m sentinel",
},
}}
}

Expand All @@ -62,5 +70,13 @@ func (tinyMemoryRequest) Run(w parser.Workload) []Finding {
Detail: fmt.Sprintf("requests.memory is %s — below 32 MiB. A workload that genuinely needs less memory is a rarity; most charts setting tiny memory requests are using a placeholder. Set it to your observed P95.", w.Requests.Memory),
Severity: SeverityLow,
Confidence: ConfidenceHigh,
Signal: &Signal{
Label: "memory",
Have: float64(w.Requests.Memory.Value),
Want: float64(tinyMemoryBytes),
HaveDisplay: w.Requests.Memory.String(),
WantDisplay: "32Mi",
Note: "below 32Mi sentinel",
},
}}
}
14 changes: 14 additions & 0 deletions testdata/golden/analyze_fixture_plain.txt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@
│ │
│ Replica count past the HA inflection point │
│ │
│ replicas 20 ███████████████████░░░░░ 25 25 replicas │
│ │
│ Replicas set to 25. Past ~20 replicas the cost grows linearly while the │
│ marginal availability gain approaches zero — most cloud zones can't fit │
│ that many across enough fault domains. Cap the HPA's maxReplicas or split │
Expand All @@ -168,6 +170,8 @@
│ │
│ CPU limit forces large-node scheduling │
│ │
│ CPU 2 ██████░░░░░░░░░░░░░░░░░░ 8 limit > 4 vCPU │
│ │
│ CPU limit is 8. Above 4 vCPU, the pod can only land on large nodes; │
│ smaller / Spot instance types are excluded from bin-packing. Either split │
│ the workload or confirm the high limit is justified by P99. │
Expand All @@ -179,6 +183,8 @@
│ │
│ Memory limit forces large-node scheduling │
│ │
│ memory 8Gi ██████░░░░░░░░░░░░░░░░░░ 32Gi limit > 16 GiB │
│ │
│ Memory limit is 32Gi. Above 16 GiB, the pod can only land on memory-class │
│ nodes; balanced / Spot bin-packing is excluded. Confirm the workload │
│ genuinely uses this much, or split the workload. │
Expand Down Expand Up @@ -286,6 +292,8 @@
│ │
│ CPU request set without memory request │
│ │
│ memory unset ░░░░░░░░░░░░░░░░░░░░░░░░ required memory request missing │
│ │
│ requests.cpu is set but requests.memory is not. The scheduler can't │
│ reserve memory accurately, so the pod is BestEffort for memory — first │
│ to evict under pressure. Add a memory request matching observed P95. │
Expand All @@ -297,6 +305,8 @@
│ │
│ CPU request below the placeholder threshold │
│ │
│ CPU 5m ████████████░░░░░░░░░░░░ 10m below 10m sentinel │
│ │
│ requests.cpu is 5m — below the 10m threshold most charts use as a │
│ sentinel. Probably a placeholder from a Helm scaffold. Set it to your │
│ observed P95 (or remove the limit-without-request asymmetry the scheduler │
Expand All @@ -309,6 +319,8 @@
│ │
│ Memory request set without CPU request │
│ │
│ CPU unset ░░░░░░░░░░░░░░░░░░░░░░░░ required CPU request missing │
│ │
│ requests.memory is set but requests.cpu is not. The scheduler can't │
│ reserve CPU, so bin-packing assumes zero — pods can stack onto a single │
│ node and starve each other. Add a CPU request matching observed P95. │
Expand All @@ -320,6 +332,8 @@
│ │
│ Memory request below the placeholder threshold │
│ │
│ memory 16Mi ████████████░░░░░░░░░░░░ 32Mi below 32Mi sentinel │
│ │
│ requests.memory is 16Mi — below 32 MiB. A workload that genuinely needs │
│ less memory is a rarity; most charts setting tiny memory requests are │
│ using a placeholder. Set it to your observed P95. │
Expand Down
Loading