Documentation
¶
Overview ¶
Package unresolved implements the UnresolvedBuffer: the late-binding holding area for flows whose VM-side MAC is not yet in the metadata map. Without it, every unknown-MAC flow would take its own GlobalState key and grow that map without bound under a Neutron outage or a flood of uncatalogued MACs.
The accounting rule that makes it safe: on eviction the accumulated total is folded to a synthetic "unknown" key AND the flow's kernel telemetry_map entry is deleted. Deleting is what makes counting the whole cumulative safe — a flow that reappears starts from a fresh kernel value, so folded bytes are never folded twice.
The scrape goroutine is the single owner, so there is no internal locking.
docs/architecture/data-structures.md#userspace-structures
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Buffer ¶
type Buffer struct {
// contains filtered or unexported fields
}
Buffer holds unknown-MAC flows until their MAC resolves (a later sprint), their TTL elapses, or the cap evicts them. It is driven solely by the scraper goroutine — Capture and Sweep are not safe for concurrent use, and need none, since the depth gauge is Set from the same goroutine.
func (*Buffer) Capture ¶
func (b *Buffer) Capture(key bpf.FlowKey, raw bpf.FlowMetrics)
Capture integrates one drained reading for an unknown-MAC flow into its buffer entry. First sight seeds the cumulative from the current kernel value; later sightings add the delta. Capturing touches the entry's LRU position. A first sight that pushes the buffer over its cap triggers an LRU eviction of the oldest entry.
func (*Buffer) Resolve ¶
Resolve hands a buffered flow to its real tenant once the MAC becomes known, seeding both the accumulated total and the kernel baseline so the next delta continues without re-counting. Unlike an eviction it does not fold to "unknown" and does not reset the kernel entry — the flow lives on and the scraper keeps integrating it.
func (*Buffer) Sweep ¶
Sweep folds out entries whose TTL has elapsed, then publishes the buffer depth. With force set it folds every entry regardless of TTL — the graceful-shutdown drain, so no unknown bytes are lost to the final WAL flush. Force does NOT delete kernel entries: the maps are about to be replaced on the next boot, so resetting them is pointless, and skipping ~10k deletes keeps shutdown inside its budget. Run once per scrape tick by the Classifier.
type Classifier ¶
type Classifier struct {
// contains filtered or unexported fields
}
Classifier routes each drained reading by whether its VM-side MAC is known: known flows (including lingering ghosts, which are still in the metadata map until the GC sweeps them — so a dying VM's tail traffic never lands in the buffer) go straight to GlobalState; unknown flows divert into the Buffer. It satisfies the scraper's FlowSink seam.
Ghost precedence over the UnresolvedBuffer is exactly this Lookup-first ordering: a ghosted MAC is a hit, so Absorb takes the GlobalState branch and never the buffer. The ordering is pinned by a test that fails if the branches are inverted.
Lingering Ghost: docs/architecture/data-structures.md#lingering-ghost
func NewClassifier ¶
func NewClassifier(st *state.GlobalState, meta *metadata.ShardedMetadataMap, buf *Buffer) *Classifier
NewClassifier wires the routing seam over the agent's GlobalState, metadata map, and unresolved buffer.
func (*Classifier) Absorb ¶
func (c *Classifier) Absorb(key bpf.FlowKey, raw bpf.FlowMetrics)
Absorb integrates one drained reading. Known VM-MAC → GlobalState delta math; unknown → buffer.
Late binding: a MAC that was unknown when the flow was first buffered can become known between scrapes (a Neutron reconcile or Kafka port.created inserts it into the metadata map). On the first drain after that, Buffer.Resolve hands the buffered bytes to the right tenant and seeds the delta baseline before [GlobalState.ApplyDelta] integrates the current reading — so the same bytes are never counted twice and the pre-resolve traffic is attributed correctly instead of folding to "unknown" at TTL. Resolve is a no-op when nothing was buffered for the key (the common steady-state path).
func (*Classifier) Sweep ¶
func (c *Classifier) Sweep(force bool)
Sweep ages out buffered entries (force folds the whole buffer — the graceful-shutdown drain). Called once per scrape tick after the Absorb loop.
type FlowEvictor ¶
FlowEvictor deletes a flow entry from the kernel telemetry_map — the consumer-defined seam the buffer resets a flow's kernel counter through on eviction. The agent wires a *ebpf.Map adapter (the same one the pressure-relief GC uses); tests wire a recording mock. Delete must be idempotent on a key already gone.
type Metrics ¶
type Metrics struct {
// contains filtered or unexported fields
}
Metrics holds the Prometheus instruments for the UnresolvedBuffer. The instruments are:
- lachesis_unresolved_buffer_depth gauge
- lachesis_unresolved_buffer_evictions_total{reason} counter
- lachesis_unresolved_resolved_total counter
depth tracks live buffer occupancy (an SLO panic threshold sits near the cap); evictions counts entries folded to "unknown", by reason; resolved counts late-binding successes — it stays at zero until the Kafka consumer can make a buffered MAC newly known (a later sprint), and is declared now so the series exists from the start.
Metric catalogue: docs/architecture/metrics.md
func NewMetrics ¶
func NewMetrics() *Metrics
NewMetrics constructs the bundle with both eviction reasons seeded at zero so lachesis_unresolved_buffer_evictions_total{reason="lru"} and {reason="expired"} both exist before either path first fires.
func (*Metrics) Collectors ¶
func (m *Metrics) Collectors() []prometheus.Collector
Collectors returns the underlying prometheus.Collector values for registration by the agent.
func (*Metrics) RecordExpiredEvictions ¶
RecordExpiredEvictions adds n entries dropped because their TTL elapsed (the per-sweep count).
func (*Metrics) RecordLRUEviction ¶
func (m *Metrics) RecordLRUEviction()
RecordLRUEviction counts one entry evicted to stay under the cap.
func (*Metrics) RecordResolved ¶
func (m *Metrics) RecordResolved()
RecordResolved counts one buffered flow whose MAC became known and was handed off to the right tenant — call once per late-binding success.
type Options ¶
type Options struct {
State *state.GlobalState
Evictor FlowEvictor
Metrics *Metrics
Now func() time.Time
// Tunables supplies the live buffer bounds (cap, TTL), read at
// each admission / expiry check — a cap shrink applies through the
// normal LRU eviction on the next admission. REQUIRED; unit tests
// construct a store with the bounds they exercise.
Tunables *tunables.Store
}
Options bundles the inputs to NewBuffer. State, Evictor, and Metrics are required. Cap (≤0 → [defaultCap]) and TTL (≤0 → [defaultTTL]) override the production bounds for tests. Now (nil → time.Now) injects a clock.