spendcap

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 9 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"
)

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 (
	ReasonWithinBudget       = "WithinBudget"
	ReasonThresholdReached   = "ThresholdReached"
	ReasonLimitAlertOnly     = "LimitReachedAlertOnly"
	ReasonThrottled          = "Throttled"
	ReasonPaused             = "Paused"
	ReasonEnforcementPending = "EnforcementPending"
)

Condition reasons.

Variables

View Source
var DefaultThresholds = []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 ParseBytes

func ParseBytes(s string) (int64, error)

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

func Period

func Period(t time.Time) string

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

Types

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"`
}

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 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