metrics

package
v0.1.11 Latest Latest
Warning

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

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

Documentation

Overview

Package metrics measures what a run cost: how busy the processors were and what the NIC's own counters say.

It is here because the interesting question about a packet path is not how fast it can go but what it spends to get there. A rate on its own says nothing without the processors that produced it, and the driver's own counters say nothing about what actually reached the wire.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CPUHz

func CPUHz() float64

CPUHz is the processor's nominal speed in hertz, for turning busy time into an estimate of cycles per packet.

It is nominal on purpose. What is wanted is a figure comparable between two runs on the same machine, and a measured frequency would move with turbo and with how busy the machine is, which is exactly what is being compared.

Types

type CPUSample

type CPUSample struct {
	When time.Time

	// PerCPU is indexed the same way as the CPU list the sampler was given.
	PerCPU []CPUTime

	// All is the sum over those processors.
	All CPUTime

	// Process is how much processor time this process itself had used, in
	// seconds, across all its threads.
	Process float64
}

CPUSample is one reading of the processors a run is using and of the process itself.

func Delta

func Delta(a, b CPUSample) CPUSample

Delta is what happened between two samples.

func (CPUSample) Cores

func (c CPUSample) Cores() float64

Cores is how many whole processors' worth of time was used: 2.0 means two processors were busy for the whole interval.

func (CPUSample) CyclesPerPacket

func (c CPUSample) CyclesPerPacket(packets uint64, hz float64) float64

CyclesPerPacket estimates what each packet cost, given the processor speed in hertz. It is an estimate: without a performance counter to read, the only thing available is time multiplied by a nominal frequency, which ignores turbo and frequency scaling.

type CPUSampler

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

CPUSampler reads processor time for a fixed set of processors.

Which processors matters. A packet path that bypasses the kernel spends its time in this process, and one that goes through the kernel spends much of it in soft interrupt handlers that belong to no process at all. Counting only the process would flatter the second; counting whole processors counts both.

func NewCPUSampler

func NewCPUSampler(cpus []int) *CPUSampler

NewCPUSampler watches the given processors. An empty list watches all of them.

func (*CPUSampler) Sample

func (s *CPUSampler) Sample() (CPUSample, error)

Sample reads the counters now.

type CPUTime

type CPUTime struct {
	User    float64
	System  float64
	SoftIRQ float64 // where a kernel network stack does its work
	IRQ     float64
	Idle    float64
	Steal   float64
	Total   float64
}

CPUTime is how a processor spent an interval, in seconds.

func (CPUTime) Busy

func (c CPUTime) Busy() float64

Busy is everything that was not idle.

func (CPUTime) Utilisation

func (c CPUTime) Utilisation() float64

Utilisation is the fraction of the interval that was not idle.

type ENAAllowances added in v0.1.11

type ENAAllowances struct {
	PPSExceeded        uint64
	BwInExceeded       uint64
	BwOutExceeded      uint64
	ConntrackExceeded  uint64
	LinklocalExceeded  uint64
	ConntrackAvailable uint64

	// Present is false on anything that is not an ENA, which is every NIC
	// outside EC2 and an ENA behind a driver too old to publish them.
	Present bool
}

ENAAllowances are the counters EC2's Nitro card publishes to say which of an instance's network allowances it is shaping traffic against. They are the difference between "the rate flattened here" and "the fabric shaped us here, and this is which quota did it", which is the only way to tell an instance's real limit from the generator running out of steam.

Every one is cumulative since the driver last reset, so only deltas mean anything, and a counter that goes backwards means the driver reset underneath the measurement and the window is void.

PPSExceeded is the one the packet-per-second work is about, and AWS documents it as bidirectional and per instance: receive and transmit draw on one budget. Note that it counts packets "queued or dropped", so a nonzero value alone only proves a microburst grazed the shaper -- it takes a shortfall of delivered against offered to show a real limit.

func Allowances added in v0.1.11

func Allowances(raw map[string]uint64) ENAAllowances

Allowances picks the ENA allowance counters out of any set of named counters: NICCounters.Raw from ethtool, or the driver's own xstats read straight from a device this process owns.

func (ENAAllowances) String added in v0.1.11

func (a ENAAllowances) String() string

String renders a delta as one line for a periodic report, naming only the allowances that actually fired so that a quiet run stays quiet.

func (ENAAllowances) Sub added in v0.1.11

Sub returns the change between two readings. ConntrackAvailable is a level rather than a running total, so it is carried across as the later reading instead of a difference.

func (ENAAllowances) WentBackwards added in v0.1.11

func (a ENAAllowances) WentBackwards(o ENAAllowances) bool

WentBackwards reports whether any cumulative counter fell, which means the driver reset mid-measurement and the deltas are meaningless.

type NICCounters

type NICCounters struct {
	// TxPackets and TxBytes are what the port put on the wire, and RxPackets
	// and RxBytes what it took off.
	TxPackets uint64
	TxBytes   uint64
	RxPackets uint64
	RxBytes   uint64

	// TxErrors and TxDiscards are frames the port would not send, and
	// RxDiscards frames it could not take. On the receiving side of a fast
	// test this is usually where the missing packets are.
	TxErrors   uint64
	TxDiscards uint64
	RxDiscards uint64

	// RxOutOfBuffer is receive work the NIC had nowhere to put, which means
	// the receive queue was not refilled fast enough.
	RxOutOfBuffer uint64

	// Raw is every counter that was read, for the ones not named above.
	Raw map[string]uint64
}

NICCounters are the NIC's own counts of what crossed the wire.

They are the only honest answer to "did the packets actually leave". A driver counts what it handed to the hardware, which is not the same thing: a frame the NIC rejects, or drops for want of buffer, is counted as sent by everyone except the NIC.

func ReadNIC

func ReadNIC(iface string) (NICCounters, error)

ReadNIC reads a port's counters.

The physical counters this wants are not in sysfs, because sysfs reports what the kernel's own driver saw and this packet path does not go through it. They come from ethtool, which asks the hardware.

func (NICCounters) HasPhysicalCounters

func (c NICCounters) HasPhysicalCounters() bool

HasPhysicalCounters reports whether the port exposed the counters that describe the wire rather than the kernel's view of it.

func (NICCounters) Sub

Sub is the difference between two readings.

Jump to

Keyboard shortcuts

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