common

package
v0.0.0-...-c77258e Latest Latest
Warning

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

Go to latest
Published: Mar 11, 2026 License: MIT Imports: 21 Imported by: 0

README

Common Library

Shared library providing CLI flags, data generation, statistics, and utilities used by all SRT tools in the contrib directory.

Overview

The common package provides:

  • Unified CLI flags for all SRT configuration options
  • Rate-limited data generation for testing
  • Throughput statistics display
  • Prometheus metrics endpoints (HTTP and Unix socket)
  • Output writers (file, stdout, null, io_uring)
  • Terminal color support

Contents

File Purpose
flags.go Unified CLI flags for all SRT configuration
data_generator.go Token bucket rate-limited data generation
statistics.go Connection statistics formatting and display
metrics_server.go Prometheus metrics HTTP/UDS server
metrics_client.go Prometheus metrics client for scraping
writer.go Output writers (DirectWriter, NullWriter)
writer_iouring_linux.go io_uring-based writer (Linux only)
writer_iouring_stub.go Stub for non-Linux platforms
colors.go ANSI color codes for terminal output

Flags

The flags.go file defines all CLI flags shared across tools. Flags are organized by category:

Connection Flags
Flag Description
-conntimeo Connection timeout (milliseconds)
-peeridletimeo Peer idle timeout (milliseconds)
-latency Maximum transmission latency (milliseconds)
-rcvlatency Receiver-side latency (milliseconds)
-peerlatency Peer latency request (milliseconds)
Buffer Flags
Flag Description
-fc Flow control window (packets)
-rcvbuf Receiver buffer size (bytes)
-sndbuf Sender buffer size (bytes)
-payloadsize Maximum payload size (bytes)
io_uring Flags
Flag Description
-iouringenabled Enable io_uring for send
-iouringrecvenabled Enable io_uring for receive
-iouringrecvringsize Receive ring size (power of 2)
-iouringrecvringcount Number of receive rings (1-16)
-iouringrecvbatchsize Batch size for resubmissions
Lock-Free Receiver Flags
Flag Description
-usepacketring Enable lock-free packet ring
-packetringsize Ring size per shard
-packetringshards Number of shards
-useeventloop Enable continuous EventLoop
-userecvcontrolring Enable receiver control ring
Lock-Free Sender Flags
Flag Description
-usesendbtree Enable btree for sender packets
-usesendring Enable lock-free sender ring
-usesendcontrolring Enable control ring for ACK/NAK
-usesendeventloop Enable sender EventLoop
NAK Configuration Flags
Flag Description
-usenakbtree Enable NAK btree for gap detection
-fastnakenabled Enable FastNAK optimization
-fastnakrecentenabled Enable FastNAKRecent
-honornakorder Retransmit in NAK packet order
Performance Test Flags
Flag Description
-initial Starting bitrate for search
-min-bitrate Minimum bitrate floor
-max-bitrate Maximum bitrate ceiling
-warmup Warm-up duration after bitrate change
-stability-window Stability evaluation window
Application Flags
Flag Description
-statsperiod Throughput display interval
-color Terminal output color
-promhttp Prometheus HTTP endpoint
-promuds Prometheus Unix socket path
-name Instance name for labeling

Usage

Parsing Flags
import "github.com/randomizedcoder/gosrt/contrib/common"

func main() {
    // Parse all flags and track which were explicitly set
    common.ParseFlags()

    // Apply flags to SRT config
    config := srt.DefaultConfig()
    common.ApplyFlagsToConfig(config)
}
Building Flag Arguments
// Get explicitly-set flags for subprocess
args := common.BuildFlagArgs()
// Returns: ["-fc=102400", "-useeventloop", ...]

// Filter specific flags
args := common.BuildFlagArgsFiltered("addr", "promuds")
Flag Dependencies
// Validate and auto-enable dependencies
warnings := common.ValidateFlagDependencies()
// Auto-enables: usesendbtree if usesendring is set, etc.

DataGenerator

The DataGenerator provides rate-limited test data generation with precise bitrate control.

Features
  • Token bucket rate limiting
  • High-rate optimization (time-based pacing for >1000 pkt/s)
  • Zero allocations in hot path (pre-filled payload)
  • Atomic statistics counters
Usage
// Create rate-limited generator
gen := common.NewDataGenerator(ctx, 10_000_000, 1456) // 10 Mb/s, 1456 byte packets

// Read generates data at the configured rate
buf := make([]byte, 1500)
n, err := gen.Read(buf)

// Get statistics
stats := gen.Stats()
fmt.Printf("Actual: %.2f Mb/s\n", stats.ActualBitrate/1_000_000)

// Unlimited mode (for stress testing)
gen := common.NewDataGeneratorUnlimited(ctx, 1456)
Rate Calculation
packets_per_sec = bitrate / 8 / payload_size

Example: 10 Mb/s with 1456-byte packets
  bytes/sec = 10,000,000 / 8 = 1,250,000
  pkt/sec = 1,250,000 / 1456 = 858.5
  interval = 1.165 ms per packet
High-Rate Mode

For >1000 packets/sec, the generator uses time-based pacing:

  1. Calculate target time for each packet: startTime + N * interval
  2. For intervals >1ms: sleep + busy-wait
  3. For intervals <1ms: pure busy-wait (required for sub-millisecond precision)

Statistics

The statistics.go file provides throughput display formatting.

Usage
// Create throughput display
go common.RunThroughputDisplayWithLabelAndColor(
    ctx,
    srtConn,           // ThroughputGetter interface
    "SUB",             // Label
    "green",           // Color
    1*time.Second,     // Display interval
)
Output Format
[SUB             ] 12:34:56.78 |  1234.5 pkt/s |   56.78 MB | 10.123 Mb/s |  123.4k ok /     5 gaps /     3 NAKs /     2 retx | recovery= 99.5%

Writers

NullWriter

Discards all data without I/O overhead. Use for benchmarking:

writer := common.NewNullWriter()
io.Copy(writer, reader) // Data is discarded
stats := writer.Stats()
DirectWriter

Writes directly to an underlying writer with statistics:

file, _ := os.Create("/tmp/output.ts")
writer := common.NewDirectWriter(file)
io.Copy(writer, reader)
IoUringWriter (Linux only)

High-performance writer using io_uring for zero-copy writes:

writer, _ := common.NewIoUringWriter(file, 256)
io.Copy(writer, reader)
writer.Close()

Metrics Server

Start Prometheus metrics endpoints:

// Start HTTP and/or Unix socket metrics servers
common.StartMetricsServers(ctx, ":9090", "/tmp/metrics.sock")
Scraping
# HTTP endpoint
curl http://localhost:9090/metrics

# Unix socket
curl --unix-socket /tmp/metrics.sock http://localhost/metrics

Colors

Terminal colors for output differentiation:

color := common.GetColor("green")
reset := common.ColorReset
fmt.Printf("%s[HighPerf]%s Processing...\n", color, reset)

Available colors: red, green, yellow, blue, magenta, cyan

Building

This is a library package, not a standalone binary. It's imported by other tools:

import "github.com/randomizedcoder/gosrt/contrib/common"

Build tools that use this library:

make build-performance  # Builds all tools
make server client      # Builds specific tools

Documentation

Overview

Package common provides shared utilities for SRT client applications.

Package common provides shared utilities for contrib programs.

This file contains high-performance writers for output destinations. All writers in this file use only the Go standard library - no unsafe package.

Package common provides shared utilities for contrib programs.

This file contains the io_uring-based async writer for Linux.

IMPORTANT: This file uses the 'unsafe' package for io_uring integration.

WHY UNSAFE IS NEEDED: io_uring is a Linux kernel interface that performs async I/O by sharing memory between userspace and kernel. To submit a write operation, we must pass the actual memory address of the buffer to the kernel. Go's type system does not allow this without unsafe.Pointer.

The io_uring interface requires: 1. A raw pointer (uintptr) to the buffer data 2. The buffer must not be moved by GC during the async operation 3. The kernel reads directly from this memory address

SAFETY MEASURES: - Buffers are allocated from sync.Pool and kept alive until completion - The completion handler returns buffers to the pool only after kernel is done - This pattern is proven in the SRT send path (connection_linux.go)

USAGE: This writer is OPTIONAL and requires the -iouringoutput CLI flag. For most use cases, DirectWriter (writer.go) is recommended as it: - Has zero unsafe code - Uses battle-tested stdlib - Works on all platforms

Index

Constants

View Source
const (
	ColorReset   = "\033[0m"
	ColorRed     = "\033[31m"
	ColorGreen   = "\033[32m"
	ColorYellow  = "\033[33m"
	ColorBlue    = "\033[34m"
	ColorMagenta = "\033[35m"
	ColorCyan    = "\033[36m"
	ColorWhite   = "\033[37m"
)

ANSI color codes for terminal output

Variables

View Source
var (
	// Map to track which flags were explicitly set by the user
	FlagSet = make(map[string]bool)

	// Connection configuration flags (shared between client and server)
	Congestion         = flag.String("congestion", "", "Type of congestion control ('live' or 'file')")
	Conntimeo          = flag.Int("conntimeo", 0, "Connection timeout in milliseconds")
	Streamid           = flag.String("streamid", "", "Stream ID (settable in caller mode only)")
	PassphraseFlag     = flag.String("passphrase-flag", "", "Password for encrypted transmission (alternative to passphrase)")
	PBKeylen           = flag.Int("pbkeylen", 0, "Crypto key length in bytes (16, 24, or 32)")
	KMPreAnnounce      = flag.Uint64("kmpreannounce", 0, "Duration of Stream Encryption key switchover (packets)")
	KMRefreshRate      = flag.Uint64("kmrefreshrate", 0, "Stream encryption key refresh rate (packets)")
	EnforcedEncryption = flag.Bool("enforcedencryption", false, "Reject connection if parties set different passphrase")
	Latency            = flag.Int("latency", 0, "Maximum accepted transmission latency in milliseconds")
	PeerLatency        = flag.Int("peerlatency", 0, "Minimum receiver latency to be requested by sender in milliseconds")
	RcvLatency         = flag.Int("rcvlatency", 0, "Receiver-side latency in milliseconds")
	FC                 = flag.Uint64("fc", 0, "Flow control window size (packets)")
	SndBuf             = flag.Uint64("sndbuf", 0, "Sender buffer size in bytes")
	RcvBuf             = flag.Uint64("rcvbuf", 0, "Receiver buffer size in bytes")
	MSS                = flag.Uint64("mss", 0, "MTU size")
	PayloadSize        = flag.Uint64("payloadsize", 0, "Maximum payload size in bytes")
	MaxBW              = flag.Int64("maxbw", 0, "Bandwidth limit in bytes/s (-1 for unlimited)")
	InputBW            = flag.Int64("inputbw", 0, "Input bandwidth in bytes")
	MinInputBW         = flag.Int64("mininputbw", 0, "Minimum input bandwidth in bytes")
	OheadBW            = flag.Int64("oheadbw", 0, "Limit bandwidth overhead in percents")
	PeerIdleTimeo      = flag.Int("peeridletimeo", 0, "Peer idle timeout in milliseconds")
	KeepaliveThreshold = flag.Float64("keepalivethreshold", 0.75, "Fraction of peer idle timeout at which to send proactive keepalives (0 to disable, default 0.75)")
	SndDropDelay       = flag.Int("snddropdelay", 0, "Sender's delay before dropping packets in milliseconds")
	IPTOS              = flag.Int("iptos", 0, "IP socket type of service")
	IPTTL              = flag.Int("ipttl", 0, "IP socket 'time to live' option")
	IPv6Only           = flag.Int("ipv6only", -1, "Allow only IPv6 (-1 for default)")
	DriftTracer        = flag.Bool("drifttracer", false, "Enable drift tracer")
	TLPktDrop          = flag.Bool("tlpktdrop", false, "Drop too late packets")
	TSBPDMode          = flag.Bool("tsbpdmode", false, "Enable timestamp-based packet delivery mode")
	MessageAPI         = flag.Bool("messageapi", false, "Enable SRT message mode")
	NAKReport          = flag.Bool("nakreport", false, "Enable periodic NAK reports")
	LossMaxTTL         = flag.Uint64("lossmaxttl", 0, "Packet reorder tolerance")
	PacketFilter       = flag.String("packetfilter", "", "Set up the packet filter")
	Transtype          = flag.String("transtype", "", "Transmission type ('live' or 'file')")
	GroupConnect       = flag.Bool("groupconnect", false, "Accept group connections")
	GroupStabTimeo     = flag.Int("groupstabtimeo", 0, "Group stability timeout in milliseconds")
	AllowPeerIpChange  = flag.Bool("allowpeeripchange", false, "Allow new IP to send data on existing socket id")

	// io_uring configuration flags
	IoUringEnabled      = flag.Bool("iouringenabled", false, "Enable io_uring for per-connection send queues (requires Linux kernel 5.1+)")
	IoUringSendRingSize = flag.Int("iouringsendringsize", 0, "Size of the io_uring ring for per-connection send queues (must be power of 2, 16-1024)")

	// Channel buffer size configuration flags
	NetworkQueueSize = flag.Int("networkqueuesize", 0, "Size of the network queue channel buffer (packets from network)")
	WriteQueueSize   = flag.Int("writequeuesize", 0, "Size of the write queue channel buffer (packets from application writes)")
	ReadQueueSize    = flag.Int("readqueuesize", 0, "Size of the read queue channel buffer (packets ready for application reads)")
	ReceiveQueueSize = flag.Int("receivequeuesize", 0, "Size of the receive queue channel buffer (packets from network before routing to connections)")

	// Packet reordering configuration flags
	PacketReorderAlgorithm = flag.String("packetreorderalgorithm", "", "Packet reordering algorithm: 'list' (default, O(n) insertions) or 'btree' (O(log n) operations, better for large buffers)")
	BTreeDegree            = flag.Int("btreedegree", 0, "B-tree degree for packet reordering (only used if packetreorderalgorithm='btree', default: 32)")

	// io_uring receive configuration flags
	IoUringRecvEnabled        = flag.Bool("iouringrecvenabled", false, "Enable io_uring for receive operations (requires Linux kernel 5.1+)")
	IoUringRecvRingSize       = flag.Int("iouringrecvringsize", 0, "Size of the io_uring receive ring (must be power of 2, 64-32768)")
	IoUringRecvInitialPending = flag.Int("iouringrecvinitialpending", 0, "Initial number of pending receive requests at startup (default: ring size)")
	IoUringRecvBatchSize      = flag.Int("iouringrecvbatchsize", 0, "Batch size for resubmitting receive requests after completions (default: 256)")

	// Multiple io_uring rings configuration flags (Phase 1: multi_iouring_design.md)
	IoUringRecvRingCount = flag.Int("iouringrecvringcount", 1, "Number of io_uring receive rings for parallel processing (1-16)")
	IoUringSendRingCount = flag.Int("iouringsendringcount", 1, "Number of io_uring send rings per connection (1-8)")

	// Timer interval configuration flags
	TickIntervalMs          = flag.Uint64("tickintervalms", 0, "TSBPD delivery tick interval in milliseconds (default: 10)")
	PeriodicNakIntervalMs   = flag.Uint64("periodicnakintervalms", 0, "Periodic NAK timer interval in milliseconds (default: 20)")
	PeriodicAckIntervalMs   = flag.Uint64("periodicackintervalms", 0, "Periodic ACK timer interval in milliseconds (default: 10)")
	SendDropIntervalMs      = flag.Uint64("senddropintervalms", 0, "Sender drop ticker interval in milliseconds (default: 100)")
	EventLoopRateIntervalMs = flag.Uint64("eventlooprateintervalms", 0, "Rate calculation interval in milliseconds (default: 1000)")

	// NAK btree configuration flags
	UseNakBtree              = flag.Bool("usenakbtree", false, "Enable NAK btree for efficient gap detection (auto-enabled with -iouringrecvenabled)")
	NakRecentPercent         = flag.Float64("nakrecentpercent", 0, "Percentage of TSBPD delay for 'too recent' threshold (default: 0.10)")
	NakMergeGap              = flag.Uint64("nakmergegap", 0, "Max sequence gap to merge in NAK consolidation (default: 3)")
	NakConsolidationBudgetMs = flag.Uint64("nakconsolidationbudgetms", 0, "Max time for NAK consolidation in milliseconds (default: 2)")

	// FastNAK configuration flags
	FastNakEnabled       = flag.Bool("fastnakenabled", false, "Enable FastNAK optimization (default: true when NAK btree enabled)")
	FastNakThresholdMs   = flag.Uint64("fastnakthresholdms", 0, "Silent period to trigger FastNAK in milliseconds (default: 50)")
	FastNakRecentEnabled = flag.Bool("fastnakrecentenabled", false, "Add recent gap immediately on FastNAK trigger (default: true)")

	// Sender retransmission configuration flags
	HonorNakOrder = flag.Bool("honornakorder", false, "Retransmit packets in NAK packet order (oldest first)")

	// Sender lockless configuration flags (Phase 1: Lockless Sender)
	UseSendBtree    = flag.Bool("usesendbtree", false, "Enable btree for sender packet storage (O(log n) NAK lookup)")
	SendBtreeDegree = flag.Int("sendbtreesize", 32, "B-tree degree for sender (default: 32)")

	// Sender lock-free ring configuration flags (Phase 2: Lockless Sender)
	UseSendRing    = flag.Bool("usesendring", false, "Enable lock-free ring for sender Push() (requires -usesendbtree)")
	SendRingSize   = flag.Int("sendringsize", 1024, "Sender ring size per shard (default: 1024)")
	SendRingShards = flag.Int("sendringshards", 1, "Sender ring shards (1=strict ordering, >1=high throughput)")

	// Sender control ring configuration flags (Phase 3: Lockless Sender)
	UseSendControlRing    = flag.Bool("usesendcontrolring", false, "Enable lock-free ring for ACK/NAK (requires -usesendring)")
	SendControlRingSize   = flag.Int("sendcontrolringsize", 128, "Sender control ring size per shard (default: 128)")
	SendControlRingShards = flag.Int("sendcontrolringshards", 1, "Sender control ring shards (default: 1)")

	// Receiver control ring configuration flags (Completely Lock-Free Receiver)
	UseRecvControlRing    = flag.Bool("userecvcontrolring", false, "Enable lock-free ring for ACKACK/KEEPALIVE (requires -useeventloop)")
	RecvControlRingSize   = flag.Int("recvcontrolringsize", 128, "Receiver control ring size per shard (default: 128)")
	RecvControlRingShards = flag.Int("recvcontrolringshards", 1, "Receiver control ring shards (default: 1)")

	// Sender EventLoop configuration flags (Phase 4: Lockless Sender)
	UseSendEventLoop             = flag.Bool("usesendeventloop", false, "Enable sender EventLoop (requires -usesendcontrolring)")
	SendEventLoopBackoffMinSleep = flag.Duration("sendeventloopbackoffminsleep", 100*time.Microsecond, "Sender EventLoop minimum sleep (default: 100µs)")
	SendEventLoopBackoffMaxSleep = flag.Duration("sendeventloopbackoffmaxsleep", 1*time.Millisecond, "Sender EventLoop maximum sleep (default: 1ms)")
	SendTsbpdSleepFactor         = flag.Float64("sendtsbpdsleepfactor", 0.9, "Sender TSBPD sleep factor (default: 0.9)")
	SendDropThresholdUs          = flag.Uint64("senddropthresholdus", 0, "Sender drop threshold in microseconds (0 = auto-calculated from 1.25 * peerTsbpdDelay)")

	// Adaptive Backoff flags (adaptive_eventloop_mode_design.md)
	// Switches between Yield (~6M ops/sec) and Sleep (~1K ops/sec) based on activity
	UseAdaptiveBackoff           = flag.Bool("useadaptivebackoff", true, "Enable adaptive Yield/Sleep mode switching in EventLoop (Yield=high throughput, Sleep=CPU friendly when idle)")
	AdaptiveBackoffIdleThreshold = flag.Duration("adaptivebackoffidlethreshold", 1*time.Second, "Duration without activity before switching to Sleep mode (default: 1s)")

	// EventLoop Tight Loop flags (eventloop_batch_sizing_design.md)
	// Enables control-priority tight loop: control ring checked after EVERY data packet
	// -1 = use library default (512 for tight loop)
	// 0 = unbounded legacy mode (no tight loop)
	// >0 = tight loop with specified batch size
	EventLoopMaxDataPerIteration = flag.Int("eventloopmaxdata", -1, "Max data packets per EventLoop iteration (-1 = default 512, 0 = unbounded legacy, >0 = custom)")

	// Zero-copy payload pool flags (Phase 5: Lockless Sender)
	ValidateSendPayloadSize = flag.Bool("validatesendpayloadsize", false, "Validate payload size in Push() (rejects > 1316 bytes)")

	// RTO-based suppression configuration flags (Phase 6: RTO Suppression)
	RTOMode = flag.String("rtomode", "",
		"RTO calculation mode: 'rtt_rttvar' (RTT+RTTVar, default), "+
			"'rtt_4rttvar' (RTT+4*RTTVar, RFC 6298 conservative), "+
			"'rtt_rttvar_margin' (RTT+RTTVar with extra margin)")
	ExtraRTTMargin = flag.Float64("extrarttmargin", 0,
		"Extra RTT margin as decimal (0.1 = 10%, default: 0.1). Only used with rtomode=rtt_rttvar_margin")

	// NAK Btree Expiry Optimization flags (nak_btree_expiry_optimization.md)
	NakExpiryMargin = flag.Float64("nakexpirymargin", 0.10,
		"NAK btree expiry margin as percentage (0.1 = 10%). "+
			"Formula: expiryThreshold = now + (RTO * (1 + nakExpiryMargin)). "+
			"Higher values keep NAK entries longer, favoring recovery over phantom NAK reduction.")
	EWMAWarmupThreshold = flag.Uint("ewmawarmupthreshold", 32,
		"Minimum packets before inter-packet EWMA is considered warm (reliable). "+
			"Set to 0 to disable warm-up check. "+
			"Higher values improve accuracy but delay time-based expiry. "+
			"Default: 32 (balanced for most streams)")

	// Lock-free ring buffer configuration flags (Phase 3: Lockless Design)
	UsePacketRing             = flag.Bool("usepacketring", false, "Enable lock-free ring buffer for packet handoff (decouples io_uring completion from Tick processing)")
	PacketRingSize            = flag.Int("packetringsize", 0, "Capacity of the lock-free ring buffer per shard (must be power of 2, default: 1024)")
	PacketRingShards          = flag.Int("packetringshards", 0, "Number of shards for the lock-free ring (must be power of 2, default: 4)")
	PacketRingMaxRetries      = flag.Int("packetringmaxretries", -1, "Maximum immediate retries before backoff when ring is full (default: 10, -1 = not set)")
	PacketRingBackoffDuration = flag.Duration("packetringbackoffduration", 0, "Delay between backoff retries when ring is full (default: 100µs)")
	PacketRingMaxBackoffs     = flag.Int("packetringmaxbackoffs", -1, "Maximum backoff iterations before dropping packet (0 = unlimited, -1 = not set)")
	PacketRingRetryStrategy   = flag.String("packetringretrystrategy", "",
		"Ring write retry strategy when shard is full: "+
			"'sleep' (default, retry same shard then sleep 100µs), "+
			"'next' (try all shards before sleeping - avoids blocking), "+
			"'random' (try random shards - best load distribution), "+
			"'adaptive' (exponential backoff with jitter), "+
			"'spin' (yield CPU instead of sleep - lowest latency, highest CPU), "+
			"'hybrid' (next + adaptive), "+
			"'autoadaptive' or 'auto' (⭐ RECOMMENDED for >300 Mb/s: Yield when active, Sleep when idle). "+
			"Empty string uses default 'sleep' strategy.")

	// Event loop configuration flags (Phase 4: Lockless Design)
	UseEventLoop          = flag.Bool("useeventloop", false, "Enable continuous event loop (requires -usepacketring, replaces timer-driven Tick)")
	EventLoopRateInterval = flag.Duration("eventlooprateinterval", 0, "Rate metric calculation interval in event loop (default: 1s)")
	BackoffColdStartPkts  = flag.Int("backoffcoldstartpkts", -1, "Packets before adaptive backoff engages (default: 1000, -1 = not set)")
	BackoffMinSleep       = flag.Duration("backoffminsleep", 0, "Minimum sleep during idle periods (default: 10µs)")
	BackoffMaxSleep       = flag.Duration("backoffmaxsleep", 0, "Maximum sleep during idle periods (default: 1ms)")

	// ACK optimization configuration flags (Phase 5: ACK Optimization)
	LightACKDifference = flag.Int("lightackdifference", -1,
		"Send Light ACK after N contiguous packets progress (default: 64, max: 5000). "+
			"Higher values reduce ACK overhead at high bitrates.")

	// Debug configuration flags
	ReceiverDebug = flag.Bool("receiverdebug", false, "Enable debug logging in receiver (NAK dispatch, ring ops, gap detection)")

	// Statistics configuration flags
	StatisticsPrintInterval = flag.Duration("statisticsinterval", 0, "Interval for printing connection statistics (e.g., 10s). 0 disables periodic statistics printing")

	// Timeout and shutdown configuration flags
	HandshakeTimeout = flag.Duration("handshaketimeout", 0, "Maximum time allowed for complete handshake exchange (e.g., 1.5s). Must be less than peeridletimeo")
	ShutdownDelay    = flag.Duration("shutdowndelay", 0, "Time to wait for graceful shutdown after signal (e.g., 5s)")

	// Local address binding for clients
	LocalAddr = flag.String("localaddr", "", "Local IP address to bind to when connecting (e.g., 127.0.0.20)")

	// Instance name for labeling in metrics and logs
	InstanceName = flag.String("name", "", "Instance name for labeling in metrics, logs, and JSON output (e.g., Control, Test, Server1)")

	// io_uring output configuration flag (client-side)
	// WARNING: This feature uses the 'unsafe' package for direct memory access.
	IoUringOutput = flag.Bool("iouringoutput", false,
		"Enable io_uring for output writes (Linux only, ADVANCED). "+
			"Uses the 'unsafe' package for io_uring's zero-copy interface. "+
			"The unsafe code is isolated in contrib/common/writer_iouring_linux.go. "+
			"For most use cases, the default DirectWriter is recommended.")

	// Stats display period flag
	// This is application-level configuration, NOT part of srt.Config
	// Controls how often the [PUB]/[SUB] throughput display lines are printed
	StatsPeriod = flag.Duration("statsperiod", 1*time.Second,
		"Period for throughput display updates (e.g., 1s, 10s). "+
			"Controls how often the [PUB]/[SUB] lines are printed to stderr.")

	// Output color flag for terminal display
	// This is application-level configuration, NOT part of srt.Config
	OutputColor = flag.String("color", "",
		"ANSI color for terminal output (red, green, yellow, blue, magenta, cyan). "+
			"Useful for distinguishing between baseline and highperf pipelines in parallel tests.")

	// Prometheus metrics endpoint flags
	// These are application-level configuration, NOT part of srt.Config
	// By default (when not specified), NO metrics listeners are opened
	PromHTTPAddr = flag.String("promhttp", "",
		"TCP address for Prometheus metrics HTTP endpoint (e.g., :9090 or 127.0.0.1:9090). "+
			"If not specified, no TCP metrics listener is opened.")
	PromUDSPath = flag.String("promuds", "",
		"Unix Domain Socket path for Prometheus metrics endpoint "+
			"(e.g., /tmp/srt_metrics_server.sock). "+
			"If not specified, no UDS metrics listener is opened. "+
			"UDS allows metrics collection from processes in isolated network namespaces.")

	// Search parameters (used by performance orchestrator)
	TestInitialBitrate = flag.Int64("initial", 200_000_000,
		"Starting bitrate for performance search in bps (default: 200M). "+
			"Supports suffixes: K, M, G (e.g., -initial 350M)")
	TestMinBitrate = flag.Int64("min-bitrate", 50_000_000,
		"Minimum bitrate floor in bps (default: 50M)")
	TestMaxBitrate = flag.Int64("max-bitrate", 600_000_000,
		"Maximum bitrate ceiling in bps (default: 600M)")
	TestStepSize = flag.Int64("step", 10_000_000,
		"Additive increase step in bps (default: 10M)")
	TestPrecision = flag.Int64("precision", 5_000_000,
		"Search stops when high-low < precision (default: 5M)")
	TestSearchTimeout = flag.Duration("search-timeout", 10*time.Minute,
		"Maximum search time (default: 10m)")
	TestDecreasePercent = flag.Float64("decrease", 0.25,
		"Multiplicative decrease on failure (default: 0.25 = 25%)")

	// Stability evaluation parameters
	TestWarmUpDuration = flag.Duration("warmup", 2*time.Second,
		"Warm-up duration after bitrate change (default: 2s)")
	TestStabilityWindow = flag.Duration("stability-window", 5*time.Second,
		"Stability evaluation window (default: 5s)")
	TestSampleInterval = flag.Duration("sample-interval", 500*time.Millisecond,
		"Prometheus scrape interval (default: 500ms)")

	// Stability thresholds
	TestMaxGapRate = flag.Float64("max-gap-rate", 0.01,
		"Max gaps per second for stability (default: 0.01)")
	TestMaxNAKRate = flag.Float64("max-nak-rate", 0.02,
		"Max NAKs per second for stability (default: 0.02)")
	TestMaxRTTMs = flag.Float64("max-rtt", 100,
		"Max RTT in milliseconds for stability (default: 100)")
	TestMinThroughput = flag.Float64("min-throughput", 0.95,
		"Min throughput ratio vs target (default: 0.95)")

	// Test output options
	TestVerbose = flag.Bool("test-verbose", false,
		"Enable verbose test output")
	TestJSONOutput = flag.Bool("test-json", false,
		"Output results as JSON")
	TestOutputFile = flag.String("test-output", "",
		"Path for test result output file")
	TestProfileDir = flag.String("profile-dir", "/tmp/srt_profiles",
		"Directory for CPU/heap profile captures")
	TestStatusInterval = flag.Duration("status-interval", 5*time.Second,
		"Interval for printing progress status during search (default: 5s, 0=disabled)")

	// Client-seeker specific flags
	SeekerTarget = flag.String("target", "",
		"SRT target URL for client-seeker (e.g., srt://127.0.0.1:6000/stream)")
	SeekerControlUDS = flag.String("control-socket", "/tmp/srt_seeker_control.sock",
		"Unix domain socket path for seeker control commands")
	SeekerMetricsUDS = flag.String("metrics-socket", "/tmp/srt_seeker_metrics.sock",
		"Unix domain socket path for seeker Prometheus metrics")
	SeekerWatchdogTimeout = flag.Duration("watchdog-timeout", 10*time.Second,
		"Watchdog timeout - stop if no heartbeat received (default: 10s)")
	SeekerHeartbeatInterval = flag.Duration("heartbeat-interval", 2*time.Second,
		"Expected heartbeat interval from orchestrator (default: 2s)")
)

Functions

func ApplyFlagsToConfig

func ApplyFlagsToConfig(config *srt.Config)

ApplyFlagsToConfig applies CLI flag values to the provided config. Only flags that were explicitly set (tracked in FlagSet map) override the default config.

This function delegates to the table-driven implementation in flags_applicators.go, reducing cyclomatic complexity from 116 to ~10.

func BuildFlagArgs

func BuildFlagArgs() []string

BuildFlagArgs returns CLI arguments for all explicitly-set flags. Used by the performance orchestrator to spawn subprocesses (server, client-seeker) with the same SRT configuration.

Excludes test-only flags (search params, stability thresholds, etc.) that are orchestrator-specific and shouldn't be passed to subprocesses.

Example output: ["-fc=102400", "-rcvbuf=67108864", "-useeventloop", ...]

func BuildFlagArgsFiltered

func BuildFlagArgsFiltered(exclude ...string) []string

BuildFlagArgsFiltered returns CLI arguments for explicitly-set flags, excluding the specified flag names.

Use this when you need to override certain flags for a subprocess. Example: exclude "-addr" when spawning server on a different port.

func ColorCode

func ColorCode(colorName string) string

ColorCode returns the ANSI escape code for the given color name. Returns empty string if color is not recognized or empty. Supported colors: red, green, yellow, blue, magenta, cyan, white

func Colorize

func Colorize(text, colorName string) string

Colorize wraps the text with the specified color. If color name is empty or not recognized, returns text unchanged.

func ColorizeCode

func ColorizeCode(text, code string) string

ColorizeCode wraps the text with the specified ANSI color code. If code is empty, returns text unchanged.

func GetConditionalFlagApplicatorCount

func GetConditionalFlagApplicatorCount() int

GetConditionalFlagApplicatorCount returns the number of conditional flag applicators for testing.

func GetFlagApplicatorCount

func GetFlagApplicatorCount() int

GetFlagApplicatorCount returns the number of flag applicators for testing.

func HandleTestFlags

func HandleTestFlags(testflags bool, configModifier func(*srt.Config)) (exitCode int, handled bool)

HandleTestFlags checks if testflags mode is active. If so, it creates a default config, applies CLI flags, marshals to JSON, prints to stdout, and returns (exitCode, true). If testflags is not active, returns (0, false). The configModifier callback allows binaries to apply additional config changes (e.g., server-specific passphrase override) before marshaling.

func IoUringOutputAvailable

func IoUringOutputAvailable() bool

IoUringOutputAvailable returns true on Linux where io_uring is supported.

func IsTestFlag

func IsTestFlag(name string) bool

IsTestFlag returns true if the flag name is a test-only flag.

func ParseFlags

func ParseFlags()

ParseFlags parses command-line flags and populates FlagSet map with flags that were explicitly set by the user.

func PrintFlagSummary

func PrintFlagSummary()

PrintFlagSummary prints a summary of explicitly-set flags for debugging.

func ProfileOption

func ProfileOption(flag string) func(*profile.Profile)

ProfileOption converts a profile flag string to a profile.Profile option. Returns nil if the flag is empty or unrecognized. Supported: "cpu", "mem", "allocs", "heap", "rate", "mutex", "block", "thread", "trace"

func ResetFlagSet

func ResetFlagSet()

ResetFlagSet clears the FlagSet map for testing purposes.

func RunThroughputDisplay

func RunThroughputDisplay(ctx context.Context, period time.Duration, getter ThroughputGetter)

RunThroughputDisplay runs a throughput display loop that periodically prints stats The getter function is called to retrieve current byte/packet/success/loss/retrans totals The loop exits when ctx is canceled

Output format (fixed-width columns):

[label           ] HH:MM:SS.xx | 999.9 pkt/s | 99.99 MB | 9.999 Mb/s | 9999k ok / 999 gaps / 999 NAKs / 999 retx | recovery=100.0%

The label column is 16 chars wide for consistent vertical alignment.

- "gaps" = sequence gaps detected (triggers NAK/retransmission) - "NAKs" = NAK packets sent requesting retransmission - "retx" = retransmissions sent/received (ARQ recovery activity) - "recovery" = (gaps - TSBPD_skips) / gaps = % of gaps successfully recovered

  • 100% = all gaps recovered via retransmission (no true losses)
  • <100% = some packets never arrived before TSBPD timeout (true losses)

func RunThroughputDisplayWithLabel

func RunThroughputDisplayWithLabel(ctx context.Context, period time.Duration, label string, getter ThroughputGetter)

RunThroughputDisplayWithLabel runs a throughput display loop with a component label

func RunThroughputDisplayWithLabelAndColor

func RunThroughputDisplayWithLabelAndColor(ctx context.Context, period time.Duration, label, color string, getter ThroughputGetter)

RunThroughputDisplayWithLabelAndColor runs a throughput display loop with a component label and color Color can be: red, green, yellow, blue, magenta, cyan, white (or empty for no color)

func SetupPauseHandler

func SetupPauseHandler(paused *atomic.Bool, label string)

SetupPauseHandler installs a SIGUSR1 handler that sets the paused flag to true when received. The label is printed to stderr when the signal is received (e.g., "stopping data generation", "stopping UDP receive").

func StartMetricsServers

func StartMetricsServers(ctx context.Context, wg *sync.WaitGroup, httpAddr, udsPath string) error

StartMetricsServers starts Prometheus metrics endpoint(s) based on the provided flags.

This follows the exact pattern from context_and_cancellation_new_design.md: - Shutdown watcher goroutine: fire-and-forget (NO wg.Add), listens for ctx.Done() - Server goroutine: tracked with wg.Add(1) and defer wg.Done()

Parameters:

  • ctx: Parent context - when canceled, triggers graceful shutdown of servers
  • wg: WaitGroup to track server goroutines
  • httpAddr: TCP address (e.g., ":9090") - empty string means no TCP listener
  • udsPath: Unix socket path - empty string means no UDS listener

If both httpAddr and udsPath are empty, this function does nothing (no listeners opened).

Endpoints:

  • /metrics - Full Prometheus metrics export
  • /stabilize - Minimal 6-metric output for stabilization detection

func StartThroughputDisplay

func StartThroughputDisplay(ctx context.Context, wg *sync.WaitGroup, period time.Duration,
	defaultLabel string, instanceName string, color string, getter ThroughputGetter)

StartThroughputDisplay starts a goroutine that runs RunThroughputDisplayWithLabelAndColor. Uses instanceName if non-empty, otherwise falls back to defaultLabel.

func ValidateFlagDependencies

func ValidateFlagDependencies() []string

ValidateFlagDependencies checks flag combinations and auto-enables dependencies. It returns a list of warnings for any auto-enabled flags. Call this after flag.Parse() to ensure consistent configuration.

func WaitForShutdown

func WaitForShutdown(wg *sync.WaitGroup, shutdownStart time.Time, delay time.Duration)

WaitForShutdown waits for all goroutines tracked by wg to complete, with a timeout. Prints elapsed time on completion or timeout message.

Types

type DataGenerator

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

DataGenerator generates test data at a specified bitrate. It implements io.Reader and can be used with any io.Writer.

The generator uses a token bucket rate limiter to control the rate of packet generation. Each Read() call returns one packet's worth of data (up to payloadSize bytes) and waits for the rate limiter.

The payload is pre-filled with a test pattern at initialization and reused for every Read() call, ensuring zero allocations in the hot path.

For high-rate traffic (>10k packets/sec), the generator uses a time-based pacing approach that calculates when each packet should be sent based on its sequence number, avoiding timer allocation overhead.

func NewDataGenerator

func NewDataGenerator(ctx context.Context, bitrate uint64, payloadSize uint32) *DataGenerator

NewDataGenerator creates a rate-limited data generator.

Parameters:

  • ctx: Context for cancellation
  • bitrate: Target bits per second
  • payloadSize: Bytes per packet (default: 1456 if 0)

The generator calculates packets per second from the bitrate and payload size, then uses a token bucket limiter to maintain the target rate.

func NewDataGeneratorUnlimited

func NewDataGeneratorUnlimited(ctx context.Context, payloadSize uint32) *DataGenerator

NewDataGeneratorUnlimited creates a data generator with no rate limiting. This is useful for testing maximum throughput of downstream components. The generator will produce data as fast as possible (limited only by memcpy speed).

func (*DataGenerator) ActualBitrate

func (g *DataGenerator) ActualBitrate() float64

ActualBitrate returns the measured bitrate since start in bits per second.

func (*DataGenerator) Close

func (g *DataGenerator) Close() error

Close is a no-op that satisfies io.Closer. The DataGenerator has no resources to release since it uses no channels or goroutines.

func (*DataGenerator) PacketsPerSecond

func (g *DataGenerator) PacketsPerSecond() float64

PacketsPerSecond returns the target packets per second rate.

func (*DataGenerator) PayloadSize

func (g *DataGenerator) PayloadSize() int

PayloadSize returns the configured payload size in bytes.

func (*DataGenerator) Read

func (g *DataGenerator) Read(p []byte) (int, error)

Read generates data at the configured rate. Returns up to payloadSize bytes per call.

Each call to Read: 1. Checks for context cancellation (non-blocking) 2. Waits for the rate limiter (blocking, but context-aware) - unless unlimited mode 3. Copies pre-filled payload to the buffer (single memcpy)

Returns io.EOF when the context is canceled.

func (*DataGenerator) Stats

func (g *DataGenerator) Stats() DataGeneratorStats

Stats returns generation statistics.

type DataGeneratorStats

type DataGeneratorStats struct {
	PacketsGenerated uint64
	BytesGenerated   uint64
	StartTime        time.Time
	Duration         time.Duration
	TargetBitrate    uint64
	ActualBitrate    float64
}

DataGeneratorStats holds statistics about data generation.

type DirectWriter

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

DirectWriter wraps os.File for zero-overhead writes.

Benefits over NonblockingWriter:

  • No locks in our code
  • No channels
  • Single syscall per write
  • Uses battle-tested stdlib

This is the recommended writer for most use cases (stdout, files).

func NewDirectWriter

func NewDirectWriter(f *os.File, closeOnClose bool) *DirectWriter

NewDirectWriter creates a writer for the given file. If closeOnClose is true, the file will be closed when Close() is called.

func NewFileWriter

func NewFileWriter(path string) (*DirectWriter, error)

NewFileWriter creates a writer for a new file at the given path. The file is created with default permissions (0644). The file will be closed when Close() is called.

func NewStderrWriter

func NewStderrWriter() *DirectWriter

NewStderrWriter creates a writer for stderr. The underlying file (os.Stderr) is not closed on Close().

func NewStdoutWriter

func NewStdoutWriter() *DirectWriter

NewStdoutWriter creates a writer for stdout. The underlying file (os.Stdout) is not closed on Close().

func (*DirectWriter) Close

func (w *DirectWriter) Close() error

Close closes the underlying file if closeFile is true. For stdout/stderr writers, this is a no-op.

func (*DirectWriter) Fd

func (w *DirectWriter) Fd() int

Fd returns the file descriptor of the underlying file. This is useful for the io_uring upgrade path (Phase 2). Returns -1 if the file is nil.

func (*DirectWriter) Write

func (w *DirectWriter) Write(p []byte) (int, error)

Write writes p to the underlying file. This is a direct syscall with no buffering or locking.

type FlagApplicator

type FlagApplicator struct {
	Name  string                   // Flag name (must match flag definition)
	Apply func(config *srt.Config) // Function to apply the flag value to config
}

FlagApplicator defines how a single CLI flag is applied to srt.Config. The table-driven approach reduces cyclomatic complexity from 116 to ~5.

type IoUringWriter

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

IoUringWriter provides async writes using Linux io_uring.

Benefits over blocking writes: - Submit returns immediately (non-blocking) - Kernel executes write asynchronously - Application never blocks on slow consumers

Trade-offs: - Requires unsafe package (see file-level comments) - More complex than DirectWriter - Linux only

func NewIoUringFileWriter

func NewIoUringFileWriter(fd int) (*IoUringWriter, error)

NewIoUringFileWriter creates an io_uring writer for a file.

func NewIoUringStdoutWriter

func NewIoUringStdoutWriter() (*IoUringWriter, error)

NewIoUringStdoutWriter creates an io_uring writer for stdout.

func NewIoUringWriter

func NewIoUringWriter(fd int, ringSize uint32) (*IoUringWriter, error)

NewIoUringWriter creates an io_uring-based async writer for the given fd.

Parameters:

  • fd: File descriptor to write to (e.g., 1 for stdout)
  • ringSize: Size of the io_uring ring (0 for default of 256)

Returns error if io_uring initialization fails.

func (*IoUringWriter) Close

func (w *IoUringWriter) Close() error

Close shuts down the io_uring writer. It waits for pending completions before returning.

func (*IoUringWriter) Write

func (w *IoUringWriter) Write(p []byte) (int, error)

Write submits data to io_uring and returns immediately.

The write is executed asynchronously by the kernel. This method: 1. Copies data to a pooled buffer (required for async safety) 2. Submits the write to io_uring 3. Returns immediately (non-blocking)

The completion handler returns the buffer to the pool when done.

type MetricsClient

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

MetricsClient provides methods to fetch Prometheus metrics from TCP or UDS endpoints

func NewMetricsClient

func NewMetricsClient() *MetricsClient

NewMetricsClient creates a new metrics client

func (*MetricsClient) FetchHTTP

func (mc *MetricsClient) FetchHTTP(ctx context.Context, addr string) ([]byte, error)

FetchHTTP fetches metrics from a TCP HTTP endpoint

func (*MetricsClient) FetchUDS

func (mc *MetricsClient) FetchUDS(ctx context.Context, socketPath string) ([]byte, error)

FetchUDS fetches metrics from a Unix Domain Socket endpoint

type NullWriter

type NullWriter struct{}

NullWriter discards all data written to it. Useful for profiling/benchmarking without I/O overhead.

func (*NullWriter) Close

func (n *NullWriter) Close() error

Close is a no-op for NullWriter.

func (*NullWriter) Write

func (n *NullWriter) Write(p []byte) (int, error)

Write discards all data and returns success.

type ThroughputGetter

type ThroughputGetter func() (bytes uint64, pkts uint64, gapsPkts uint64, naksPkts uint64, skipsPkts uint64, retransPkts uint64)

ThroughputGetter is a function that returns current bytes, packets, gaps, NAKs, skips, and retransmits This allows the display to work with any counter source (metrics.ConnectionMetrics, etc.) Returns: (bytes, pkts, gapsPkts, naksPkts, skipsPkts, retransPkts)

  • bytes: total bytes transferred (for MB display)
  • pkts: packets for rate calculation (for pkt/s display)
  • gapsPkts: sequence gaps detected (triggers NAK/retransmission)
  • naksPkts: NAK packets sent requesting retransmission
  • skipsPkts: TSBPD skips = packets that NEVER arrived (TRUE losses)
  • retransPkts: total retransmissions (ARQ recovery activity)

Jump to

Keyboard shortcuts

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