diff --git a/agent_stats.go b/agent_stats.go index 785e7ff7..b18c1381 100644 --- a/agent_stats.go +++ b/agent_stats.go @@ -54,6 +54,56 @@ func (a *Agent) GetCandidatePairsStats() []CandidatePairStats { return res } +// GetSelectedCandidatePairStats returns a candidate pair stats for selected candidate pair. +// Returns false if there is no selected pair +func (a *Agent) GetSelectedCandidatePairStats() (CandidatePairStats, bool) { + isAvailable := false + var res CandidatePairStats + err := a.loop.Run(a.loop, func(_ context.Context) { + sp := a.getSelectedPair() + if sp == nil { + return + } + + isAvailable = true + res = CandidatePairStats{ + Timestamp: time.Now(), + LocalCandidateID: sp.Local.ID(), + RemoteCandidateID: sp.Remote.ID(), + State: sp.state, + Nominated: sp.nominated, + // PacketsSent uint32 + // PacketsReceived uint32 + // BytesSent uint64 + // BytesReceived uint64 + // LastPacketSentTimestamp time.Time + // LastPacketReceivedTimestamp time.Time + // FirstRequestTimestamp time.Time + // LastRequestTimestamp time.Time + // LastResponseTimestamp time.Time + TotalRoundTripTime: sp.TotalRoundTripTime(), + CurrentRoundTripTime: sp.CurrentRoundTripTime(), + // AvailableOutgoingBitrate float64 + // AvailableIncomingBitrate float64 + // CircuitBreakerTriggerCount uint32 + // RequestsReceived uint64 + // RequestsSent uint64 + ResponsesReceived: sp.ResponsesReceived(), + // ResponsesSent uint64 + // RetransmissionsReceived uint64 + // RetransmissionsSent uint64 + // ConsentRequestsSent uint64 + // ConsentExpiredTimestamp time.Time + } + }) + if err != nil { + a.log.Errorf("Failed to get selected candidate pair stats: %v", err) + return CandidatePairStats{}, false + } + + return res, isAvailable +} + // GetLocalCandidatesStats returns a list of local candidates stats func (a *Agent) GetLocalCandidatesStats() []CandidateStats { var res []CandidateStats diff --git a/agent_test.go b/agent_test.go index 5622ec15..168fcddb 100644 --- a/agent_test.go +++ b/agent_test.go @@ -635,7 +635,7 @@ func TestInvalidGather(t *testing.T) { }) } -func TestCandidatePairStats(t *testing.T) { +func TestCandidatePairsStats(t *testing.T) { defer test.CheckRoutines(t)() // Avoid deadlocks? @@ -789,6 +789,89 @@ func TestCandidatePairStats(t *testing.T) { } } +func TestSelectedCandidatePairStats(t *testing.T) { + defer test.CheckRoutines(t)() + + // Avoid deadlocks? + defer test.TimeOut(1 * time.Second).Stop() + + a, err := NewAgent(&AgentConfig{}) + if err != nil { + t.Fatalf("Failed to create agent: %s", err) + } + defer func() { + require.NoError(t, a.Close()) + }() + + hostConfig := &CandidateHostConfig{ + Network: "udp", + Address: "192.168.1.1", + Port: 19216, + Component: 1, + } + hostLocal, err := NewCandidateHost(hostConfig) + if err != nil { + t.Fatalf("Failed to construct local host candidate: %s", err) + } + + srflxConfig := &CandidateServerReflexiveConfig{ + Network: "udp", + Address: "10.10.10.2", + Port: 19218, + Component: 1, + RelAddr: "4.3.2.1", + RelPort: 43212, + } + srflxRemote, err := NewCandidateServerReflexive(srflxConfig) + if err != nil { + t.Fatalf("Failed to construct remote srflx candidate: %s", err) + } + + // no selected pair, should return not available + _, ok := a.GetSelectedCandidatePairStats() + require.False(t, ok) + + // add pair and populate some RTT stats + p := a.findPair(hostLocal, srflxRemote) + if p == nil { + a.addPair(hostLocal, srflxRemote) + p = a.findPair(hostLocal, srflxRemote) + } + for i := 0; i < 10; i++ { + p.UpdateRoundTripTime(time.Duration(i+1) * time.Second) + } + + // set the pair as selected + a.setSelectedPair(p) + + stats, ok := a.GetSelectedCandidatePairStats() + require.True(t, ok) + + if stats.LocalCandidateID != hostLocal.ID() { + t.Fatal("invalid local candidate id") + } + if stats.RemoteCandidateID != srflxRemote.ID() { + t.Fatal("invalid remote candidate id") + } + + expectedCurrentRoundTripTime := time.Duration(10) * time.Second + if stats.CurrentRoundTripTime != expectedCurrentRoundTripTime.Seconds() { + t.Fatalf("expected current round trip time to be %f, it is %f instead", + expectedCurrentRoundTripTime.Seconds(), stats.CurrentRoundTripTime) + } + + expectedTotalRoundTripTime := time.Duration(55) * time.Second + if stats.TotalRoundTripTime != expectedTotalRoundTripTime.Seconds() { + t.Fatalf("expected total round trip time to be %f, it is %f instead", + expectedTotalRoundTripTime.Seconds(), stats.TotalRoundTripTime) + } + + if stats.ResponsesReceived != 10 { + t.Fatalf("expected responses received to be 10, it is %d instead", + stats.ResponsesReceived) + } +} + func TestLocalCandidateStats(t *testing.T) { defer test.CheckRoutines(t)()