spendcap

package
v0.8.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package spendcap holds per-app monthly spend and egress guardrails: the pure threshold math and the level-triggered controller step that decides whether an app is throttled or paused. Persistence and the ingress effect live with the callers.

Index

Constants

View Source
const (
	EventThreshold  = "threshold"
	EventEnforced   = "enforced"
	EventReleased   = "released"
	EventRollover   = "rollover"
	EventConfigured = "configured"
	EventCleared    = "cleared"
	EventOverride   = "override"
)

Event kinds recorded in an app's history.

View Source
const (
	ActionNone     = "none"
	ActionThrottle = "throttle"
	ActionPause    = "pause"
)

Hard actions taken when a limit is reached.

View Source
const (
	DefaultThrottleRPS = 5
	MaxThrottleRPS     = 100000
	MaxThresholds      = 8
)

Limits on what an operator can configure.

View Source
const (
	GuardAlert        = "alert"
	GuardBlockDeploys = "block_deploys"
)

Guard modes decide whether reaching the limit also refuses new deploys.

View Source
const (
	ReasonWithinBudget       = "WithinBudget"
	ReasonThresholdReached   = "ThresholdReached"
	ReasonLimitAlertOnly     = "LimitReachedAlertOnly"
	ReasonThrottled          = "Throttled"
	ReasonPaused             = "Paused"
	ReasonDeploysBlocked     = "DeploysBlocked"
	ReasonEnforcementPending = "EnforcementPending"
)

Condition reasons.

View Source
const EnvOverrideTTL = "APP_SPEND_OVERRIDE_TTL"

EnvOverrideTTL sets how long a root override of the deploy guard lasts, as a Go duration such as "1h".

View Source
const EnvWarnPercents = "APP_SPEND_WARN_PERCENTS"

EnvWarnPercents overrides the default alert levels, for example "80,100".

View Source
const (

	// MaxReasonLen bounds the audited reason text.
	MaxReasonLen = 500
)

Variables

View Source
var DefaultThresholds = ParseWarnPercents(os.Getenv(EnvWarnPercents), []int{50, 80, 100})

DefaultThresholds are the alert levels, in percent of the limit.

View Source
var ErrNotFound = errors.New("spendcap: not found")

ErrNotFound is returned when an app has no cap.

Functions

func NextPeriodStart

func NextPeriodStart(t time.Time) time.Time

NextPeriodStart is the first instant of the month after t.

func OverrideTTL

func OverrideTTL() time.Duration

OverrideTTL reads the override lifetime from the environment.

func ParseBytes

func ParseBytes(s string) (int64, error)

ParseBytes reads 500GiB (binary) or 500GB (decimal) into bytes.

func ParseWarnPercents

func ParseWarnPercents(raw string, fallback []int) []int

ParseWarnPercents reads comma separated whole percents. Anything invalid falls back, so a typo in the environment can never disable alerting.

func Period

func Period(t time.Time) string

Period is the UTC month key a usage window belongs to.

func ValidateGuardMode

func ValidateGuardMode(m string) error

ValidateGuardMode rejects an unknown guard mode.

Types

type AppLoad

type AppLoad struct {
	Name        string
	Replicas    int
	MonthToDate float64
	RecentDaily []float64
}

AppLoad is what a pause would stop for one app. Reclaimable cost is the part a pause removes (compute and egress); storage and idle node cost stay.

type Breach

type Breach struct {
	Key  string
	Text string
}

Breach is one crossed threshold, keyed so a new level notifies again.

type Cap

type Cap struct {
	App                string  `json:"app"`
	MonthlyEgressBytes int64   `json:"monthly_egress_bytes"`
	MonthlyCostCeiling float64 `json:"monthly_cost_ceiling"`
	Thresholds         []int   `json:"thresholds"`
	Action             string  `json:"action"`
	ThrottleRPS        int     `json:"throttle_rps"`
	GuardMode          string  `json:"guard_mode"`
}

Cap is one app's monthly guardrail. A zero limit is unset.

func (Cap) Normalize

func (c Cap) Normalize() Cap

Normalize fills defaults and returns the cap ready to validate or store.

func (Cap) Validate

func (c Cap) Validate() error

Validate rejects a cap the controller could not act on sensibly.

type Condition

type Condition struct {
	Healthy bool   `json:"healthy"`
	Reason  string `json:"reason"`
	Message string `json:"message"`
}

Condition is the controller's status for one app.

func Conditions

func Conditions(c Cap, e Eval, enforced, nudged string) Condition

Conditions derives the status. enforced is the stored action and nudged the one ingress was last told about; they differ while a change is still reaching ingress.

type Controller

type Controller struct {
	Store  Store
	Usage  UsageSource
	Nudge  func(ctx context.Context) error
	Logger *slog.Logger
	Now    func() time.Time
}

Controller converges each capped app's enforcement on its usage. It holds no state of its own: every tick re-derives the decision from the store and usage, so a restart or a missed tick changes nothing.

func (*Controller) Breaches

func (c *Controller) Breaches(ctx context.Context) ([]Breach, error)

Breaches lists the highest threshold each capped app crossed this month, for the budget_threshold alert rule.

func (*Controller) Status

func (c *Controller) Status(ctx context.Context, app string) (Status, error)

Status returns the cap, its live position and the controller's condition.

func (*Controller) Tick

func (c *Controller) Tick(ctx context.Context) error

Tick reconciles every capped app. A usage failure leaves enforcement as it is, so an outage in telemetry never lifts or applies a limit by accident.

type Enforcement

type Enforcement struct {
	App         string
	Action      string
	ThrottleRPS int
}

Enforcement is one app's action ingress must apply now.

type Eval

type Eval struct {
	EgressPct float64
	CostPct   float64
	Pct       float64
	Crossed   []int
	Exhausted bool
	// Desired is the action that should be in force right now.
	Desired string
}

Eval is the position of usage against a cap.

func Evaluate

func Evaluate(c Cap, u Usage) Eval

Evaluate positions usage against the cap. The binding limit is whichever is closest to exhaustion, and the hard action applies once it reaches 100%.

func (Eval) Highest

func (e Eval) Highest() int

Highest is the largest crossed threshold, or 0.

type Event

type Event struct {
	ID     int64     `json:"id"`
	App    string    `json:"app"`
	Period string    `json:"period"`
	Kind   string    `json:"kind"`
	Detail string    `json:"detail"`
	Pct    float64   `json:"pct"`
	At     time.Time `json:"at"`
}

Event is one entry of an app's history.

type Override

type Override struct {
	ID        int64     `json:"id"`
	App       string    `json:"app"`
	Period    string    `json:"period"`
	Reason    string    `json:"reason"`
	Actor     string    `json:"actor"`
	CreatedAt time.Time `json:"created_at"`
	ExpiresAt time.Time `json:"expires_at"`
}

Override is one audited, expiring permission to deploy a blocked app.

func NewOverride

func NewOverride(app, reason, actor string, now time.Time, ttl time.Duration) (Override, error)

NewOverride validates the reason and stamps the expiry.

func (Override) Active

func (o Override) Active(now time.Time) bool

Active reports whether the override still permits deploys at now.

type Preview

type Preview struct {
	Apps            []PreviewApp `json:"apps"`
	TotalReplicas   int          `json:"total_replicas"`
	RemainingDays   float64      `json:"remaining_days"`
	EstimatedSaving float64      `json:"estimated_saving"`
	DryRun          bool         `json:"dry_run"`
}

Preview is what pausing a scope would do. It is computed, never applied.

func PreviewPause

func PreviewPause(apps []AppLoad, now time.Time) Preview

PreviewPause estimates stopping every replica of each app until month end. The run rate is the recent daily average, or month to date over elapsed days when no full day has been recorded yet.

type PreviewApp

type PreviewApp struct {
	Name            string  `json:"name"`
	Replicas        int     `json:"replicas"`
	DailyRunRate    float64 `json:"daily_run_rate"`
	EstimatedSaving float64 `json:"estimated_saving"`
}

PreviewApp is one app's line in a pause preview.

type State

type State struct {
	App    string
	Period string
	// Enforced is "" or the action ingress must apply; Nudged is the value
	// ingress was last told about.
	Enforced    string
	Nudged      string
	Highest     int
	Pct         float64
	EgressBytes int64
	Cost        float64
	UpdatedAt   time.Time
}

State is what the controller last decided for an app in a period.

type Status

type Status struct {
	Cap
	Period      string    `json:"period"`
	EgressBytes int64     `json:"egress_bytes"`
	Cost        float64   `json:"cost"`
	EgressPct   float64   `json:"egress_pct"`
	CostPct     float64   `json:"cost_pct"`
	Pct         float64   `json:"pct"`
	Enforced    string    `json:"enforced"`
	ResetsAt    time.Time `json:"resets_at"`
	Condition   Condition `json:"condition"`
}

Status is one app's cap with live usage and condition.

type Store

type Store interface {
	ListSpendCaps(ctx context.Context) ([]Cap, error)
	GetSpendCap(ctx context.Context, app string) (Cap, error)
	SaveSpendCap(ctx context.Context, c Cap, ev Event, now time.Time) error
	DeleteSpendCap(ctx context.Context, app string, ev Event) error
	GetSpendCapState(ctx context.Context, app string) (State, bool, error)
	SaveSpendCapState(ctx context.Context, st State, events []Event) error
	ListSpendCapEvents(ctx context.Context, app string, limit int) ([]Event, error)
}

Store is the persistence the controller needs; *store.DB satisfies it.

type Usage

type Usage struct {
	EgressBytes int64
	Cost        float64
}

Usage is an app's month to date consumption.

type UsageSource

type UsageSource interface {
	SpendCapUsage(ctx context.Context, now time.Time) (map[string]Usage, error)
}

UsageSource reports month to date usage by app name.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL