Skip to content

Commit 8cded16

Browse files
Improve JVB loss statistics (#1911)
* Calculate outgoing packet loss as a side-effect of TransportCcEngine. Factor out LossTracker from EndpointConnectionStats. * Feed incomingLossListener from TccGeneratorNode.
1 parent 3c9d3c5 commit 8cded16

File tree

12 files changed

+265
-54
lines changed

12 files changed

+265
-54
lines changed

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/RtpReceiver.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.jitsi.nlj
1717

18+
import org.jitsi.nlj.rtp.LossListener
1819
import org.jitsi.nlj.srtp.SrtpTransformers
1920
import org.jitsi.nlj.stats.EndpointConnectionStats
2021
import org.jitsi.nlj.stats.RtpReceiverStats
@@ -50,6 +51,8 @@ abstract class RtpReceiver :
5051
abstract fun isReceivingAudio(): Boolean
5152
abstract fun isReceivingVideo(): Boolean
5253

54+
abstract fun addLossListener(lossListener: LossListener)
55+
5356
abstract fun setFeature(feature: Features, enabled: Boolean)
5457
abstract fun isFeatureEnabled(feature: Features): Boolean
5558

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/RtpReceiverImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import org.jitsi.nlj.rtcp.RembHandler
2323
import org.jitsi.nlj.rtcp.RtcpEventNotifier
2424
import org.jitsi.nlj.rtcp.RtcpRrGenerator
2525
import org.jitsi.nlj.rtp.AudioRtpPacket
26+
import org.jitsi.nlj.rtp.LossListener
2627
import org.jitsi.nlj.rtp.VideoRtpPacket
2728
import org.jitsi.nlj.rtp.bandwidthestimation.BandwidthEstimator
2829
import org.jitsi.nlj.srtp.SrtpTransformers
@@ -146,6 +147,9 @@ class RtpReceiverImpl @JvmOverloads constructor(
146147

147148
override fun isReceivingAudio() = audioBitrateCalculator.active
148149
override fun isReceivingVideo() = videoBitrateCalculator.active
150+
override fun addLossListener(lossListener: LossListener) {
151+
tccGenerator.addLossListener(lossListener)
152+
}
149153

150154
companion object {
151155
val queueErrorCounter = CountingErrorHandler()

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/RtpSender.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.jitsi.nlj
1717

18+
import org.jitsi.nlj.rtp.LossListener
1819
import org.jitsi.nlj.rtp.TransportCcEngine
1920
import org.jitsi.nlj.rtp.bandwidthestimation.BandwidthEstimator
2021
import org.jitsi.nlj.srtp.SrtpTransformers
@@ -42,6 +43,7 @@ abstract class RtpSender :
4243
abstract fun getPacketStreamStats(): PacketStreamStats.Snapshot
4344
abstract fun getTransportCcEngineStats(): TransportCcEngine.StatisticsSnapshot
4445
abstract fun requestKeyframe(mediaSsrc: Long? = null)
46+
abstract fun addLossListener(lossListener: LossListener)
4547
abstract fun setFeature(feature: Features, enabled: Boolean)
4648
abstract fun isFeatureEnabled(feature: Features): Boolean
4749
abstract fun tearDown()

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/RtpSenderImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.jitsi.nlj.rtcp.KeyframeRequester
2222
import org.jitsi.nlj.rtcp.NackHandler
2323
import org.jitsi.nlj.rtcp.RtcpEventNotifier
2424
import org.jitsi.nlj.rtcp.RtcpSrUpdater
25+
import org.jitsi.nlj.rtp.LossListener
2526
import org.jitsi.nlj.rtp.TransportCcEngine
2627
import org.jitsi.nlj.rtp.bandwidthestimation.BandwidthEstimator
2728
import org.jitsi.nlj.rtp.bandwidthestimation.GoogleCcEstimator
@@ -234,6 +235,10 @@ class RtpSenderImpl(
234235
keyframeRequester.requestKeyframe(mediaSsrc)
235236
}
236237

238+
override fun addLossListener(lossListener: LossListener) {
239+
transportCcEngine.addLossListener(lossListener)
240+
}
241+
237242
override fun setFeature(feature: Features, enabled: Boolean) {
238243
when (feature) {
239244
Features.TRANSCEIVER_PCAP_DUMP -> {

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/Transceiver.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ class Transceiver(
146146
}
147147
)
148148

149+
rtpReceiver.addLossListener(endpointConnectionStats.incomingLossTracker)
150+
rtpSender.addLossListener(endpointConnectionStats.outgoingLossTracker)
151+
149152
rtcpEventNotifier.addRtcpEventListener(endpointConnectionStats)
150153

151154
endpointConnectionStats.addListener(rtpSender)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright @ 2019 - present 8x8, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jitsi.nlj.rtp
18+
19+
import org.jitsi.utils.OrderedJsonObject
20+
import org.jitsi.utils.secs
21+
import org.jitsi.utils.stats.RateTracker
22+
23+
class LossTracker : LossListener {
24+
private val lostPackets = RateTracker(60.secs, 1.secs)
25+
private val receivedPackets = RateTracker(60.secs, 1.secs)
26+
27+
@Synchronized
28+
override fun packetReceived(previouslyReportedLost: Boolean) {
29+
receivedPackets.update(1)
30+
if (previouslyReportedLost) {
31+
lostPackets.update(-1)
32+
}
33+
}
34+
35+
@Synchronized
36+
override fun packetLost(numLost: Int) {
37+
lostPackets.update(numLost.toLong())
38+
}
39+
40+
@Synchronized
41+
fun getSnapshot(): Snapshot {
42+
return Snapshot(
43+
lostPackets.getAccumulatedCount(),
44+
receivedPackets.getAccumulatedCount()
45+
)
46+
}
47+
48+
data class Snapshot(
49+
val packetsLost: Long,
50+
val packetsReceived: Long
51+
) {
52+
fun toJson() = OrderedJsonObject().apply {
53+
put("packets_lost", packetsLost)
54+
put("packets_received", packetsReceived)
55+
}
56+
}
57+
}
58+
59+
/**
60+
* An interface to report when a packet is received, or is observed to be lost.
61+
*/
62+
/* TODO? This kind of overlaps with BandwidthEstimator? But it can be used in cases where we
63+
* don't have all the information the BandwidthEstimator API needs. */
64+
interface LossListener {
65+
fun packetReceived(previouslyReportedLost: Boolean)
66+
fun packetLost(numLost: Int)
67+
}

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/rtp/TransportCcEngine.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import org.json.simple.JSONObject
3535
import java.time.Clock
3636
import java.time.Duration
3737
import java.time.Instant
38+
import java.util.*
3839
import java.util.concurrent.atomic.LongAdder
3940

4041
/**
@@ -89,6 +90,8 @@ class TransportCcEngine(
8990

9091
private var lastRtt: Duration? = null
9192

93+
private val lossListeners = LinkedList<LossListener>()
94+
9295
/**
9396
* Called when an RTP sender has a new round-trip time estimate.
9497
*/
@@ -104,6 +107,24 @@ class TransportCcEngine(
104107
}
105108
}
106109

110+
/**
111+
* Adds a loss listener to be notified about packet arrival and loss reports.
112+
* @param listener
113+
*/
114+
@Synchronized
115+
fun addLossListener(listener: LossListener) {
116+
lossListeners.add(listener)
117+
}
118+
119+
/**
120+
* Removes a loss listener.
121+
* @param listener
122+
*/
123+
@Synchronized
124+
fun removeLossListener(listener: LossListener) {
125+
lossListeners.remove(listener)
126+
}
127+
107128
private fun tccReceived(tccPacket: RtcpFbTccPacket) {
108129
val now = clock.instant()
109130
var currArrivalTimestamp = instantOfEpochMicro(tccPacket.GetBaseTimeUs())
@@ -132,6 +153,11 @@ class TransportCcEngine(
132153
packetDetail.state = PacketDetailState.reportedLost
133154
numPacketsReported.increment()
134155
numPacketsReportedLost.increment()
156+
synchronized(this) {
157+
lossListeners.forEach {
158+
it.packetLost(1)
159+
}
160+
}
135161
}
136162
}
137163
is ReceivedPacketReport -> {
@@ -156,6 +182,11 @@ class TransportCcEngine(
156182
tccSeqNum, packetDetail.packetLength,
157183
previouslyReportedLost = previouslyReportedLost
158184
)
185+
synchronized(this) {
186+
lossListeners.forEach {
187+
it.packetReceived(previouslyReportedLost)
188+
}
189+
}
159190
packetDetail.state = PacketDetailState.reportedReceived
160191
}
161192

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/stats/EndpointConnectionStats.kt

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,18 @@
1717
package org.jitsi.nlj.stats
1818

1919
import org.jitsi.nlj.rtcp.RtcpListener
20+
import org.jitsi.nlj.rtp.LossTracker
2021
import org.jitsi.nlj.util.toDoubleMillis
2122
import org.jitsi.rtp.rtcp.RtcpPacket
2223
import org.jitsi.rtp.rtcp.RtcpReportBlock
2324
import org.jitsi.rtp.rtcp.RtcpRrPacket
2425
import org.jitsi.rtp.rtcp.RtcpSrPacket
25-
import org.jitsi.rtp.rtcp.rtcpfb.transport_layer_fb.tcc.RtcpFbTccPacket
26-
import org.jitsi.rtp.rtcp.rtcpfb.transport_layer_fb.tcc.UnreceivedPacketReport
2726
import org.jitsi.utils.LRUCache
2827
import org.jitsi.utils.OrderedJsonObject
2928
import org.jitsi.utils.logging2.Logger
3029
import org.jitsi.utils.logging2.cdebug
3130
import org.jitsi.utils.logging2.createChildLogger
3231
import org.jitsi.utils.secs
33-
import org.jitsi.utils.stats.RateTracker
3432
import java.time.Clock
3533
import java.time.Duration
3634
import java.time.Instant
@@ -55,8 +53,8 @@ class EndpointConnectionStats(
5553
}
5654
data class Snapshot(
5755
val rtt: Double,
58-
val incomingLossStats: LossStatsSnapshot,
59-
val outgoingLossStats: LossStatsSnapshot
56+
val incomingLossStats: LossTracker.Snapshot,
57+
val outgoingLossStats: LossTracker.Snapshot
6058
) {
6159
fun toJson() = OrderedJsonObject().apply {
6260
put("rtt", rtt)
@@ -65,16 +63,6 @@ class EndpointConnectionStats(
6563
}
6664
}
6765

68-
data class LossStatsSnapshot(
69-
val packetsLost: Long,
70-
val packetsReceived: Long
71-
) {
72-
fun toJson() = OrderedJsonObject().apply {
73-
put("packets_lost", packetsLost)
74-
put("packets_received", packetsReceived)
75-
}
76-
}
77-
7866
private val endpointConnectionStatsListeners: MutableList<EndpointConnectionStatsListener> = CopyOnWriteArrayList()
7967

8068
// Per-SSRC, maps the compacted NTP timestamp found in an SR SenderInfo to
@@ -89,8 +77,8 @@ class EndpointConnectionStats(
8977
*/
9078
private var rtt: Double = 0.0
9179

92-
private val incomingLossTracker = LossTracker()
93-
private val outgoingLossTracker = LossTracker()
80+
val incomingLossTracker = LossTracker()
81+
val outgoingLossTracker = LossTracker()
9482

9583
fun addListener(listener: EndpointConnectionStatsListener) {
9684
endpointConnectionStatsListeners.add(listener)
@@ -104,14 +92,8 @@ class EndpointConnectionStats(
10492
return synchronized(lock) {
10593
Snapshot(
10694
rtt = rtt,
107-
incomingLossStats = LossStatsSnapshot(
108-
packetsLost = incomingLossTracker.lostPackets.getAccumulatedCount(),
109-
packetsReceived = incomingLossTracker.receivedPackets.getAccumulatedCount()
110-
),
111-
outgoingLossStats = LossStatsSnapshot(
112-
packetsLost = outgoingLossTracker.lostPackets.getAccumulatedCount(),
113-
packetsReceived = outgoingLossTracker.receivedPackets.getAccumulatedCount()
114-
)
95+
incomingLossStats = incomingLossTracker.getSnapshot(),
96+
outgoingLossStats = outgoingLossTracker.getSnapshot()
11597
)
11698
}
11799
}
@@ -126,8 +108,6 @@ class EndpointConnectionStats(
126108
logger.cdebug { "Received RR packet with ${packet.reportBlocks.size} report blocks" }
127109
packet.reportBlocks.forEach { reportBlock -> processReportBlock(receivedTime, reportBlock) }
128110
}
129-
// Received TCC feedback reports loss on packets we *sent*
130-
is RtcpFbTccPacket -> processTcc(packet, outgoingLossTracker)
131111
}
132112
}
133113

@@ -141,8 +121,6 @@ class EndpointConnectionStats(
141121
val entry = SsrcAndTimestamp(packet.senderSsrc, packet.senderInfo.compactedNtpTimestamp)
142122
srSentTimes[entry] = clock.instant()
143123
}
144-
// Sent TCC feedback reports loss on packets we *received*
145-
is RtcpFbTccPacket -> processTcc(packet, incomingLossTracker)
146124
}
147125
}
148126

@@ -188,22 +166,4 @@ class EndpointConnectionStats(
188166
}
189167
}
190168
}
191-
192-
private fun processTcc(tccPacket: RtcpFbTccPacket, lossTracker: LossTracker) = synchronized(lock) {
193-
var lost = 0L
194-
var received = 0L
195-
for (packetReport in tccPacket) {
196-
when (packetReport) {
197-
is UnreceivedPacketReport -> lost++
198-
else -> received++
199-
}
200-
}
201-
lossTracker.lostPackets.update(lost)
202-
lossTracker.receivedPackets.update(received)
203-
}
204-
205-
private class LossTracker {
206-
val lostPackets = RateTracker(60.secs, 1.secs)
207-
val receivedPackets = RateTracker(60.secs, 1.secs)
208-
}
209169
}

0 commit comments

Comments
 (0)