budget

package
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package budget meters model spend against the policy's gateway budgets (ADR-0017). It is shared by both sides of the design: halo-server keeps the authoritative counters (Ledger), and every halo-proxy replica reports usage to it and caches its verdicts (Client). Scope keys and windows are computed here once so both sides agree.

Index

Constants

View Source
const MaxRetention = 95 * 24 * time.Hour

MaxRetention is how far back any budget policy can still count: the longest rolling period policy accepts (90 days) plus one of its buckets (period/48, under 2 days), and it covers any calendar window (a month is at most 31 days). A shared ledger drops buckets older than this regardless of which budgets a replica's policy names (TestMaxRetentionCoversPolicy).

Variables

This section is empty.

Functions

func Cost

func Cost(prices map[string]policy.ModelPrice, model string, u Usage) (usd float64, priced bool)

Cost prices u for a provider model. priced is false when the model has no price: its tokens still count toward token limits, but it costs $0.

func Cutoff

func Cutoff(b *policy.Budget, now time.Time) time.Time

Cutoff is the oldest moment b still counts at now: buckets starting before it are outside every window and can be dropped.

func Window

func Window(b *policy.Budget, t time.Time) (start, reset time.Time)

Window returns the calendar window (UTC) containing t: its start and the next reset. Rolling budgets have no fixed window; see bucketSize.

Types

type Charge

type Charge struct {
	Budget *policy.Budget
	Key    string // "user:<id>", "group:<name>", "ring:<name>" or "org"
}

Charge is one counter a request is charged to.

func Charges

func Charges(budgets []policy.Budget, p Principal) []Charge

Charges returns every counter p's requests are charged to. A budget with a rings or groups filter skips callers outside it; user and group scopes skip anonymous callers (there is no verified identity to key on).

type Counters

type Counters = map[string]map[string]map[int64]Spend

Counters is a ledger's counter state: budget -> key -> bucket start (unix seconds) -> spend. It is what an external store (the HA backend) persists.

func Increments

func Increments(budgets []policy.Budget, recs []Record, now time.Time) Counters

Increments returns what Apply would add to the counters for recs, without batch dedupe: a shared store adds it to its own rows atomically.

type Ledger

type Ledger struct {
	// contains filtered or unexported fields
}

Ledger holds the authoritative counters. It lives only in halo-server, a single replica (ADR-0017), so a mutex is the whole concurrency story.

func FromCounters

func FromCounters(c Counters) *Ledger

FromCounters is a read-only view over counters loaded from an external store, for Status and All.

func LoadLedger

func LoadLedger(b []byte) (*Ledger, error)

LoadLedger restores a Snapshot.

func NewLedger

func NewLedger() *Ledger

NewLedger returns an empty ledger.

func (*Ledger) All

func (l *Ledger) All(budgets []policy.Budget, now time.Time) []Status

All returns every counter with spend in its current window, plus the fixed org and group counters even at zero, sorted by budget then key.

func (*Ledger) Apply

func (l *Ledger) Apply(budgets []policy.Budget, batch string, recs []Record, now time.Time) (duplicate bool)

Apply charges every record to the counters its principal maps to under budgets. A batch id already applied within the last hour is ignored and reported as a duplicate. Records are trusted to be validated by the caller.

func (*Ledger) Snapshot

func (l *Ledger) Snapshot() (b []byte, changed bool, err error)

Snapshot serialises the ledger and reports whether anything changed since the previous snapshot (callers skip the write when not).

func (*Ledger) Status

func (l *Ledger) Status(budgets []policy.Budget, p Principal, now time.Time) []Status

Status returns the state of every counter p is charged to.

type Principal

type Principal struct {
	Subject string   `json:"subject,omitempty"`
	Groups  []string `json:"groups,omitempty"`
	Ring    string   `json:"ring,omitempty"`
}

Principal is who a request is charged to: the verified subject (empty when anonymous) with its IdP groups, and the ring the gateway routed it to. It never comes from client-supplied headers (invariant 4).

type Record

type Record struct {
	Principal
	Spend
	At time.Time `json:"at"`
}

Record is usage a gateway replica observed for one principal. At is the minute it happened, so usage reported late (after an outage) still lands in the window it belongs to.

type Spend

type Spend struct {
	USD    float64 `json:"usd"`
	Tokens int64   `json:"tokens"`
}

Spend is accumulated usage.

type Status

type Status struct {
	Budget      string    `json:"budget"`
	Scope       string    `json:"scope"`
	Key         string    `json:"key"`
	Window      string    `json:"window"`
	Period      string    `json:"period,omitempty"`
	Action      string    `json:"action"`
	LimitUSD    float64   `json:"limitUSD,omitempty"`
	LimitTokens int64     `json:"limitTokens,omitempty"`
	SpentUSD    float64   `json:"spentUSD"`
	SpentTokens int64     `json:"spentTokens"`
	AlertAt     []float64 `json:"alertAt,omitempty"`
	// ResetAt is when the counter drops below its limit again: the next
	// calendar window, or for rolling windows when enough old usage expires.
	ResetAt time.Time `json:"resetAt"`
}

Status is one counter's state in its current window.

func (Status) Alert

func (s Status) Alert() float64

Alert is the highest soft threshold the counter has crossed (0 = none).

func (Status) Exceeded

func (s Status) Exceeded() bool

Exceeded reports whether the counter has reached a limit.

func (Status) Utilization

func (s Status) Utilization() float64

Utilization is the larger of spent/limit over USD and tokens (0 when unlimited).

type Usage

type Usage struct {
	Input, Output, CacheRead, CacheWrite int64
}

Usage is the token usage of one model response, normalised across wire protocols so the four counts never overlap: Input excludes cached tokens, Output includes reasoning/thinking tokens.

func (Usage) Tokens

func (u Usage) Tokens() int64

Tokens is every token billed for the response.

Jump to

Keyboard shortcuts

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