Documentation
¶
Overview ¶
Package recommendations provides AWS Cost Explorer recommendations client
Index ¶
- Constants
- func AdjustExistingCoverageForExpiringCommitments(recs []common.Recommendation, commitments []common.Commitment, windowDays int) int
- func AdjustExistingCoverageForExpiringCommitmentsWithCoverage(recs []common.Recommendation, commitments []common.Commitment, windowDays int, ...) (adjusted, missingDemand int)
- func AggregateRDSFamilyCoverage(coverage PoolCoverageMap) map[string]FamilyCoverage
- func ApplyCoverageMapToRecommendations(recs []common.Recommendation, coverage PoolCoverageMap)
- func RDSFamilyFromType(instanceType string) string
- func RDSInstanceNUFromType(instanceType string) float64
- type Client
- func (c *Client) AttachDailyUsageHistory(ctx context.Context, recs []common.Recommendation)
- func (c *Client) GetAllRecommendations(ctx context.Context) ([]common.Recommendation, error)
- func (c *Client) GetDailyUsagePcts(ctx context.Context, serviceFilter, resourceType, region string) ([]float64, error)
- func (c *Client) GetOnDemandSeries(ctx context.Context, region string, lookbackDays int) ([]DailyCost, error)
- func (c *Client) GetRICoverageMap(ctx context.Context, lookbackDays int, regions []string) (PoolCoverageMap, error)
- func (c *Client) GetRIUtilization(ctx context.Context, lookbackDays int, region string) ([]RIUtilization, error)
- func (c *Client) GetRecommendations(ctx context.Context, params *common.RecommendationParams) ([]common.Recommendation, error)
- func (c *Client) GetRecommendationsForService(ctx context.Context, service common.ServiceType) ([]common.Recommendation, error)
- func (c *Client) GetSPCoverageSummary(ctx context.Context, region string, lookbackDays int) (SPCoverageSummary, error)
- func (c *Client) GetSPUtilization(ctx context.Context, planType types.SupportedSavingsPlansType, region string, ...) (SPUtilizationSummary, error)
- func (c *Client) SetInstanceTypePagerFactory(f func() InstanceTypePager)
- func (c *Client) SetRecLookbackPeriod(period string)
- type CostExplorerAPI
- type DailyCost
- type DescribeInstanceTypesAPI
- type FamilyCoverage
- type FamilyDropCounts
- type IncompleteRecommendationsError
- type InstanceTypePager
- type PoolCoverage
- type PoolCoverageMap
- type RIUtilization
- type RateLimiter
- type SPCoverageSummary
- type SPUtilizationSummary
Constants ¶
const DefaultRecLookbackPeriod = "7d"
DefaultRecLookbackPeriod is the LookbackPeriod string forwarded to GetReservationPurchaseRecommendation when --rec-lookback-period is not specified. Kept in the recommendations package so the cmd flag default, the cmd-side fallback, and the client-side fallback all refer to a single source of truth (avoids the magic-value duplication called out by feedback_no_hardcoded_magic_values.md). Valid CE values are 7d/30d/60d (see convertLookbackPeriodE); 7d matches the prior hardcoded behavior from before --rec-lookback-period existed.
Variables ¶
This section is empty.
Functions ¶
func AdjustExistingCoverageForExpiringCommitments ¶
func AdjustExistingCoverageForExpiringCommitments( recs []common.Recommendation, commitments []common.Commitment, windowDays int, ) int
AdjustExistingCoverageForExpiringCommitments returns the number of adjusted recommendations. Demand must be complete disjoint pool shares, or freshly rebalanced by ApplyCoverageMapToRecommendations.
Partial raw slices and duplicated aggregate averages cannot be detected here. Callers with authoritative pool demand should use AdjustExistingCoverageForExpiringCommitmentsWithCoverage.
func AdjustExistingCoverageForExpiringCommitmentsWithCoverage ¶
func AdjustExistingCoverageForExpiringCommitmentsWithCoverage( recs []common.Recommendation, commitments []common.Commitment, windowDays int, coverage PoolCoverageMap, ) (adjusted, missingDemand int)
AdjustExistingCoverageForExpiringCommitmentsWithCoverage uses authoritative pool demand, never row sums. Eligible rows lacking positive finite demand remain unchanged and are counted in missingDemand.
func AggregateRDSFamilyCoverage ¶
func AggregateRDSFamilyCoverage(coverage PoolCoverageMap) map[string]FamilyCoverage
AggregateRDSFamilyCoverage walks the per-pool coverage map and returns a family-NU-aggregated view keyed by rdsFamilyKey. Used by family-NU sizing to size buys against the total family demand rather than per-size pool demand — matching how AWS's GetReservationPurchaseRecommendation bundles size-flex demand into a single rec at one size.
Skips non-RDS entries (their keys are 2-part, not 4-part) and entries whose instance size isn't in rdsInstanceNU.
func ApplyCoverageMapToRecommendations ¶
func ApplyCoverageMapToRecommendations(recs []common.Recommendation, coverage PoolCoverageMap)
ApplyCoverageMapToRecommendations sets ExistingCoveragePct on each rec whose pool key appears in the map, and rebalances each rec's AverageInstancesUsedPerHour so that the per-pool sum across recs equals the coverage map's org-wide AvgInstancesPerHour for the pool. Pool key shape depends on service: RDS recs (DatabaseDetails carrying an engine + AZConfig) look up by "region:instance_type:engine:deployment"; other services look up by "region:instance_type". Recs without a match stay at Known=false, which RI --target-coverage sizing drops (target-coverage-unknown) rather than treating them as 0% covered.
Why rebalance instead of just trusting per-rec avgs: AWS's GetReservationPurchaseRecommendation returns one rec per (pool, account) where it sees demand worth recommending. When AWS rec API only returns per-account avgs that sum to less than what CE coverage reports org-wide for the same pool (some linked accounts in the pool aren't surfaced as recs), per-rec sizing under-buys for the pool as a whole. Rebalancing scales each rec's avg by (cov.avg / sum-of-rec-avgs-in-pool) so the sized per-rec purchases sum to what the coverage CSV's gap math implies. When AWS rec API already matches coverage, the scale factor is ~1.0 and behavior is unchanged. When multiple recs have zero avg (no per-account signal at all), the coverage avg is split evenly across them so the total still lines up.
Mutates recs in place to mirror the way the sizing pipeline already hands recs around by value within each loop iteration.
func RDSFamilyFromType ¶
RDSFamilyFromType is the exported counterpart of rdsFamilyFromType for callers outside this package that need the family prefix of an RDS instance type (e.g. CSV writers grouping rows by family). Empty string when the type doesn't carry a recognizable size suffix.
func RDSInstanceNUFromType ¶
RDSInstanceNUFromType is the exported counterpart of rdsInstanceNUFromType for callers outside this package that need the NU value for an RDS instance type (e.g. CSV writers displaying per-row family-NU contribution). Returns 0 for unknown sizes — same fallback semantics as the unexported helper.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps the AWS Cost Explorer client for RI recommendations.
func NewClientWithAPI ¶
func NewClientWithAPI(api CostExplorerAPI, region string) *Client
NewClientWithAPI creates a new recommendations client with a custom Cost Explorer API (for testing).
func (*Client) AttachDailyUsageHistory ¶
func (c *Client) AttachDailyUsageHistory(ctx context.Context, recs []common.Recommendation)
AttachDailyUsageHistory enriches each recommendation in recs with a UsageHistory slice sourced from GetDailyUsagePcts. It batches by unique (serviceFilter, region, resourceType) so a 20-rec list for the same SKU fires only one CE call. Errors from individual tuples are logged and skipped so a single CE failure doesn't drop the whole collection.
SavingsPlans have no per-instance-type coverage breakdown in CE, so recs whose Service resolves to the empty coverage-filter string are silently skipped.
func (*Client) GetAllRecommendations ¶
GetAllRecommendations fetches recommendations for all supported services.
All five service calls run concurrently under errgroup. Each goroutine captures its own error in a closure-scoped variable and returns nil to the group so a single per-service failure does not cancel its siblings (matching the previous loop's `continue`-on-error tolerance). Results are merged in the canonical order EC2 → RDS → ElastiCache → OpenSearch → Redshift after all goroutines finish so order-sensitive consumers stay stable.
Partial results retain diagnostics through mergeServiceResults; callers decide whether their workflow can use an incomplete collection.
func (*Client) GetDailyUsagePcts ¶
func (c *Client) GetDailyUsagePcts(ctx context.Context, serviceFilter, resourceType, region string) ([]float64, error)
GetDailyUsagePcts returns a slice of daily RI-coverage percentages (0-100) for the given (service, resourceType, region) over the last usageHistoryLookbackDays days, ordered oldest-to-newest. Each element corresponds to one calendar day. If CE returns no data for a day the slot is filled with 0.0 so the sparkline always has exactly usageHistoryLookbackDays points when data is present.
Returns (nil, nil) on API success when CE has no historical data at all for the tuple; callers store nil so the frontend renders "—" rather than a flat-zero sparkline.
serviceFilter is the canonical CE SERVICE dimension value (e.g. "Amazon Elastic Compute Cloud - Compute"). The resourceType is matched by the INSTANCE_TYPE dimension so we only pick up coverage for the exact SKU the recommendation targets.
func (*Client) GetOnDemandSeries ¶
func (c *Client) GetOnDemandSeries(ctx context.Context, region string, lookbackDays int) ([]DailyCost, error)
GetOnDemandSeries fetches daily on-demand EC2 compute cost for the given region over the past lookbackDays days from CE GetCostAndUsage and returns one DailyCost per returned calendar day, ordered oldest-to-newest with strictly increasing unique UTC days (map accumulation dedupes by date; lexicographic sort of "YYYY-MM-DD" keys is chronological). The last element is the most recent day CE has data for, which the ladder baseline's freshness check relies on.
CE query parameters:
- Granularity: DAILY (one entry per calendar day)
- Metric: UnblendedCost (actual on-demand billed rate; see onDemandMetric)
- Filter: SERVICE = EC2 compute AND PURCHASE_TYPE = On Demand Instances AND REGION = region (three-clause AND)
- Time window: [now-lookbackDays, now) exclusive end
Each day's total USD is divided by 24.0 to yield USD/hr. Dividing by 24 is derived from the DAILY granularity contract (one day = 24 hours), not a magic constant.
CE typically lags ~24-48h, so the returned series may be shorter than lookbackDays. The caller (baseline.GetUsageBaseline) enforces minimum length, in-window coverage, and freshness.
Fail-loud conditions (feedback_no_silent_fallbacks):
- Empty result: error.
- A result row missing the requested metric key (or with a nil Amount): error. CE echoes every requested metric on every row (genuine $0 days arrive as Amount:"0"), so a missing key means the request vocabulary is wrong, and fabricating a 0 would silently corrupt the baseline.
- An all-zero series: error. When a filter or metric name is wrong, CE returns a complete, fresh, chronological series of $0 rows that would pass every downstream validation and make the engine size purchases from fabricated data. An account with genuinely zero on-demand EC2 spend for the whole lookback has nothing to ladder, so erroring is correct there too.
func (*Client) GetRICoverageMap ¶
func (c *Client) GetRICoverageMap(ctx context.Context, lookbackDays int, regions []string) (PoolCoverageMap, error)
GetRICoverageMap fetches existing-RI coverage % and avg-running-instances over the last lookbackDays days, returning a map keyed by org-wide pool (no account dimension — matches the AWS console reservations-coverage report). Operators wiring the result back onto Recommendations should call ApplyCoverageMapToRecommendations rather than walking the map manually.
Non-RDS calls group by INSTANCE_TYPE alone (REGION + SERVICE in the Filter). RDS calls group by INSTANCE_TYPE + DEPLOYMENT_OPTION (REGION + SERVICE + DATABASE_ENGINE in the Filter) so the resulting keys split Single-AZ vs Multi-AZ pools, which have separate RI scopes.
Missing pools (no demand in the pool over the window) are omitted from the map; ApplyCoverageMapToRecommendations leaves rec.ExistingCoverageKnown false for those recs, which RI --target-coverage sizing drops (target-coverage-unknown) rather than treating them as 0% covered.
func (*Client) GetRIUtilization ¶
func (c *Client) GetRIUtilization(ctx context.Context, lookbackDays int, region string) ([]RIUtilization, error)
GetRIUtilization fetches per-RI utilization from Cost Explorer for the last N days, scoped to EC2 RIs in region.
Without a Filter, GetReservationUtilization blends utilization across every reserved-resource type in the account (RDS, ElastiCache, OpenSearch, Redshift, standard EC2 RIs) and every region into one SUBSCRIPTION_ID-grouped number. Callers that read this as "EC2 convertible-RI utilization for this region" (e.g. the ladder ConvertibleRI layer) would then trigger real reshape/exchange decisions off an unrelated RI's utilization (PR #1361). region is optional: an empty string omits the REGION dimension, matching callers that haven't resolved a specific region.
func (*Client) GetRecommendations ¶
func (c *Client) GetRecommendations(ctx context.Context, params *common.RecommendationParams) ([]common.Recommendation, error)
GetRecommendations fetches Reserved Instance recommendations for any service.
func (*Client) GetRecommendationsForService ¶
func (c *Client) GetRecommendationsForService(ctx context.Context, service common.ServiceType) ([]common.Recommendation, error)
GetRecommendationsForService fetches recommendations for a specific service across the full Cartesian product of defaultDiscoveryTerms x defaultDiscoveryPaymentOptions (currently 2 x 3 = 6 Cost Explorer calls per service). Each call returns the recs for that single (term, payment) cell and the parser tags them with params.Term / params.PaymentOption so the resulting slice contains every combo for the user to choose from in the UI.
Failed details or scopes accompany surviving results as a typed diagnostic. If every API call fails, the ordinary error remains fatal.
func (*Client) GetSPCoverageSummary ¶
func (c *Client) GetSPCoverageSummary(ctx context.Context, region string, lookbackDays int) (SPCoverageSummary, error)
GetSPCoverageSummary fetches Savings Plans coverage over the last lookbackDays days and returns a summary the ladder engine can consume.
Unlike GetSPUtilization, coverage is NOT plan-type-scoped, for two reasons:
- CE filter contract: GetSavingsPlansCoverage's Filter supports only the LINKED_ACCOUNT, REGION, SERVICE, and INSTANCE_FAMILY dimensions (see the GetSavingsPlansCoverageInput.Filter SDK doc). SAVINGS_PLANS_TYPE is a utilization-only dimension; passing it here fails with a ValidationException on every real call.
- Semantics: coverage measures the shared pool of SP-eligible on-demand spend that is covered by ANY Savings Plan, so attributing coverage to a single plan type is not meaningful - a dollar of eligible spend covered by a Compute SP is indistinguishable, coverage-wise, from one covered by an EC2 Instance SP.
region == "" means all regions (no Filter at all); a non-empty region adds a REGION dimension filter (a supported coverage dimension).
Coverage carries no pool key (unlike RI coverage's PoolCoverageMap) because SP commitment is dollar-denominated and applies cross-service; the return type is therefore a flat SPCoverageSummary rather than a keyed map.
lookbackDays must be positive. Zero or negative is an error - the caller is responsible for supplying the window (per the no-hardcoded-window rule).
An empty CE response (Days==0) is not an error: it means CE has no coverage data for the requested scope and window. Note that a nonexistent or misspelled region does NOT error either - CE simply matches nothing and the result is Days==0 with all pointer fields nil. Callers must read Days==0 as "no data for this scope", not "no SPs in the account", and check Days before dereferencing the pointer fields.
Concurrent calls are safe: fetchSPCoveragePage derives operation-local retry state from the client's shared retry policy, so callers never mutate the same retry counter.
func (*Client) GetSPUtilization ¶
func (c *Client) GetSPUtilization(ctx context.Context, planType types.SupportedSavingsPlansType, region string, lookbackDays int) (SPUtilizationSummary, error)
GetSPUtilization fetches Savings Plans utilization for one plan type over the last lookbackDays days.
planType scopes the CE call to a single SAVINGS_PLANS_TYPE dimension value (typed SDK enum, validated at the boundary; empty or unknown values are an explicit error). Utilization is per-commitment: the ladder holds two distinct SP layers, and a blended account-wide utilization number would mask under-utilization in one layer being offset by over-utilization in the other. region == "" means all regions (no REGION filter); a non-empty region is ANDed with the plan-type filter. Unlike coverage, utilization's Filter contract DOES support SAVINGS_PLANS_TYPE - see spUtilizationFilter.
GetSavingsPlansUtilization is a single-page API (the response has no NextToken); the Total field already aggregates the full window, so no pagination loop is needed.
lookbackDays must be positive.
Returned money fields (UsedCommitmentUSDPerHour, TotalCommitmentUSDPerHour) are CE's period totals divided by windowHours so callers receive consistent $/hr values. Nil fields mean CE returned no utilization data. As with coverage, a nonexistent or misspelled region (or a plan type with no commitments) does NOT error - CE matches nothing and every pointer field comes back nil. Callers must read all-nil as "no data for this scope", not "no SPs in the account".
MANUAL VERIFICATION REQUIRED (mock-unprovable): the plan-type filter relies on the parameter-enum -> dimension-vocabulary mapping in spPlanTypeDimensionValues ("COMPUTE_SP" -> "ComputeSavingsPlans", ...). A wrong dimension value does not error - CE silently matches nothing - so no unit test can prove the mapping. Before trusting this path in production, verify once against the real API: call GetDimensionValues with Context=SAVINGS_PLANS and Dimension=SAVINGS_PLANS_TYPE (or run one real GetSavingsPlansUtilization per plan type) and confirm the returned dimension values match the map.
Concurrent calls are safe: fetchSPUtilizationPage derives operation-local retry state from the client's shared retry policy, so callers never mutate the same retry counter.
func (*Client) SetInstanceTypePagerFactory ¶
func (c *Client) SetInstanceTypePagerFactory(f func() InstanceTypePager)
SetInstanceTypePagerFactory injects a pager factory for the instance-type SKU catalog. Must be called before the first GetRecommendations call. Intended for tests that need to verify the one-fetch-per-lifetime invariant without hitting AWS.
func (*Client) SetRecLookbackPeriod ¶
SetRecLookbackPeriod configures the LookbackPeriodInDays used by GetRecommendationsForService. Valid values: "7d", "30d", "60d". An empty or unrecognized value falls back to "7d" at call time.
type CostExplorerAPI ¶
type CostExplorerAPI interface {
GetReservationPurchaseRecommendation(ctx context.Context, params *costexplorer.GetReservationPurchaseRecommendationInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetReservationPurchaseRecommendationOutput, error)
GetSavingsPlansPurchaseRecommendation(ctx context.Context, params *costexplorer.GetSavingsPlansPurchaseRecommendationInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetSavingsPlansPurchaseRecommendationOutput, error)
GetReservationUtilization(ctx context.Context, params *costexplorer.GetReservationUtilizationInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetReservationUtilizationOutput, error)
GetReservationCoverage(ctx context.Context, params *costexplorer.GetReservationCoverageInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetReservationCoverageOutput, error)
GetSavingsPlansCoverage(ctx context.Context, params *costexplorer.GetSavingsPlansCoverageInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetSavingsPlansCoverageOutput, error)
GetSavingsPlansUtilization(ctx context.Context, params *costexplorer.GetSavingsPlansUtilizationInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetSavingsPlansUtilizationOutput, error)
// GetCostAndUsage fetches cost and usage data for arbitrary time periods
// and granularities. Added for the daily on-demand series adapter that
// powers GetUsageBaseline (L2).
GetCostAndUsage(ctx context.Context, params *costexplorer.GetCostAndUsageInput, optFns ...func(*costexplorer.Options)) (*costexplorer.GetCostAndUsageOutput, error)
}
CostExplorerAPI defines the interface for Cost Explorer operations.
type DailyCost ¶
type DailyCost struct {
// Date is the UTC calendar day (midnight UTC) this entry covers.
Date time.Time
// USDPerHour is the on-demand-equivalent spend averaged over the day.
USDPerHour float64
}
DailyCost is one calendar day of on-demand cost from CE GetCostAndUsage. Date is midnight UTC of the calendar day the entry covers; USDPerHour is the day's total unblended cost divided by 24. The ladder package maps this to its own DailyPoint type (identical fields) via a thin adapter; the type is duplicated because recommendations cannot import providers/aws/ladder (ladder already imports recommendations).
type DescribeInstanceTypesAPI ¶
type DescribeInstanceTypesAPI interface {
DescribeInstanceTypes(ctx context.Context, params *awsec2.DescribeInstanceTypesInput, optFns ...func(*awsec2.Options)) (*awsec2.DescribeInstanceTypesOutput, error)
}
DescribeInstanceTypesAPI is the subset of the EC2 client interface needed to build a DescribeInstanceTypes paginator. The production implementation is *ec2.Client; tests inject a stub.
type FamilyCoverage ¶
FamilyCoverage is the family-NU-aggregated view of coverage for an RDS instance family. TotalNU is the sum of (avg × NU(size)) across every size in the family that has CE coverage data; CoveredNU is the sum of (avg × NU(size) × pct/100). Pct can be derived as CoveredNU / TotalNU × 100 (callers do this inline).
type FamilyDropCounts ¶
type FamilyDropCounts struct {
// AlreadyAtTarget is the number of recs from families where the
// existing coverage already meets or exceeds the target (gap <= 0).
AlreadyAtTarget int
// NoNUSignal is the number of recs dropped because the family's
// AWS-recommended counts summed to zero NU (e.g. all recs at
// unknown/unrecognized sizes), so there is no scalable NU to apply
// the family target against. This is distinct from AlreadyAtTarget.
NoNUSignal int
// SizedToZero is the number of recs dropped because the family-wide
// scale factor produced a floor(0) count for that rec's size.
SizedToZero int
}
FamilyDropCounts holds the counts of family-level drops from ApplyFamilyNUSizingRDS, split by the two distinct drop reasons so the caller can record them into a DropSummary without importing cmd.
func ApplyFamilyNUSizingRDS ¶
func ApplyFamilyNUSizingRDS( recs []common.Recommendation, coverage PoolCoverageMap, targetPct float64, ) (sizedRDS, nonRDS []common.Recommendation, drops FamilyDropCounts)
ApplyFamilyNUSizingRDS replaces per-pool RI sizing with family-NU sizing for RDS recommendations. AWS's GetReservationPurchaseRecommendation already bundles size-flex demand within an instance family into a single recommendation at one size; per-pool sizing under-buys because it only sees that size's pool demand. Family-NU sizing rescales each RDS rec's Count so the family-wide NU sum matches the user's target across the whole family.
Algorithm:
- Group RDS recs by (region, family, engine, deployment).
- For each family: a. Compute family existing_pct = covered_NU / total_NU * 100 b. gap = targetPct − existing_pct (drop family if ≤ 0) c. target_NU_need = gap / 100 * total_NU d. current_rec_NU = Σ rec.Count × NU(rec.size) e. scale = target_NU_need / current_rec_NU f. Apply scale to each rec.Count and cost-bearing fields
- Recs scaled to zero are dropped (size flex left no room).
- Non-RDS recs are returned unchanged so callers can continue them through the per-pool sizing path.
Returns (sizedRDS, nonRDS, drops). When targetPct is outside (0,100] all recs are returned as nonRDS unchanged. When the CE coverage for a family has no NU signal (family.TotalNU <= 0), that family's recs are returned sized (as-is) in sizedRDS. When the family's AWS-recommended counts sum to zero NU (currentNU <= 0 — all recs at unrecognized sizes), those recs are dropped and recorded in drops.NoNUSignal; they are NOT passed through to the per-pool path.
type IncompleteRecommendationsError ¶
IncompleteRecommendationsError accompanies survivors of an incomplete collection. Each cause represents one rejected detail or one failed collection scope.
func (*IncompleteRecommendationsError) Error ¶
func (e *IncompleteRecommendationsError) Error() string
func (*IncompleteRecommendationsError) Unwrap ¶
func (e *IncompleteRecommendationsError) Unwrap() []error
type InstanceTypePager ¶
type InstanceTypePager interface {
HasMorePages() bool
NextPage(ctx context.Context, optFns ...func(*awsec2.Options)) (*awsec2.DescribeInstanceTypesOutput, error)
}
InstanceTypePager defines the iteration contract for DescribeInstanceTypes pages. Production code uses ec2.NewDescribeInstanceTypesPaginator; tests inject a stub.
type PoolCoverage ¶
PoolCoverage captures the two CE coverage signals we use downstream: the share of historical demand already covered by existing reservations (Pct, 0-100) and the average concurrent-instances figure derived from TotalRunningHours over the lookback window (AvgInstancesPerHour).
AvgInstancesPerHour matches the "Total running hours / window hours" figure shown in the AWS console reservations-coverage report; it's what --target-coverage sizing now anchors on (linear in avg × gap%) so a pool's CUDly buy lines up with the same math operators see in the console CSV. Zero means CE returned no running hours for the pool over the window — sizing then falls back to rec.AverageInstancesUsedPerHour from the rec parser (per-account signal from GetReservationPurchaseRecommendation).
type PoolCoverageMap ¶
type PoolCoverageMap map[string]PoolCoverage
PoolCoverageMap maps a pool key to the (pct, avg) pair for that pool. Used by --target-coverage sizing to subtract existing commitments from the under-buy formula and to size linearly off the org-wide demand. Keys are org-wide (no account dimension) to match the AWS console's reservations-coverage report: non-RDS keys are "region:instance_type"; RDS keys are "region:instance_type:engine:deployment" so Single-AZ vs Multi-AZ pools stay distinct (a Single-AZ RI cannot cover a Multi-AZ instance).
type RIUtilization ¶
type RIUtilization = common.RIUtilization
RIUtilization is an alias for common.RIUtilization so existing callers that import this package continue to compile without changes. The canonical definition lives in pkg/common so the Azure provider can return the same type without a cross-provider dependency.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter provides rate limiting with exponential backoff.
func NewRateLimiter ¶
func NewRateLimiter() *RateLimiter
NewRateLimiter creates a new rate limiter with default settings.
func NewRateLimiterWithOptions ¶
func NewRateLimiterWithOptions(baseDelay, maxDelay time.Duration, maxRetries int) *RateLimiter
NewRateLimiterWithOptions creates a rate limiter with custom settings.
func (*RateLimiter) GetRetryCount ¶
func (r *RateLimiter) GetRetryCount() int
GetRetryCount returns the current retry count.
func (*RateLimiter) ShouldRetry ¶
func (r *RateLimiter) ShouldRetry(err error) bool
ShouldRetry checks if we should retry based on error type and retry count. Only throttling and transient errors are retried.
type SPCoverageSummary ¶
type SPCoverageSummary struct {
// CoveragePct is covered / (covered + on-demand) * 100 over the window
// (0-100): the share of SP-ELIGIBLE spend actually covered by Savings
// Plans. CE's OnDemandCost is the UNCOVERED remainder, not the eligible
// total, so the denominator must be the sum of both fields - dividing
// by OnDemandCost alone overstates coverage and exceeds 100% as soon as
// real coverage passes 50%. Nil ONLY when the eligible total is zero
// (no SP-eligible activity in the window) or Days==0; a fully covered
// window (OnDemandCost==0, covered>0) yields &100.0, not nil.
CoveragePct *float64
// CoveredUSDPerHour normalizes reported SP-covered spend over the requested
// window, without adjusting for reporting completeness. Nil when Days==0.
CoveredUSDPerHour *float64
// OnDemandUSDPerHour normalizes reported uncovered SP-eligible spend over
// the same requested window. Nil when Days==0.
OnDemandUSDPerHour *float64
// EligibleUSDPerHour is CoveredUSDPerHour + OnDemandUSDPerHour, normalized
// over the requested window including zero-activity days. Nil when Days==0.
EligibleUSDPerHour *float64
// Days counts daily CE data points with a non-nil Coverage block.
// It does not indicate reporting completeness or change the rate divisor.
Days int
}
SPCoverageSummary is the Savings Plans coverage summary (optionally scoped to one region) over a lookback window.
Unlike PoolCoverageMap (keyed by region:instance_type), SP coverage carries no pool key. Savings Plans commitment is dollar-denominated and applies cross-service (and, for Compute SPs, cross-region), so there is no meaningful per-instance-type breakdown at the coverage level. Any region scoping happens via the CE Filter on the request instead, so the summary stays flat. Coverage is also NOT plan-type-scoped - see GetSPCoverageSummary for why (CE filter contract).
All money/percent fields are pointers so "CE returned no data" (nil) is distinguishable from "CE returned 0%" (pointer to 0.0). Days holds the number of daily data points that had a non-nil Coverage block in the CE response; zero means CE returned no coverage data at all for the window (e.g., no Savings Plans have ever been purchased in the account).
type SPUtilizationSummary ¶
type SPUtilizationSummary struct {
// UtilizationPct is the percentage of the SP commitment actually consumed
// over the window (0-100). Nil when CE returned no utilization data.
UtilizationPct *float64
// UsedCommitmentUSDPerHour is the average used SP commitment in $/hr.
// Nil when CE returned no data.
UsedCommitmentUSDPerHour *float64
// TotalCommitmentUSDPerHour is the average total SP commitment in $/hr.
// Nil when CE returned no data.
TotalCommitmentUSDPerHour *float64
}
SPUtilizationSummary is the Savings Plans utilization summary for one plan type (and optionally one region) over a lookback window. All money/percent fields are pointers so "CE returned no data" (nil) is distinguishable from "CE returned 0%".
UsedCommitmentUSDPerHour and TotalCommitmentUSDPerHour are derived by dividing the CE-reported period totals by windowHours so callers receive a consistent $/hr denomination, matching PoolCoverage.AvgInstancesPerHour.