Documentation
¶
Overview ¶
Package forecast projects a node's traffic for the rest of its billing cycle, and says when it would reach its plan limit.
Why this exists: hitting a traffic limit is currently discovered by hitting it, and for a NAT'd node that means the provider suspends it. The number is useful; the number's *basis* is what makes it actable, so every forecast here carries the method, the samples it used and an uncertainty rather than a bare figure.
The rule that shapes the design, and the first thing the acceptance criteria ask for: **a forecast that cannot be made is not made.** Extrapolating two points into a confident number is worse than saying "not enough data", because the number is what an operator acts on — and the failure mode of a wrong projection is either a suspension that was predicted as fine, or a plan upgrade that was never needed. So `Method` can be `insufficient`, and that is a first-class answer rather than an error.
Index ¶
Constants ¶
const DefaultThreshold = 0.9
DefaultThreshold is the warning fraction when the caller does not choose one.
Ninety percent, because the warning has to arrive while there is still time to act: at the limit itself the only remaining options are a plan upgrade or an outage, and a node that is suspended has no traffic to measure.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Basis ¶
type Basis struct {
// Samples is how many observations were used.
Samples int `json:"samples"`
// ObservedFor is how long the samples span.
ObservedFor time.Duration `json:"observed_for"`
// Coverage is the fraction of the cycle the samples span.
Coverage float64 `json:"coverage"`
// BytesPerDay is the observed average rate, which is the projection's slope.
BytesPerDay float64 `json:"bytes_per_day"`
// Uncertainty is the projection's relative error, from the spread of the per-interval rates.
//
// Estimated from the observed variance of the rate rather than asserted: a node with a steady
// transfer has a small one and a node with a daily peak has a large one, and an operator deciding
// whether to buy headroom needs to know which they are looking at. It is the relative standard
// error of the mean rate times the projection's extrapolation factor, which is the honest
// consequence of projecting further than you observed.
Uncertainty float64 `json:"uncertainty"`
// CycleDays is the cycle's length in days.
CycleDays float64 `json:"cycle_days"`
}
Basis is the projection's own account of itself.
type Forecast ¶
type Forecast struct {
// Method is how the projection was reached, or MethodInsufficient.
Method Method `json:"method"`
// Reason explains an insufficient forecast, in the terms the operator needs: what was missing.
Reason string `json:"reason,omitempty"`
// UsedBytes is the observed total at Now, which is reported even when no projection is made: an
// operator asking "how much have I used" deserves an answer even when the cycle is too young to
// project.
UsedBytes int64 `json:"used_bytes"`
// ProjectedBytes is the cycle's projected end total. Zero when Method is insufficient.
ProjectedBytes int64 `json:"projected_bytes"`
// LimitSet is false when the node has no plan limit, in which case there is nothing to project
// against and no crossing date to report.
LimitSet bool `json:"limit_set"`
// ProjectedFraction is ProjectedBytes / LimitBytes. Zero when no limit is set.
ProjectedFraction float64 `json:"projected_fraction"`
// WouldExceed is whether the projection reaches the limit before the cycle ends.
WouldExceed bool `json:"would_exceed"`
// CrossesAt is when the limit would be reached, and CrossesInDays how far away that is. Both are
// zero when no crossing can be computed within time.Duration and JSON's year 0..9999 range.
CrossesAt time.Time `json:"crosses_at,omitempty"`
CrossesInDays float64 `json:"crosses_in_days,omitempty"`
// CrossesInCycle is whether the crossing falls inside this cycle. A crossing after the cycle ends
// is not a problem this cycle, and saying so is the difference between a warning and noise.
CrossesInCycle bool `json:"crosses_in_cycle"`
// Basis is the arithmetic behind the projection, stated so a reader can check it.
Basis Basis `json:"basis"`
// Warning is set when the projection crosses the threshold and a notification is warranted.
Warning *Warning `json:"warning,omitempty"`
}
Forecast is the answer.
type Request ¶
type Request struct {
// CycleStart and CycleEnd bound the billing cycle. The window is half-open, matching how the
// ledger rotates: `[start, end)`.
CycleStart time.Time
CycleEnd time.Time
// Samples are the cycle's observations, in any order.
Samples []Sample
// LimitBytes is the plan's traffic allowance. Zero means no limit is set, and a node with no
// limit is not forecast at all — see Forecast.LimitSet.
LimitBytes int64
// Threshold is the fraction of the limit at which a warning is worth raising, 0 < Threshold <= 1.
// Zero means the default.
Threshold float64
}
Request is what a forecast is computed from.
type Sample ¶
type Sample struct {
// At is when the total was observed.
At time.Time
// Total is the cycle-to-date traffic in bytes at that instant.
Total int64
}
Sample is one observed traffic total for the cycle, in bytes.
A cumulative total rather than a delta, because the projection is about where the total will end up: the caller has the ledger's month-to-date value, and deltas would need re-summing here to say anything about the end of the cycle.
type Warning ¶
type Warning struct {
// Threshold is the fraction that was crossed.
Threshold float64 `json:"threshold"`
// ProjectedFraction is the projection's fraction of the limit.
ProjectedFraction float64 `json:"projected_fraction"`
// Message is a sentence for a human.
Message string `json:"message"`
}
Warning is what the notifier sends.