The Stats interceptor provides comprehensive statistics collection for RTP and RTCP streams, recording metrics about packets, bytes, jitter, loss, and more.
Overview
The Stats interceptor:
Records all packets : Tracks both incoming and outgoing RTP/RTCP
Per-stream statistics : Maintains separate stats for each SSRC
Real-time metrics : Provides current statistics on demand
Minimal overhead : Uses efficient background processing
Basic Usage
import (
" github.com/pion/interceptor "
" github.com/pion/interceptor/pkg/stats "
)
// Create stats interceptor
statsFactory , err := stats . NewInterceptor ()
if err != nil {
panic ( err )
}
// Get notified when new peer connections are created
statsFactory . OnNewPeerConnection ( func ( id string , getter stats . Getter ) {
log . Printf ( "New peer connection: %s " , id )
// Use getter to retrieve stats for specific SSRCs
go monitorStats ( getter )
})
// Register with interceptor registry
m := & interceptor . Registry {}
m . Add ( statsFactory )
Getting Statistics
Per-Stream Stats
// Get stats for a specific SSRC
ssrc := uint32 ( 12345 )
streamStats := getter . Get ( ssrc )
if streamStats != nil {
log . Printf ( "Stats for SSRC %d :" , ssrc )
log . Printf ( " Packets sent: %d " , streamStats . OutboundRTPStreamStats . PacketsSent )
log . Printf ( " Bytes sent: %d " , streamStats . OutboundRTPStreamStats . BytesSent )
log . Printf ( " Packets received: %d " , streamStats . InboundRTPStreamStats . PacketsReceived )
log . Printf ( " Packets lost: %d " , streamStats . InboundRTPStreamStats . PacketsLost )
}
Available Statistics
type OutboundRTPStreamStats struct {
// Number of packets sent
PacketsSent uint32
// Number of bytes sent (excluding headers)
BytesSent uint64
// Number of header bytes sent
HeaderBytesSent uint64
// Number of retransmitted packets
RetransmittedPacketsSent uint32
// Number of retransmitted bytes
RetransmittedBytesSent uint64
// Target bitrate (if available)
TargetBitrate float64
// Total packet send delay
TotalPacketSendDelay time . Duration
}
type InboundRTPStreamStats struct {
// Number of packets received
PacketsReceived uint32
// Number of bytes received
BytesReceived uint64
// Number of header bytes received
HeaderBytesReceived uint64
// Number of packets lost
PacketsLost uint32
// Jitter in timestamp units
Jitter float64
// Last packet timestamp
LastPacketReceivedTimestamp time . Time
// Fraction lost (0.0 to 1.0)
FractionLost float64
}
// Both inbound and outbound RTCP stats available
type RTCPStats struct {
// Number of RTCP packets sent/received
PacketsSent uint32
PacketsReceived uint32
// Bytes sent/received
BytesSent uint64
BytesReceived uint64
}
Configuration Options
SetRecorderFactory
Provide a custom recorder factory for specialized statistics:
statsFactory , err := stats . NewInterceptor (
stats . SetRecorderFactory ( func ( ssrc uint32 , clockRate float64 ) stats . Recorder {
// Return custom recorder implementation
return myCustomRecorder {}
}),
)
WithLoggerFactory
Configure logging:
statsFactory , err := stats . NewInterceptor (
stats . WithLoggerFactory ( loggerFactory ),
)
SetNowFunc
Useful for testing with controlled time:
statsFactory , err := stats . NewInterceptor (
stats . SetNowFunc ( func () time . Time {
return testClock . Now ()
}),
)
Complete Example
package main
import (
" log "
" time "
" github.com/pion/interceptor "
" github.com/pion/interceptor/pkg/stats "
" github.com/pion/webrtc/v4 "
)
func main () {
// Create stats interceptor
statsFactory , err := stats . NewInterceptor ()
if err != nil {
panic ( err )
}
// Monitor new connections
statsFactory . OnNewPeerConnection ( func ( id string , getter stats . Getter ) {
log . Printf ( "New peer connection: %s " , id )
// Start monitoring stats
go func () {
ticker := time . NewTicker ( 5 * time . Second )
defer ticker . Stop ()
for range ticker . C {
// Get stats for all tracked SSRCs
// In practice, you'd track SSRCs from your streams
ssrcs := getTrackedSSRCs ()
for _ , ssrc := range ssrcs {
stats := getter . Get ( ssrc )
if stats != nil {
printStats ( ssrc , stats )
}
}
}
}()
})
// Create interceptor registry
i := & interceptor . Registry {}
i . Add ( statsFactory )
// Create media engine and API
m := & webrtc . MediaEngine {}
if err := m . RegisterDefaultCodecs (); err != nil {
panic ( err )
}
api := webrtc . NewAPI (
webrtc . WithMediaEngine ( m ),
webrtc . WithInterceptorRegistry ( i ),
)
// Create peer connection
pc , err := api . NewPeerConnection ( webrtc . Configuration {})
if err != nil {
panic ( err )
}
defer pc . Close ()
// Add tracks, establish connection, etc.
// ...
select {}
}
func printStats ( ssrc uint32 , s * stats . Stats ) {
log . Printf ( "=== Stats for SSRC %d ===" , ssrc )
// Outbound stats
if s . OutboundRTPStreamStats . PacketsSent > 0 {
log . Printf ( "Outbound:" )
log . Printf ( " Packets: %d " , s . OutboundRTPStreamStats . PacketsSent )
log . Printf ( " Bytes: %d " , s . OutboundRTPStreamStats . BytesSent )
if s . OutboundRTPStreamStats . RetransmittedPacketsSent > 0 {
log . Printf ( " Retransmitted: %d packets" ,
s . OutboundRTPStreamStats . RetransmittedPacketsSent )
}
}
// Inbound stats
if s . InboundRTPStreamStats . PacketsReceived > 0 {
log . Printf ( "Inbound:" )
log . Printf ( " Packets: %d " , s . InboundRTPStreamStats . PacketsReceived )
log . Printf ( " Bytes: %d " , s . InboundRTPStreamStats . BytesReceived )
log . Printf ( " Lost: %d ( %.2f%% )" ,
s . InboundRTPStreamStats . PacketsLost ,
s . InboundRTPStreamStats . FractionLost * 100 )
log . Printf ( " Jitter: %.2f " , s . InboundRTPStreamStats . Jitter )
}
}
func getTrackedSSRCs () [] uint32 {
// Return SSRCs you're tracking
return [] uint32 {}
}
Monitoring Patterns
Real-time Monitoring
// Monitor stats every second
ticker := time . NewTicker ( 1 * time . Second )
defer ticker . Stop ()
for range ticker . C {
stats := getter . Get ( ssrc )
if stats == nil {
continue
}
// Check for issues
if stats . InboundRTPStreamStats . FractionLost > 0.05 {
log . Printf ( "WARNING: High packet loss: %.2f%% " ,
stats . InboundRTPStreamStats . FractionLost * 100 )
}
if stats . InboundRTPStreamStats . Jitter > 30 {
log . Printf ( "WARNING: High jitter: %.2f " ,
stats . InboundRTPStreamStats . Jitter )
}
}
Periodic Reporting
// Generate report every 30 seconds
ticker := time . NewTicker ( 30 * time . Second )
defer ticker . Stop ()
for range ticker . C {
report := generateReport ( getter )
sendToAnalytics ( report )
}
func generateReport ( getter stats . Getter ) Report {
report := Report {
Timestamp : time . Now (),
Streams : make ( map [ uint32 ] StreamReport ),
}
for _ , ssrc := range trackedSSRCs {
stats := getter . Get ( ssrc )
if stats != nil {
report . Streams [ ssrc ] = StreamReport {
PacketsSent : stats . OutboundRTPStreamStats . PacketsSent ,
PacketsReceived : stats . InboundRTPStreamStats . PacketsReceived ,
PacketsLost : stats . InboundRTPStreamStats . PacketsLost ,
Jitter : stats . InboundRTPStreamStats . Jitter ,
}
}
}
return report
}
Quality Alerts
type QualityMonitor struct {
getter stats . Getter
alerts chan Alert
}
func ( m * QualityMonitor ) Monitor ( ssrc uint32 ) {
ticker := time . NewTicker ( 1 * time . Second )
defer ticker . Stop ()
for range ticker . C {
stats := m . getter . Get ( ssrc )
if stats == nil {
continue
}
// Check various quality metrics
if stats . InboundRTPStreamStats . FractionLost > 0.10 {
m . alerts <- Alert {
SSRC : ssrc ,
Type : "high_packet_loss" ,
Value : stats . InboundRTPStreamStats . FractionLost ,
Severity : "critical" ,
}
}
if stats . InboundRTPStreamStats . Jitter > 50 {
m . alerts <- Alert {
SSRC : ssrc ,
Type : "high_jitter" ,
Value : stats . InboundRTPStreamStats . Jitter ,
Severity : "warning" ,
}
}
// Check for stalled stream
if time . Since ( stats . InboundRTPStreamStats . LastPacketReceivedTimestamp ) > 5 * time . Second {
m . alerts <- Alert {
SSRC : ssrc ,
Type : "stream_stalled" ,
Severity : "critical" ,
}
}
}
}
Calculating Metrics
Bitrate Calculation
type BitrateCalculator struct {
lastBytes uint64
lastTime time . Time
}
func ( c * BitrateCalculator ) Calculate ( stats * stats . Stats ) float64 {
currentBytes := stats . OutboundRTPStreamStats . BytesSent
currentTime := time . Now ()
if c . lastTime . IsZero () {
c . lastBytes = currentBytes
c . lastTime = currentTime
return 0
}
deltaBytes := currentBytes - c . lastBytes
deltaTime := currentTime . Sub ( c . lastTime ). Seconds ()
c . lastBytes = currentBytes
c . lastTime = currentTime
// Return bits per second
return float64 ( deltaBytes ) * 8 / deltaTime
}
Packet Rate
func calculatePacketRate ( stats * stats . Stats , duration time . Duration ) float64 {
packets := float64 ( stats . OutboundRTPStreamStats . PacketsSent )
seconds := duration . Seconds ()
return packets / seconds
}
Loss Percentage
func calculateLossPercentage ( stats * stats . Stats ) float64 {
received := float64 ( stats . InboundRTPStreamStats . PacketsReceived )
lost := float64 ( stats . InboundRTPStreamStats . PacketsLost )
if received + lost == 0 {
return 0
}
return ( lost / ( received + lost )) * 100
}
Best Practices
Regular Polling : Check stats at regular intervals (1-5 seconds)
Track SSRCs : Maintain a list of active SSRCs from your tracks
Delta Calculations : Calculate rates by comparing with previous values
Alert Thresholds : Set appropriate thresholds for your use case
Resource Cleanup : Untrack SSRCs when streams end
The stats interceptor automatically starts a background goroutine for each stream to process statistics.
CPU : Minimal - processes packets asynchronously
Memory : ~1KB per tracked SSRC
Network : No additional network traffic
Integration with Other Interceptors
With Reports
// Combine stats with standard reports
reportFactory , _ := report . NewSenderInterceptor ()
i . Add ( reportFactory )
statsFactory , _ := stats . NewInterceptor ()
i . Add ( statsFactory )
// Reports provide RTCP feedback
// Stats provide detailed metrics
With NACK
// Monitor retransmission effectiveness
stats := getter . Get ( ssrc )
if stats != nil {
retransmitRate := float64 ( stats . OutboundRTPStreamStats . RetransmittedPacketsSent ) /
float64 ( stats . OutboundRTPStreamStats . PacketsSent )
log . Printf ( "Retransmission rate: %.2f%% " , retransmitRate * 100 )
}
Custom Recorders
Implement custom statistics recorders:
type CustomRecorder struct {
ssrc uint32
clockRate float64
// Your custom fields
}
func ( r * CustomRecorder ) QueueIncomingRTP ( ts time . Time , buf [] byte , attr interceptor . Attributes ) {
// Process incoming RTP
}
func ( r * CustomRecorder ) QueueIncomingRTCP ( ts time . Time , buf [] byte , attr interceptor . Attributes ) {
// Process incoming RTCP
}
func ( r * CustomRecorder ) QueueOutgoingRTP ( ts time . Time , header * rtp . Header , payload [] byte , attr interceptor . Attributes ) {
// Process outgoing RTP
}
func ( r * CustomRecorder ) QueueOutgoingRTCP ( ts time . Time , pkts [] rtcp . Packet , attr interceptor . Attributes ) {
// Process outgoing RTCP
}
func ( r * CustomRecorder ) GetStats () stats . Stats {
// Return computed statistics
return stats . Stats {}
}
func ( r * CustomRecorder ) Start () {
// Start background processing
}
func ( r * CustomRecorder ) Stop () {
// Stop and cleanup
}
Troubleshooting
Check that:
The SSRC has been seen in RTP traffic
The stream is active
The interceptor is properly bound
The recorder processes asynchronously. Stats may lag slightly behind real-time.
Ensure you’re tracking the correct SSRCs from your streams. SSRCs are assigned when tracks are added.
Report - Standard RTCP sender/receiver reports
NACK - Monitor retransmission statistics
TWCC - Track congestion control effectiveness