Skip to content

Commit f92d05f

Browse files
authored
Add Req/Res count/time to candidate stats (#763)
These details will provide information for connectivity issue.
1 parent d21ae5e commit f92d05f

6 files changed

+163
-15
lines changed

agent.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -980,18 +980,23 @@ func (a *Agent) findRemoteCandidate(networkType NetworkType, addr net.Addr) Cand
980980
return nil
981981
}
982982

983-
func (a *Agent) sendBindingRequest(m *stun.Message, local, remote Candidate) {
983+
func (a *Agent) sendBindingRequest(msg *stun.Message, local, remote Candidate) {
984984
a.log.Tracef("Ping STUN from %s to %s", local, remote)
985985

986986
a.invalidatePendingBindingRequests(time.Now())
987987
a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{
988988
timestamp: time.Now(),
989-
transactionID: m.TransactionID,
989+
transactionID: msg.TransactionID,
990990
destination: remote.addr(),
991-
isUseCandidate: m.Contains(stun.AttrUseCandidate),
991+
isUseCandidate: msg.Contains(stun.AttrUseCandidate),
992992
})
993993

994-
a.sendSTUN(m, local, remote)
994+
if pair := a.findPair(local, remote); pair != nil {
995+
pair.UpdateRequestSent()
996+
} else {
997+
a.log.Warnf("Failed to find pair for add binding request from %s to %s", local, remote)
998+
}
999+
a.sendSTUN(msg, local, remote)
9951000
}
9961001

9971002
func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote Candidate) {
@@ -1014,6 +1019,11 @@ func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote Candidate) {
10141019
); err != nil {
10151020
a.log.Warnf("Failed to handle inbound ICE from: %s to: %s error: %s", local, remote, err)
10161021
} else {
1022+
if pair := a.findPair(local, remote); pair != nil {
1023+
pair.UpdateResponseSent()
1024+
} else {
1025+
a.log.Warnf("Failed to find pair for add binding response from %s to %s", local, remote)
1026+
}
10171027
a.sendSTUN(out, local, remote)
10181028
}
10191029
}

agent_stats.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,22 @@ func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
2626
// BytesReceived uint64
2727
// LastPacketSentTimestamp time.Time
2828
// LastPacketReceivedTimestamp time.Time
29-
// FirstRequestTimestamp time.Time
30-
// LastRequestTimestamp time.Time
31-
// LastResponseTimestamp time.Time
29+
FirstRequestTimestamp: cp.FirstRequestSentAt(),
30+
LastRequestTimestamp: cp.LastRequestSentAt(),
31+
FirstResponseTimestamp: cp.FirstReponseReceivedAt(),
32+
LastResponseTimestamp: cp.LastResponseReceivedAt(),
33+
FirstRequestReceivedTimestamp: cp.FirstRequestReceivedAt(),
34+
LastRequestReceivedTimestamp: cp.LastRequestReceivedAt(),
35+
3236
TotalRoundTripTime: cp.TotalRoundTripTime(),
3337
CurrentRoundTripTime: cp.CurrentRoundTripTime(),
3438
// AvailableOutgoingBitrate float64
3539
// AvailableIncomingBitrate float64
3640
// CircuitBreakerTriggerCount uint32
37-
// RequestsReceived uint64
38-
// RequestsSent uint64
41+
RequestsReceived: cp.RequestsReceived(),
42+
RequestsSent: cp.RequestsSent(),
3943
ResponsesReceived: cp.ResponsesReceived(),
40-
// ResponsesSent uint64
44+
ResponsesSent: cp.ResponsesSent(),
4145
// RetransmissionsReceived uint64
4246
// RetransmissionsSent uint64
4347
// ConsentRequestsSent uint64

agent_test.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ func TestInvalidGather(t *testing.T) {
636636
})
637637
}
638638

639-
func TestCandidatePairsStats(t *testing.T) { //nolint:cyclop
639+
func TestCandidatePairsStats(t *testing.T) { //nolint:cyclop,gocyclo
640640
defer test.CheckRoutines(t)()
641641

642642
// Avoid deadlocks?
@@ -715,14 +715,18 @@ func TestCandidatePairsStats(t *testing.T) { //nolint:cyclop
715715
p := agent.findPair(hostLocal, remote)
716716

717717
if p == nil {
718-
agent.addPair(hostLocal, remote)
718+
p = agent.addPair(hostLocal, remote)
719719
}
720+
p.UpdateRequestReceived()
721+
p.UpdateRequestSent()
722+
p.UpdateResponseSent()
723+
p.UpdateRoundTripTime(time.Second)
720724
}
721725

722726
p := agent.findPair(hostLocal, prflxRemote)
723727
p.state = CandidatePairStateFailed
724728

725-
for i := 0; i < 10; i++ {
729+
for i := 1; i < 10; i++ {
726730
p.UpdateRoundTripTime(time.Duration(i+1) * time.Second)
727731
}
728732

@@ -749,6 +753,13 @@ func TestCandidatePairsStats(t *testing.T) { //nolint:cyclop
749753
default:
750754
t.Fatal("invalid remote candidate ID")
751755
}
756+
757+
if cps.FirstRequestTimestamp.IsZero() || cps.LastRequestTimestamp.IsZero() ||
758+
cps.FirstResponseTimestamp.IsZero() || cps.LastResponseTimestamp.IsZero() ||
759+
cps.FirstRequestReceivedTimestamp.IsZero() || cps.LastRequestReceivedTimestamp.IsZero() ||
760+
cps.RequestsReceived == 0 || cps.RequestsSent == 0 || cps.ResponsesSent == 0 || cps.ResponsesReceived == 0 {
761+
t.Fatal("failed to verify pair stats counter and timestamps", cps)
762+
}
752763
}
753764

754765
if relayPairStat.RemoteCandidateID != relayRemote.ID() {

candidatepair.go

+109-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,18 @@ type CandidatePair struct {
3333
// stats
3434
currentRoundTripTime int64 // in ns
3535
totalRoundTripTime int64 // in ns
36-
responsesReceived uint64
36+
37+
requestsReceived uint64
38+
requestsSent uint64
39+
responsesReceived uint64
40+
responsesSent uint64
41+
42+
firstRequestSentAt atomic.Value // time.Time
43+
lastRequestSentAt atomic.Value // time.Time
44+
firstReponseReceivedAt atomic.Value // time.Time
45+
lastResponseReceivedAt atomic.Value // time.Time
46+
firstRequestReceivedAt atomic.Value // time.Time
47+
lastRequestReceivedAt atomic.Value // time.Time
3748
}
3849

3950
func (p *CandidatePair) String() string {
@@ -127,6 +138,10 @@ func (p *CandidatePair) UpdateRoundTripTime(rtt time.Duration) {
127138
atomic.StoreInt64(&p.currentRoundTripTime, rttNs)
128139
atomic.AddInt64(&p.totalRoundTripTime, rttNs)
129140
atomic.AddUint64(&p.responsesReceived, 1)
141+
142+
now := time.Now()
143+
p.firstReponseReceivedAt.CompareAndSwap(nil, now)
144+
p.lastResponseReceivedAt.Store(now)
130145
}
131146

132147
// CurrentRoundTripTime returns the current round trip time in seconds
@@ -141,8 +156,101 @@ func (p *CandidatePair) TotalRoundTripTime() float64 {
141156
return time.Duration(atomic.LoadInt64(&p.totalRoundTripTime)).Seconds()
142157
}
143158

159+
// RequestsReceived returns the total number of connectivity checks received
160+
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-requestsreceived
161+
func (p *CandidatePair) RequestsReceived() uint64 {
162+
return atomic.LoadUint64(&p.requestsReceived)
163+
}
164+
165+
// RequestsSent returns the total number of connectivity checks sent
166+
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-requestssent
167+
func (p *CandidatePair) RequestsSent() uint64 {
168+
return atomic.LoadUint64(&p.requestsSent)
169+
}
170+
144171
// ResponsesReceived returns the total number of connectivity responses received
145172
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-responsesreceived
146173
func (p *CandidatePair) ResponsesReceived() uint64 {
147174
return atomic.LoadUint64(&p.responsesReceived)
148175
}
176+
177+
// ResponsesSent returns the total number of connectivity responses sent
178+
// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-responsessent
179+
func (p *CandidatePair) ResponsesSent() uint64 {
180+
return atomic.LoadUint64(&p.responsesSent)
181+
}
182+
183+
// FirstRequestSentAt returns the timestamp of the first connectivity check sent.
184+
func (p *CandidatePair) FirstRequestSentAt() time.Time {
185+
if v, ok := p.firstRequestSentAt.Load().(time.Time); ok {
186+
return v
187+
}
188+
189+
return time.Time{}
190+
}
191+
192+
// LastRequestSentAt returns the timestamp of the last connectivity check sent.
193+
func (p *CandidatePair) LastRequestSentAt() time.Time {
194+
if v, ok := p.lastRequestSentAt.Load().(time.Time); ok {
195+
return v
196+
}
197+
198+
return time.Time{}
199+
}
200+
201+
// FirstReponseReceivedAt returns the timestamp of the first connectivity response received.
202+
func (p *CandidatePair) FirstReponseReceivedAt() time.Time {
203+
if v, ok := p.firstReponseReceivedAt.Load().(time.Time); ok {
204+
return v
205+
}
206+
207+
return time.Time{}
208+
}
209+
210+
// LastResponseReceivedAt returns the timestamp of the last connectivity response received.
211+
func (p *CandidatePair) LastResponseReceivedAt() time.Time {
212+
if v, ok := p.lastResponseReceivedAt.Load().(time.Time); ok {
213+
return v
214+
}
215+
216+
return time.Time{}
217+
}
218+
219+
// FirstRequestReceivedAt returns the timestamp of the first connectivity check received.
220+
func (p *CandidatePair) FirstRequestReceivedAt() time.Time {
221+
if v, ok := p.firstRequestReceivedAt.Load().(time.Time); ok {
222+
return v
223+
}
224+
225+
return time.Time{}
226+
}
227+
228+
// LastRequestReceivedAt returns the timestamp of the last connectivity check received.
229+
func (p *CandidatePair) LastRequestReceivedAt() time.Time {
230+
if v, ok := p.lastRequestReceivedAt.Load().(time.Time); ok {
231+
return v
232+
}
233+
234+
return time.Time{}
235+
}
236+
237+
// UpdateRequestSent increments the number of requests sent and updates the timestamp.
238+
func (p *CandidatePair) UpdateRequestSent() {
239+
atomic.AddUint64(&p.requestsSent, 1)
240+
now := time.Now()
241+
p.firstRequestSentAt.CompareAndSwap(nil, now)
242+
p.lastRequestSentAt.Store(now)
243+
}
244+
245+
// UpdateResponseSent increments the number of responses sent.
246+
func (p *CandidatePair) UpdateResponseSent() {
247+
atomic.AddUint64(&p.responsesSent, 1)
248+
}
249+
250+
// UpdateRequestReceived increments the number of requests received and updates the timestamp.
251+
func (p *CandidatePair) UpdateRequestReceived() {
252+
atomic.AddUint64(&p.requestsReceived, 1)
253+
now := time.Now()
254+
p.firstRequestReceivedAt.CompareAndSwap(nil, now)
255+
p.lastRequestReceivedAt.Store(now)
256+
}

selection.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,12 @@ func (s *controllingSelector) HandleBindingRequest(message *stun.Message, local,
100100
pair := s.agent.findPair(local, remote)
101101

102102
if pair == nil {
103-
s.agent.addPair(local, remote)
103+
pair = s.agent.addPair(local, remote)
104+
pair.UpdateRequestReceived()
104105

105106
return
106107
}
108+
pair.UpdateRequestReceived()
107109

108110
if pair.state == CandidatePairStateSucceeded && s.nominatedPair == nil && s.agent.getSelectedPair() == nil {
109111
bestPair := s.agent.getBestAvailableCandidatePair()
@@ -281,6 +283,7 @@ func (s *controlledSelector) HandleBindingRequest(message *stun.Message, local,
281283
if pair == nil {
282284
pair = s.agent.addPair(local, remote)
283285
}
286+
pair.UpdateRequestReceived()
284287

285288
if message.Contains(stun.AttrUseCandidate) { //nolint:nestif
286289
// https://tools.ietf.org/html/rfc8445#section-7.3.1.5

stats.go

+12
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,22 @@ type CandidatePairStats struct {
5858
// (LastRequestTimestamp - FirstRequestTimestamp) / RequestsSent.
5959
LastRequestTimestamp time.Time
6060

61+
// FirstResponseTimestamp represents the timestamp at which the first STUN response
62+
// was received on this particular candidate pair.
63+
FirstResponseTimestamp time.Time
64+
6165
// LastResponseTimestamp represents the timestamp at which the last STUN response
6266
// was received on this particular candidate pair.
6367
LastResponseTimestamp time.Time
6468

69+
// FirstRequestReceivedTimestamp represents the timestamp at which the first
70+
// connectivity check request was received.
71+
FirstRequestReceivedTimestamp time.Time
72+
73+
// LastRequestReceivedTimestamp represents the timestamp at which the last
74+
// connectivity check request was received.
75+
LastRequestReceivedTimestamp time.Time
76+
6577
// TotalRoundTripTime represents the sum of all round trip time measurements
6678
// in seconds since the beginning of the session, based on STUN connectivity
6779
// check responses (ResponsesReceived), including those that reply to requests

0 commit comments

Comments
 (0)