Skip to content

feat(aws): CE savings plans coverage and utilization queries - #1346

Merged
cristim merged 3 commits into
mainfrom
feat/ladder-sp-coverage
Jul 3, 2026
Merged

feat(aws): CE savings plans coverage and utilization queries#1346
cristim merged 3 commits into
mainfrom
feat/ladder-sp-coverage

Conversation

@cristim

@cristim cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

  • Extend CostExplorerAPI with GetSavingsPlansCoverage and GetSavingsPlansUtilization (aws-sdk-go-v2 signatures); all test mocks updated.
  • New providers/aws/recommendations/sp_coverage.go:
    • GetSPCoverageSummary(ctx, region, lookbackDays) -> SPCoverageSummary{CoveragePct, CoveredUSDPerHour, OnDemandUSDPerHour *float64; Days int}: paginated (NextToken loop) daily coverage aggregation with terminal ctx cancellation (per feedback_ctx_cancel_terminal).
    • GetSPUtilization(ctx, planType, region, lookbackDays) -> SPUtilizationSummary{UtilizationPct, UsedCommitmentUSDPerHour, TotalCommitmentUSDPerHour *float64}: per-plan-type utilization via the typed SDK enum (types.SupportedSavingsPlansType), validated at the boundary against the SDK's own value set.
  • Strict CE number parsing (parseSPFloat): errors on unparseable/NaN/Inf/negative, never a silent zero fallback.
  • Nil-vs-zero semantics: all money/percent fields are pointers (absent != zero); Days distinguishes "CE returned no data for this scope" (Days==0, all nil) from "genuine 0% coverage" (Days>0, CoveragePct == &0.0). A nonexistent/typo region yields Days==0 rather than an error - documented in the godocs.
  • Rate limiter respected exactly like the existing RI coverage path (acquire at the API call).

Coverage vs utilization filter asymmetry (deliberate)

The two functions intentionally have different signatures and separate filter builders:

  • GetSavingsPlansUtilization supports the SAVINGS_PLANS_TYPE filter dimension, so utilization is per-plan-type (the ladder holds two SP layers; a blended number would mask per-layer under-utilization).
  • GetSavingsPlansCoverage's Filter contract supports only LINKED_ACCOUNT, REGION, SERVICE, INSTANCE_FAMILY - passing SAVINGS_PLANS_TYPE fails with a ValidationException on every real call. Coverage also semantically measures the shared pool of SP-eligible spend covered by any SP, so per-type attribution would not be meaningful anyway.

Hence spUtilizationFilter (plan type +/- region AND) and spCoverageRegionFilter (region-only or nil) are deliberately separate helpers, each carrying a do-not-re-symmetrize warning for future editors.

Part of #1335 (phase 2 of #1333).

Test plan

  • go test ./recommendations/ -count=1 from providers/aws/: 375 tests pass, exit 0 (11 new SP test functions, 44 pass counts incl. subtests: multi-page pagination, empty response, nil-Coverage-block skipping, zero-coverage vs no-data distinction, unparseable/negative numbers, filter-shape assertions against captured mock inputs for all four plan types, region ANDing, invalid plan type/lookback rejection, pre-canceled ctx terminality).
  • go build ./..., go vet ./recommendations/, gofmt -l recommendations/: all exit 0.
  • golangci-lint run --config ../../.golangci.yml ./recommendations/...: zero issues in the new files; the 128 pre-existing module issues are out of scope here and tracked in chore(lint): burn down 2,769 remaining non-errcheck golangci findings keeping Lint Code red #1342.

Notes for reviewers

These functions are the data source for the upcoming providers/aws/ladder PR (PR 5 of the laddering series), which consumes the 3-arg coverage signature (coverage is shared across ladder layers per region scope) and the 4-arg per-layer utilization signature.

Summary by CodeRabbit

  • New Features
    • Added Savings Plans coverage and utilization summaries for AWS Cost Explorer.
    • Improved filtering behavior by region and Savings Plans type when retrieving coverage/utilization data.
  • Bug Fixes
    • Strengthened validation for missing/unsupported plan types and invalid lookback values.
    • Improved handling of missing data and invalid numeric values to prevent misleading results (including correct treatment of zero vs “no data”).
  • Tests
    • Expanded test coverage for pagination, filtering, cancellation, empty responses, error propagation, and numeric parsing.

SPCoverageSummary is region-scoped only: CE's GetSavingsPlansCoverage
filter contract lacks SAVINGS_PLANS_TYPE (utilization-only dimension).
Per-plan-type utilization uses typed SDK enum filters, validated at the
boundary. Strict number parsing rejects NaN/Inf/negative; nil-vs-zero
data-density semantics via Days; paginated coverage with terminal ctx
cancellation. Part of #1335.
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/medium Moderate harm urgency/this-quarter Within the quarter impact/many Affects most users effort/m Days type/feat New capability labels Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 19 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5edfe12-681c-4a29-b4a8-4101585de674

📥 Commits

Reviewing files that changed from the base of the PR and between ead9a82 and 7329ffc.

📒 Files selected for processing (2)
  • providers/aws/recommendations/sp_coverage.go
  • providers/aws/recommendations/sp_coverage_test.go
📝 Walkthrough

Walkthrough

This PR adds Savings Plans coverage and utilization retrieval to the AWS recommendations provider. It extends the CostExplorerAPI surface, implements new coverage/utilization summary logic, updates mocks, and adds tests for pagination, filter construction, validation, cancellation, and numeric parsing.

Changes

Savings Plans Coverage/Utilization

Layer / File(s) Summary
Interface extension and mock updates
providers/aws/recommendations/client.go, providers/aws/recommendations/client_test.go, providers/aws/recommendations/parser_sp_additional_test.go
CostExplorerAPI gains GetSavingsPlansCoverage, and existing mocks implement the Savings Plans coverage and utilization methods.
Summary types and validation/filter helpers
providers/aws/recommendations/sp_coverage.go
Introduces SPCoverageSummary, SPUtilizationSummary, plan-type validation, and the Savings Plans coverage/utilization filter builders.
Coverage retrieval and accumulation
providers/aws/recommendations/sp_coverage.go
Adds coverage aggregation, paginated coverage retrieval, and the coverage fetch helper with retry and rate-limiting.
Utilization retrieval and parsing
providers/aws/recommendations/sp_coverage.go
Adds utilization retrieval, the utilization fetch helper, summary conversion, and strict float parsing.
Test suite
providers/aws/recommendations/sp_coverage_test.go
Adds the Savings Plans test mock and table-driven tests for aggregation, filters, validation, cancellation, and parsing.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Client as recommendations.Client
    participant CE as CostExplorer API
    participant Accum as spCoverageAccumulator

    Caller->>Client: GetSPCoverageSummary(region, lookbackDays)
    Client->>Client: validate lookbackDays
    Client->>Client: build window + spCoverageRegionFilter
    loop paginate
        Client->>CE: GetSavingsPlansCoverage(input)
        CE-->>Client: page with Coverage entries
        Client->>Accum: add(page)
    end
    Client->>Accum: summarize()
    Accum-->>Client: SPCoverageSummary
    Client-->>Caller: SPCoverageSummary, error

    Caller->>Client: GetSPUtilization(planType, region, lookbackDays)
    Client->>Client: validateSPPlanType + validate lookbackDays
    Client->>Client: build window + spUtilizationFilter
    Client->>CE: GetSavingsPlansUtilization(input)
    CE-->>Client: utilization totals
    Client->>Client: buildSPUtilizationSummary(totals)
    Client-->>Caller: SPUtilizationSummary, error
Loading

Suggested labels: effort/l

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding AWS Cost Explorer savings plans coverage and utilization queries.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ladder-sp-coverage

Comment @coderabbitai help to get the list of available commands.

@cristim

cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@providers/aws/recommendations/sp_coverage.go`:
- Around line 227-238: The GetSavingsPlansCoverage request is missing the
required Metrics field, which will cause the API call to fail. Update the input
construction in spCoverageRegionFilter/GetSavingsPlansCoverage to include the
SpendCoveredBySavingsPlans metric alongside the existing TimePeriod,
Granularity, and Filter fields. Keep GetSavingsPlansUtilization unchanged, since
that path does not use Metrics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16445cb6-e812-4693-b478-fce7467aba00

📥 Commits

Reviewing files that changed from the base of the PR and between 5f712a0 and 637c570.

📒 Files selected for processing (5)
  • providers/aws/recommendations/client.go
  • providers/aws/recommendations/client_test.go
  • providers/aws/recommendations/parser_sp_additional_test.go
  • providers/aws/recommendations/sp_coverage.go
  • providers/aws/recommendations/sp_coverage_test.go

Comment thread providers/aws/recommendations/sp_coverage.go
GetSavingsPlansCoverage requires the Metrics parameter (sole valid
value "SpendCoveredBySavingsPlans"); the SDK's client-side validator
only checks TimePeriod, so the omission passed mocks but would fail or
return incomplete data on real calls. Added as a named constant with
captured-input test assertions. GetSavingsPlansUtilization has no
Metrics parameter, so no counterpart gap exists there. Part of #1335.
@cristim

cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…ocabulary

CE's OnDemandCost is the UNCOVERED spend, not the eligible total:
coverage is now covered/(covered+onDemand), with EligibleUSDPerHour
exposed and pct nil only when eligible spend is zero. The
SAVINGS_PLANS_TYPE dimension uses CamelCase CUR vocabulary
("ComputeSavingsPlans"), not the parameter enum ("COMPUTE_SP") CE
silently matches nothing on; added a fail-loud translation map. Also
caps coverage pagination at 20 pages (issue #692 pattern) and documents
single-goroutine-per-Client rate limiter semantics. Part of #1335.
@cristim

cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Required manual verification before merge (mock-unprovable): the SAVINGS_PLANS_TYPE filter relies on the parameter-enum -> dimension-vocabulary mapping (COMPUTE_SP -> ComputeSavingsPlans, EC2_INSTANCE_SP -> EC2InstanceSavingsPlans, SAGEMAKER_SP -> SageMakerSavingsPlans, DATABASE_SP -> DatabaseSavingsPlans). A wrong dimension value does not error - CE silently matches nothing - so no unit test can prove it. Verify once against the real API: aws ce get-dimension-values --context SAVINGS_PLANS --dimension SAVINGS_PLANS_TYPE --time-period Start=<30d-ago>,End=<today> and confirm the returned values match the map (or run one real GetSavingsPlansUtilization per plan type on an account holding that SP type and confirm non-empty results). Same session also corrected the coverage percentage denominator (covered/(covered+onDemand), was covered/onDemand) - fixture arithmetic now cross-checked against the CE-reported CoveragePercentage field.

@cristim

cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
cristim merged commit 8b1d49f into main Jul 3, 2026
14 of 17 checks passed
cristim added a commit that referenced this pull request Jul 3, 2026
Four compatibility fixes required after rebasing onto main which included
PRs #1343 (errcheck), #1346 (ladder-sp-coverage), and #1340 (ladder-allocate):

- cmd/cleanup-lambda: remove unused `errors` and `pgx` imports left over
  from conflict resolution (main's rollback pattern doesn't use them)
- internal/mocks/stores.go: update ListStoredRecommendations signature to
  pointer (*RecommendationFilter) matching the interface change in #1343
- internal/scheduler/scheduler.go: pass &params (pointer) to
  GetRecommendations after PR #1346 changed the function signature
- providers/aws/service_client_test.go: add GetSavingsPlansCoverage and
  GetSavingsPlansUtilization stub methods to mockCostExplorerClient after
  PR #1346 extended the CostExplorerAPI interface
cristim added a commit that referenced this pull request Jul 9, 2026
Four compatibility fixes required after rebasing onto main which included
PRs #1343 (errcheck), #1346 (ladder-sp-coverage), and #1340 (ladder-allocate):

- cmd/cleanup-lambda: remove unused `errors` and `pgx` imports left over
  from conflict resolution (main's rollback pattern doesn't use them)
- internal/mocks/stores.go: update ListStoredRecommendations signature to
  pointer (*RecommendationFilter) matching the interface change in #1343
- internal/scheduler/scheduler.go: pass &params (pointer) to
  GetRecommendations after PR #1346 changed the function signature
- providers/aws/service_client_test.go: add GetSavingsPlansCoverage and
  GetSavingsPlansUtilization stub methods to mockCostExplorerClient after
  PR #1346 extended the CostExplorerAPI interface
cristim added a commit that referenced this pull request Jul 10, 2026
Four compatibility fixes required after rebasing onto main which included
PRs #1343 (errcheck), #1346 (ladder-sp-coverage), and #1340 (ladder-allocate):

- cmd/cleanup-lambda: remove unused `errors` and `pgx` imports left over
  from conflict resolution (main's rollback pattern doesn't use them)
- internal/mocks/stores.go: update ListStoredRecommendations signature to
  pointer (*RecommendationFilter) matching the interface change in #1343
- internal/scheduler/scheduler.go: pass &params (pointer) to
  GetRecommendations after PR #1346 changed the function signature
- providers/aws/service_client_test.go: add GetSavingsPlansCoverage and
  GetSavingsPlansUtilization stub methods to mockCostExplorerClient after
  PR #1346 extended the CostExplorerAPI interface
cristim added a commit that referenced this pull request Jul 11, 2026
Four compatibility fixes required after rebasing onto main which included
PRs #1343 (errcheck), #1346 (ladder-sp-coverage), and #1340 (ladder-allocate):

- cmd/cleanup-lambda: remove unused `errors` and `pgx` imports left over
  from conflict resolution (main's rollback pattern doesn't use them)
- internal/mocks/stores.go: update ListStoredRecommendations signature to
  pointer (*RecommendationFilter) matching the interface change in #1343
- internal/scheduler/scheduler.go: pass &params (pointer) to
  GetRecommendations after PR #1346 changed the function signature
- providers/aws/service_client_test.go: add GetSavingsPlansCoverage and
  GetSavingsPlansUtilization stub methods to mockCostExplorerClient after
  PR #1346 extended the CostExplorerAPI interface
@cristim
cristim deleted the feat/ladder-sp-coverage branch July 27, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p1 Next up; this sprint severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant