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 ¶
Examples ¶
Constants ¶
const ( DefaultMaxDuration = 12 * time.Second DefaultMaxBytes = 250 << 20 // 250 MiB DefaultMaxFlows = 16 DefaultIdleProbes = 5 // DefaultConfigTimeout bounds config discovery, which happens before the // per-direction budgets apply. DefaultConfigTimeout = 10 * time.Second )
Default safety limits and probe counts.
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 string
Confidence is the draft's Section 5.4.1 confidence score.
const ( // ConfidenceLow: fewer than MAD intervals ran; moving average is partial. ConfidenceLow Confidence = "low" // ConfidenceMedium: at least MAD intervals ran but stability was not reached. ConfidenceMedium Confidence = "medium" // ConfidenceHigh: stability was reached. ConfidenceHigh Confidence = "high" )
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.
Loaded LoadedLatency `json:"loaded"`
// 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"`
// 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 test goroutines and must return promptly.
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 LatencySample ¶
type LatencySample struct {
Total time.Duration // full wall time of the probe (foreign: incl. connect+TLS; self: request only)
DNS time.Duration
Connect time.Duration // TCP handshake
TLS time.Duration // raw TLS handshake time
TLSRTTs int // number of round trips the negotiated TLS version needs (0 = no TLS)
TTFB time.Duration // request sent -> first response byte
HTTP time.Duration // request sent -> full body read (http_f / http_l in the draft)
Staged bool // true when DNS/Connect/TLS/TTFB are populated (fresh connection)
}
LatencySample is one probe measurement.
type LatencyStats ¶
type LatencyStats struct {
Samples int `json:"samples"`
Min time.Duration `json:"min_ns"`
Median time.Duration `json:"median_ns"`
Mean time.Duration `json:"mean_ns"`
P95 time.Duration `json:"p95_ns"`
Max time.Duration `json:"max_ns"`
Jitter time.Duration `json:"jitter_ns"`
Stages *StageMedians `json:"stages,omitempty"`
}
LatencyStats summarises a set of latency samples.
Jitter is the mean absolute deviation of the samples from their mean. Stages holds per-stage medians (dns, connect, tls, ttfb) when the samples carry stage timings (foreign probes and idle probes do; self probes do not).
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 bounds the bytes moved by each direction's load phase, probes
// included (default 250 MiB). Hitting it truncates the phase.
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.
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
// contains filtered or unexported fields
}
Options configures a test run. The zero value is valid and uses the defaults.
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"`
// HTTPVersion is the protocol negotiated by load flows ("HTTP/2.0", "HTTP/1.1").
HTTPVersion string `json:"http_version,omitempty"`
Config ServerConfig `json:"config"`
}
ResolvedTarget describes the server actually used.
type Result ¶
type Result struct {
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.
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 struct {
// MovingAverageDistance (MAD): number of intervals in the moving average.
MovingAverageDistance int
// Interval (ID): how often stability is re-evaluated and flows are added.
Interval time.Duration
// TrimmedMeanPercent (TMP): single-sided trimmed-mean percentile for latency.
TrimmedMeanPercent float64
// StdDevTolerance (SDT): stability is declared when the standard deviation of
// the last MAD moving averages is below this fraction of the current one.
StdDevTolerance float64
// InitialFlows (INP) and FlowIncrement (INC).
InitialFlows int
FlowIncrement int
// MaxProbesPerSecond (MPS) and ProbeCapacityPercent (PTC).
MaxProbesPerSecond int
ProbeCapacityPercent float64
}
StabilityParams are the draft's algorithm parameters (Section 5.2).
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 struct {
DNS time.Duration `json:"dns_ns"`
Connect time.Duration `json:"connect_ns"`
TLS time.Duration `json:"tls_ns"`
TLSPerRTT time.Duration `json:"tls_per_rtt_ns"`
TTFB time.Duration `json:"ttfb_ns"`
}
StageMedians holds median per-stage timings from net/http/httptrace. TLS is the raw handshake time; TLSPerRTT is the same value normalised to a single round trip (TLS 1.3 = 1 RTT, TLS 1.2 = 2 RTTs) as the draft requires.
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=...". |
|
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. |