Documentation
¶
Overview ¶
Package netquality measures download/upload capacity, idle and loaded latency, jitter and responsiveness (RPM) of a network path by running the IETF "Responsiveness under Working Conditions" test (draft-ietf-ippm-responsiveness) against a compatible server.
The library depends only on the standard library, emits no traffic besides the test itself, and bounds every run by duration, bytes and flow count before it starts. See Options for the limits and Result for how each number was obtained.
Typical use:
res, err := netquality.Run(ctx, netquality.Cloudflare, netquality.Options{})
if err != nil { ... }
fmt.Printf("%.0f RPM, %.1f Mbps down\n", res.Download.RPM, res.Download.ThroughputBPS/1e6)
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/korya/netquality"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
res, err := netquality.Run(ctx, netquality.WellKnown("nq.example.com"), netquality.Options{
MaxDuration: 10 * time.Second,
MaxBytes: 100 << 20,
MaxFlows: 8,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("idle %v, down %.1f Mbps / %.0f RPM (truncated=%v), up %.1f Mbps / %.0f RPM\n",
res.Idle.Median, res.Download.ThroughputBPS/1e6, res.Download.RPM, res.Download.Truncated,
res.Upload.ThroughputBPS/1e6, res.Upload.RPM)
}
Output:
Index ¶
- Constants
- Variables
- type Confidence
- type DirectionResult
- type Directions
- type Event
- type EventKind
- type IntervalWindow
- type LatencySample
- type LatencyStats
- type LoadedLatency
- type Options
- type ProxyInfo
- type ResolvedTarget
- type Result
- type ServerConfig
- type StabilityParams
- type StageMedians
- type Target
- type TruncationReason
Examples ¶
Constants ¶
const ( DefaultMaxDuration = 12 * time.Second // DefaultMaxBytes is 0: no byte cap by default. Time is the budget — // cost is at most rate × MaxDuration per direction — so a confident // result is reachable at any link speed. Callers on metered links set // MaxBytes explicitly. DefaultMaxBytes = 0 DefaultMaxFlows = 16 // DefaultUploadSendBuffer is the per-flow credit assumed for uploads: Go's // HTTP/2 stream window, which the transport fills before bytes reach the // wire (StabilityParams.SendBufferBytes). DefaultUploadSendBuffer = 4 << 20 DefaultIdleProbes = 5 // DefaultConfigTimeout bounds config discovery, which happens before the // per-direction budgets apply. DefaultConfigTimeout = 10 * time.Second )
Default safety limits and probe counts.
const ( ConfidenceLow = engine.ConfidenceLow ConfidenceMedium = engine.ConfidenceMedium ConfidenceHigh = engine.ConfidenceHigh )
Confidence levels.
const ResultSchemaVersion = 1
ResultSchemaVersion identifies the JSON shape of Result. It is bumped when a field is renamed, removed, retyped, or changes meaning; adding fields never bumps it. Each bump is noted in the CHANGELOG.
Variables ¶
var ( // Apple is Apple's public responsiveness server (used by macOS `networkQuality`). Apple = Target{ConfigURL: "https://mensura.cdn-apple.com/api/v1/gm/config"} // Cloudflare is Cloudflare's public responsiveness server. Its `mach` CLI // hardcodes the h3.speed.cloudflare.com URLs; this config document returns // the same URLs. Cloudflare = Target{ConfigURL: "https://aim.cloudflare.com/responsiveness/api/v1/config"} )
Well-known public targets.
var ErrInvalidConfig = errors.New("netquality: invalid server configuration")
ErrInvalidConfig is returned (wrapped) when a configuration document must be ignored per draft Section 8.1.
Functions ¶
This section is empty.
Types ¶
type Confidence ¶
type Confidence = engine.Confidence
Confidence is the draft's Section 5.4.1 confidence score.
type DirectionResult ¶
type DirectionResult struct {
Direction string `json:"direction"`
// ThroughputBPS is the moving-average goodput (bits/s) over the last MAD
// intervals at the end of the phase.
ThroughputBPS float64 `json:"throughput_bps"`
// PeakThroughputBPS is the highest single-interval goodput observed.
PeakThroughputBPS float64 `json:"peak_throughput_bps"`
// MeanThroughputBPS is Bytes*8/Duration over the whole phase, ramp-up
// included. It is the only throughput figure when no interval completed.
MeanThroughputBPS float64 `json:"mean_throughput_bps"`
// Bytes moved by this phase (payload of load flows plus probe bodies).
Bytes int64 `json:"bytes"`
// Duration is the wall time of the phase.
Duration time.Duration `json:"duration_ns"`
// Flows is the number of load-generating connections in use at the end.
Flows int `json:"flows"`
// Intervals is the number of stability intervals that completed.
Intervals int `json:"intervals"`
// ThroughputStable / ResponsivenessStable report whether the draft's stability
// criterion was met for each series.
ThroughputStable bool `json:"throughput_stable"`
ResponsivenessStable bool `json:"responsiveness_stable"`
// ThroughputConfidence / ResponsivenessConfidence per draft Section 5.4.1.
ThroughputConfidence Confidence `json:"throughput_confidence"`
ResponsivenessConfidence Confidence `json:"responsiveness_confidence"`
// Truncated is true when a limit ended the phase before both series were
// stable; Reason says which limit.
Truncated bool `json:"truncated"`
Reason TruncationReason `json:"reason,omitempty"`
// Loaded latency statistics gathered while the link was under load, over
// LoadedWindow: every interval since throughput became stable, or the
// last MovingAverageDistance intervals when it never did (LOAD-5). A
// probe series with samples in the phase but none in the window is
// omitted and explained in Warnings.
Loaded LoadedLatency `json:"loaded"`
LoadedWindow *IntervalWindow `json:"loaded_window,omitempty"`
// ThroughputLowerBoundBPS is a conservative figure that holds even when
// the estimate has not converged: the lowest goodput of the latest
// sustained window of measured intervals (LOAD-13). Absent when no such
// window formed (a run cut short). LowerBoundWindow says which window.
ThroughputLowerBoundBPS float64 `json:"throughput_lower_bound_bps,omitempty"`
LowerBoundWindow *IntervalWindow `json:"lower_bound_window,omitempty"`
// RPMUpperBound is the responsiveness over the lower-bound window. Queues
// may not have been full there, so the loaded RPM is at most this.
RPMUpperBound float64 `json:"rpm_upper_bound,omitempty"`
// RPM is the draft's Responsiveness score: the mean of ForeignRPM and SelfRPM
// (or ForeignRPM alone when self probes were unavailable).
RPM float64 `json:"rpm"`
// ForeignRPM = 60000 / mean(TM(tcp_f), TM(tls_f), TM(http_f)).
ForeignRPM float64 `json:"foreign_rpm"`
// SelfRPM = 60000 / TM(http_l). 0 when no self probes were possible.
SelfRPM float64 `json:"self_rpm"`
// HTTPVersion negotiated by this phase's flows.
HTTPVersion string `json:"http_version,omitempty"`
// FlowErrors counts load flows that ended with an error.
FlowErrors int `json:"flow_errors"`
}
DirectionResult is the outcome of one load phase.
type Directions ¶
type Directions int
Directions selects which load phases run. Phases always run sequentially.
const ( // Both runs download then upload (default). Both Directions = iota // Download runs only the download phase. Download // Upload runs only the upload phase. Upload )
func (Directions) MarshalJSON ¶
func (d Directions) MarshalJSON() ([]byte, error)
MarshalJSON keeps Directions readable in JSON output.
func (Directions) String ¶
func (d Directions) String() string
type Event ¶
type Event struct {
Time time.Time `json:"time"`
Kind EventKind `json:"kind"`
Phase string `json:"phase"` // "discover", "idle", "download", "upload", "done"
Direction string `json:"direction,omitempty"`
Message string `json:"message,omitempty"`
// Interval fields.
Interval int `json:"interval,omitempty"`
Flows int `json:"flows,omitempty"`
ThroughputBPS float64 `json:"throughput_bps,omitempty"`
Bytes int64 `json:"bytes,omitempty"`
RPM float64 `json:"rpm,omitempty"`
// Hold marks an interval no flow was added into (LOAD-13).
Hold bool `json:"hold,omitempty"`
// Probe fields.
ProbeKind string `json:"probe_kind,omitempty"` // "idle", "foreign", "self"
Latency time.Duration `json:"latency_ns,omitempty"`
}
Event is a progress notification delivered to RunWithEvents' sink. Sinks are called synchronously from the flow and probe goroutines, so calls can and do overlap: a sink must be safe for concurrent use and must return promptly (RES-8).
type EventKind ¶
type EventKind string
EventKind classifies an Event.
const ( // EventPhase marks a phase boundary; Phase names it, Message may add detail. EventPhase EventKind = "phase" // EventInterval reports the moving-average throughput after each interval. EventInterval EventKind = "interval" // EventProbe reports one latency sample. EventProbe EventKind = "probe" // EventFlow reports a flow being added (Flows is the new count). EventFlow EventKind = "flow" // EventWarning carries a warning as it is discovered. EventWarning EventKind = "warning" )
type IntervalWindow ¶ added in v0.4.0
type IntervalWindow struct {
Start time.Duration `json:"start_ns"`
Duration time.Duration `json:"duration_ns"`
Intervals int `json:"intervals"`
}
IntervalWindow locates the intervals a figure was computed over, as an offset from the phase start and a length, in nominal intervals.
type LatencySample ¶
type LatencySample = engine.LatencySample
LatencySample is one probe measurement.
type LatencyStats ¶
type LatencyStats = engine.LatencyStats
LatencyStats summarises a set of latency samples (see engine.LatencyStats).
type LoadedLatency ¶
type LoadedLatency struct {
// Foreign: fresh-connection probes (TCP+TLS+HTTP, like idle probes).
Foreign *LatencyStats `json:"foreign,omitempty"`
// Self: probes multiplexed on load-generating HTTP/2 connections.
Self *LatencyStats `json:"self,omitempty"`
// Combined: all probes, with foreign probes counted by their HTTP time
// (http_f) so both kinds measure "request to full response".
Combined *LatencyStats `json:"combined,omitempty"`
}
LoadedLatency groups latency samples taken under load.
type Options ¶
type Options struct {
// MaxDuration bounds each direction's load phase (default 12s). A phase that
// has not stabilised when the bound hits is reported as truncated.
MaxDuration time.Duration
// MaxBytes, if > 0, bounds the bytes moved by each direction's load
// phase, probes included; hitting it truncates the phase with
// reason=bytes_cap. 0 (the default) means no byte cap: MaxDuration alone
// bounds the run. Set it on metered links.
MaxBytes int64
// MaxFlows caps the number of concurrent load-generating connections
// (default 16, draft MNP).
MaxFlows int
// Directions selects the phases to run (default Both).
Directions Directions
// IdleProbes is the number of fresh-connection probes for idle latency
// (default 5). 0 uses the default; negative skips idle measurement.
IdleProbes int
// Stability holds the draft's algorithm parameters; zero fields use
// defaults. SendBufferBytes applies to upload phases only and defaults to
// DefaultUploadSendBuffer there; set it negative to disable.
Stability StabilityParams
// HTTPClient supplies the base transport (proxy, TLS config, dialer). Only
// its Transport is used; each load flow gets its own clone so flows do not
// share a connection. If the Transport is not an *http.Transport it is used
// as-is and flows may share connections (a warning is recorded).
HTTPClient *http.Client
// Logger receives debug logs; nil discards them.
Logger *slog.Logger
// ConfigTimeout bounds config discovery (default 10s).
ConfigTimeout time.Duration
// Header is added to every request the test sends (config fetch, probes,
// load flows): credentials for a protected server, for example
// Authorization: Bearer <token>. Keys set here override the defaults.
// It is read from every flow and probe goroutine, so it must not be
// mutated while Run is in flight.
Header http.Header
// contains filtered or unexported fields
}
Options configures a test run. The zero value is valid and uses the defaults.
type ProxyInfo ¶ added in v0.2.0
type ProxyInfo struct {
// Explicit is true when the HTTP transport routed requests via a proxy.
Explicit bool `json:"explicit"`
// URL of the explicit proxy, credentials removed.
URL string `json:"url,omitempty"`
// TLSInterception is true when the server certificate chain verified but
// the leaf is not publicly trusted (no Certificate Transparency SCTs): a
// TLS-inspecting proxy or a private CA re-issued it.
TLSInterception bool `json:"tls_interception"`
// Issuer of the intercepted leaf certificate.
Issuer string `json:"issuer,omitempty"`
// Reason is a human-readable explanation of the detection.
Reason string `json:"reason,omitempty"`
}
ProxyInfo describes a detected proxy. Explicit and TLSInterception may both be set.
type ResolvedTarget ¶
type ResolvedTarget struct {
ConfigURL string `json:"config_url"`
Host string `json:"host"`
TestEndpoint string `json:"test_endpoint,omitempty"`
ResolvedIPs []string `json:"resolved_ips,omitempty"`
// LocalIPs are the source addresses the test's connections went out on,
// deduplicated, ports stripped, IPv6 zone IDs kept. They identify the
// interface/network a stored result was taken on. Under an explicit proxy
// this is the address used toward the proxy. Empty with a custom
// RoundTripper or DialTLSContext, which bypass the library's dialer.
LocalIPs []string `json:"local_ips,omitempty"`
// HTTPVersion is the protocol negotiated by load flows ("HTTP/2.0", "HTTP/1.1").
HTTPVersion string `json:"http_version,omitempty"`
Config ServerConfig `json:"config"`
// Proxy is set when the measured path involves a proxy: an explicit one
// from the transport's Proxy function, or TLS interception inferred from
// the certificate chain. Nil when nothing was detected. Numbers are still
// valid measurements, but of the client→proxy leg.
Proxy *ProxyInfo `json:"proxy,omitempty"`
}
ResolvedTarget describes the server actually used.
type Result ¶
type Result struct {
// SchemaVersion is ResultSchemaVersion at the time the result was produced.
SchemaVersion int `json:"schema_version"`
Target ResolvedTarget `json:"target"`
StartedAt time.Time `json:"started_at"`
Duration time.Duration `json:"duration_ns"`
// Idle is the idle latency measured on fresh connections before any load.
// Nil when IdleProbes < 0 or no probe succeeded.
Idle *LatencyStats `json:"idle,omitempty"`
// Download / Upload are nil when the direction was not requested.
Download *DirectionResult `json:"download,omitempty"`
Upload *DirectionResult `json:"upload,omitempty"`
// Cancelled is true when the context was cancelled; the result is partial.
Cancelled bool `json:"cancelled"`
Warnings []string `json:"warnings,omitempty"`
}
Result is the outcome of a run. It serialises to JSON with stable snake_case names; the CLI's --json output is exactly this struct.
func Run ¶
Run executes the responsiveness test against t and returns the Result.
If ctx is cancelled mid-run, Run returns the partial Result (Cancelled=true) together with ctx.Err(). If a load phase fails before completing an interval, Run returns the partial Result (that direction flagged reason=flow_error, everything measured before it intact) together with the error. Only discovery failures return a nil Result.
type ServerConfig ¶
type ServerConfig struct {
Version int `json:"version"`
TestEndpoint string `json:"test_endpoint,omitempty"`
SmallDownloadURL string `json:"small_download_url"`
LargeDownloadURL string `json:"large_download_url"`
UploadURL string `json:"upload_url"`
}
ServerConfig is the parsed JSON configuration document served by a target.
func ParseServerConfig ¶
func ParseServerConfig(data []byte) (*ServerConfig, error)
ParseServerConfig parses a configuration document. It accepts both the draft field names (small_download_url, large_download_url, upload_url) and the older Apple/Cloudflare *_https_* names, preferring the https-prefixed ones when both are present. Unknown fields are ignored. Duplicate keys, a missing mandatory field, a version other than 1, or mismatched hosts make the document invalid.
type StabilityParams ¶
type StabilityParams = engine.StabilityParams
StabilityParams are the draft's algorithm parameters (Section 5.2); see the engine package for field documentation.
func DefaultStabilityParams ¶
func DefaultStabilityParams() StabilityParams
DefaultStabilityParams returns the draft-09 defaults, except Interval, which is 1s instead of 5s so that a phase can stabilise within the 12s MaxDuration budget (see README "Deviations").
type StageMedians ¶
type StageMedians = engine.StageMedians
StageMedians holds per-stage medians from net/http/httptrace.
type Target ¶
type Target struct {
// ConfigURL is the URL of the JSON configuration document, e.g.
// https://example.com/.well-known/nq or a vendor-specific path.
ConfigURL string `json:"config_url"`
}
Target identifies a responsiveness server by its configuration URL.
type TruncationReason ¶
type TruncationReason string
TruncationReason says why a phase ended before stabilising.
const ( ReasonNone TruncationReason = "" ReasonBytesCap TruncationReason = "bytes_cap" ReasonDurationCap TruncationReason = "duration_cap" ReasonCancelled TruncationReason = "cancelled" ReasonFlowError TruncationReason = "flow_error" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
nq
command
Command nq measures throughput, latency and responsiveness (RPM) against a responsiveness test server.
|
Command nq measures throughput, latency and responsiveness (RPM) against a responsiveness test server. |
|
nqserver
command
Command nqserver is a minimal, self-hostable responsiveness test server.
|
Command nqserver is a minimal, self-hostable responsiveness test server. |
|
internal
|
|
|
buildinfo
Package buildinfo carries version metadata injected at build time via -ldflags "-X github.com/korya/netquality/internal/buildinfo.Version=...".
|
Package buildinfo carries version metadata injected at build time via -ldflags "-X github.com/korya/netquality/internal/buildinfo.Version=...". |
|
engine
Package engine holds the pure, clock-free measurement logic of netquality: latency statistics, the draft's stability criterion, and (after extraction) the per-interval decisions.
|
Package engine holds the pure, clock-free measurement logic of netquality: latency statistics, the draft's stability criterion, and (after extraction) the per-interval decisions. |
|
linksim
Package linksim is a deliberately simple fluid model of a network path used to drive the measurement engine in tests.
|
Package linksim is a deliberately simple fluid model of a network path used to drive the measurement engine in tests. |
|
Package server implements a minimal responsiveness test server following draft-ietf-ippm-responsiveness Section 7 and network-quality/server's SERVER_SPEC.md.
|
Package server implements a minimal responsiveness test server following draft-ietf-ippm-responsiveness Section 7 and network-quality/server's SERVER_SPEC.md. |