Skip to content

Commit 854fdfd

Browse files
authored
Add ability to get selected candidate pair stats (#735)
It is useful to have stats from just the selected pair as a lightweight option where a lot of agents are running, for example, an SFU. lint Switch udp_mux_test to use sha256 instead of sha1 (#733) Minor change to this test to stop using sha1 and remove the linter exceptions. Co-authored-by: Daniel Kessler <[email protected]> Update module golang.org/x/net to v0.29.0 Generated by renovateBot Update module github.com/pion/dtls/v3 to v3.0.3 Generated by renovateBot
1 parent 410d6ec commit 854fdfd

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

agent_stats.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,56 @@ func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
5454
return res
5555
}
5656

57+
// GetSelectedCandidatePairStats returns a candidate pair stats for selected candidate pair.
58+
// Returns false if there is no selected pair
59+
func (a *Agent) GetSelectedCandidatePairStats() (CandidatePairStats, bool) {
60+
isAvailable := false
61+
var res CandidatePairStats
62+
err := a.loop.Run(a.loop, func(_ context.Context) {
63+
sp := a.getSelectedPair()
64+
if sp == nil {
65+
return
66+
}
67+
68+
isAvailable = true
69+
res = CandidatePairStats{
70+
Timestamp: time.Now(),
71+
LocalCandidateID: sp.Local.ID(),
72+
RemoteCandidateID: sp.Remote.ID(),
73+
State: sp.state,
74+
Nominated: sp.nominated,
75+
// PacketsSent uint32
76+
// PacketsReceived uint32
77+
// BytesSent uint64
78+
// BytesReceived uint64
79+
// LastPacketSentTimestamp time.Time
80+
// LastPacketReceivedTimestamp time.Time
81+
// FirstRequestTimestamp time.Time
82+
// LastRequestTimestamp time.Time
83+
// LastResponseTimestamp time.Time
84+
TotalRoundTripTime: sp.TotalRoundTripTime(),
85+
CurrentRoundTripTime: sp.CurrentRoundTripTime(),
86+
// AvailableOutgoingBitrate float64
87+
// AvailableIncomingBitrate float64
88+
// CircuitBreakerTriggerCount uint32
89+
// RequestsReceived uint64
90+
// RequestsSent uint64
91+
ResponsesReceived: sp.ResponsesReceived(),
92+
// ResponsesSent uint64
93+
// RetransmissionsReceived uint64
94+
// RetransmissionsSent uint64
95+
// ConsentRequestsSent uint64
96+
// ConsentExpiredTimestamp time.Time
97+
}
98+
})
99+
if err != nil {
100+
a.log.Errorf("Failed to get selected candidate pair stats: %v", err)
101+
return CandidatePairStats{}, false
102+
}
103+
104+
return res, isAvailable
105+
}
106+
57107
// GetLocalCandidatesStats returns a list of local candidates stats
58108
func (a *Agent) GetLocalCandidatesStats() []CandidateStats {
59109
var res []CandidateStats

agent_test.go

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ func TestInvalidGather(t *testing.T) {
635635
})
636636
}
637637

638-
func TestCandidatePairStats(t *testing.T) {
638+
func TestCandidatePairsStats(t *testing.T) {
639639
defer test.CheckRoutines(t)()
640640

641641
// Avoid deadlocks?
@@ -789,6 +789,89 @@ func TestCandidatePairStats(t *testing.T) {
789789
}
790790
}
791791

792+
func TestSelectedCandidatePairStats(t *testing.T) {
793+
defer test.CheckRoutines(t)()
794+
795+
// Avoid deadlocks?
796+
defer test.TimeOut(1 * time.Second).Stop()
797+
798+
a, err := NewAgent(&AgentConfig{})
799+
if err != nil {
800+
t.Fatalf("Failed to create agent: %s", err)
801+
}
802+
defer func() {
803+
require.NoError(t, a.Close())
804+
}()
805+
806+
hostConfig := &CandidateHostConfig{
807+
Network: "udp",
808+
Address: "192.168.1.1",
809+
Port: 19216,
810+
Component: 1,
811+
}
812+
hostLocal, err := NewCandidateHost(hostConfig)
813+
if err != nil {
814+
t.Fatalf("Failed to construct local host candidate: %s", err)
815+
}
816+
817+
srflxConfig := &CandidateServerReflexiveConfig{
818+
Network: "udp",
819+
Address: "10.10.10.2",
820+
Port: 19218,
821+
Component: 1,
822+
RelAddr: "4.3.2.1",
823+
RelPort: 43212,
824+
}
825+
srflxRemote, err := NewCandidateServerReflexive(srflxConfig)
826+
if err != nil {
827+
t.Fatalf("Failed to construct remote srflx candidate: %s", err)
828+
}
829+
830+
// no selected pair, should return not available
831+
_, ok := a.GetSelectedCandidatePairStats()
832+
require.False(t, ok)
833+
834+
// add pair and populate some RTT stats
835+
p := a.findPair(hostLocal, srflxRemote)
836+
if p == nil {
837+
a.addPair(hostLocal, srflxRemote)
838+
p = a.findPair(hostLocal, srflxRemote)
839+
}
840+
for i := 0; i < 10; i++ {
841+
p.UpdateRoundTripTime(time.Duration(i+1) * time.Second)
842+
}
843+
844+
// set the pair as selected
845+
a.setSelectedPair(p)
846+
847+
stats, ok := a.GetSelectedCandidatePairStats()
848+
require.True(t, ok)
849+
850+
if stats.LocalCandidateID != hostLocal.ID() {
851+
t.Fatal("invalid local candidate id")
852+
}
853+
if stats.RemoteCandidateID != srflxRemote.ID() {
854+
t.Fatal("invalid remote candidate id")
855+
}
856+
857+
expectedCurrentRoundTripTime := time.Duration(10) * time.Second
858+
if stats.CurrentRoundTripTime != expectedCurrentRoundTripTime.Seconds() {
859+
t.Fatalf("expected current round trip time to be %f, it is %f instead",
860+
expectedCurrentRoundTripTime.Seconds(), stats.CurrentRoundTripTime)
861+
}
862+
863+
expectedTotalRoundTripTime := time.Duration(55) * time.Second
864+
if stats.TotalRoundTripTime != expectedTotalRoundTripTime.Seconds() {
865+
t.Fatalf("expected total round trip time to be %f, it is %f instead",
866+
expectedTotalRoundTripTime.Seconds(), stats.TotalRoundTripTime)
867+
}
868+
869+
if stats.ResponsesReceived != 10 {
870+
t.Fatalf("expected responses received to be 10, it is %d instead",
871+
stats.ResponsesReceived)
872+
}
873+
}
874+
792875
func TestLocalCandidateStats(t *testing.T) {
793876
defer test.CheckRoutines(t)()
794877

0 commit comments

Comments
 (0)