// 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 utils import ( "time" "github.com/livekit/protocol/livekit" "google.golang.org/protobuf/types/known/timestamppb" ) func AggregateRTPStats(statsList []*livekit.RTPStats, gapHistogramSize int) *livekit.RTPStats { if len(statsList) == 0 { return nil } startTime := time.Time{} endTime := time.Time{} packets := uint32(0) bytes := uint64(0) headerBytes := uint64(0) packetsLost := uint32(0) packetsDuplicate := uint32(0) bytesDuplicate := uint64(0) headerBytesDuplicate := uint64(0) packetsPadding := uint32(0) bytesPadding := uint64(0) headerBytesPadding := uint64(0) packetsOutOfOrder := uint32(0) frames := uint32(0) keyFrames := uint32(0) lastKeyFrame := time.Time{} jitter := 0.0 maxJitter := float64(0) gapHistogram := make(map[int32]uint32, gapHistogramSize) nacks := uint32(0) nackAcks := uint32(0) nackMisses := uint32(0) nackRepeated := uint32(0) plis := uint32(0) lastPli := time.Time{} layerLockPlis := uint32(0) lastLayerLockPli := time.Time{} firs := uint32(0) lastFir := time.Time{} rtt := uint32(0) maxRtt := uint32(0) for _, stats := range statsList { if startTime.IsZero() || startTime.After(stats.StartTime.AsTime()) { startTime = stats.StartTime.AsTime() } if endTime.IsZero() || endTime.Before(stats.EndTime.AsTime()) { endTime = stats.EndTime.AsTime() } packets += stats.Packets bytes += stats.Bytes headerBytes += stats.HeaderBytes packetsLost += stats.PacketsLost packetsDuplicate += stats.PacketsDuplicate bytesDuplicate += stats.BytesDuplicate headerBytesDuplicate += stats.HeaderBytesDuplicate packetsPadding += stats.PacketsPadding bytesPadding += stats.BytesPadding headerBytesPadding += stats.HeaderBytesPadding packetsOutOfOrder += stats.PacketsOutOfOrder frames += stats.Frames keyFrames += stats.KeyFrames if lastKeyFrame.IsZero() || lastKeyFrame.Before(stats.LastKeyFrame.AsTime()) { lastKeyFrame = stats.LastKeyFrame.AsTime() } jitter += stats.JitterCurrent if stats.JitterMax > maxJitter { maxJitter = stats.JitterMax } for burst, count := range stats.GapHistogram { gapHistogram[burst] += count } nacks += stats.Nacks nackAcks += stats.NackAcks nackMisses += stats.NackMisses nackRepeated += stats.NackRepeated plis += stats.Plis if lastPli.IsZero() || lastPli.Before(stats.LastPli.AsTime()) { lastPli = stats.LastPli.AsTime() } layerLockPlis += stats.LayerLockPlis if lastLayerLockPli.IsZero() || lastLayerLockPli.Before(stats.LastLayerLockPli.AsTime()) { lastLayerLockPli = stats.LastLayerLockPli.AsTime() } firs += stats.Firs if lastFir.IsZero() || lastPli.Before(stats.LastFir.AsTime()) { lastFir = stats.LastFir.AsTime() } rtt += stats.RttCurrent if stats.RttMax > maxRtt { maxRtt = stats.RttMax } } if endTime.IsZero() { endTime = time.Now() } elapsed := endTime.Sub(startTime).Seconds() packetLostRate := float64(packetsLost) / elapsed packetLostPercentage := float32(packetsLost) / (float32(packets) + float32(packetsLost)) * 100.0 packetRate := float64(packets) / elapsed packetDuplicateRate := float64(packetsDuplicate) / elapsed packetPaddingRate := float64(packetsPadding) / elapsed bitrate := float64(bytes) * 8.0 / elapsed bitrateDuplicate := float64(bytesDuplicate) * 8.0 / elapsed bitratePadding := float64(bytesPadding) * 8.0 / elapsed frameRate := float64(frames) / elapsed return &livekit.RTPStats{ StartTime: timestamppb.New(startTime), EndTime: timestamppb.New(endTime), Duration: elapsed, Packets: packets, PacketRate: packetRate, Bytes: bytes, HeaderBytes: headerBytes, Bitrate: bitrate, PacketsLost: packetsLost, PacketLossRate: packetLostRate, PacketLossPercentage: packetLostPercentage, PacketsDuplicate: packetsDuplicate, PacketDuplicateRate: packetDuplicateRate, BytesDuplicate: bytesDuplicate, HeaderBytesDuplicate: headerBytesDuplicate, BitrateDuplicate: bitrateDuplicate, PacketsPadding: packetsPadding, PacketPaddingRate: packetPaddingRate, BytesPadding: bytesPadding, HeaderBytesPadding: headerBytesPadding, BitratePadding: bitratePadding, PacketsOutOfOrder: packetsOutOfOrder, Frames: frames, FrameRate: frameRate, KeyFrames: keyFrames, LastKeyFrame: timestamppb.New(lastKeyFrame), JitterCurrent: jitter / float64(len(statsList)), JitterMax: maxJitter, GapHistogram: gapHistogram, Nacks: nacks, NackAcks: nackAcks, NackMisses: nackMisses, NackRepeated: nackRepeated, Plis: plis, LastPli: timestamppb.New(lastPli), LayerLockPlis: layerLockPlis, LastLayerLockPli: timestamppb.New(lastLayerLockPli), Firs: firs, LastFir: timestamppb.New(lastFir), RttCurrent: rtt / uint32(len(statsList)), RttMax: maxRtt, // no aggregation for drift calculations } }