diff --git a/api/stats/rtcstatsreport.h b/api/stats/rtcstatsreport.h index f2267d6c685..8485a08e08b 100644 --- a/api/stats/rtcstatsreport.h +++ b/api/stats/rtcstatsreport.h @@ -57,6 +57,7 @@ class RTCStatsReport : public rtc::RefCountInterface { explicit RTCStatsReport(int64_t timestamp_us); RTCStatsReport(const RTCStatsReport& other) = delete; + rtc::scoped_refptr Copy() const; int64_t timestamp_us() const { return timestamp_us_; } void AddStats(std::unique_ptr stats); diff --git a/pc/rtcstatscollector.cc b/pc/rtcstatscollector.cc index e9e9f1c2530..2d6a7ac7670 100644 --- a/pc/rtcstatscollector.cc +++ b/pc/rtcstatscollector.cc @@ -23,6 +23,7 @@ #include "p2p/base/p2pconstants.h" #include "p2p/base/port.h" #include "pc/peerconnection.h" +#include "pc/rtcstatstraversal.h" #include "rtc_base/checks.h" #include "rtc_base/ptr_util.h" #include "rtc_base/stringutils.h" @@ -606,8 +607,93 @@ void ProduceReceiverMediaTrackStats( } } +rtc::scoped_refptr CreateReportFilteredBySelector( + bool filter_by_sender_selector, + rtc::scoped_refptr report, + rtc::scoped_refptr sender_selector, + rtc::scoped_refptr receiver_selector) { + std::vector rtpstream_ids; + if (filter_by_sender_selector) { + // Filter mode: RTCStatsCollector::RequestInfo::kSenderSelector + if (sender_selector) { + // Find outbound-rtp(s) of the sender, i.e. the outbound-rtp(s) that + // reference the sender stats. + // Because we do not implement sender stats, we look at outbound-rtp(s) + // that reference the track attachment stats for the sender instead. + std::string track_id = + RTCMediaStreamTrackStatsIDFromDirectionAndAttachment( + kSender, sender_selector->AttachmentId()); + for (const auto& stats : *report) { + if (stats.type() != RTCOutboundRTPStreamStats::kType) + continue; + const auto& outbound_rtp = stats.cast_to(); + if (outbound_rtp.track_id.is_defined() && + *outbound_rtp.track_id == track_id) { + rtpstream_ids.push_back(outbound_rtp.id()); + } + } + } + } else { + // Filter mode: RTCStatsCollector::RequestInfo::kReceiverSelector + if (receiver_selector) { + // Find inbound-rtp(s) of the receiver, i.e. the inbound-rtp(s) that + // reference the receiver stats. + // Because we do not implement receiver stats, we look at inbound-rtp(s) + // that reference the track attachment stats for the receiver instead. + std::string track_id = + RTCMediaStreamTrackStatsIDFromDirectionAndAttachment( + kReceiver, receiver_selector->AttachmentId()); + for (const auto& stats : *report) { + if (stats.type() != RTCInboundRTPStreamStats::kType) + continue; + const auto& inbound_rtp = stats.cast_to(); + if (inbound_rtp.track_id.is_defined() && + *inbound_rtp.track_id == track_id) { + rtpstream_ids.push_back(inbound_rtp.id()); + } + } + } + } + if (rtpstream_ids.empty()) + return RTCStatsReport::Create(report->timestamp_us()); + return TakeReferencedStats(report->Copy(), rtpstream_ids); +} + } // namespace +RTCStatsCollector::RequestInfo::RequestInfo( + rtc::scoped_refptr callback) + : RequestInfo(FilterMode::kAll, std::move(callback), nullptr, nullptr) {} + +RTCStatsCollector::RequestInfo::RequestInfo( + rtc::scoped_refptr selector, + rtc::scoped_refptr callback) + : RequestInfo(FilterMode::kSenderSelector, + std::move(callback), + std::move(selector), + nullptr) {} + +RTCStatsCollector::RequestInfo::RequestInfo( + rtc::scoped_refptr selector, + rtc::scoped_refptr callback) + : RequestInfo(FilterMode::kReceiverSelector, + std::move(callback), + nullptr, + std::move(selector)) {} + +RTCStatsCollector::RequestInfo::RequestInfo( + RTCStatsCollector::RequestInfo::FilterMode filter_mode, + rtc::scoped_refptr callback, + rtc::scoped_refptr sender_selector, + rtc::scoped_refptr receiver_selector) + : filter_mode_(filter_mode), + callback_(std::move(callback)), + sender_selector_(std::move(sender_selector)), + receiver_selector_(std::move(receiver_selector)) { + RTC_DCHECK(callback_); + RTC_DCHECK(!sender_selector_ || !receiver_selector_); +} + rtc::scoped_refptr RTCStatsCollector::Create( PeerConnectionInternal* pc, int64_t cache_lifetime_us) { @@ -640,9 +726,25 @@ RTCStatsCollector::~RTCStatsCollector() { void RTCStatsCollector::GetStatsReport( rtc::scoped_refptr callback) { + GetStatsReportInternal(RequestInfo(std::move(callback))); +} + +void RTCStatsCollector::GetStatsReport( + rtc::scoped_refptr selector, + rtc::scoped_refptr callback) { + GetStatsReportInternal(RequestInfo(std::move(selector), std::move(callback))); +} + +void RTCStatsCollector::GetStatsReport( + rtc::scoped_refptr selector, + rtc::scoped_refptr callback) { + GetStatsReportInternal(RequestInfo(std::move(selector), std::move(callback))); +} + +void RTCStatsCollector::GetStatsReportInternal( + RTCStatsCollector::RequestInfo request) { RTC_DCHECK(signaling_thread_->IsCurrent()); - RTC_DCHECK(callback); - callbacks_.push_back(callback); + requests_.push_back(std::move(request)); // "Now" using a monotonically increasing timer. int64_t cache_now_us = rtc::TimeMicros(); @@ -651,13 +753,12 @@ void RTCStatsCollector::GetStatsReport( // We have a fresh cached report to deliver. Deliver asynchronously, since // the caller may not be expecting a synchronous callback, and it avoids // reentrancy problems. - std::vector> callbacks; - callbacks.swap(callbacks_); + std::vector requests; + requests.swap(requests_); invoker_.AsyncInvoke( RTC_FROM_HERE, signaling_thread_, rtc::Bind(&RTCStatsCollector::DeliverCachedReport, this, cached_report_, - std::move(callbacks))); - callbacks_.clear(); + std::move(requests))); } else if (!num_pending_partial_reports_) { // Only start gathering stats if we're not already gathering stats. In the // case of already gathering stats, |callback_| will be invoked when there @@ -781,24 +882,40 @@ void RTCStatsCollector::AddPartialResults_s( TRACE_EVENT_INSTANT1("webrtc_stats", "webrtc_stats", "report", cached_report_->ToJson()); - // Swap the list of callbacks, in case one of them recursively calls - // GetStatsReport again and modifies the callback list. - std::vector> callbacks; - callbacks.swap(callbacks_); - DeliverCachedReport(cached_report_, std::move(callbacks)); + // Deliver report and clear |requests_|. + std::vector requests; + requests.swap(requests_); + DeliverCachedReport(cached_report_, std::move(requests)); } } void RTCStatsCollector::DeliverCachedReport( rtc::scoped_refptr cached_report, - std::vector> callbacks) { + std::vector requests) { RTC_DCHECK(signaling_thread_->IsCurrent()); - RTC_DCHECK(!callbacks.empty()); + RTC_DCHECK(!requests.empty()); RTC_DCHECK(cached_report); - for (const rtc::scoped_refptr& callback : - callbacks) { - callback->OnStatsDelivered(cached_report); + for (const RequestInfo& request : requests) { + if (request.filter_mode() == RequestInfo::FilterMode::kAll) { + request.callback()->OnStatsDelivered(cached_report); + } else { + bool filter_by_sender_selector; + rtc::scoped_refptr sender_selector; + rtc::scoped_refptr receiver_selector; + if (request.filter_mode() == RequestInfo::FilterMode::kSenderSelector) { + filter_by_sender_selector = true; + sender_selector = request.sender_selector(); + } else { + RTC_DCHECK(request.filter_mode() == + RequestInfo::FilterMode::kReceiverSelector); + filter_by_sender_selector = false; + receiver_selector = request.receiver_selector(); + } + request.callback()->OnStatsDelivered(CreateReportFilteredBySelector( + filter_by_sender_selector, cached_report, sender_selector, + receiver_selector)); + } } } diff --git a/pc/rtcstatscollector.h b/pc/rtcstatscollector.h index be5980bffea..0018d603260 100644 --- a/pc/rtcstatscollector.h +++ b/pc/rtcstatscollector.h @@ -35,6 +35,9 @@ namespace webrtc { +class RtpSenderInternal; +class RtpReceiverInternal; + // All public methods of the collector are to be called on the signaling thread. // Stats are gathered on the signaling, worker and network threads // asynchronously. The callback is invoked on the signaling thread. Resulting @@ -50,7 +53,18 @@ class RTCStatsCollector : public virtual rtc::RefCountInterface, // it is returned, otherwise new stats are gathered and returned. A report is // considered fresh for |cache_lifetime_| ms. const RTCStatsReports are safe // to use across multiple threads and may be destructed on any thread. + // If the optional selector argument is used, stats are filtered according to + // stats selection algorithm before delivery. + // https://w3c.github.io/webrtc-pc/#dfn-stats-selection-algorithm void GetStatsReport(rtc::scoped_refptr callback); + // If |selector| is null the selection algorithm is still applied (interpreted + // as: no RTP streams are sent by selector). The result is empty. + void GetStatsReport(rtc::scoped_refptr selector, + rtc::scoped_refptr callback); + // If |selector| is null the selection algorithm is still applied (interpreted + // as: no RTP streams are received by selector). The result is empty. + void GetStatsReport(rtc::scoped_refptr selector, + rtc::scoped_refptr callback); // Clears the cache's reference to the most recent stats report. Subsequently // calling |GetStatsReport| guarantees fresh stats. void ClearCachedStatsReport(); @@ -73,6 +87,49 @@ class RTCStatsCollector : public virtual rtc::RefCountInterface, const rtc::scoped_refptr& partial_report); private: + class RequestInfo { + public: + enum class FilterMode { kAll, kSenderSelector, kReceiverSelector }; + + // Constructs with FilterMode::kAll. + explicit RequestInfo( + rtc::scoped_refptr callback); + // Constructs with FilterMode::kSenderSelector. The selection algorithm is + // applied even if |selector| is null, resulting in an empty report. + RequestInfo(rtc::scoped_refptr selector, + rtc::scoped_refptr callback); + // Constructs with FilterMode::kReceiverSelector. The selection algorithm is + // applied even if |selector| is null, resulting in an empty report. + RequestInfo(rtc::scoped_refptr selector, + rtc::scoped_refptr callback); + + FilterMode filter_mode() const { return filter_mode_; } + rtc::scoped_refptr callback() const { + return callback_; + } + rtc::scoped_refptr sender_selector() const { + RTC_DCHECK(filter_mode_ == FilterMode::kSenderSelector); + return sender_selector_; + } + rtc::scoped_refptr receiver_selector() const { + RTC_DCHECK(filter_mode_ == FilterMode::kReceiverSelector); + return receiver_selector_; + } + + private: + RequestInfo(FilterMode filter_mode, + rtc::scoped_refptr callback, + rtc::scoped_refptr sender_selector, + rtc::scoped_refptr receiver_selector); + + FilterMode filter_mode_; + rtc::scoped_refptr callback_; + rtc::scoped_refptr sender_selector_; + rtc::scoped_refptr receiver_selector_; + }; + + void GetStatsReportInternal(RequestInfo request); + struct CertificateStatsPair { std::unique_ptr local; std::unique_ptr remote; @@ -96,7 +153,7 @@ class RTCStatsCollector : public virtual rtc::RefCountInterface, void AddPartialResults_s(rtc::scoped_refptr partial_report); void DeliverCachedReport( rtc::scoped_refptr cached_report, - std::vector> callbacks); + std::vector requests); // Produces |RTCCertificateStats|. void ProduceCertificateStats_n( @@ -168,7 +225,7 @@ class RTCStatsCollector : public virtual rtc::RefCountInterface, int num_pending_partial_reports_; int64_t partial_report_timestamp_us_; rtc::scoped_refptr partial_report_; - std::vector> callbacks_; + std::vector requests_; // Set in |GetStatsReport|, read in |ProducePartialResultsOnNetworkThread| and // |ProducePartialResultsOnSignalingThread|, reset after work is complete. Not diff --git a/pc/rtcstatscollector_unittest.cc b/pc/rtcstatscollector_unittest.cc index 8ec0929fd1d..f27e953b56f 100644 --- a/pc/rtcstatscollector_unittest.cc +++ b/pc/rtcstatscollector_unittest.cc @@ -299,26 +299,36 @@ class RTCStatsCollectorWrapper { return stats_collector_; } - rtc::scoped_refptr GetFreshStatsReport() { - stats_collector_->ClearCachedStatsReport(); - return GetStatsReport(); - } - rtc::scoped_refptr GetStatsReport() { rtc::scoped_refptr callback = RTCStatsObtainer::Create(); stats_collector_->GetStatsReport(callback); - EXPECT_TRUE_WAIT(callback->report(), kGetStatsReportTimeoutMs); - int64_t after = rtc::TimeUTCMicros(); - for (const RTCStats& stats : *callback->report()) { - EXPECT_LE(stats.timestamp_us(), after); - } - return callback->report(); + return WaitForReport(callback); + } + + rtc::scoped_refptr GetStatsReportWithSenderSelector( + rtc::scoped_refptr selector) { + rtc::scoped_refptr callback = RTCStatsObtainer::Create(); + stats_collector_->GetStatsReport(selector, callback); + return WaitForReport(callback); + } + + rtc::scoped_refptr GetStatsReportWithReceiverSelector( + rtc::scoped_refptr selector) { + rtc::scoped_refptr callback = RTCStatsObtainer::Create(); + stats_collector_->GetStatsReport(selector, callback); + return WaitForReport(callback); + } + + rtc::scoped_refptr GetFreshStatsReport() { + stats_collector_->ClearCachedStatsReport(); + return GetStatsReport(); } - void SetupLocalTrackAndSender(cricket::MediaType media_type, - const std::string& track_id, - uint32_t ssrc, - bool add_stream) { + rtc::scoped_refptr SetupLocalTrackAndSender( + cricket::MediaType media_type, + const std::string& track_id, + uint32_t ssrc, + bool add_stream) { rtc::scoped_refptr local_stream; if (add_stream) { local_stream = MediaStream::Create("LocalStreamId"); @@ -343,13 +353,16 @@ class RTCStatsCollectorWrapper { rtc::scoped_refptr sender = CreateMockSender(track, ssrc, 50, {}); pc_->AddSender(sender); + return sender; } - void SetupRemoteTrackAndReceiver(cricket::MediaType media_type, - const std::string& track_id, - uint32_t ssrc) { + rtc::scoped_refptr SetupRemoteTrackAndReceiver( + cricket::MediaType media_type, + const std::string& track_id, + const std::string& stream_id, + uint32_t ssrc) { rtc::scoped_refptr remote_stream = - MediaStream::Create("RemoteStreamId"); + MediaStream::Create(stream_id); pc_->mutable_remote_streams()->AddStream(remote_stream); rtc::scoped_refptr track; @@ -370,6 +383,7 @@ class RTCStatsCollectorWrapper { Return(std::vector>( {remote_stream}))); pc_->AddReceiver(receiver); + return receiver; } // Attaches tracks to peer connections by configuring RTP senders and RTP @@ -471,6 +485,16 @@ class RTCStatsCollectorWrapper { } private: + rtc::scoped_refptr WaitForReport( + rtc::scoped_refptr callback) { + EXPECT_TRUE_WAIT(callback->report(), kGetStatsReportTimeoutMs); + int64_t after = rtc::TimeUTCMicros(); + for (const RTCStats& stats : *callback->report()) { + EXPECT_LE(stats.timestamp_us(), after); + } + return callback->report(); + } + rtc::scoped_refptr pc_; rtc::scoped_refptr stats_collector_; }; @@ -502,6 +526,115 @@ class RTCStatsCollectorTest : public testing::Test { } } + struct ExampleStatsGraph { + rtc::scoped_refptr sender; + rtc::scoped_refptr receiver; + + rtc::scoped_refptr full_report; + std::string send_codec_id; + std::string recv_codec_id; + std::string outbound_rtp_id; + std::string inbound_rtp_id; + std::string transport_id; + std::string sender_track_id; + std::string receiver_track_id; + std::string remote_stream_id; + std::string peer_connection_id; + }; + + // Sets up the example stats graph (see ASCII art below) used for testing the + // stats selection algorithm, + // https://w3c.github.io/webrtc-pc/#dfn-stats-selection-algorithm. + // These tests test the integration of the stats traversal algorithm inside of + // RTCStatsCollector. See rtcstatstraveral_unittest.cc for more stats + // traversal tests. + ExampleStatsGraph SetupExampleStatsGraphForSelectorTests() { + ExampleStatsGraph graph; + + // codec (send) + graph.send_codec_id = "RTCCodec_VideoMid_Outbound_1"; + cricket::VideoMediaInfo video_media_info; + RtpCodecParameters send_codec; + send_codec.payload_type = 1; + send_codec.clock_rate = 0; + video_media_info.send_codecs.insert( + std::make_pair(send_codec.payload_type, send_codec)); + // codec (recv) + graph.recv_codec_id = "RTCCodec_VideoMid_Inbound_2"; + RtpCodecParameters recv_codec; + recv_codec.payload_type = 2; + recv_codec.clock_rate = 0; + video_media_info.receive_codecs.insert( + std::make_pair(recv_codec.payload_type, recv_codec)); + // outbound-rtp + graph.outbound_rtp_id = "RTCOutboundRTPVideoStream_3"; + video_media_info.senders.push_back(cricket::VideoSenderInfo()); + video_media_info.senders[0].local_stats.push_back( + cricket::SsrcSenderInfo()); + video_media_info.senders[0].local_stats[0].ssrc = 3; + video_media_info.senders[0].codec_payload_type = send_codec.payload_type; + // inbound-rtp + graph.inbound_rtp_id = "RTCInboundRTPVideoStream_4"; + video_media_info.receivers.push_back(cricket::VideoReceiverInfo()); + video_media_info.receivers[0].local_stats.push_back( + cricket::SsrcReceiverInfo()); + video_media_info.receivers[0].local_stats[0].ssrc = 4; + video_media_info.receivers[0].codec_payload_type = recv_codec.payload_type; + // transport + graph.transport_id = "RTCTransport_TransportName_1"; + auto* video_media_channel = + pc_->AddVideoChannel("VideoMid", "TransportName"); + video_media_channel->SetStats(video_media_info); + // track (sender) + graph.sender = stats_->SetupLocalTrackAndSender( + cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 3, false); + graph.sender_track_id = "RTCMediaStreamTrack_sender_" + + rtc::ToString<>(graph.sender->AttachmentId()); + // track (receiver) and stream (remote stream) + graph.receiver = stats_->SetupRemoteTrackAndReceiver( + cricket::MEDIA_TYPE_VIDEO, "RemoteVideoTrackID", "RemoteStreamId", 4); + graph.receiver_track_id = "RTCMediaStreamTrack_receiver_" + + rtc::ToString<>(graph.receiver->AttachmentId()); + graph.remote_stream_id = "RTCMediaStream_RemoteStreamId"; + // peer-connection + graph.peer_connection_id = "RTCPeerConnection"; + + // Expected stats graph: + // + // track (sender) stream (remote stream) ---> track (receiver) + // ^ ^ + // | | + // outbound-rtp inbound-rtp ---------------+ + // | | | | + // v v v v + // codec (send) transport codec (recv) peer-connection + + // Verify the stats graph is set up correctly. + graph.full_report = stats_->GetStatsReport(); + EXPECT_EQ(graph.full_report->size(), 9u); + EXPECT_TRUE(graph.full_report->Get(graph.send_codec_id)); + EXPECT_TRUE(graph.full_report->Get(graph.recv_codec_id)); + EXPECT_TRUE(graph.full_report->Get(graph.outbound_rtp_id)); + EXPECT_TRUE(graph.full_report->Get(graph.inbound_rtp_id)); + EXPECT_TRUE(graph.full_report->Get(graph.transport_id)); + EXPECT_TRUE(graph.full_report->Get(graph.sender_track_id)); + EXPECT_TRUE(graph.full_report->Get(graph.receiver_track_id)); + EXPECT_TRUE(graph.full_report->Get(graph.remote_stream_id)); + EXPECT_TRUE(graph.full_report->Get(graph.peer_connection_id)); + const auto& outbound_rtp = graph.full_report->Get(graph.outbound_rtp_id) + ->cast_to(); + EXPECT_EQ(*outbound_rtp.codec_id, graph.send_codec_id); + EXPECT_EQ(*outbound_rtp.track_id, graph.sender_track_id); + EXPECT_EQ(*outbound_rtp.transport_id, graph.transport_id); + const auto& inbound_rtp = graph.full_report->Get(graph.inbound_rtp_id) + ->cast_to(); + EXPECT_EQ(*inbound_rtp.codec_id, graph.recv_codec_id); + EXPECT_EQ(*inbound_rtp.track_id, graph.receiver_track_id); + EXPECT_EQ(*inbound_rtp.transport_id, graph.transport_id); + + return graph; + } + protected: rtc::ScopedFakeClock fake_clock_; rtc::scoped_refptr pc_; @@ -1465,8 +1598,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRTPStreamStats_Audio) { auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName"); voice_media_channel->SetStats(voice_media_info); - stats_->SetupRemoteTrackAndReceiver(cricket::MEDIA_TYPE_AUDIO, - "RemoteAudioTrackID", 1); + stats_->SetupRemoteTrackAndReceiver( + cricket::MEDIA_TYPE_AUDIO, "RemoteAudioTrackID", "RemoteStreamId", 1); rtc::scoped_refptr report = stats_->GetStatsReport(); @@ -1523,8 +1656,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRTPStreamStats_Video) { auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName"); video_media_channel->SetStats(video_media_info); - stats_->SetupRemoteTrackAndReceiver(cricket::MEDIA_TYPE_VIDEO, - "RemoteVideoTrackID", 1); + stats_->SetupRemoteTrackAndReceiver( + cricket::MEDIA_TYPE_VIDEO, "RemoteVideoTrackID", "RemoteStreamId", 1); rtc::scoped_refptr report = stats_->GetStatsReport(); @@ -1872,6 +2005,78 @@ TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) { EXPECT_TRUE(report->Get(*expected_audio.codec_id)); } +TEST_F(RTCStatsCollectorTest, GetStatsWithSenderSelector) { + ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests(); + // Expected stats graph when filtered by sender: + // + // track (sender) + // ^ + // | + // outbound-rtp + // | | + // v v + // codec (send) transport + rtc::scoped_refptr sender_report = + stats_->GetStatsReportWithSenderSelector(graph.sender); + EXPECT_TRUE(sender_report); + EXPECT_EQ(sender_report->timestamp_us(), graph.full_report->timestamp_us()); + EXPECT_EQ(sender_report->size(), 4u); + EXPECT_TRUE(sender_report->Get(graph.send_codec_id)); + EXPECT_FALSE(sender_report->Get(graph.recv_codec_id)); + EXPECT_TRUE(sender_report->Get(graph.outbound_rtp_id)); + EXPECT_FALSE(sender_report->Get(graph.inbound_rtp_id)); + EXPECT_TRUE(sender_report->Get(graph.transport_id)); + EXPECT_TRUE(sender_report->Get(graph.sender_track_id)); + EXPECT_FALSE(sender_report->Get(graph.receiver_track_id)); + EXPECT_FALSE(sender_report->Get(graph.remote_stream_id)); + EXPECT_FALSE(sender_report->Get(graph.peer_connection_id)); +} + +TEST_F(RTCStatsCollectorTest, GetStatsWithReceiverSelector) { + ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests(); + // Expected stats graph when filtered by receiver: + // + // track (receiver) + // ^ + // | + // inbound-rtp ---------------+ + // | | + // v v + // transport codec (recv) + rtc::scoped_refptr receiver_report = + stats_->GetStatsReportWithReceiverSelector(graph.receiver); + EXPECT_TRUE(receiver_report); + EXPECT_EQ(receiver_report->size(), 4u); + EXPECT_EQ(receiver_report->timestamp_us(), graph.full_report->timestamp_us()); + EXPECT_FALSE(receiver_report->Get(graph.send_codec_id)); + EXPECT_TRUE(receiver_report->Get(graph.recv_codec_id)); + EXPECT_FALSE(receiver_report->Get(graph.outbound_rtp_id)); + EXPECT_TRUE(receiver_report->Get(graph.inbound_rtp_id)); + EXPECT_TRUE(receiver_report->Get(graph.transport_id)); + EXPECT_FALSE(receiver_report->Get(graph.sender_track_id)); + EXPECT_TRUE(receiver_report->Get(graph.receiver_track_id)); + EXPECT_FALSE(receiver_report->Get(graph.remote_stream_id)); + EXPECT_FALSE(receiver_report->Get(graph.peer_connection_id)); +} + +TEST_F(RTCStatsCollectorTest, GetStatsWithNullSenderSelector) { + ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests(); + rtc::scoped_refptr empty_report = + stats_->GetStatsReportWithSenderSelector(nullptr); + EXPECT_TRUE(empty_report); + EXPECT_EQ(empty_report->timestamp_us(), graph.full_report->timestamp_us()); + EXPECT_EQ(empty_report->size(), 0u); +} + +TEST_F(RTCStatsCollectorTest, GetStatsWithNullReceiverSelector) { + ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests(); + rtc::scoped_refptr empty_report = + stats_->GetStatsReportWithReceiverSelector(nullptr); + EXPECT_TRUE(empty_report); + EXPECT_EQ(empty_report->timestamp_us(), graph.full_report->timestamp_us()); + EXPECT_EQ(empty_report->size(), 0u); +} + // When the PC has not had SetLocalDescription done, tracks all have // SSRC 0, meaning "unconnected". // In this state, we report on track stats, but not RTP stats. diff --git a/pc/rtcstatstraversal.cc b/pc/rtcstatstraversal.cc index 2b74beed6fe..62b8c52a107 100644 --- a/pc/rtcstatstraversal.cc +++ b/pc/rtcstatstraversal.cc @@ -61,7 +61,8 @@ void AddIdsIfDefined(const RTCStatsMember>& ids, rtc::scoped_refptr TakeReferencedStats( rtc::scoped_refptr report, const std::vector& ids) { - rtc::scoped_refptr result = RTCStatsReport::Create(); + rtc::scoped_refptr result = + RTCStatsReport::Create(report->timestamp_us()); for (const auto& id : ids) { TraverseAndTakeVisitedStats(report.get(), result.get(), id); } diff --git a/stats/rtcstatsreport.cc b/stats/rtcstatsreport.cc index 55e4d3d4af2..0918567e5d3 100644 --- a/stats/rtcstatsreport.cc +++ b/stats/rtcstatsreport.cc @@ -69,6 +69,14 @@ RTCStatsReport::RTCStatsReport(int64_t timestamp_us) RTCStatsReport::~RTCStatsReport() { } +rtc::scoped_refptr RTCStatsReport::Copy() const { + rtc::scoped_refptr copy = Create(timestamp_us_); + for (auto it = stats_.begin(); it != stats_.end(); ++it) { + copy->AddStats(it->second->copy()); + } + return copy; +} + void RTCStatsReport::AddStats(std::unique_ptr stats) { auto result = stats_.insert(std::make_pair(std::string(stats->id()), std::move(stats)));