From fb8462b56ab2371dfd045dfdf7bd328495b98e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Fri, 16 Dec 2022 13:49:51 +0100 Subject: [PATCH] Add WebRTC candidate metrics (#18) * stats: add protocol to iceCandidates. * add ice candidates used metric. * NewICECandidate use whole struct. --- internal/webrtc/manager.go | 20 +++++++++- internal/webrtc/metrics.go | 75 ++++++++++++++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/internal/webrtc/manager.go b/internal/webrtc/manager.go index e227d6df..794fda3a 100644 --- a/internal/webrtc/manager.go +++ b/internal/webrtc/manager.go @@ -545,13 +545,31 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, bitrate int) manager.metrics.SetSctpTransportStats(session, data) } + remoteCandidates := map[string]webrtc.ICECandidateStats{} + nominatedRemoteCandidates := map[string]struct{}{} for _, entry := range stats { // only remote ice candidate stats candidate, ok := entry.(webrtc.ICECandidateStats) if ok && candidate.Type == webrtc.StatsTypeRemoteCandidate { - manager.metrics.NewICECandidate(session, candidate.ID) + manager.metrics.NewICECandidate(session, candidate) + remoteCandidates[candidate.ID] = candidate + } + + // only nominated ice candidate pair stats + pair, ok := entry.(webrtc.ICECandidatePairStats) + if ok && pair.Nominated { + nominatedRemoteCandidates[pair.RemoteCandidateID] = struct{}{} } } + + iceCandidatesUsed := []webrtc.ICECandidateStats{} + for id := range nominatedRemoteCandidates { + if candidate, ok := remoteCandidates[id]; ok { + iceCandidatesUsed = append(iceCandidatesUsed, candidate) + } + } + + manager.metrics.SetICECandidatesUsed(session, iceCandidatesUsed) } }() diff --git a/internal/webrtc/metrics.go b/internal/webrtc/metrics.go index aad26491..dce22809 100644 --- a/internal/webrtc/metrics.go +++ b/internal/webrtc/metrics.go @@ -15,9 +15,13 @@ type metrics struct { connectionStateCount prometheus.Counter connectionCount prometheus.Counter - iceCandidates map[string]struct{} - iceCandidatesMu *sync.Mutex - iceCandidatesCount prometheus.Counter + iceCandidates map[string]struct{} + iceCandidatesMu *sync.Mutex + iceCandidatesUdpCount prometheus.Counter + iceCandidatesTcpCount prometheus.Counter + + iceCandidatesUsedUdp prometheus.Gauge + iceCandidatesUsedTcp prometheus.Gauge videoIds map[string]prometheus.Gauge videoIdsMu *sync.Mutex @@ -86,13 +90,45 @@ func (m *metricsCtx) getBySession(session types.Session) metrics { iceCandidates: map[string]struct{}{}, iceCandidatesMu: &sync.Mutex{}, - iceCandidatesCount: promauto.NewCounter(prometheus.CounterOpts{ + iceCandidatesUdpCount: promauto.NewCounter(prometheus.CounterOpts{ Name: "ice_candidates_count", Namespace: "neko", Subsystem: "webrtc", Help: "Count of ICE candidates sent by a remote client.", ConstLabels: map[string]string{ "session_id": session.ID(), + "protocol": "udp", + }, + }), + iceCandidatesTcpCount: promauto.NewCounter(prometheus.CounterOpts{ + Name: "ice_candidates_count", + Namespace: "neko", + Subsystem: "webrtc", + Help: "Count of ICE candidates sent by a remote client.", + ConstLabels: map[string]string{ + "session_id": session.ID(), + "protocol": "tcp", + }, + }), + + iceCandidatesUsedUdp: promauto.NewGauge(prometheus.GaugeOpts{ + Name: "ice_candidates_used", + Namespace: "neko", + Subsystem: "webrtc", + Help: "Used ICE candidates that are currently in use.", + ConstLabels: map[string]string{ + "session_id": session.ID(), + "protocol": "udp", + }, + }), + iceCandidatesUsedTcp: promauto.NewGauge(prometheus.GaugeOpts{ + Name: "ice_candidates_used", + Namespace: "neko", + Subsystem: "webrtc", + Help: "Used ICE candidates that are currently in use.", + ConstLabels: map[string]string{ + "session_id": session.ID(), + "protocol": "tcp", }, }), @@ -191,6 +227,9 @@ func (m *metricsCtx) reset(met metrics) { } met.videoIdsMu.Unlock() + met.iceCandidatesUsedUdp.Set(float64(0)) + met.iceCandidatesUsedTcp.Set(float64(0)) + met.receiverEstimatedMaximumBitrate.Set(0) met.receiverReportDelay.Set(0) @@ -202,18 +241,38 @@ func (m *metricsCtx) NewConnection(session types.Session) { met.connectionCount.Add(1) } -func (m *metricsCtx) NewICECandidate(session types.Session, id string) { +func (m *metricsCtx) NewICECandidate(session types.Session, candidate webrtc.ICECandidateStats) { met := m.getBySession(session) met.iceCandidatesMu.Lock() defer met.iceCandidatesMu.Unlock() - if _, found := met.iceCandidates[id]; found { + if _, found := met.iceCandidates[candidate.ID]; found { return } - met.iceCandidates[id] = struct{}{} - met.iceCandidatesCount.Add(1) + met.iceCandidates[candidate.ID] = struct{}{} + if candidate.Protocol == "udp" { + met.iceCandidatesUdpCount.Add(1) + } else if candidate.Protocol == "tcp" { + met.iceCandidatesTcpCount.Add(1) + } +} + +func (m *metricsCtx) SetICECandidatesUsed(session types.Session, candidates []webrtc.ICECandidateStats) { + met := m.getBySession(session) + + udp, tcp := 0, 0 + for _, candidate := range candidates { + if candidate.Protocol == "udp" { + udp++ + } else if candidate.Protocol == "tcp" { + tcp++ + } + } + + met.iceCandidatesUsedUdp.Set(float64(udp)) + met.iceCandidatesUsedTcp.Set(float64(tcp)) } func (m *metricsCtx) SetState(session types.Session, state webrtc.PeerConnectionState) {