Documentation
¶
Overview ¶
Package netstats reports network observations for a benchmark run: a TCP socket summary observed at each boundary and per-interface throughput, packet, error and drop counters accumulated between them.
Everything here is display-only. No value produced by this package feeds an advisor threshold, deliberately: interval averages cannot see instantaneous saturation, /proc/net/sockstat cannot tell an inbound TIME_WAIT socket from an outbound one, and a single NIC's MTU says nothing about whether the whole path agrees on it. Judging those is left to the reader.
Both filesystems are injected separately because procfs and sysfs are different mounts: a collector rooted at /proc cannot reach /sys/class/net/<if>/speed. Injection also makes every parser testable with fstest.MapFS on a non-Linux development host.
Index ¶
- Constants
- Variables
- type Collector
- func (c *Collector) CaptureBaseline(ctx context.Context, runID string, ep runctl.Epoch) (runctl.SampleResult, error)
- func (c *Collector) CaptureFinal(ctx context.Context, runID string, ep runctl.Epoch) (runctl.SampleResult, error)
- func (c *Collector) Collect(base, final runctl.BaselineHandle) (any, error)
- func (c *Collector) Name() string
- func (c *Collector) Release(h runctl.BaselineHandle)
- type HealthNote
- type Interface
- type NetworkStats
- type TCPSummary
Constants ¶
const ( // HealthSysfsUnreadable reports link attributes that were present but // unusable (non-numeric or out of range). A missing file is not reported: // virtual NICs, network namespaces and older kernels legitimately lack // these attributes, so reporting absence would fire on every run. HealthSysfsUnreadable = "netstats-sysfs-unreadable" // HealthLinkChanged reports a link attribute whose value differed between // the two boundaries. The final value is displayed; the note exists so the // reader knows the displayed value did not hold for the whole interval. HealthLinkChanged = "netstats-link-changed" // HealthCounterRewind reports an interface whose counters went backwards, // which happens when a NIC is reset or replaced mid-run. Its rates are // suppressed rather than shown as an implausible spike. HealthCounterRewind = "netstats-counter-rewind" // HealthProcUnreadable reports a /proc/net file that could not be parsed. // Collection continues with whatever did parse (fail-open): a broken // sockstat must not cost the reader the interface table. HealthProcUnreadable = "netstats-proc-unreadable" )
Health keys this package reports. The set is fixed at four so a run's health stays readable on a host with dozens of virtual interfaces; a new condition reuses one of these keys with a different detail rather than adding a key.
const CodeCounterRewind = "counter-rewind"
CodeCounterRewind marks an interface whose counters rewound during the interval. Its deltas and rates are omitted; the row is kept so the reader still sees that the interface existed.
Variables ¶
Default is the collector isutools registers. It reads the real /proc and /sys; on a host where either is absent, capture fails and the section is simply missing from the run.
Functions ¶
This section is empty.
Types ¶
type Collector ¶
type Collector struct {
// contains filtered or unexported fields
}
Collector implements runctl.BaselineCollector for network observations.
procFS and sysFS are separate filesystems on purpose: /proc/net/dev and /sys/class/net live on different mounts, and a single injected root cannot reach both.
func New ¶
New returns a collector reading network state from the given filesystems. Pass os.DirFS("/proc") and os.DirFS("/sys") in production and fstest.MapFS in tests. A nil filesystem is tolerated and surfaces as a degraded section rather than a panic, because measurement may not break the application.
func (*Collector) CaptureBaseline ¶
func (c *Collector) CaptureBaseline(ctx context.Context, runID string, ep runctl.Epoch) (runctl.SampleResult, error)
CaptureBaseline samples the opening boundary.
func (*Collector) CaptureFinal ¶
func (c *Collector) CaptureFinal(ctx context.Context, runID string, ep runctl.Epoch) (runctl.SampleResult, error)
CaptureFinal samples the closing boundary.
func (*Collector) Collect ¶
func (c *Collector) Collect(base, final runctl.BaselineHandle) (any, error)
Collect derives the interval from two frozen samples.
It reads nothing but base.Sample(), final.Sample() and the two SampledAt timestamps: no /proc, no /sys, no clock, no field of the collector. That is what makes a run's numbers reproducible — load applied after the closing boundary cannot leak into an interval that was already fixed.
func (*Collector) Release ¶
func (c *Collector) Release(h runctl.BaselineHandle)
Release drops the collector's reference to a handle's sample. It is idempotent: releasing twice, or releasing a handle this collector never issued, is a no-op.
type HealthNote ¶
type HealthNote struct {
Key string `json:"key"`
// Detail enumerates up to maxHealthDetails offending items, comma
// separated, in a stable order.
Detail string `json:"detail,omitempty"`
}
HealthNote is one degradation, aggregated per key. It is carried in the section rather than pushed into a registry because Collect must be pure: the wiring layer copies these into the process-wide health registry.
type Interface ¶
type Interface struct {
Name string `json:"name"`
// Appeared marks an interface absent from the opening sample. Its
// cumulative counters are not an interval delta, so no delta and no rate
// are reported for it.
Appeared bool `json:"appeared,omitempty"`
// Code is a stable machine-readable reason the interval values are absent;
// empty means the row is complete.
Code string `json:"code,omitempty"`
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
RxPackets uint64 `json:"rx_packets"`
TxPackets uint64 `json:"tx_packets"`
RxErrors uint64 `json:"rx_errors"`
TxErrors uint64 `json:"tx_errors"`
RxDropped uint64 `json:"rx_dropped"`
TxDropped uint64 `json:"tx_dropped"`
// RxMbitPerSec and TxMbitPerSec are Mbit/s so they can be compared with
// SpeedMbit directly. They are nil, not zero, when no rate can be derived
// (appeared interface, counter rewind, non-positive interval): zero would
// read as "idle" when the truth is "unknown".
RxMbitPerSec *float64 `json:"rx_mbit_per_s,omitempty"`
TxMbitPerSec *float64 `json:"tx_mbit_per_s,omitempty"`
// SpeedMbit is /sys/class/net/<if>/speed. Zero means "not accepted" and
// disappears from JSON; zero is not a real link speed, so it is a safe
// sentinel.
SpeedMbit int64 `json:"speed_mbit,omitempty"`
// MTU is /sys/class/net/<if>/mtu, displayed verbatim. No judgement is
// attached to it: a jumbo frame only helps when every hop agrees, which a
// single NIC's attribute cannot show.
MTU int64 `json:"mtu,omitempty"`
}
Interface is one NIC's interval counters. The loopback device is excluded by default: its traffic is process-to-process and would dominate the table without describing the network.
type NetworkStats ¶
type NetworkStats struct {
TCP TCPSummary `json:"tcp"`
Interfaces []Interface `json:"interfaces"`
Health []HealthNote `json:"health,omitempty"`
}
NetworkStats is the network section of a run's snapshot.
type TCPSummary ¶
type TCPSummary struct {
InUse int64 `json:"in_use"`
// TimeWait counts sockets in TIME_WAIT. It distinguishes neither direction
// nor local port ownership, so it cannot by itself demonstrate ephemeral
// port exhaustion.
TimeWait int64 `json:"time_wait"`
Orphan int64 `json:"orphan"`
InUse6 int64 `json:"in_use6"`
}
TCPSummary is a point observation of /proc/net/sockstat and /proc/net/sockstat6 taken at the closing boundary. These are gauges, not counters, so they are never turned into a delta.