Skip to content

Commit 989e32f

Browse files
committed
Add round trip time measurement to candidate pair (#731)
* Add round trip time measurement to candidate pair Use the round trip time measurement to populate RTT fields in CandidatePairStats. Atomic and tests * Use int64 nanosecnods to make atomic easier
1 parent e3fb72f commit 989e32f

File tree

5 files changed

+67
-8
lines changed

5 files changed

+67
-8
lines changed

agent.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,16 +1055,16 @@ func (a *Agent) invalidatePendingBindingRequests(filterTime time.Time) {
10551055

10561056
// Assert that the passed TransactionID is in our pendingBindingRequests and returns the destination
10571057
// If the bindingRequest was valid remove it from our pending cache
1058-
func (a *Agent) handleInboundBindingSuccess(id [stun.TransactionIDSize]byte) (bool, *bindingRequest) {
1058+
func (a *Agent) handleInboundBindingSuccess(id [stun.TransactionIDSize]byte) (bool, *bindingRequest, time.Duration) {
10591059
a.invalidatePendingBindingRequests(time.Now())
10601060
for i := range a.pendingBindingRequests {
10611061
if a.pendingBindingRequests[i].transactionID == id {
10621062
validBindingRequest := a.pendingBindingRequests[i]
10631063
a.pendingBindingRequests = append(a.pendingBindingRequests[:i], a.pendingBindingRequests[i+1:]...)
1064-
return true, &validBindingRequest
1064+
return true, &validBindingRequest, time.Since(validBindingRequest.timestamp)
10651065
}
10661066
}
1067-
return false, nil
1067+
return false, nil, 0
10681068
}
10691069

10701070
// handleInbound processes STUN traffic from a remote candidate

agent_stats.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
2929
// FirstRequestTimestamp time.Time
3030
// LastRequestTimestamp time.Time
3131
// LastResponseTimestamp time.Time
32-
// TotalRoundTripTime float64
33-
// CurrentRoundTripTime float64
32+
TotalRoundTripTime: cp.TotalRoundTripTime(),
33+
CurrentRoundTripTime: cp.CurrentRoundTripTime(),
3434
// AvailableOutgoingBitrate float64
3535
// AvailableIncomingBitrate float64
3636
// CircuitBreakerTriggerCount uint32
3737
// RequestsReceived uint64
3838
// RequestsSent uint64
39-
// ResponsesReceived uint64
39+
ResponsesReceived: cp.ResponsesReceived(),
4040
// ResponsesSent uint64
4141
// RetransmissionsReceived uint64
4242
// RetransmissionsSent uint64

agent_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,10 @@ func TestCandidatePairStats(t *testing.T) {
705705
p := a.findPair(hostLocal, prflxRemote)
706706
p.state = CandidatePairStateFailed
707707

708+
for i := 0; i < 10; i++ {
709+
p.UpdateRoundTripTime(time.Duration(i+1) * time.Second)
710+
}
711+
708712
stats := a.GetCandidatePairsStats()
709713
if len(stats) != 4 {
710714
t.Fatal("expected 4 candidate pairs stats")
@@ -751,6 +755,23 @@ func TestCandidatePairStats(t *testing.T) {
751755
prflxPairStat.State.String())
752756
}
753757

758+
expectedCurrentRoundTripTime := time.Duration(10) * time.Second
759+
if prflxPairStat.CurrentRoundTripTime != expectedCurrentRoundTripTime.Seconds() {
760+
t.Fatalf("expected current round trip time to be %f, it is %f instead",
761+
expectedCurrentRoundTripTime.Seconds(), prflxPairStat.CurrentRoundTripTime)
762+
}
763+
764+
expectedTotalRoundTripTime := time.Duration(55) * time.Second
765+
if prflxPairStat.TotalRoundTripTime != expectedTotalRoundTripTime.Seconds() {
766+
t.Fatalf("expected total round trip time to be %f, it is %f instead",
767+
expectedTotalRoundTripTime.Seconds(), prflxPairStat.TotalRoundTripTime)
768+
}
769+
770+
if prflxPairStat.ResponsesReceived != 10 {
771+
t.Fatalf("expected responses received to be 10, it is %d instead",
772+
prflxPairStat.ResponsesReceived)
773+
}
774+
754775
assert.NoError(t, a.Close())
755776
}
756777

candidatepair.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package ice
55

66
import (
77
"fmt"
8+
"sync/atomic"
9+
"time"
810

911
"github.com/pion/stun"
1012
)
@@ -28,6 +30,11 @@ type CandidatePair struct {
2830
state CandidatePairState
2931
nominated bool
3032
nominateOnBindingSuccess bool
33+
34+
// stats
35+
currentRoundTripTime int64 // in ns
36+
totalRoundTripTime int64 // in ns
37+
responsesReceived uint64
3138
}
3239

3340
func (p *CandidatePair) String() string {
@@ -100,3 +107,30 @@ func (a *Agent) sendSTUN(msg *stun.Message, local, remote Candidate) {
100107
a.log.Tracef("Failed to send STUN message: %s", err)
101108
}
102109
}
110+
111+
// UpdateRoundTripTime sets the current round time of this pair and
112+
// accumulates total round trip time and responses received
113+
func (p *CandidatePair) UpdateRoundTripTime(rtt time.Duration) {
114+
rttNs := rtt.Nanoseconds()
115+
atomic.StoreInt64(&p.currentRoundTripTime, rttNs)
116+
atomic.AddInt64(&p.totalRoundTripTime, rttNs)
117+
atomic.AddUint64(&p.responsesReceived, 1)
118+
}
119+
120+
// CurrentRoundTripTime returns the current round trip time in seconds
121+
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-currentroundtriptime
122+
func (p *CandidatePair) CurrentRoundTripTime() float64 {
123+
return time.Duration(atomic.LoadInt64(&p.currentRoundTripTime)).Seconds()
124+
}
125+
126+
// TotalRoundTripTime returns the current round trip time in seconds
127+
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-totalroundtriptime
128+
func (p *CandidatePair) TotalRoundTripTime() float64 {
129+
return time.Duration(atomic.LoadInt64(&p.totalRoundTripTime)).Seconds()
130+
}
131+
132+
// ResponsesReceived returns the total number of connectivity responses received
133+
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-responsesreceived
134+
func (p *CandidatePair) ResponsesReceived() uint64 {
135+
return atomic.LoadUint64(&p.responsesReceived)
136+
}

selection.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remot
120120
}
121121

122122
func (s *controllingSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) {
123-
ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID)
123+
ok, pendingRequest, rtt := s.agent.handleInboundBindingSuccess(m.TransactionID)
124124
if !ok {
125125
s.log.Warnf("Discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID)
126126
return
@@ -149,6 +149,8 @@ func (s *controllingSelector) HandleSuccessResponse(m *stun.Message, local, remo
149149
if pendingRequest.isUseCandidate && s.agent.getSelectedPair() == nil {
150150
s.agent.setSelectedPair(p)
151151
}
152+
153+
p.UpdateRoundTripTime(rtt)
152154
}
153155

154156
func (s *controllingSelector) PingCandidate(local, remote Candidate) {
@@ -211,7 +213,7 @@ func (s *controlledSelector) HandleSuccessResponse(m *stun.Message, local, remot
211213
// request with an appropriate error code response (e.g., 400)
212214
// [RFC5389].
213215

214-
ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID)
216+
ok, pendingRequest, rtt := s.agent.handleInboundBindingSuccess(m.TransactionID)
215217
if !ok {
216218
s.log.Warnf("Discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID)
217219
return
@@ -245,6 +247,8 @@ func (s *controlledSelector) HandleSuccessResponse(m *stun.Message, local, remot
245247
s.log.Tracef("Ignore nominate new pair %s, already nominated pair %s", p, selectedPair)
246248
}
247249
}
250+
251+
p.UpdateRoundTripTime(rtt)
248252
}
249253

250254
func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) {

0 commit comments

Comments
 (0)