Documentation
¶
Overview ¶
Package callharness is the call harness: the single test seam for Relais media. It starts the system in-process, a Pion WebRTC client plays the caller, and tests check only what the caller can observe: the SDP answer, connection-state changes, the packets it receives and when, whether the echoed video decodes, SRTP decryption failures, keyframe requests, and any renegotiation or ICE restart.
Today the system is one media worker serving WHIP-style signaling, or several workers sharing one UDP socket so that a call can move between them (Call.Handover, a planned handover), or, with Options.Relay, one or more workers behind a relay (see topology.go). A test looks like this:
h, _ := callharness.Start(callharness.Options{})
defer h.Close()
call, _ := h.Dial(ctx, callharness.CallOptions{Video: true})
_ = call.SendMedia(ctx, 30*time.Second)
report, _ := call.Hangup(ctx)
t.Log(report.Summary())
Index ¶
- Constants
- Variables
- func EnoughConclusive(conclusive, total int) bool
- func JudgeDistributedTrials(moves []MoveReport) (string, []string)
- func RequireFreshnessRecovered(t interface{ ... }, event EventMeasurement, required FreshnessPolicy)
- type AnswerFacts
- type AnswerMedia
- type Call
- func (c *Call) ArmDistributedEvent(id OperationIdentity) error
- func (c *Call) ClockProbes() []ClockProbe
- func (c *Call) DialReport() DialReport
- func (c *Call) Handover(opts HandoverOptions) error
- func (c *Call) Hangup(ctx context.Context) (*Report, error)
- func (c *Call) MoveExternal(ctx context.Context, to int) error
- func (c *Call) RecordDistributedEvent(e DistributedEvent) error
- func (c *Call) RecordExternalMove(result controlplane.MoveResult)
- func (c *Call) Recording() RecordingReport
- func (c *Call) RequestKeyframe(request KeyframeRequest) error
- func (c *Call) SendLongRun(ctx context.Context, o LongRunOptions) (*LongRunReport, error)
- func (c *Call) SendMedia(ctx context.Context, duration time.Duration) error
- func (c *Call) SessionID() string
- type CallOptions
- type CandidatePairChecks
- type ClockProbe
- type ClockRateInterval
- type ClockTracker
- func (c *ClockTracker) Add(p ClockProbe) error
- func (c *ClockTracker) DriftPPM() float64
- func (c *ClockTracker) Err() error
- func (c *ClockTracker) Invalidate(err error)
- func (c *ClockTracker) Map(server time.Time, host OffsetInterval) (TimeInterval, error)
- func (c *ClockTracker) ObservedRate() *ClockRateInterval
- func (c *ClockTracker) Probes() []ClockProbe
- type ConsentReport
- type ContentDelivery
- type ContentLossCounts
- type ContentLossEvidence
- type ContentLossWindow
- type DecodeFailure
- type DecryptionFailures
- type DialError
- type DialReport
- type DistributedEvent
- type DistributedGap
- type DistributedMeasurement
- type DistributedOptions
- type DistributedRecording
- type DurationInterval
- type EventMeasurement
- type ExternalTopology
- type FirstBaselines
- type FreshnessBaseline
- type FreshnessPolicy
- type FreshnessReport
- type FullDecode
- type HandoverOptions
- type Harness
- func (h *Harness) Close() error
- func (h *Harness) Dial(ctx context.Context, opts CallOptions) (call *Call, err error)
- func (h *Harness) Drain(worker int) error
- func (h *Harness) ExchangeOffer(ctx context.Context, offer string) (AnswerFacts, error)
- func (h *Harness) Kill(worker int) error
- func (h *Harness) KillBeforeSnapshot(ctx context.Context, worker int, id string) error
- func (h *Harness) KillMidKeyframe(ctx context.Context, worker int, id string, n int) error
- func (h *Harness) RecordProcessKill(worker string, at time.Time)
- func (h *Harness) RelayAddr() netip.AddrPort
- func (h *Harness) ReplayStats(worker int) (mediaworker.ReplayStats, error)
- func (h *Harness) RestartRelay() (time.Duration, error)
- func (h *Harness) RestartWorker(worker int) error
- func (h *Harness) SessionOwner(sessionID string) (int, bool)
- func (h *Harness) SignalingURL() string
- func (h *Harness) Status(ctx context.Context) (controlplane.Status, error)
- func (h *Harness) WaitForSnapshot(ctx context.Context, worker int, id string) error
- type KeyframeRequest
- type LongRunOptions
- type LongRunReport
- type MediaGap
- type MemorySample
- type MetricVerdict
- type MoveReport
- type MoveTrackReport
- type OffsetInterval
- type OnlineDecodeReport
- type OperationIdentity
- type Options
- type RTPSequenceNumbers
- type RecordingOptions
- type RecordingReport
- type Report
- type SRTPDiagnosticReport
- type SRTPFailureSample
- type SentTrack
- type StateChange
- type TimeInterval
- type TrackReport
- type VideoRecovery
- type VideoReport
- type WholeCallDelivery
Constants ¶
const ( DialConnected = "connected" DialFailed = "failed" )
Dial outcomes (DialReport.Outcome).
const ( // DialFailureSetup: the dial failed before it sent its offer. DialFailureSetup = "setup" // DialFailureSignaling: the offer/answer exchange failed. DialFailureSignaling = "signaling" // DialFailurePathNeverOpened: no connectivity check got an answer, so // the UDP path to the server never completed a round trip. DialFailurePathNeverOpened = "path_never_opened" // DialFailureICEStalled: some checks were answered, but ICE never // connected. DialFailureICEStalled = "ice_stalled" // DialFailureICEConnectedStalled: ICE connected, but DTLS or the // connected state did not follow. The path worked, so this is a product // failure. DialFailureICEConnectedStalled = "ice_connected_stalled" // DialFailureHarnessClosed: the harness closed during the dial. DialFailureHarnessClosed = "harness_closed" )
Dial failure kinds (DialReport.Failure). The kind comes from what the caller observed, so a path that never opened is told apart from a server that opened the path and then stalled.
const MaxRetainedLostUnits = 4096
MaxRetainedLostUnits bounds each kind's whole-call lost-unit inventory.
const MaxRetainedMediaGaps = 4096
const MaxToleratedStall = 2*toleratedSendLateness + opusFrameDuration
MaxToleratedStall is the largest caller stall the load judges tolerate in an isolated generator sample: two 20 ms send-lateness limits plus one audio frame. Such a stall can stamp the packet before an outage late and shorten the measured gap by up to this much (#133).
const MediaGapCeiling = 150 * time.Millisecond
MediaGapCeiling is the whole-call interruption limit that the load judges apply outside scheduled events.
const MediaGapRetention = MediaGapCeiling - MaxToleratedStall
MediaGapRetention is the shortest gap kept in MediaGaps. A gap above MediaGapCeiling that a tolerated stall shortened is still retained.
const MinimumConclusivePercent = 80
MinimumConclusivePercent applies to every metric a scenario asserts. Unknown events do not count as passes; at least 80% must have trustworthy judgments.
Variables ¶
var ErrClockEvidence = errors.New("invalid clock evidence")
var ErrHarnessClosed = errors.New("callharness: harness closed")
ErrHarnessClosed is returned by Dial once the harness is closing.
Functions ¶
func EnoughConclusive ¶
func JudgeDistributedTrials ¶
func JudgeDistributedTrials(moves []MoveReport) (string, []string)
JudgeDistributedTrials preserves the existing count policy. It does not invent a network-loss allowance for a real unshaped run.
func RequireFreshnessRecovered ¶
func RequireFreshnessRecovered(t interface {
Helper()
Name() string
Fatalf(string, ...any)
}, event EventMeasurement, required FreshnessPolicy)
RequireFreshnessRecovered gates acceptance freshness, independently of loss. Phase-1 scenarios only report freshness; buffered takeovers in #27 can use this helper without the race detector. Acceptance tests must be named TestAcceptance… and must not skip when run without race; the timing CI target includes that prefix. Measure using CallOptions.Freshness with the required recovery limit, at least its stability window, and at most its absolute ceiling and live lateness cap. Observe the full measured stability window first. Use StrictAcceptancePolicy for in-process/LAN tests; #37 network profiles must pass their required absolute ceiling explicitly. The recovery limit is always 2s, stability is at least 3s, and live lateness is at most 200ms, even if both the measurement and required policy are weaker. Audio is required; video is required when the call expects video. Unknown or failed freshness cannot pass this gate.
Types ¶
type AnswerFacts ¶
type AnswerFacts struct {
SDP string
ICELite bool // a=ice-lite at session level
Bundle []string // MIDs in a=group:BUNDLE
RTCPMux bool // every accepted m-line has a=rtcp-mux
Candidates []string // every a=candidate value
// Media are the answer's m-lines in order.
Media []AnswerMedia
}
AnswerFacts is what the caller can read from the SDP answer.
func (AnswerFacts) AcceptedMedia ¶
func (a AnswerFacts) AcceptedMedia(kind string) *AnswerMedia
AcceptedMedia returns the first accepted m-line of a kind, or nil.
func (AnswerFacts) HostCandidates ¶
func (a AnswerFacts) HostCandidates() []string
HostCandidates returns the answer's host candidates.
type AnswerMedia ¶
type AnswerMedia struct {
Kind string // "audio", "video", "application", ...
MID string
Port int // 0 when the answer rejects the m-line
// Formats are the payload types; Codecs are their a=rtpmap values (for
// example "opus/48000/2"), and RTCPFeedback the a=rtcp-fb values.
Formats []string
Codecs []string
RTCPFeedback []string
}
AnswerMedia is one m-line of the answer.
func (AnswerMedia) Accepted ¶
func (m AnswerMedia) Accepted() bool
Accepted reports whether the answer accepts the m-line.
type Call ¶
type Call struct {
// contains filtered or unexported fields
}
Call is one call made by the caller: a Pion PeerConnection that sends pre-encoded Opus audio (and VP8 video) and records everything it observes.
func (*Call) ArmDistributedEvent ¶
func (c *Call) ArmDistributedEvent(id OperationIdentity) error
func (*Call) ClockProbes ¶
func (c *Call) ClockProbes() []ClockProbe
func (*Call) DialReport ¶
func (c *Call) DialReport() DialReport
DialReport returns what the caller observed while the call connected.
func (*Call) Handover ¶
func (c *Call) Handover(opts HandoverOptions) error
Handover moves the call to another worker through the relay control plane or on a shared socket. The old worker exports the session to bytes and the new one resumes them. It needs Options.Workers of 2 or more. The report describes each move as the caller saw it (Report.Moves).
func (*Call) Hangup ¶
Hangup ends the call and returns what the caller observed. It waits briefly for the last echoed packets, stops recording, sends the WHIP-style DELETE, closes the PeerConnection and runs the full video decode.
func (*Call) MoveExternal ¶
MoveExternal uses the operator HTTP endpoint and records the same caller yardstick as Handover. Worker names in the process driver are integer IDs. A definite control_not_ready refusal before any move starts can wait for readiness within the original two-second API budget. Started or uncertain outcomes are never replayed.
func (*Call) RecordDistributedEvent ¶
func (c *Call) RecordDistributedEvent(e DistributedEvent) error
func (*Call) RecordExternalMove ¶
func (c *Call) RecordExternalMove(result controlplane.MoveResult)
RecordExternalMove records a completed operator move (for example a drain). It observes the real control-plane result; it does not initiate recovery.
func (*Call) Recording ¶
func (c *Call) Recording() RecordingReport
Recording snapshots retained caller history while a call is still running.
func (*Call) RequestKeyframe ¶
func (c *Call) RequestKeyframe(request KeyframeRequest) error
RequestKeyframe asks the system for a keyframe on the echoed video, as a browser's receiver does when it cannot decode. It fails until the echoed video track has arrived.
func (*Call) SendLongRun ¶
func (c *Call) SendLongRun(ctx context.Context, o LongRunOptions) (*LongRunReport, error)
SendLongRun runs one long call, schedules events, samples its memory, then hangs up. Dial with RecordingOptions{History:15*time.Second, DecodeEvery:30} to select bounded online sampling. Cancellation still closes the call.
type CallOptions ¶
type CallOptions struct {
// Freshness configures the caller's bounded recovery evaluation.
// Defaults stay strict (2s recovery + 3s stability), including under race.
// Acceptance callers such as #27 can set RecoveryLimit: 2*time.Second
// explicitly and use RequireFreshnessRecovered with the required policy
// (StrictAcceptancePolicy by default) without race. Phase-1
// scenarios report freshness without gating it, with or without race.
// These defaults and measured recovery times remain strict. Allow
// enough media to observe Freshness.EvaluationWindow().
Freshness FreshnessPolicy
// SendTiming observes each successful RTP socket write. It must be fast
// and safe for concurrent audio/video sends. Scheduled is the delivered
// tick that triggered the frame; actual is after the UDP write.
SendTiming func(kind string, scheduled, actual time.Time)
// SendPacing counts successful frames and dropped ticker opportunities.
// Video fragments count as one frame. The callback must be concurrent-safe.
SendPacing func(kind string, sent, skipped uint64)
Recording RecordingOptions
// SRTPDiagnostics authenticates a separate copy of received media for
// bounded packet/index diagnostics. It never changes Pion's accounting.
SRTPDiagnostics bool
// InitialSequenceNumbers replaces random RTP starts for wrap tests. Nil
// keeps Pion's random starts. Both tracks keep their normal packetizers.
InitialSequenceNumbers *RTPSequenceNumbers
// Video adds a VP8 video track next to the Opus audio track, on the same
// bundled connection.
Video bool
// VideoData optionally supplies a VP8 IVF fixture. Nil uses the embedded
// 320x240/30 fps fixture (natural keyframe interval one second).
VideoData []byte
// BrowserLikeOffer makes the caller offer what a browser offers rather
// than just the codecs it sends: every codec Pion knows (VP8, VP9, H264,
// H265 and AV1, each with RTX), RED, ULPFEC, telephone-event and comfort
// noise, extra RTP header extensions, and an RTX ssrc-group for video.
BrowserLikeOffer bool
// Worker is the index of the media worker that takes the call (see
// Options.Workers); 0 is the first in an owned topology.
// In external mode the number is the literal registration name: the zero
// value CallOptions{} pins to "0", which must exist.
// -1 leaves placement unpinned on relay/control-plane topologies.
Worker int
}
CallOptions shape the call the caller makes.
type CandidatePairChecks ¶
type CandidatePairChecks struct {
Local string `json:"local"`
Remote string `json:"remote"`
ChecksSent uint64 `json:"checks_sent"`
ResponsesReceived uint64 `json:"responses_received"`
FirstCheck time.Duration `json:"first_check_ns,omitempty"`
LastCheck time.Duration `json:"last_check_ns,omitempty"`
LastResponse time.Duration `json:"last_response_ns,omitempty"`
}
CandidatePairChecks counts the binding requests the caller sent on one candidate pair and the success responses it received. A zero time means the event did not happen.
type ClockProbe ¶
type ClockProbe struct {
CallerSend time.Time `json:"caller_send"`
CallerReceive time.Time `json:"caller_receive"`
ServerReceive time.Time `json:"server_receive"`
ServerSend time.Time `json:"server_send"`
}
func ProbeClock ¶
ProbeClock uses the caller's existing trusted/authenticated HTTP client.
func (ClockProbe) Offset ¶
func (p ClockProbe) Offset() (OffsetInterval, error)
type ClockRateInterval ¶
type ClockRateInterval struct {
LowerPPM float64 `json:"lower_ppm"`
UpperPPM float64 `json:"upper_ppm"`
}
ClockRateInterval is the envelope of frontend-minus-caller average rate bounds from probe pairs; the relative rate need not stay constant. A WAN pair may provide only a wide bound; its midpoint is never treated as a rate.
type ClockTracker ¶
type ClockTracker struct {
// contains filtered or unexported fields
}
ClockTracker retains timestamp probes. A detected jump invalidates the run, including earlier events; accepting a later sample cannot repair clock trust.
func NewClockTracker ¶
func NewClockTracker(ppm float64) (*ClockTracker, error)
func (*ClockTracker) Add ¶
func (c *ClockTracker) Add(p ClockProbe) error
func (*ClockTracker) DriftPPM ¶
func (c *ClockTracker) DriftPPM() float64
DriftPPM is the immutable relative rate bound used for event mapping.
func (*ClockTracker) Err ¶
func (c *ClockTracker) Err() error
func (*ClockTracker) Invalidate ¶
func (c *ClockTracker) Invalidate(err error)
Invalidate preserves a failed probe as lost clock trust for this run.
func (*ClockTracker) Map ¶
func (c *ClockTracker) Map(server time.Time, host OffsetInterval) (TimeInterval, error)
Map composes frontend-minus-caller probes with host-minus-frontend bounds. Intersect all drift-widened samples at the event. A low-RTT earlier probe can provide a tighter bound than the nearest probe; repeated measurements never assume symmetric one-way latency or divide the full bound by two.
func (*ClockTracker) ObservedRate ¶
func (c *ClockTracker) ObservedRate() *ClockRateInterval
func (*ClockTracker) Probes ¶
func (c *ClockTracker) Probes() []ClockProbe
type ConsentReport ¶
type ConsentReport struct {
// RequestsSent and ResponsesReceived are the totals until hangup.
RequestsSent uint64
ResponsesReceived uint64
// Since is when the window below starts: the end of the last move, or
// when the call connected if nothing moved. ObservedFor runs from there
// to hangup, or to when the caller's connection closed or failed if that
// came first.
Since time.Duration
ObservedFor time.Duration
// ResponsesAfter counts responses received in the window, and
// LongestWithoutResponse is the longest stretch of it without a new
// response, the stretch from the last response to the end of the window
// included. The caller checks every 2 s; with no packet at all for 5 s
// its connection goes "disconnected".
ResponsesAfter uint64
LongestWithoutResponse time.Duration
}
ConsentReport is what the caller observed of its ICE consent checks: the STUN binding requests it sent and the binding success responses to them, seen on its own UDP socket (see consent.go).
type ContentDelivery ¶
type ContentDelivery struct {
Sent, Returned, Lost, Unsettled int
LostAt []time.Duration
LostOverflow int
}
ContentDelivery counts one kind's identified content over the whole call, once per successfully written audio unit or video frame. A unit settles when it leaves rolling history, or at hangup when it was sent at least the 500ms content settle window earlier; later units are Unsettled and unjudged. Lost uses the yardstick's rule: no valid complete return by then. Late returns are not lost. LostAt holds each lost unit's send time (caller clock, sorted), up to MaxRetainedLostUnits; further losses only add LostOverflow.
type ContentLossCounts ¶
ContentLossCounts counts identified, successfully written content, once per audio unit or complete video frame. Loss means no valid return by the same observation horizon used by EventMeasurement, independent of decoding.
type ContentLossEvidence ¶
type ContentLossEvidence struct {
Event ContentLossWindow
Baselines []ContentLossWindow
}
ContentLossEvidence retains raw denominators and matched no-event windows. Baselines have the event window's exact length, do not overlap each other, and end at least one settle window before the event window starts. It adds evidence without changing the caller yardstick's exact zero-loss verdicts.
type ContentLossWindow ¶
type ContentLossWindow struct {
Start, End time.Duration
Audio, Video ContentLossCounts
}
type DecodeFailure ¶
type DecodeFailure string
DecodeFailure separates confirmed invalid bitstreams from an unavailable check (timeout, missing decoder, or execution failure).
const ( DecodeInvalid DecodeFailure = "invalid bitstream" )
type DecryptionFailures ¶
type DecryptionFailures struct {
AuthTag int // authentication tag did not verify (wrong key, index or rollover counter)
Replay int // rejected by the replay window
Other int
}
DecryptionFailures classifies packets the caller could not decrypt.
func (DecryptionFailures) Total ¶
func (d DecryptionFailures) Total() int
Total is the number of packets the caller could not decrypt.
type DialError ¶
type DialError struct {
Report DialReport
// contains filtered or unexported fields
}
DialError is every error Dial returns. It keeps the dial's evidence; errors.Is and errors.As see the underlying error, and Error returns its message unchanged.
type DialReport ¶
type DialReport struct {
Outcome string `json:"outcome"`
// Failure is the kind of a failed dial (DialFailure*); empty if the
// dial connected. Error is the dial's error.
Failure string `json:"failure,omitempty"`
Error string `json:"error,omitempty"`
// Session is the call's session ID, the server's ICE username fragment;
// empty if no answer was applied.
Session string `json:"session,omitempty"`
// Elapsed is when the dial connected or failed.
Elapsed time.Duration `json:"elapsed_ns"`
// The ICE, DTLS and PeerConnection states when the dial connected or
// failed, and every change of each before then.
ICEState string `json:"ice_state"`
DTLSState string `json:"dtls_state"`
ConnectionState string `json:"connection_state"`
ICEStates []StateChange `json:"ice_states"`
DTLSStates []StateChange `json:"dtls_states"`
ConnectionStates []StateChange `json:"connection_states"`
// CandidatePairs counts the caller's connectivity checks on each
// candidate pair, as observed on the caller's socket.
CandidatePairs []CandidatePairChecks `json:"candidate_pairs"`
}
DialReport is what the caller observed while one dial set up its call: how ICE, DTLS and the PeerConnection progressed, and how the caller's connectivity checks were answered. Times are offsets from the start of the dial. A failed dial returns it in a *DialError; a connected one keeps it in Call.DialReport.
type DistributedEvent ¶
type DistributedEvent struct {
Identity OperationIdentity `json:"identity"`
Kind string `json:"kind"`
Start time.Time `json:"start"`
End time.Time `json:"end"`
Result controlplane.MoveResult `json:"result"`
Uncertain bool `json:"uncertain,omitempty"`
}
DistributedEvent is a durable server receipt, never a command submission. Start is control-host move start or worker-host actual SIGKILL time. End is control-host completion. Uncertain preserves unknown operation outcomes.
type DistributedGap ¶
type DistributedGap struct {
BaselinePossible []time.Duration `json:"baseline_possible_ns"`
BaselineCertain []time.Duration `json:"baseline_certain_ns"`
Kind string `json:"kind"`
BaselineP95 DurationInterval `json:"baseline_p95"`
EventGap DurationInterval `json:"event_gap"`
Outcome string `json:"outcome"`
Causes []string `json:"causes,omitempty"`
}
type DistributedMeasurement ¶
type DistributedMeasurement struct {
Durations map[string]DurationInterval `json:"durations,omitempty"`
// Evaluations counts start/end/floor cells, each checked against all first baselines.
Evaluations int `json:"evaluations"`
FirstBaselines []FirstBaselines `json:"first_baselines,omitempty"`
BaselineFloor DurationInterval `json:"baseline_floor"`
Mapped bool `json:"mapped"`
AudioLoss MetricVerdict `json:"audio_loss"`
VideoLoss MetricVerdict `json:"video_loss"`
WindowOutcome string `json:"window_outcome"`
Start TimeInterval `json:"start"`
End TimeInterval `json:"end"`
KillToLive *DurationInterval `json:"kill_to_live,omitempty"`
Gaps []DistributedGap `json:"gaps"`
Outcome string `json:"outcome"`
Causes []string `json:"causes,omitempty"`
}
DistributedMeasurement keeps raw caller gaps independent of event clocks. Union windows prove passes; intersection windows can prove failures. A failure seen only in the uncertainty margin is inconclusive.
type DistributedOptions ¶
type DistributedOptions struct {
RunID string
Clock *ClockTracker
// Host minus frontend offsets. These are measured input bounds, not a
// claim that /clock observes control or worker time.
ControlOffset OffsetInterval
WorkerOffset OffsetInterval
}
type DistributedRecording ¶
type DistributedRecording struct {
ObservedRate *ClockRateInterval `json:"observed_rate_ppm,omitempty"`
DriftPPM float64 `json:"drift_ppm"`
Armed []OperationIdentity `json:"armed"`
Missing []OperationIdentity `json:"missing_receipts,omitempty"`
RunID string `json:"run_id"`
ControlOffset OffsetInterval `json:"control_offset"`
WorkerOffset OffsetInterval `json:"worker_offset"`
Probes []ClockProbe `json:"probes"`
ClockError string `json:"clock_error,omitempty"`
Outcome string `json:"outcome"`
Causes []string `json:"causes,omitempty"`
}
DistributedRecording makes clock evidence reviewable in the final artifact.
type DurationInterval ¶
type EventMeasurement ¶
type EventMeasurement struct {
ContentLoss ContentLossEvidence
FirstContentIdentity, FirstNewContentIdentity uint64
VideoExpected bool
AudioPause, VideoPause time.Duration
WindowStart, WindowEnd, SettledAt, ObservedUntil time.Duration
SettleWindow time.Duration
LostAudio, LateAudio time.Duration
LostVideoFrames, LateVideoFrames int
AudioLoss, VideoLoss MetricVerdict
Audio, Video FreshnessReport
MediaResumedAt time.Duration
ResumeVerdict MetricVerdict
FirstLiveFrame, FirstNewFrame time.Duration
FirstContent, FirstNewContent string
FirstContentPictureID, FirstNewContentPictureID uint16
FirstContentSentAt, FirstNewContentSentAt time.Duration
FirstContentVerdict, FirstNewContentVerdict, FirstLiveVerdict MetricVerdict
DecodedFrames int
DecodedSamples int
Inconclusive, Failed bool
Reasons []string
}
EventMeasurement separates interruption, missing content, content age and decoded recovery. All times use the caller clock, not RTP timestamps. Pause retains the existing per-kind MoveTrackReport.Gap. Loss counts successful sends in [Start-250ms, max(End,MediaResumedAt)+250ms). Lost means no valid complete return by ObservedUntil: hangup, or the saved summary horizon for an event whose bounded history expired. Returns after SettledAt are late, separately from loss. Hangup before SettledAt makes loss unknown. Duplicate/reordered returns count once. Nearby event windows can overlap; never sum their losses. Video loss does not depend on decoding. FirstContent classifies the first decoded frame after resume. Prior complete receipt before Start means cache, regardless of who replays the frame. FirstNewContent skips those prior receipts. Its classes are keyframe (PLI/FIR requested at/after Start), outage media (sent from WindowStart until resume with latency above baseline p95+tolerance), natural keyframe (a keyframe restarting a broken decode chain without a post-event request), or live. Ticket #27 passes its first-new-content gate only for "outage media"; a natural keyframe wait does not pass it. Ordinary in-flight frames are live. FirstNewFrame is event-to-decode for that new content. FirstLiveFrame retains event-to-decode for the first frame sent at/after MediaResumedAt. Each verdict applies to one metric. Inconclusive is informational: at least one metric is unknown. Failed means at least one trusted metric failed. Missing resume/decoded content/recovery are failures, not uncertainty. Conflicting payload identities fail the affected video metrics, including when their observation window or another metric is inconclusive.
func (EventMeasurement) Conclusive ¶
func (m EventMeasurement) Conclusive() bool
func (EventMeasurement) ContentRecoveryFailed ¶
func (m EventMeasurement) ContentRecoveryFailed() bool
ContentRecoveryFailed gates resume and decoded-content evidence, including identity conflicts, without applying a freshness acceptance limit. Phase-1 crash scenarios use this gate and retain loss/coverage as separate checks. Lost outage content is expected in today's unbuffered crashes.
func (EventMeasurement) LossConclusive ¶
func (m EventMeasurement) LossConclusive() bool
func (EventMeasurement) RecoveryFailed ¶
func (m EventMeasurement) RecoveryFailed() bool
RecoveryFailed excludes loss, which is expected in today's unbuffered crashes.
func (EventMeasurement) Summary ¶
func (m EventMeasurement) Summary() string
Summary prints an event record with explicit units and measurement policy.
type ExternalTopology ¶
type ExternalTopology struct {
Distributed *DistributedOptions
SignalingURL string
RelayAddr netip.AddrPort
// HTTPClient supplies signaling and control requests, including status and
// hangup. Nil uses the ordinary ten-second client. The caller owns the
// client and its transport; the harness neither changes nor closes them.
HTTPClient *http.Client
// CallerSocket creates the caller's UDP4 media socket. Nil preserves the
// loopback socket and candidates. A supplied socket must bind a specific
// IPv4 address; the harness owns and closes it. Signaling stays separate.
CallerSocket func() (net.PacketConn, error)
// Status optionally supplies a shared status view for external callers.
// Nil keeps the ordinary per-call HTTP reads and local harness behavior.
Status func(context.Context) (controlplane.Status, error)
// FinalStatus optionally refreshes the shared status view at Hangup with
// a final-collection timeout. Nil preserves the ordinary Status path.
FinalStatus func(context.Context) (controlplane.Status, error)
}
ExternalTopology connects the real caller to an already running system. The harness owns only its callers; it never stops external processes.
type FirstBaselines ¶
type FirstBaselines struct {
Audio FreshnessBaseline `json:"audio"`
Video FreshnessBaseline `json:"video"`
}
type FreshnessBaseline ¶
type FreshnessBaseline struct {
Samples int `json:"samples"`
Median time.Duration `json:"median_ns"`
P95 time.Duration `json:"p95_ns"`
}
These are caller-local baseline observations, not server-clock estimates. Keep every distinct first-event baseline so later events cannot silently treat one possible noisy/slow first baseline as an established reference.
type FreshnessPolicy ¶
type FreshnessPolicy struct {
RecoveryLimit, StabilityWindow time.Duration
AbsoluteLatencyCeiling, LiveLatenessCap time.Duration
}
FreshnessPolicy configures event recovery attribution. Nonpositive values use defaults: 2s recovery, 3s stability, a 50ms absolute median ceiling, and a 200ms live lateness cap above baseline p95. Set the ceiling per call for a different network latency profile. RecoveryLimit is explicit through CallOptions.Freshness and retained on the report. Buffered-takeover acceptance (#27) uses 2s without race via RequireFreshnessRecovered. Existing phase-1 scenarios only report freshness, with or without race. Defaults and measured recovery times never change under the race detector.
func StrictAcceptancePolicy ¶
func StrictAcceptancePolicy() FreshnessPolicy
StrictAcceptancePolicy is the in-process/LAN buffered-takeover acceptance policy. Network profiles can set their required absolute ceiling explicitly.
func (FreshnessPolicy) EvaluationWindow ¶
func (p FreshnessPolicy) EvaluationWindow() time.Duration
EvaluationWindow is the full event observation duration, applying defaults.
type FreshnessReport ¶
type FreshnessReport struct {
Verdict MetricVerdict
RecoveryLimit, StabilityWindow, EvaluationEnd time.Duration
StabilityObserved time.Duration
AbsoluteLatencyCeiling, LiveLatenessCap time.Duration
AbsoluteLag bool
LateWindowUnits, LateWindowLimit, LatePercentLimit int
LiveUnits, LateLiveUnits int
JitterUnits int
JitterMaxExcess time.Duration
FirstBaselineTrusted bool
LastAboveTolerance time.Duration
FirstBaselineSamples int
FirstBaselineMedian, FirstBaselineP95 time.Duration
LastingLag bool
BaselineStart time.Duration
BaselineSamples, PostEventSamples int
BaselineMedian, BaselineP95 time.Duration
PeakLatency, PeakAboveBaseline time.Duration
BackToBaseline time.Duration
Recovered bool
Tolerance time.Duration
SustainedUnits int
SpreadLimit time.Duration
}
FreshnessReport uses one latency sample per unique returned content unit: arrival minus send time (video arrival is completion of all frame packets). BaselineMedian and BaselineP95 use units received in the preceding 1s whose sends also precede the event. After a preceding takeover, the window starts no earlier than that event's measured audio/video recovery, or its end/resume if recovery failed. A failed event cannot truncate every later baseline. BaselineStart records the actual window start. Ten samples are required; p95-minus-median must be <=20ms. PeakLatency includes every post-event cache/outage return, even repeated content. Repeats cannot add samples or confirm recovery, but a stale repeat interrupts a run of fresh units. Recovery is evaluated only through EvaluationEnd: event start plus RecoveryLimit (default 2s) and StabilityWindow (default 3s), clipped at the next event/hangup. StabilityObserved measures the observed portion after Start+RecoveryLimit. A shortened window is inconclusive, unless a known absolute-latency or identity failure applies. Later units do not affect this event, including its peak. BackToBaseline is the start of the last fresh run confirmed by three unique units. Any post-resume receipt sent before resume breaks the run, regardless of latency. After a candidate run starts, 3 or more late returns in any 10 consecutive eligible live returns break it. At window end, more than 10% late returns since the final candidate start also break it. LateWindowUnits, LateWindowLimit and LatePercentLimit record these rules. Fresh duplicates cannot improve confirmation or dilute either budget. LiveUnits/LateLiveUnits count eligible post-resume returns across the whole evaluation. Short live lateness streaks remain diagnostics, independently of the budget verdict. Any live return later than baseline p95 + LiveLatenessCap (default 200ms) breaks recovery, including duplicates, inside the evaluation window. JitterUnits and JitterMaxExcess report their count and maximum excess over baseline p95+tolerance. Repeats cannot confirm recovery. LastAboveTolerance records the last above-threshold unit. No recovery at window end, or recovery after RecoveryLimit, fails a trusted metric. FirstBaseline* retain the first call baseline; LastingLag flags a relative median/p95 above that trusted baseline by more than tolerance. LastingLag is diagnostic-only: small-sample p95 changes do not gate. AbsoluteLag fails when the trusted first baseline median exceeds the configurable AbsoluteLatencyCeiling (50ms by default for in-process/LAN). Freshness measures content age independently of whether it decoded.
type FullDecode ¶
type FullDecode struct {
// Skipped says why the decode did not finish (for example, a timeout or
// ffmpeg not on PATH); empty when it completed.
Skipped string
Decoder string // the ffmpeg binary
FramesIn int // frames given to the decoder
FramesDecoded int // frames the decoder output
Errors string // the decoder's error output; empty when clean
Failure DecodeFailure
}
FullDecode is the result of decoding the received video with ffmpeg.
type HandoverOptions ¶
type HandoverOptions struct {
// To is the index of the worker that takes the call over (0 is the
// worker calls start on).
To int
// SequenceMargin moves the echoed tracks' sequence numbers forward by
// this much across the move; see mediaworker.ResumeOptions. Zero, the
// right value for a planned handover, keeps them continuous.
SequenceMargin uint16
}
HandoverOptions shape a planned handover.
type Harness ¶
type Harness struct {
// contains filtered or unexported fields
}
Harness runs a caller against either local workers and signaling, or an externally owned process topology. External mode owns only its callers.
func (*Harness) Close ¶
Close closes any calls still open, then stops the signaling server, the media workers and the relay. In external mode it closes callers only. Dial fails with ErrHarnessClosed from the moment Close starts.
func (*Harness) Dial ¶
Dial starts a call: it makes one WHIP-style offer/answer exchange with the system and waits until the caller's connection is "connected". It fails with ErrHarnessClosed once the harness is closing. Every error it returns is a *DialError that carries what the caller observed.
func (*Harness) Drain ¶
Drain moves all calls off worker through the HTTP endpoint and records each move for the caller's gap and consent reports.
func (*Harness) ExchangeOffer ¶
ExchangeOffer posts an SDP offer the way a browser would, for example one captured from Chrome, and returns what the answer says. It then hangs the call up again; no media flows.
func (*Harness) Kill ¶
Kill simulates process death without a flush, export, release or close_notify. Recovery is driven solely by missed heartbeats, never by this action.
func (*Harness) KillBeforeSnapshot ¶
KillBeforeSnapshot gates the next periodic copy and kills without flushing, so a stale-snapshot kill is deterministic rather than chosen by chance.
func (*Harness) KillMidKeyframe ¶
KillMidKeyframe stops after n packets were echoed, before the marker packet. The cancellable gate prevents the worker from finishing the frame first.
func (*Harness) RecordProcessKill ¶
RecordProcessKill aligns the control plane's takeover event with the real SIGKILL action. It does not cause or accelerate recovery.
func (*Harness) RelayAddr ¶
RelayAddr is the relay's public address, which every answer advertises; the zero value without Options.Relay.
func (*Harness) ReplayStats ¶
func (h *Harness) ReplayStats(worker int) (mediaworker.ReplayStats, error)
ReplayStats snapshots a worker's replay diagnostics. A fresh harness per recovery trial makes these counters specific to that trial.
func (*Harness) RestartRelay ¶
RestartRelay replaces the relay with a new one on the same public and relay-leg addresses, as a restarted relay process would come back: the flow table is lost, while the session-owner store and the workers carry on, and the new relay is configured with the same workers. It returns once the new relay is listening, with how long no relay was listening.
func (*Harness) RestartWorker ¶
RestartWorker creates an empty replacement on a new private socket after all the previous registration's leases have been handled.
func (*Harness) SessionOwner ¶
SessionOwner returns the index of the worker that owns a session, as the relay's session-owner store records it (see Call.SessionID). It reports false without Options.Relay, or when no worker owns the session.
func (*Harness) SignalingURL ¶
SignalingURL is where a caller POSTs its SDP offer.
type KeyframeRequest ¶
type KeyframeRequest int
KeyframeRequest is an RTCP message that asks a sender for a keyframe.
const ( // PLI is a Picture Loss Indication (RFC 4585). PLI KeyframeRequest = iota // FIR is a Full Intra Request (RFC 5104). FIR )
type LongRunOptions ¶
type LongRunOptions struct {
Duration, EventEvery, SampleEvery time.Duration
Event func(context.Context, int) error
HeapAfterGC bool
ObserveMemory func(MemorySample)
}
LongRunOptions schedules caller/operator events while the normal SendMedia path runs. Event receives a zero-based event index; nil means no events. HeapAfterGC is for isolated memory checks, and pauses this Go process at each sample. Heap numbers describe the whole process, not just the recorder.
type LongRunReport ¶
type LongRunReport struct {
Call *Report
Memory []MemorySample
SamplesDropped int
}
LongRunReport retains at most 256 memory samples, plus the normal per-event caller summaries. SamplesDropped records earlier samples rolled away.
type MediaGap ¶
MediaGap records an interval between successfully decrypted packets. These sparse observations survive rolling history, up to MaxRetainedMediaGaps. Only gaps above MediaGapRetention are kept. MediaGapOverflow counts dropped gaps above MediaGapCeiling; a full inventory may also omit shorter gaps.
type MemorySample ¶
type MemorySample struct {
At time.Duration
HeapAlloc uint64
Recording RecordingReport
}
type MetricVerdict ¶
MetricVerdict separates trust from an observed failure. Unknown metrics must not mask failures in other trusted metrics. Reasons explains either result.
func (MetricVerdict) Summary ¶
func (v MetricVerdict) Summary() string
type MoveReport ¶
type MoveReport struct {
Receipt *DistributedEvent `json:"receipt,omitempty"`
Identity *OperationIdentity `json:"identity,omitempty"`
Distributed *DistributedMeasurement `json:"distributed,omitempty"`
Lost bool `json:"lost,omitempty"`
LossCause string `json:"loss_cause,omitempty"`
// Recovery measures video recovery from the caller's first post-margin RTP
// packet, including replay received while ResumeSession is still returning.
Recovery VideoRecovery
// Measurement is the caller content yardstick, independent of sequence
// margins and of the system account in Result.
Measurement EventMeasurement
Kind string // "move" or "takeover"
DetectionTime time.Duration // failure action to control-plane detection
DecryptionFailuresAfterResume int
From, To int // worker indexes
// Start and End bracket the handover call, as offsets from dialing.
Start, End time.Duration
// Error is the handover's error; empty when it succeeded.
Error string
// Result is the system's account of the move: exported state size,
// caller packets held and timings.
Result mediaworker.HandoverResult
// Tracks describe each received track around the move.
Tracks []MoveTrackReport
// contains filtered or unexported fields
}
MoveReport is one planned handover as the caller observed it, plus what the system reported about it.
type MoveTrackReport ¶
type MoveTrackReport struct {
Kind string
// Gap is the longest interval between consecutive packets around the
// move: the media gap the move caused. TypicalInterval is the track's
// median interval between packets, for comparison.
BaselineGapP95 time.Duration
BaselineGapSamples int
Gap time.Duration
TypicalInterval time.Duration
// SkippedSequenceNumbers counts sequence numbers missing around the
// move: a sequence margin's jump, or lost packets.
SkippedSequenceNumbers int
// LargestTimestampStep is the largest RTP timestamp step between
// consecutive packets around the move; TypicalTimestampStep is the
// track's median step between packets of different frames. With
// continuous timestamps the first is no larger than one frame's step.
LargestTimestampStep uint32
TypicalTimestampStep uint32
// PacketsAfter counts packets that arrived after the move ended.
PacketsAfter int
// FirstDecodableFrameAfter is, for video, the time from the end of the
// move to the first decodable frame after it; zero if none arrived.
FirstDecodableFrameAfter time.Duration
// contains filtered or unexported fields
}
MoveTrackReport is one received track around a move. "Around the move" runs from the last packet that arrived before the move started to the first that arrived after it ended.
type OffsetInterval ¶
type OffsetInterval struct {
Lower time.Duration `json:"lower_ns"`
Upper time.Duration `json:"upper_ns"`
}
OffsetInterval is server minus caller time. Bounds are inclusive. Durations and media gaps always stay on the caller's monotonic clock.
func (OffsetInterval) Compose ¶
func (i OffsetInterval) Compose(other OffsetInterval) (OffsetInterval, error)
func (OffsetInterval) Map ¶
func (i OffsetInterval) Map(server time.Time) TimeInterval
func (OffsetInterval) Valid ¶
func (i OffsetInterval) Valid() bool
func (OffsetInterval) Widen ¶
func (i OffsetInterval) Widen(elapsed time.Duration, ppm float64) (OffsetInterval, error)
type OnlineDecodeReport ¶
OnlineDecodeReport counts checked target frames, not the reference frames ffmpeg must decode before an interframe. Keyframes are always decoded in Go. A full queue drops a sample explicitly; it never blocks the RTP reader.
func (OnlineDecodeReport) Conclusive ¶
func (d OnlineDecodeReport) Conclusive() bool
func (OnlineDecodeReport) SampleRate ¶
func (d OnlineDecodeReport) SampleRate(frames int) float64
type OperationIdentity ¶
type OperationIdentity struct {
RunID string `json:"run_id"`
OperationID string `json:"operation_id"`
SessionID string `json:"session_id"`
Worker string `json:"worker"`
Epoch uint64 `json:"epoch"`
}
OperationIdentity is supplied by the operator feed, on arming and on the durable server receipt. Worker is the unique registration/generation name; Epoch identifies the ownership tenure affected, not its successor.
func (OperationIdentity) Valid ¶
func (i OperationIdentity) Valid() bool
type Options ¶
type Options struct {
// External selects a process topology instead of starting local workers.
External *ExternalTopology
// WorkerLoggerFactory is passed to the media workers (and the relay).
// Defaults to Pion's default logger factory (PION_LOG_* environment
// variables).
WorkerLoggerFactory logging.LoggerFactory
// Workers is how many media workers run. Without Relay, one (the
// default) owns its UDP socket; two or more share one UDP socket, calls
// start on the first, and Call.Handover moves a call between them.
// With Relay, each worker owns a private socket behind the relay, and a
// call picks its worker with CallOptions.Worker. Call.Handover uses the
// control plane to transfer ownership and re-point the relay.
Workers int
// Relay runs every call through a relay with a session-owner store
// (Memory by default): answers advertise the relay's public address, and the workers
// bind only private sockets behind it.
Relay bool
// SessionStore selects the relay topology's store. Nil uses Memory.
// The caller owns its lifetime; it must outlive Harness.Close.
SessionStore sessionstore.Store
// FrameCache selects a shared cache, including Redis. Nil uses Memory.
// The caller owns its lifetime and must keep it open through Harness.Close.
FrameCache framecache.Store
// SnapshotInterval sets the worker snapshot cadence for crash tests.
// TakeoverConfig overrides control-plane bounds for fault scenarios.
TakeoverConfig controlplane.Config
SnapshotInterval time.Duration
// DisableFrameCache and DisableResumePLI independently select the video
// recovery paths. Both paths are enabled by default.
DisableFrameCache bool
DisableResumePLI bool
// ReplayMaxBurstDuration overrides the worker's one-frame replay cap.
ReplayMaxBurstDuration time.Duration
}
Options configures the system the harness starts.
type RTPSequenceNumbers ¶
RTPSequenceNumbers selects deterministic starts for the caller's tracks.
type RecordingOptions ¶
type RecordingOptions struct {
History time.Duration
DecodeEvery int
// MaintenancePhase delays the call's one-second recording maintenance
// (automatic event discovery and history pruning) by this much, so it
// runs at connect + phase + k seconds. Zero keeps connect + k seconds.
// Load generators give each call its own phase in [0, 1s), so many
// callers do not run this work on the same instant (#85).
MaintenancePhase time.Duration
}
RecordingOptions bounds caller history. Zero values preserve the short-call full-decode check, with at most two minutes of packet/content history and 4096 offline frames. Long calls should select DecodeEvery (e.g. 30), which checks samples online and retains no whole-call decode input. Event summaries survive history expiry. Their ObservedUntil names the finite observation horizon: packets older than History cannot repair a saved loss. History must cover the configured freshness evaluation plus baseline/margin.
type RecordingReport ¶
type RecordingReport struct {
History time.Duration
HistoryStart time.Duration
Packets, ContentUnits, Assemblies, FrameMarks, OfflineFrames int
EventSummaries int
DecodedPictures int
HistoryBytes int64
Delivery *WholeCallDelivery `json:",omitempty"`
}
RecordingReport is a caller-owned memory gauge, separate from process heap. EventSummaries grow only with events, not with packets or call duration. HistoryBytes counts retained payload bytes; it excludes Go object overhead. Delivery is set only in a call's final report; live gauges leave it nil.
type Report ¶
type Report struct {
// ReportBuildDuration measures evidence snapshot, evaluation and judgment.
// It excludes call teardown, optional offline decoding and JSON transport.
ReportBuildDuration time.Duration `json:"report_build_duration_ns,omitempty"`
Distributed *DistributedRecording `json:"distributed,omitempty"`
Recording RecordingReport
// StartedAt aligns packet observations across calls sharing a worker.
StartedAt time.Time
// OfferAnswerExchanges counts the HTTP offer/answer exchanges the caller
// made to start the call.
OfferAnswerExchanges int
// Offer is the caller's SDP offer, and Answer describes the SDP answer
// the caller received.
Offer string
Answer AnswerFacts
// RemoteAddr is the remote end ("ip:port") of the caller's selected ICE
// candidate pair at hangup: the one address the caller sent to and
// accepted media from.
RemoteAddr string
// ConnectedAt is when the caller's connection state first reached
// "connected"; zero if it never did.
ConnectedAt time.Duration
// HungUpAt is when the caller hung up.
HungUpAt time.Duration
// ConnectionStates and ICEConnectionStates are the caller's
// PeerConnection and ICE connection state changes up to hangup.
ConnectionStates []StateChange
ICEConnectionStates []StateChange
// DecryptionFailures counts SRTP and SRTCP packets the caller received
// but could not decrypt.
DecryptionFailures DecryptionFailures
// SRTPDiagnostics is optional independent packet evidence. DecryptionFailures
// remains the authoritative rejection count from Pion.
SRTPDiagnostics *SRTPDiagnosticReport `json:",omitempty"`
// Renegotiations counts offer/answer rounds (or negotiationneeded events)
// after the initial exchange. ICERestarts counts ICE restarts: ICE going
// back to checking after it connected, or changed ICE credentials.
Renegotiations int
ICERestarts int
// SentAudio and SentVideo are the caller's own tracks. SentVideo is the
// zero value for a call without video. FirstVideoSentAt is when the
// caller sent its first video frame.
SentAudio SentTrack
SentVideo SentTrack
FirstVideoSentAt time.Duration
// KeyframeRequestsSent counts the PLI and FIR packets the caller sent for
// the echoed video (see Call.RequestKeyframe).
KeyframeRequestsSent int
// Tracks are the tracks the caller received, in arrival order.
Tracks []TrackReport
// Moves are planned handovers and automatic crash takeovers during the call,
// as the caller observed them. Consent is what the caller observed of
// its ICE consent checks. See handover.go.
Moves []MoveReport
Consent ConsentReport
}
Report is what the caller observed during one call. Times are offsets from the moment the caller started dialing.
func (*Report) ConnectedThroughout ¶
ConnectedThroughout reports whether the caller's connection reached "connected" and stayed there until hangup.
func (*Report) HasDistributedEvidence ¶
HasDistributedEvidence marks event-relative scalar fields as point estimates. Local capacity/soak/crash judges must not use them as clock-proven evidence.
func (*Report) TimeToFirstDecodedFrame ¶
TimeToFirstDecodedFrame is how long after the connection reached "connected" the first echoed video frame decoded; zero if none did.
func (*Report) Track ¶
func (r *Report) Track(kind string) *TrackReport
Track returns the first received track of a kind ("audio" or "video"), or nil.
type SRTPDiagnosticReport ¶
type SRTPDiagnosticReport struct {
Ready bool
Packets uint64
Skipped uint64
Failures DecryptionFailures
Samples []SRTPFailureSample
SamplesOmitted uint64
}
SRTPDiagnosticReport is a shadow receiver's evidence, not the caller verdict. Only packet metadata is retained. Keys and plaintext never enter the report.
type SRTPFailureSample ¶
type SRTPFailureSample struct {
At time.Duration
Protocol string
SSRC uint32
Sequence uint16 `json:",omitempty"`
Timestamp uint32 `json:",omitempty"`
PayloadType uint8 `json:",omitempty"`
EstimatedIndex uint64
PreviousIndex uint64
PreviousIndexKnown bool
AuthenticatedIndex *uint64 `json:",omitempty"`
CiphertextSHA256 string
Bytes int
Error string
}
SRTPFailureSample identifies ciphertext without exposing its payload. At uses the same origin as moves. AuthenticatedIndex is populated only when a fresh context verifies an auth failure using an adjacent ROC (rollover counter). This probe does not deliver the packet or update the shadow receiver.
type SentTrack ¶
type SentTrack struct {
SSRC uint32
// Frames counts the frames sent: Opus packets for audio (one frame per
// packet), VP8 frames for video. Keyframes counts VP8 keyframes.
Frames int
Keyframes int
// KeyframeRequests counts the PLI and FIR packets the caller received
// for this track's SSRC.
KeyframeRequests int
}
SentTrack describes a track the caller sent.
type StateChange ¶
StateChange is one connection state transition.
type TimeInterval ¶
TimeInterval is a server event mapped onto the caller timeline.
type TrackReport ¶
type TrackReport struct {
MediaGaps []MediaGap
MediaGapsRecorded bool
MediaGapOverflow int
Kind string
SSRC uint32
PayloadType uint8
// Packets counts all packets the caller decrypted on this track.
// Arrivals holds the rolling history named by Report.Recording.
Packets int
Arrivals []time.Duration
FirstArrival time.Duration
LastArrival time.Duration
// MediaGap is the longest interval between consecutive successfully
// decrypted packets on the track; MediaGapEndedAt is when it ended.
MediaGap time.Duration
MediaGapEndedAt time.Duration
// SequenceDiscontinuities counts packets whose sequence number does not
// follow the previous packet's.
SequenceDiscontinuities int
// DuplicatePackets and OutOfOrderPackets distinguish stale sender traffic
// from forward sequence gaps caused by lost packets.
DuplicatePackets int
OutOfOrderPackets int
// UnmatchedPayloads counts audio packets whose payload is not one the
// caller sent, i.e. packets that are not an echo of the caller's media.
// Video is matched frame by frame instead (VideoReport.UnmatchedFrames).
UnmatchedPayloads int
// Video describes the frames of a video track; nil for audio.
Video *VideoReport
}
TrackReport describes a track the caller received.
type VideoRecovery ¶
type VideoRecovery struct {
// SourceVideoSkippedSequenceNumbers measures the old-live to new-live
// sequence gap excluding inserted replay packets. The first replay index
// uses a stale snapshot, so its observed jump can be smaller than the
// margin; the resumed source stream must still carry the full margin.
SourceVideoSkippedSequenceNumbers int
ReplayPackets int
// CachedPictureIDs includes even incomplete replayed frames, identified
// from received RTP payload descriptors after media resumed. This proves
// an interrupted keyframe's packets were never replayed.
CachedPictureIDs []uint16
MediaResumedAt time.Duration
// Both common-start metrics begin at the kill, for every mode. Cached
// output can precede control-plane completion, so completion is unsuitable.
FirstDecodedAfterKill time.Duration
// FirstDecodedRepeated says the first returned decodable picture reuses
// a prior decode. Its cache provenance is known, but no new decode ran.
FirstDecodedRepeated bool
// FirstDecodedLiveAfterKill measures a decoded frame sent at/after
// MediaResumedAt, and is zero when no such live frame decoded.
FirstDecodedLiveAfterKill time.Duration
LivePath string
// FirstDecodedPLIAfterKill measures the first newly decoded response to a
// keyframe request received by the caller at/after the kill. It is zero
// without that evidence, even when a natural keyframe restores video first.
FirstDecodedPLIAfterKill time.Duration
KeyframeInterval time.Duration
FirstDecodedAfterMediaResume time.Duration
FrameInterval time.Duration
WithinFrameInterval bool
Path string // "Cache", "Keyframe", "Live", or empty if no decoded frame
PictureID uint16
FrameSentAt time.Duration
}
VideoRecovery attributes a decoded frame using the VP8 PictureID and exact encoded payload observed at the caller, never worker-internal replay flags. Source identities include the send epoch; recent payload matching expires.
type VideoReport ¶
type VideoReport struct {
OnlineDecode OnlineDecodeReport
// Frames counts complete frames (every packet, in order, ending with the
// marker bit). IncompleteFrames counts frames that lost packets.
Frames int
Keyframes int
IncompleteFrames int
// RepeatedFrames counts exact pictures already decoded. They remain
// complete receipts, but are excluded from decoder inputs and samples.
RepeatedFrames int
// FrameGaps counts complete frames that did not directly follow the
// previous complete frame: frames were lost or reordered between them.
FrameGaps int
// NonMonotonicTimestamps counts complete frames whose RTP timestamp did
// not advance from the previous complete frame (modulo the RTP clock).
NonMonotonicTimestamps int
// UnmatchedFrames counts frames that are not byte for byte a frame the
// caller sent.
UnmatchedFrames int
// KeyframesDecoded counts keyframes decoded in pure Go;
// KeyframeDecodeErrors counts keyframes that failed, the last with
// LastDecodeError.
KeyframesDecoded int
KeyframeDecodeErrors int
LastDecodeError string
// DecodableFrames counts unique frames that decode: decoded keyframes, and
// interframes in an unbroken run of complete frames since one.
// FramesBeforeFirstKeyframe counts frames that arrived before any
// keyframe decoded (a receiver waits for a keyframe). UndecodableFrames
// counts later frames whose references were lost.
DecodableFrames int
FramesBeforeFirstKeyframe int
UndecodableFrames int
// FirstDecodedFrameAt is when the first frame decoded; zero if none did.
// Width and Height are that keyframe's size.
FirstDecodedFrameAt time.Duration
Width, Height int
// FullDecode is ffmpeg's complete short-call check. Long calls select
// OnlineDecode; offline history is capped and a truncated check is skipped.
FullDecode FullDecode
}
VideoReport is what the caller could do with a received VP8 track: the frames it reassembled and whether they decode. See video.go for the method.
type WholeCallDelivery ¶
type WholeCallDelivery struct {
Audio, Video ContentDelivery
}
WholeCallDelivery survives rolling history as running counts, so a long call reports every unit without retaining every unit.
Source Files
¶
- call.go
- caller_socket.go
- clock.go
- consent.go
- content_loss.go
- controlplane.go
- crypto_diagnostics.go
- dial_report.go
- distributed.go
- distributed_classification.go
- external.go
- external_move.go
- frame_pacer.go
- freshness_assert.go
- handover.go
- harness.go
- lifecycle.go
- logger.go
- long_run.go
- measurement_snapshot.go
- media.go
- online_decode.go
- recorder.go
- recording.go
- recovery.go
- report.go
- send_timing.go
- takeover.go
- topology.go
- video.go
- yardstick.go