snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
Some checks failed
Release to Docker / docker (push) Failing after 2s
Release Test Server to Docker / docker (push) Failing after 3s
Test / test (push) Failing after 16s

This commit is contained in:
upstream-mirror
2026-09-15 16:14:22 +05:00
commit 78704103fc
422 changed files with 154805 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,557 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtpstats
import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils/mono"
"go.uber.org/zap/zapcore"
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
cGapHistogramNumBins = 101
cNumSequenceNumbers = 65536
cFirstSnapshotID = 1
)
// -------------------------------------------------------
type RTPDeltaInfoLite struct {
StartTime time.Time
EndTime time.Time
Packets uint32
Bytes uint64
PacketsLost uint32
PacketsOutOfOrder uint32
Nacks uint32
}
func (r *RTPDeltaInfoLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
if r == nil {
return nil
}
e.AddTime("StartTime", r.StartTime)
e.AddTime("EndTime", r.EndTime)
e.AddUint32("Packets", r.Packets)
e.AddUint64("Bytes", r.Bytes)
e.AddUint32("PacketsLost", r.PacketsLost)
e.AddUint32("PacketsOutOfOrder", r.PacketsOutOfOrder)
e.AddUint32("Nacks", r.Nacks)
return nil
}
// -------------------------------------------------------
type snapshotLite struct {
isValid bool
startTime int64
extStartSN uint64
bytes uint64
packetsOutOfOrder uint64
packetsLost uint64
nacks uint32
}
func (s *snapshotLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
if s == nil {
return nil
}
e.AddBool("isValid", s.isValid)
e.AddTime("startTime", time.Unix(0, s.startTime))
e.AddUint64("extStartSN", s.extStartSN)
e.AddUint64("bytes", s.bytes)
e.AddUint64("packetsOutOfOrder", s.packetsOutOfOrder)
e.AddUint64("packetsLost", s.packetsLost)
e.AddUint32("nacks", s.nacks)
return nil
}
// ------------------------------------------------------------------
type RTPStatsParams struct {
IsRTX bool
}
type rtpStatsBaseLite struct {
params RTPStatsParams
clockRate uint32
logger logger.Logger
lock sync.RWMutex
initialized bool
startTime int64
endTime int64
bytes uint64
packetsOutOfOrder uint64
packetsLost uint64
gapHistogram [cGapHistogramNumBins]uint32
nacks uint32
nackAcks uint32
nackMisses uint32
nackRepeated uint32
plis uint32
lastPli int64
nextSnapshotLiteID uint32
snapshotLites []snapshotLite
}
func newRTPStatsBaseLite(params RTPStatsParams) *rtpStatsBaseLite {
return &rtpStatsBaseLite{
params: params,
logger: logger.GetLogger(),
nextSnapshotLiteID: cFirstSnapshotID,
snapshotLites: make([]snapshotLite, 2),
}
}
func (r *rtpStatsBaseLite) seed(from *rtpStatsBaseLite) bool {
if from == nil || !from.initialized || r.initialized {
return false
}
r.initialized = from.initialized
r.startTime = from.startTime
// do not clone endTime as a non-zero endTime indicates an ended object
r.bytes = from.bytes
r.packetsOutOfOrder = from.packetsOutOfOrder
r.packetsLost = from.packetsLost
r.gapHistogram = from.gapHistogram
r.nacks = from.nacks
r.nackAcks = from.nackAcks
r.nackMisses = from.nackMisses
r.nackRepeated = from.nackRepeated
r.plis = from.plis
r.lastPli = from.lastPli
r.nextSnapshotLiteID = from.nextSnapshotLiteID
r.snapshotLites = make([]snapshotLite, cap(from.snapshotLites))
copy(r.snapshotLites, from.snapshotLites)
return true
}
func (r *rtpStatsBaseLite) SetClockRate(clockRate uint32) {
r.lock.Lock()
defer r.lock.Unlock()
r.setClockRateLocked(clockRate)
}
func (r *rtpStatsBaseLite) setClockRateLocked(clockRate uint32) {
r.clockRate = clockRate
}
func (r *rtpStatsBaseLite) SetLogger(logger logger.Logger) {
r.lock.Lock()
defer r.lock.Unlock()
r.logger = logger
}
func (r *rtpStatsBaseLite) Stop() {
r.lock.Lock()
defer r.lock.Unlock()
r.endTime = mono.UnixNano()
}
func (r *rtpStatsBaseLite) newSnapshotLiteID(extStartSN uint64) uint32 {
id := r.nextSnapshotLiteID
r.nextSnapshotLiteID++
if cap(r.snapshotLites) < int(r.nextSnapshotLiteID-cFirstSnapshotID) {
snapshotLites := make([]snapshotLite, r.nextSnapshotLiteID-cFirstSnapshotID)
copy(snapshotLites, r.snapshotLites)
r.snapshotLites = snapshotLites
}
if r.initialized {
r.snapshotLites[id-cFirstSnapshotID] = initSnapshotLite(mono.UnixNano(), extStartSN)
}
return id
}
func (r *rtpStatsBaseLite) IsActive() bool {
r.lock.RLock()
defer r.lock.RUnlock()
return r.initialized && r.endTime == 0
}
func (r *rtpStatsBaseLite) UpdateNack(nackCount uint32) {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
return
}
r.nacks += nackCount
}
func (r *rtpStatsBaseLite) UpdateNackProcessed(nackAckCount uint32, nackMissCount uint32, nackRepeatedCount uint32) {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
return
}
r.nackAcks += nackAckCount
r.nackMisses += nackMissCount
r.nackRepeated += nackRepeatedCount
}
func (r *rtpStatsBaseLite) CheckAndUpdatePli(throttle int64, force bool) bool {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 || (!force && mono.UnixNano()-r.lastPli < throttle) {
return false
}
r.updatePliLocked(1)
r.updatePliTimeLocked()
return true
}
func (r *rtpStatsBaseLite) UpdatePliAndTime(pliCount uint32) {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
return
}
r.updatePliLocked(pliCount)
r.updatePliTimeLocked()
}
func (r *rtpStatsBaseLite) UpdatePli(pliCount uint32) {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
return
}
r.updatePliLocked(pliCount)
}
func (r *rtpStatsBaseLite) updatePliLocked(pliCount uint32) {
r.plis += pliCount
}
func (r *rtpStatsBaseLite) UpdatePliTime() {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
return
}
r.updatePliTimeLocked()
}
func (r *rtpStatsBaseLite) updatePliTimeLocked() {
r.lastPli = mono.UnixNano()
}
func (r *rtpStatsBaseLite) LastPli() int64 {
r.lock.RLock()
defer r.lock.RUnlock()
return r.lastPli
}
func (r *rtpStatsBaseLite) getPacketsSeen(extStartSN, extHighestSN uint64) uint64 {
packetsExpected := getPacketsExpected(extStartSN, extHighestSN)
if r.packetsLost > packetsExpected {
// should not happen
return 0
}
return packetsExpected - r.packetsLost
}
func (r *rtpStatsBaseLite) deltaInfoLite(
snapshotLiteID uint32,
extStartSN uint64,
extHighestSN uint64,
) (deltaInfoLite *RTPDeltaInfoLite, loggingFields []any, err error) {
then, now, ok := r.getAndResetSnapshotLite(snapshotLiteID, extStartSN, extHighestSN)
if !ok {
return
}
startTime := then.startTime
endTime := now.startTime
packetsExpected := uint32(now.extStartSN - then.extStartSN)
if then.extStartSN > extHighestSN {
packetsExpected = 0
}
if packetsExpected > cNumSequenceNumbers {
loggingFields = []any{
"snapshotLiteID", snapshotLiteID,
"snapshotLiteNow", now,
"snapshotLiteThen", then,
"packetsExpected", packetsExpected,
"duration", time.Duration(endTime - startTime),
}
err = errors.New("too many packets expected in delta lite")
return
}
if packetsExpected == 0 {
deltaInfoLite = &RTPDeltaInfoLite{
StartTime: time.Unix(0, startTime),
EndTime: time.Unix(0, endTime),
}
return
}
packetsLost := uint32(now.packetsLost - then.packetsLost)
if int32(packetsLost) < 0 {
packetsLost = 0
}
if packetsLost > packetsExpected {
loggingFields = []any{
"snapshotLiteID", snapshotLiteID,
"snapshotLiteNow", now,
"snapshotLiteThen", then,
"packetsExpected", packetsExpected,
"packetsLost", packetsLost,
"duration", time.Duration(endTime - startTime),
}
err = errors.New("unexpected number of packets lost in delta lite")
}
deltaInfoLite = &RTPDeltaInfoLite{
StartTime: time.Unix(0, startTime),
EndTime: time.Unix(0, endTime),
Packets: packetsExpected,
Bytes: now.bytes - then.bytes,
PacketsLost: packetsLost,
PacketsOutOfOrder: uint32(now.packetsOutOfOrder - then.packetsOutOfOrder),
Nacks: now.nacks - then.nacks,
}
return
}
func (r *rtpStatsBaseLite) marshalLogObject(e zapcore.ObjectEncoder, packetsExpected, packetsSeenMinusPadding uint64) (float64, error) {
if r == nil || !r.initialized || r.clockRate == 0 {
return 0, errors.New("not initialized")
}
endTime := r.endTime
if endTime == 0 {
endTime = mono.UnixNano()
}
elapsed := time.Duration(endTime - r.startTime)
if elapsed == 0 {
return 0, errors.New("no time elapsed")
}
elapsedSeconds := elapsed.Seconds()
e.AddTime("startTime", time.Unix(0, r.startTime))
e.AddTime("endTime", time.Unix(0, r.endTime))
e.AddDuration("elapsed", elapsed)
e.AddUint64("packetsExpected", packetsExpected)
e.AddFloat64("packetsExpectedRate", float64(packetsExpected)/elapsedSeconds)
e.AddUint64("packetsSeenPrimary", packetsSeenMinusPadding)
e.AddFloat64("packetsSeenPrimaryRate", float64(packetsSeenMinusPadding)/elapsedSeconds)
e.AddUint64("bytes", r.bytes)
e.AddFloat64("bitrate", float64(r.bytes)*8.0/elapsedSeconds)
e.AddUint64("packetsOutOfOrder", r.packetsOutOfOrder)
e.AddUint64("packetsLost", r.packetsLost)
e.AddFloat64("packetsLostRate", float64(r.packetsLost)/elapsedSeconds)
if packetsExpected != 0 {
e.AddFloat32("packetLostPercentage", float32(r.packetsLost)/float32(packetsExpected)*100.0)
}
var sb strings.Builder
for burst, count := range r.gapHistogram {
if count == 0 {
continue
}
if sb.Len() > 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, "%d:%d", burst+1, count)
}
if sb.Len() > 0 {
e.AddString("gapHistogram", "["+sb.String()+"]")
}
e.AddUint32("nacks", r.nacks)
e.AddUint32("nackAcks", r.nackAcks)
e.AddUint32("nackMisses", r.nackMisses)
e.AddUint32("nackRepeated", r.nackRepeated)
e.AddUint32("plis", r.plis)
e.AddTime("lastPli", time.Unix(0, r.lastPli))
return elapsedSeconds, nil
}
func (r *rtpStatsBaseLite) toProto(packetsExpected, packetsSeenMinusPadding, packetsLost uint64) *livekit.RTPStats {
if r.startTime == 0 || r.clockRate == 0 {
return nil
}
endTime := r.endTime
if endTime == 0 {
endTime = mono.UnixNano()
}
elapsed := time.Duration(endTime - r.startTime).Seconds()
if elapsed == 0.0 {
return nil
}
packetRate := float64(packetsSeenMinusPadding) / elapsed
bitrate := float64(r.bytes) * 8.0 / elapsed
packetLostRate := float64(packetsLost) / elapsed
packetLostPercentage := float32(0)
if packetsExpected != 0 {
packetLostPercentage = float32(packetsLost) / float32(packetsExpected) * 100.0
}
p := &livekit.RTPStats{
StartTime: timestamppb.New(time.Unix(0, r.startTime)),
EndTime: timestamppb.New(time.Unix(0, endTime)),
Duration: elapsed,
Packets: uint32(packetsSeenMinusPadding),
PacketRate: packetRate,
Bytes: r.bytes,
Bitrate: bitrate,
PacketsLost: uint32(packetsLost),
PacketLossRate: packetLostRate,
PacketLossPercentage: packetLostPercentage,
PacketsOutOfOrder: uint32(r.packetsOutOfOrder),
Nacks: r.nacks,
NackAcks: r.nackAcks,
NackMisses: r.nackMisses,
NackRepeated: r.nackRepeated,
Plis: r.plis,
LastPli: timestamppb.New(time.Unix(0, r.lastPli)),
}
for i := range len(r.gapHistogram) {
if r.gapHistogram[i] == 0 {
continue
}
if p.GapHistogram == nil {
p.GapHistogram = make(map[int32]uint32, len(r.gapHistogram))
}
p.GapHistogram[int32(i+1)] = r.gapHistogram[i]
}
return p
}
func (r *rtpStatsBaseLite) getAndResetSnapshotLite(snapshotLiteID uint32, extStartSN uint64, extHighestSN uint64) (snapshotLite, snapshotLite, bool) {
if !r.initialized {
return snapshotLite{}, snapshotLite{}, false
}
idx := snapshotLiteID - cFirstSnapshotID
then := r.snapshotLites[idx]
if !then.isValid {
then = initSnapshotLite(r.startTime, extStartSN)
r.snapshotLites[idx] = then
}
// snapshot now
now := r.getSnapshotLite(mono.UnixNano(), extHighestSN+1)
r.snapshotLites[idx] = now
return then, now, true
}
func (r *rtpStatsBaseLite) updateGapHistogram(gap int) {
if gap < 2 {
return
}
missing := gap - 1
if missing > len(r.gapHistogram) {
r.gapHistogram[len(r.gapHistogram)-1]++
} else {
r.gapHistogram[missing-1]++
}
}
func (r *rtpStatsBaseLite) getSnapshotLite(startTime int64, extStartSN uint64) snapshotLite {
return snapshotLite{
isValid: true,
startTime: startTime,
extStartSN: extStartSN,
bytes: r.bytes,
packetsOutOfOrder: r.packetsOutOfOrder,
packetsLost: r.packetsLost,
nacks: r.nacks,
}
}
// ----------------------------------
func initSnapshotLite(startTime int64, extStartSN uint64) snapshotLite {
return snapshotLite{
isValid: true,
startTime: startTime,
extStartSN: extStartSN,
}
}
func getPacketsExpected(extStartSN, extHighestSN uint64) uint64 {
return extHighestSN - extStartSN + 1
}
func shouldLog(count int) bool {
return count < 20 || count%200 == 0
}
// ----------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtpstats
import (
"go.uber.org/zap/zapcore"
"github.com/livekit/mediatransportutil/pkg/utils"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/utils/mono"
)
type RTPFlowStateLite struct {
IsNotHandled bool
LossStartInclusive uint64
LossEndExclusive uint64
ExtSequenceNumber uint64
}
func (r *RTPFlowStateLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
if r == nil {
return nil
}
e.AddBool("IsNotHandled", r.IsNotHandled)
e.AddUint64("LossStartInclusive", r.LossStartInclusive)
e.AddUint64("LossEndExclusive", r.LossEndExclusive)
e.AddUint64("ExtSequenceNumber", r.ExtSequenceNumber)
return nil
}
// ---------------------------------------------------------------------
type RTPStatsReceiverLite struct {
*rtpStatsBaseLite
sequenceNumber *utils.WrapAround[uint16, uint64]
}
func NewRTPStatsReceiverLite(params RTPStatsParams) *RTPStatsReceiverLite {
return &RTPStatsReceiverLite{
rtpStatsBaseLite: newRTPStatsBaseLite(params),
sequenceNumber: utils.NewWrapAround[uint16, uint64](utils.WrapAroundParams{IsRestartAllowed: false}),
}
}
func (r *RTPStatsReceiverLite) NewSnapshotLiteId() uint32 {
r.lock.Lock()
defer r.lock.Unlock()
return r.newSnapshotLiteID(r.sequenceNumber.GetExtendedHighest())
}
func (r *RTPStatsReceiverLite) Update(packetTime int64, packetSize int, sequenceNumber uint16) (flowStateLite RTPFlowStateLite) {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
flowStateLite.IsNotHandled = true
return
}
var resSN utils.WrapAroundUpdateResult[uint64]
if !r.initialized {
r.initialized = true
r.startTime = mono.UnixNano()
resSN = r.sequenceNumber.Update(sequenceNumber)
// initialize lite snapshots if any
for i := uint32(0); i < r.nextSnapshotLiteID-cFirstSnapshotID; i++ {
r.snapshotLites[i] = initSnapshotLite(r.startTime, r.sequenceNumber.GetExtendedStart())
}
r.logger.Debugw(
"rtp receiver lite stream start",
"rtpStats", lockedRTPStatsReceiverLiteLogEncoder{r},
)
} else {
resSN = r.sequenceNumber.Update(sequenceNumber)
if resSN.IsUnhandled {
flowStateLite.IsNotHandled = true
return
}
}
gapSN := int64(resSN.ExtendedVal - resSN.PreExtendedHighest)
if gapSN <= 0 { // duplicate OR out-of-order
r.packetsOutOfOrder++ // counting duplicate as out-of-order
if gapSN != 0 && r.packetsLost > 0 {
r.packetsLost--
}
} else { // in-order
r.updateGapHistogram(int(gapSN))
r.packetsLost += uint64(gapSN - 1)
flowStateLite.LossStartInclusive = resSN.PreExtendedHighest + 1
flowStateLite.LossEndExclusive = resSN.ExtendedVal
}
flowStateLite.ExtSequenceNumber = resSN.ExtendedVal
r.bytes += uint64(packetSize)
return
}
func (r *RTPStatsReceiverLite) DeltaInfoLite(snapshotLiteID uint32) *RTPDeltaInfoLite {
r.lock.Lock()
defer r.lock.Unlock()
deltaInfoLite, loggingFields, err := r.deltaInfoLite(
snapshotLiteID,
r.sequenceNumber.GetExtendedStart(),
r.sequenceNumber.GetExtendedHighest(),
)
if err != nil {
r.logger.Infow(err.Error(), append(loggingFields, "rtpStats", lockedRTPStatsReceiverLiteLogEncoder{r})...)
}
return deltaInfoLite
}
func (r *RTPStatsReceiverLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
if r == nil {
return nil
}
r.lock.RLock()
defer r.lock.RUnlock()
return lockedRTPStatsReceiverLiteLogEncoder{r}.MarshalLogObject(e)
}
func (r *RTPStatsReceiverLite) ToProto() *livekit.RTPStats {
r.lock.RLock()
defer r.lock.RUnlock()
return r.rtpStatsBaseLite.toProto(r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest(), r.packetsLost)
}
// ----------------------------------
type lockedRTPStatsReceiverLiteLogEncoder struct {
*RTPStatsReceiverLite
}
func (r lockedRTPStatsReceiverLiteLogEncoder) MarshalLogObject(e zapcore.ObjectEncoder) error {
if r.RTPStatsReceiverLite == nil {
return nil
}
extStartSN, extHighestSN := r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest()
if _, err := r.rtpStatsBaseLite.marshalLogObject(
e,
getPacketsExpected(extStartSN, extHighestSN),
getPacketsExpected(extStartSN, extHighestSN),
); err != nil {
return err
}
e.AddUint64("extStartSN", r.sequenceNumber.GetExtendedStart())
e.AddUint64("extHighestSN", r.sequenceNumber.GetExtendedHighest())
return nil
}
// ----------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtpstats
import (
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/utils/mono"
"go.uber.org/zap/zapcore"
)
type RTPStatsSenderLite struct {
*rtpStatsBaseLite
extStartSN uint64
extHighestSN uint64
}
func NewRTPStatsSenderLite(params RTPStatsParams) *RTPStatsSenderLite {
return &RTPStatsSenderLite{
rtpStatsBaseLite: newRTPStatsBaseLite(params),
}
}
func (r *RTPStatsSenderLite) Update(packetTime int64, packetSize int, extSequenceNumber uint64) {
r.lock.Lock()
defer r.lock.Unlock()
if r.endTime != 0 {
return
}
if !r.initialized {
r.initialized = true
r.startTime = mono.UnixNano()
r.extStartSN = extSequenceNumber
r.extHighestSN = extSequenceNumber - 1
r.logger.Debugw(
"rtp sender lite stream start",
"rtpStats", lockedRTPStatsSenderLiteLogEncoder{r},
)
}
gapSN := int64(extSequenceNumber - r.extHighestSN)
if gapSN <= 0 { // duplicate OR out-of-order
r.packetsOutOfOrder++ // counting duplicate as out-of-order
r.packetsLost--
} else { // in-order
r.updateGapHistogram(int(gapSN))
r.packetsLost += uint64(gapSN - 1)
r.extHighestSN = extSequenceNumber
}
r.bytes += uint64(packetSize)
}
func (r *RTPStatsSenderLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
if r == nil {
return nil
}
r.lock.RLock()
defer r.lock.RUnlock()
return lockedRTPStatsSenderLiteLogEncoder{r}.MarshalLogObject(e)
}
func (r *RTPStatsSenderLite) ToProto() *livekit.RTPStats {
r.lock.RLock()
defer r.lock.RUnlock()
return r.rtpStatsBaseLite.toProto(r.extStartSN, r.extHighestSN, r.packetsLost)
}
func (r *RTPStatsSenderLite) ExtHighestSequenceNumber() uint64 {
r.lock.RLock()
defer r.lock.RUnlock()
return r.extHighestSN
}
// -------------------------------------------------------------------
type lockedRTPStatsSenderLiteLogEncoder struct {
*RTPStatsSenderLite
}
func (r lockedRTPStatsSenderLiteLogEncoder) MarshalLogObject(e zapcore.ObjectEncoder) error {
if r.RTPStatsSenderLite == nil {
return nil
}
if _, err := r.rtpStatsBaseLite.marshalLogObject(
e,
getPacketsExpected(r.extStartSN, r.extHighestSN),
getPacketsExpected(r.extStartSN, r.extHighestSN),
); err != nil {
return err
}
e.AddUint64("extStartSN", r.extStartSN)
e.AddUint64("extHighestSN", r.extHighestSN)
return nil
}
// -------------------------------------------------------------------

View File

@@ -0,0 +1,286 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtpstats
import (
"math/rand"
"testing"
"time"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func getPacket(sn uint16, ts uint32, payloadSize int) *rtp.Packet {
return &rtp.Packet{
Header: rtp.Header{
SequenceNumber: sn,
Timestamp: ts,
},
Payload: make([]byte, payloadSize),
}
}
func Test_RTPStatsReceiver_Update(t *testing.T) {
clockRate := uint32(90000)
r := NewRTPStatsReceiver(RTPStatsParams{})
r.SetClockRate(clockRate)
sequenceNumber := uint16(rand.Float64() * float64(1<<16))
timestamp := uint32(rand.Float64() * float64(1<<32))
packet := getPacket(sequenceNumber, timestamp, 1000)
flowState := r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.True(t, r.initialized)
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
require.Equal(t, timestamp, r.timestamp.GetHighest())
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
// in-order, no loss
sequenceNumber++
timestamp += 3000
packet = getPacket(sequenceNumber, timestamp, 1000)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
require.Equal(t, timestamp, r.timestamp.GetHighest())
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
// out-of-order, would cause a restart which is disallowed
packet = getPacket(sequenceNumber-10, timestamp-30000, 1000)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, RTPFlowUnhandledReasonPreStartTimestamp, flowState.UnhandledReason)
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
require.Equal(t, timestamp, r.timestamp.GetHighest())
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
require.Equal(t, uint64(0), r.packetsOutOfOrder)
require.Equal(t, uint64(0), r.packetsDuplicate)
// duplicate of the above out-of-order packet, but would not be handled as it causes a restart
packet = getPacket(sequenceNumber-10, timestamp-30000, 1000)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, RTPFlowUnhandledReasonPreStartTimestamp, flowState.UnhandledReason)
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
require.Equal(t, timestamp, r.timestamp.GetHighest())
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
require.Equal(t, uint64(0), r.packetsOutOfOrder)
require.Equal(t, uint64(0), r.packetsDuplicate)
// loss
sequenceNumber += 10
timestamp += 30000
packet = getPacket(sequenceNumber, timestamp, 1000)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, uint64(sequenceNumber-9), flowState.LossStartInclusive)
require.Equal(t, uint64(sequenceNumber), flowState.LossEndExclusive)
require.Equal(t, uint64(9), r.packetsLost)
// out-of-order should decrement number of lost packets
packet = getPacket(sequenceNumber-6, timestamp-18000, 1000)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
require.Equal(t, timestamp, r.timestamp.GetHighest())
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
require.Equal(t, uint64(1), r.packetsOutOfOrder)
require.Equal(t, uint64(0), r.packetsDuplicate)
require.Equal(t, uint64(8), r.packetsLost)
// test sequence number history
// with a gap
sequenceNumber += 2
timestamp += 6000
packet = getPacket(sequenceNumber, timestamp, 1000)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, uint64(sequenceNumber-1), flowState.LossStartInclusive)
require.Equal(t, uint64(sequenceNumber), flowState.LossEndExclusive)
require.Equal(t, uint64(9), r.packetsLost)
require.False(t, r.history.IsSet(uint64(sequenceNumber)-1))
// out-of-order
sequenceNumber--
timestamp -= 3000
packet = getPacket(sequenceNumber, timestamp, 999)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, uint64(8), r.packetsLost)
require.Equal(t, uint64(2), r.packetsOutOfOrder)
require.True(t, r.history.IsSet(uint64(sequenceNumber)))
// padding only
sequenceNumber += 2
timestamp += 3000
packet = getPacket(sequenceNumber, timestamp, 0)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
25,
)
require.Equal(t, uint64(8), r.packetsLost)
require.Equal(t, uint64(2), r.packetsOutOfOrder)
require.True(t, r.history.IsSet(uint64(sequenceNumber)))
require.True(t, r.history.IsSet(uint64(sequenceNumber)-1))
require.True(t, r.history.IsSet(uint64(sequenceNumber)-2))
// old packet, but simulating increasing sequence number after roll over
packet = getPacket(sequenceNumber+400, timestamp-6000, 300)
flowState = r.Update(
time.Now().UnixNano(),
packet.Header.SequenceNumber,
packet.Header.Timestamp,
packet.Header.Marker,
packet.Header.MarshalSize(),
len(packet.Payload),
0,
)
require.Equal(t, RTPFlowUnhandledReasonOldSequenceNumber, flowState.UnhandledReason)
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
require.Equal(t, timestamp, r.timestamp.GetHighest())
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
r.Stop()
}
func Test_RTPStatsReceiver_Restart(t *testing.T) {
clockRate := uint32(90000)
r := NewRTPStatsReceiver(RTPStatsParams{})
r.SetClockRate(clockRate)
// should not restart till there are at least threshold packets
require.False(t, r.maybeRestart(10, 20, 1000))
require.False(t, r.maybeRestart(11, 20, 1000))
require.False(t, r.maybeRestart(13, 20, 1000))
require.False(t, r.maybeRestart(14, 20, 1000))
// although adding 5th packet should have enough packets for a check,
// still should not restart as there is a sequence number gap between 11 and 13
require.False(t, r.maybeRestart(15, 20, 1000))
require.False(t, r.maybeRestart(16, 19, 1000))
// has enough packets, but still cannot restart because timestamps are not increasing
require.False(t, r.maybeRestart(17, 21, 1000))
require.False(t, r.maybeRestart(18, 21, 1000))
require.False(t, r.maybeRestart(19, 21, 1000))
// can restart as there are enough packets with proper sequencing
require.True(t, r.maybeRestart(20, 21, 1000))
require.Equal(t, restartThreshold, r.restartPacketsN)
r.resetRestart()
require.Zero(t, r.restartPacketsN)
r.Stop()
}
func Test_RTPStatsSender_getIntervalStats(t *testing.T) {
t.Run("packetsNotFoundMetadata should match lost packets", func(t *testing.T) {
r := NewRTPStatsSender(RTPStatsParams{}, 1024)
stats := r.getIntervalStats(0, 10000, 10000)
require.EqualValues(t, 8977, stats.packetsNotFoundMetadata)
})
}
func BenchmarkRTPStatsReceiver_Update(b *testing.B) {
const clockRate = 90000
const hdrSize = 12
const payloadSize = 1200
const paddingSize = 0
const tsIncrement = 3000 // ~33ms at 90kHz
r := NewRTPStatsReceiver(RTPStatsParams{})
r.SetClockRate(clockRate)
// initialize before running benchmark loop
packetTime := time.Now().UnixNano()
sn := uint16(0)
ts := uint32(0)
r.Update(packetTime, sn, ts, false, hdrSize, payloadSize, paddingSize)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn++
ts += tsIncrement
packetTime += 33 * int64(time.Millisecond) // ~30fps
marker := (i+1)%100 == 0 // marker every 100 packets
r.Update(packetTime, sn, ts, marker, hdrSize, payloadSize, paddingSize)
}
}