summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorMnikolenko Productengine <mnikolenko@productengine.com>2025-11-26 18:41:30 +0200
committerMnikolenko Productengine <mnikolenko@productengine.com>2025-11-26 19:17:44 +0200
commit6ee41d60753d31d6826994731570f0ab64b92bd9 (patch)
tree4203421e991a30aec9441a68bc24e2ea56636d2d /indra
parente740bd21e39e93666445abfeb32cf476c249cfb7 (diff)
#5018 add webrtc connection statistics
Diffstat (limited to 'indra')
-rw-r--r--indra/llwebrtc/llwebrtc.cpp51
-rw-r--r--indra/llwebrtc/llwebrtc.h6
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h2
-rw-r--r--indra/newview/app_settings/settings.xml33
-rw-r--r--indra/newview/llviewerstats.cpp14
-rw-r--r--indra/newview/llviewerstats.h2
-rw-r--r--indra/newview/llvoicewebrtc.cpp117
-rw-r--r--indra/newview/llvoicewebrtc.h6
-rw-r--r--indra/newview/skins/default/xui/en/floater_stats.xml58
9 files changed, 289 insertions, 0 deletions
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index b9f126e511..62eadecd6f 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -1521,6 +1521,57 @@ void LLWebRTCPeerConnectionImpl::unsetDataObserver(LLWebRTCDataObserver* observe
}
}
+class LLStatsCollectorCallback : public webrtc::RTCStatsCollectorCallback
+{
+public:
+ typedef std::function<void(const LLWebRTCStatsMap&)> StatsCallback;
+
+ LLStatsCollectorCallback(StatsCallback callback) : callback_(callback) {}
+
+ void OnStatsDelivered(const webrtc::scoped_refptr<const webrtc::RTCStatsReport>& report) override
+ {
+ if (callback_)
+ {
+ // Transform RTCStatsReport stats to simple map
+ LLWebRTCStatsMap stats_map;
+ for (const auto& stats : *report)
+ {
+ std::map<std::string, std::string> stat_attributes;
+
+ // Convert each attribute to string format
+ for (const auto& attribute : stats.Attributes())
+ {
+ stat_attributes[attribute.name()] = attribute.ToString();
+ }
+ stats_map[stats.id()] = stat_attributes;
+ }
+ callback_(stats_map);
+ }
+ }
+
+private:
+ StatsCallback callback_;
+};
+
+void LLWebRTCPeerConnectionImpl::gatherConnectionStats()
+{
+ if (!mPeerConnection)
+ {
+ return;
+ }
+
+ auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
+ [this](const LLWebRTCStatsMap& generic_stats)
+ {
+ for (auto& observer : mSignalingObserverList)
+ {
+ observer->OnStatsDelivered(generic_stats);
+ }
+ });
+
+ mPeerConnection->GetStats(stats_callback.get());
+}
+
LLWebRTCImpl * gWebRTCImpl = nullptr;
LLWebRTCDeviceInterface * getDeviceInterface()
{
diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h
index 7d06b7d2b4..e76e708f0c 100644
--- a/indra/llwebrtc/llwebrtc.h
+++ b/indra/llwebrtc/llwebrtc.h
@@ -38,6 +38,7 @@
#ifndef LLWEBRTC_H
#define LLWEBRTC_H
+#include <map>
#include <string>
#include <vector>
@@ -55,6 +56,7 @@
namespace llwebrtc
{
+typedef std::map<std::string, std::map<std::string, std::string>> LLWebRTCStatsMap;
class LLWebRTCLogCallback
{
@@ -240,6 +242,8 @@ class LLWebRTCSignalingObserver
// Called when the data channel has been established and data
// transfer can begin.
virtual void OnDataChannelReady(LLWebRTCDataInterface *data_interface) = 0;
+
+ virtual void OnStatsDelivered(const LLWebRTCStatsMap& stats_data) {}
};
// LLWebRTCPeerConnectionInterface representsd a connection to a peer,
@@ -273,6 +277,8 @@ class LLWebRTCPeerConnectionInterface
virtual void unsetSignalingObserver(LLWebRTCSignalingObserver* observer) = 0;
virtual void AnswerAvailable(const std::string &sdp) = 0;
+
+ virtual void gatherConnectionStats() = 0;
};
// The following define the dynamic linked library
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
index 01cfb17ced..2a00e066bd 100644
--- a/indra/llwebrtc/llwebrtc_impl.h
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -648,6 +648,8 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
void enableSenderTracks(bool enable);
void enableReceiverTracks(bool enable);
+ void gatherConnectionStats();
+
protected:
LLWebRTCImpl * mWebRTCImpl;
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index aca9910253..eac6893504 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -6060,6 +6060,39 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>OpenDebugStatVoice</key>
+ <map>
+ <key>Comment</key>
+ <string>Expand Voice (WebRTC) stats display</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>OpenDebugStatVoiceOutgoing</key>
+ <map>
+ <key>Comment</key>
+ <string>Expand Outgoing audio (Voice) stats display</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>OpenDebugStatVoiceIncoming</key>
+ <map>
+ <key>Comment</key>
+ <string>Expand Incoming audio (Voice) stats display</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
<key>OutBandwidth</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index d39d466205..db6d83db5f 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -263,6 +263,20 @@ LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > HUDS_FRAME_PCT("huds_
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > UI_FRAME_PCT("ui_frame_pct");
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > SWAP_FRAME_PCT("swap_frame_pct");
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > IDLE_FRAME_PCT("idle_frame_pct");
+
+
+
+LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST("webrtc_packets_in_lost", "Lost incoming packets"),
+ WEBRTC_PACKETS_IN_RECEIVED("webrtc_packets_in_recv", "Incoming packets received"),
+ WEBRTC_PACKETS_OUT_SENT("webrtc_packets_out_sent", "Outgoing packets sent"),
+ WEBRTC_PACKETS_OUT_LOST("webrtc_packets_out_lost", "Lost outgoing packets");
+
+LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT("webrtc_jitter_out", "Timing variation of outgoing audio"),
+ WEBRTC_JITTER_IN("webrtc_jitter_in", "Timing variation of incoming audio"),
+ WEBRTC_LATENCY("webrtc_latency", "Round-trip audio delay"),
+ WEBRTC_UPLOAD_BANDWIDTH("webrtc_upload_bandwidth", "Estimated upload bandwidth"),
+ WEBRTC_JITTER_BUFFER("webrtc_jitter_buffer", "Average delay added to smooth incoming audio");
+
}
LLViewerStats::LLViewerStats()
diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h
index 011269d7ee..92e15bb74b 100644
--- a/indra/newview/llviewerstats.h
+++ b/indra/newview/llviewerstats.h
@@ -229,6 +229,8 @@ extern LLTrace::EventStatHandle<F64Seconds > AVATAR_EDIT_TIME,
extern LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT_RATE;
+extern LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST, WEBRTC_PACKETS_IN_RECEIVED, WEBRTC_PACKETS_OUT_SENT, WEBRTC_PACKETS_OUT_LOST;
+extern LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT, WEBRTC_JITTER_IN, WEBRTC_LATENCY, WEBRTC_UPLOAD_BANDWIDTH, WEBRTC_JITTER_BUFFER;
}
class LLViewerStats : public LLSingleton<LLViewerStats>
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index e4fdf85d12..5c13b849d7 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -61,6 +61,7 @@
#include "llrand.h"
#include "llviewerwindow.h"
#include "llviewercamera.h"
+#include "llviewerstats.h"
#include "llversioninfo.h"
#include "llviewernetwork.h"
@@ -81,6 +82,8 @@
const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc";
+const F32 STATS_TIMER_DELAY = 2.0;
+
namespace {
const F32 MAX_AUDIO_DIST = 50.0f;
@@ -2905,6 +2908,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
}
mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume);
LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID);
+ resetConnectionStats();
setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL);
break;
}
@@ -2954,6 +2958,13 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
sendJoin();
}
}
+
+ static LLTimer stats_timer;
+ if (stats_timer.getElapsedTimeF32() > STATS_TIMER_DELAY)
+ {
+ mWebRTCPeerConnectionInterface->gatherConnectionStats();
+ stats_timer.reset();
+ }
}
break;
}
@@ -3288,6 +3299,112 @@ void LLVoiceWebRTCConnection::sendJoin()
mWebRTCDataInterface->sendData(json_data, false);
}
+void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data)
+{
+ LL::WorkQueue::postMaybe(mMainQueue, [=, this]
+ {
+ if (mShutDown)
+ {
+ return;
+ }
+ for (const auto& [stats_id, attributes] : stats_data)
+ {
+ if (attributes.contains("currentRoundTripTime"))
+ {
+ F32 rtt_seconds = 0.0f;
+ LLStringUtil::convertToF32(attributes.at("currentRoundTripTime"), rtt_seconds);
+ sample(LLStatViewer::WEBRTC_LATENCY, rtt_seconds * 1000.0f);
+ }
+ if (attributes.contains("availableOutgoingBitrate"))
+ {
+ F32 bitrate_bps = 0.0f;
+ LLStringUtil::convertToF32(attributes.at("availableOutgoingBitrate"), bitrate_bps);
+ sample(LLStatViewer::WEBRTC_UPLOAD_BANDWIDTH, bitrate_bps / 1000.0f);
+ }
+
+ // Stat type detection below is heuristic-based.
+ // It's relied on specific fields to distinguish outbound-rtp, remote-inbound-rtp, and inbound-rtp.
+ // This approach works with current WebRTC stats but may need updating later.
+
+ // Outbound RTP
+ if (attributes.contains("mediaSourceId"))
+ {
+ U32 out_packets_sent = 0;
+ LLStringUtil::convertToU32(attributes.at("packetsSent"), out_packets_sent);
+ sample(LLStatViewer::WEBRTC_PACKETS_OUT_SENT, out_packets_sent);
+ }
+ // Remote-Inbound RTP
+ else if (attributes.contains("localId"))
+ {
+ if (attributes.contains("packetsLost"))
+ {
+ U32 out_packets_lost = 0;
+ LLStringUtil::convertToU32(attributes.at("packetsLost"), out_packets_lost);
+ sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, out_packets_lost);
+ }
+ if (attributes.contains("jitter"))
+ {
+ F32 jitter_seconds = 0.0f;
+ LLStringUtil::convertToF32(attributes.at("jitter"), jitter_seconds);
+ sample(LLStatViewer::WEBRTC_JITTER_OUT, jitter_seconds * 1000.0f);
+ }
+ }
+ // Inbound RTP
+ else if (attributes.contains("jitterBufferDelay"))
+ {
+ if (attributes.contains("packetsLost"))
+ {
+ U32 in_packets_lost = 0;
+ LLStringUtil::convertToU32(attributes.at("packetsLost"), in_packets_lost);
+ sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, in_packets_lost);
+ }
+ if (attributes.contains("packetsReceived"))
+ {
+ U32 in_packets_recv = 0;
+ LLStringUtil::convertToU32(attributes.at("packetsReceived"), in_packets_recv);
+ sample(LLStatViewer::WEBRTC_PACKETS_IN_RECEIVED, in_packets_recv);
+ }
+ if (attributes.contains("jitter"))
+ {
+ F32 jitter_seconds = 0.0f;
+ LLStringUtil::convertToF32(attributes.at("jitter"), jitter_seconds);
+ sample(LLStatViewer::WEBRTC_JITTER_IN, jitter_seconds * 1000.0f);
+ }
+ if (attributes.contains("jitterBufferDelay") && attributes.contains("jitterBufferEmittedCount"))
+ {
+ F32 total_delay_seconds = 0.0f;
+ F32 emitted_count_f = 0.0f;
+
+ // total delay in seconds
+ LLStringUtil::convertToF32(attributes.at("jitterBufferDelay"), total_delay_seconds);
+
+ // number of packets played out
+ LLStringUtil::convertToF32(attributes.at("jitterBufferEmittedCount"), emitted_count_f);
+ if (emitted_count_f > 0.0f)
+ {
+ F32 avg_delay_seconds = total_delay_seconds / emitted_count_f;
+ F32 avg_delay_ms = avg_delay_seconds * 1000.0f;
+ sample(LLStatViewer::WEBRTC_JITTER_BUFFER, avg_delay_seconds * 1000.0f);
+ }
+ }
+ }
+ }
+ });
+}
+
+void LLVoiceWebRTCConnection::resetConnectionStats()
+{
+ sample(LLStatViewer::WEBRTC_JITTER_BUFFER, 0);
+ sample(LLStatViewer::WEBRTC_JITTER_IN, 0);
+ sample(LLStatViewer::WEBRTC_JITTER_OUT, 0);
+ sample(LLStatViewer::WEBRTC_LATENCY, 0);
+ sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, 0);
+ sample(LLStatViewer::WEBRTC_PACKETS_IN_RECEIVED, 0);
+ sample(LLStatViewer::WEBRTC_PACKETS_OUT_SENT, 0);
+ sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, 0);
+ sample(LLStatViewer::WEBRTC_UPLOAD_BANDWIDTH, 0);
+}
+
/////////////////////////////
// WebRTC Spatial Connection
diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h
index 2ce575852a..6786b049c2 100644
--- a/indra/newview/llvoicewebrtc.h
+++ b/indra/newview/llvoicewebrtc.h
@@ -540,6 +540,8 @@ private:
static bool sShuttingDown;
LLEventMailDrop mWebRTCPump;
+
+ LLSD mLastWebRTCStats;
};
@@ -603,6 +605,8 @@ class LLVoiceWebRTCConnection :
//@{
void OnDataReceived(const std::string &data, bool binary) override;
void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override;
+
+ void OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data) override;
//@}
void OnDataReceivedImpl(const std::string &data, bool binary);
@@ -638,6 +642,8 @@ class LLVoiceWebRTCConnection :
void OnVoiceConnectionRequestSuccess(const LLSD &body);
+ void resetConnectionStats();
+
protected:
typedef enum e_voice_connection_state
{
diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml
index 1600c422c3..191db4f854 100644
--- a/indra/newview/skins/default/xui/en/floater_stats.xml
+++ b/indra/newview/skins/default/xui/en/floater_stats.xml
@@ -418,6 +418,64 @@
</stat_view>
</stat_view>
</stat_view>
+ <stat_view name="voice"
+ label="Voice"
+ setting="OpenDebugStatVoice">
+ <stat_bar
+ name="webrtc_latency"
+ label="Latency"
+ stat="webrtc_latency"
+ unit_label="ms"
+ decimal_digits="1"/>
+ <stat_bar
+ name="webrtc_upload_bandwidth"
+ label="Upload bandwidth"
+ stat="webrtc_upload_bandwidth"
+ unit_label="kbps"
+ decimal_digits="0"/>
+ <stat_view name="incoming_audio"
+ label="Incoming audio"
+ setting="OpenDebugStatVoiceIncoming">
+ <stat_bar
+ name="incoming_packet_recv"
+ label="Packets received"
+ stat="webrtc_packets_in_recv"/>
+ <stat_bar
+ name="packets_in_lost"
+ label="Packets lost"
+ stat="webrtc_packets_in_lost"/>
+ <stat_bar
+ name="jitter_in"
+ label="Jitter"
+ stat="webrtc_jitter_in"
+ unit_label="ms"
+ decimal_digits="1"/>
+ <stat_bar
+ name="jitter_buffer"
+ label="Jitter buffer"
+ stat="webrtc_jitter_buffer"
+ unit_label="ms"
+ decimal_digits="1"/>
+ </stat_view>
+ <stat_view name="outgoing_audio"
+ label="Outgoing audio"
+ setting="OpenDebugStatVoiceOutgoing">
+ <stat_bar
+ name="packets_out_sent"
+ label="Packets sent"
+ stat="webrtc_packets_out_sent"/>
+ <stat_bar
+ name="packets_out_lost"
+ label="Packets lost"
+ stat="webrtc_packets_out_lost"/>
+ <stat_bar
+ name="jitter_out"
+ label="Jitter"
+ stat="webrtc_jitter_out"
+ unit_label="ms"
+ decimal_digits="1"/>
+ </stat_view>
+ </stat_view>
</container_view>
</scroll_container>
</floater>