Documentation
¶
Overview ¶
Package netstats reads per-sandbox network byte counters from /proc/<pid>/net/dev. The procfs `net/` subtree is per-netns: opening it through a process PID joins that PID's network namespace, so a host-side reader sees the container's interfaces (eth0 etc.) and their cumulative rx/tx_bytes from the container's perspective.
rx/tx semantics here are the *container's*: rx_bytes = bytes the container received (= ingress to the sandbox = NetworkBytesIn); tx_bytes = bytes the container sent (= egress from the sandbox = NetworkBytesOut). This matches what an operator would intuitively bill against.
We deliberately ignore the host-side veth (the peer interface visible in the host netns). That path is correct too, but discovering it requires matching iflink/ifindex pairs — measurably more code than just reading /proc/<pid>/net/dev for a number that is already per-netns by construction.
The "lo" interface is skipped: pure intra-container loopback shouldn't count against an external network quota.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotRunning = errors.New("netstats: container process not present")
ErrNotRunning is returned when the procfs entry for the requested PID is gone — typically because the container exited between the caller obtaining the PID and the read. Callers should treat it as "no sample this tick" rather than a hard error, since reconcile/event paths will pick up the state change shortly.
Functions ¶
This section is empty.
Types ¶
type Counters ¶
type Counters struct {
BytesIn int64 // container rx (ingress to sandbox)
BytesOut int64 // container tx (egress from sandbox)
ActiveTCP bool // at least one established TCP socket in the netns
}
Counters is one snapshot of per-container byte counters. Cumulative since container start — the poller computes deltas itself so it can survive container restarts without double-counting.
type PIDLookup ¶
PIDLookup resolves a sandbox container reference to the host PID of its init process. Implemented by *docker.Client.ContainerPID.
type Poller ¶
type Poller struct {
// contains filtered or unexported fields
}
Poller drives the netstats loop. Construct via NewPoller and call Start once; call Stop on shutdown to release the goroutine cleanly.
func NewPoller ¶
func NewPoller(logger *slog.Logger, reader *Reader, lookup PIDLookup, lister SandboxLister, sink SampleSink, interval time.Duration) *Poller
NewPoller wires the dependencies together. interval <= 0 is rejected at Start; the caller controls the cadence so deployments with thousands of sandboxes can dial it down.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader reads /proc/<pid>/net/dev. The fs.FS indirection exists so tests can supply an in-memory fixture — the production caller passes nil to use the real /proc.
func NewReaderFS ¶
NewReaderFS builds a Reader against an injected filesystem rooted such that "<pid>/net/dev" resolves correctly. Used by tests.
type Sample ¶
type Sample struct {
SandboxID string
BytesIn int64
BytesOut int64
ActiveTCP bool
SampledAt time.Time
}
Sample is the per-sandbox delta the poller produces each tick. BytesIn/BytesOut are non-negative deltas since the last successful sample for the same (sandbox, PID) pair. The first observation of any new PID produces a Sample with zero deltas — the cumulative reading at that point is stored as the baseline so subsequent ticks compute meaningful diffs. This makes the poller restart-safe: container restart → new PID → fresh baseline → no spurious giant delta from the cumulative counter resetting to a small number. ActiveTCP is the current socket-state observation, not a delta, so the first sample can still carry "this sandbox has an established TCP connection" even while byte deltas are zero.
type SampleSink ¶
SampleSink receives per-tick deltas. Implementations typically call into the store to add deltas to the cumulative counters and check quotas. Implementations MUST be safe to call from the poller goroutine.
type SandboxLister ¶
SandboxLister returns the sandboxes that should be polled this tick. Returning a fresh slice on each call is fine — the poller doesn't retain it. Items must include both the sandbox ID (for callbacks) and the container reference (for the PID lookup); when a container is not yet running the lister can omit it or include it with empty containerRef.