From d565b73121b1b7672fb7d1f115bbbbb137a838eb Mon Sep 17 00:00:00 2001 From: hbos Date: Tue, 30 Aug 2016 14:04:35 -0700 Subject: [PATCH] RTCStatsCollector and RTCPeerConnectionStats added. This is the stats collector for the new stats types, RTCStats[1] and RTCStatsReport[2]. It so far only produces RTCPeerConnectionStats[3] as an example of how it would collect stats. Each RTCStats subclass will get a corresponding RTCStatsCollector::ProduceFooStats(). Stats reports are cached and returned as const references (ref counting). This allows stats to be inspected by multiple observers and across multiple threads. No copies will have to be made when surfacing this to Blink or other places. The current implementation of ProducePeerConnectionStats() only look at existing DataChannels. This might be incorret if data channels can be removed? Will investigate in a follow-up, crbug.com/636818. [1] https://www.w3.org/TR/2016/WD-webrtc-20160531/#idl-def-rtcstats [2] https://www.w3.org/TR/2016/WD-webrtc-20160531/#rtcstatsreport-object [3] https://w3c.github.io/webrtc-stats/archives/20160526/webrtc-stats.html#pcstats-dict* BUG=chromium:627816, chromium:636818 Review-Url: https://codereview.webrtc.org/2242043002 Cr-Commit-Position: refs/heads/master@{#13979} --- webrtc/api/BUILD.gn | 2 + webrtc/api/api.gyp | 1 + webrtc/api/api_tests.gyp | 1 + webrtc/api/rtcstats_objects.h | 35 +++++ webrtc/api/test/mock_datachannel.h | 31 +++++ webrtc/stats/BUILD.gn | 20 ++- webrtc/stats/DEPS | 1 + webrtc/stats/rtcstats_objects.cc | 29 ++++ webrtc/stats/rtcstatscollector.cc | 81 +++++++++++ webrtc/stats/rtcstatscollector.h | 59 ++++++++ webrtc/stats/rtcstatscollector_unittest.cc | 152 +++++++++++++++++++++ webrtc/stats/stats.gyp | 4 + 12 files changed, 410 insertions(+), 6 deletions(-) create mode 100644 webrtc/api/rtcstats_objects.h create mode 100644 webrtc/api/test/mock_datachannel.h create mode 100644 webrtc/stats/rtcstats_objects.cc create mode 100644 webrtc/stats/rtcstatscollector.cc create mode 100644 webrtc/stats/rtcstatscollector.h create mode 100644 webrtc/stats/rtcstatscollector_unittest.cc diff --git a/webrtc/api/BUILD.gn b/webrtc/api/BUILD.gn index 2c26724917..68c6c8eb7a 100644 --- a/webrtc/api/BUILD.gn +++ b/webrtc/api/BUILD.gn @@ -70,6 +70,7 @@ source_set("libjingle_peerconnection") { "remoteaudiosource.cc", "remoteaudiosource.h", "rtcstats.h", + "rtcstats_objects.h", "rtcstatsreport.h", "rtpparameters.h", "rtpreceiver.cc", @@ -341,6 +342,7 @@ if (rtc_include_tests) { "test/fakeperiodicvideocapturer.h", "test/fakertccertificategenerator.h", "test/fakevideotrackrenderer.h", + "test/mock_datachannel.h", "test/mock_peerconnection.h", "test/mock_webrtcsession.h", "test/mockpeerconnectionobservers.h", diff --git a/webrtc/api/api.gyp b/webrtc/api/api.gyp index f9f846b544..b0f76badc7 100644 --- a/webrtc/api/api.gyp +++ b/webrtc/api/api.gyp @@ -145,6 +145,7 @@ 'remoteaudiosource.cc', 'remoteaudiosource.h', 'rtcstats.h', + 'rtcstats_objects.h', 'rtcstatsreport.h', 'rtpparameters.h', 'rtpreceiver.cc', diff --git a/webrtc/api/api_tests.gyp b/webrtc/api/api_tests.gyp index 73aebaaac3..51fb63df41 100644 --- a/webrtc/api/api_tests.gyp +++ b/webrtc/api/api_tests.gyp @@ -49,6 +49,7 @@ 'test/fakeperiodicvideocapturer.h', 'test/fakertccertificategenerator.h', 'test/fakevideotrackrenderer.h', + 'test/mock_datachannel.h', 'test/mock_peerconnection.h', 'test/mock_webrtcsession.h', 'test/mockpeerconnectionobservers.h', diff --git a/webrtc/api/rtcstats_objects.h b/webrtc/api/rtcstats_objects.h new file mode 100644 index 0000000000..8c03edfb14 --- /dev/null +++ b/webrtc/api/rtcstats_objects.h @@ -0,0 +1,35 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_API_RTCSTATS_OBJECTS_H_ +#define WEBRTC_API_RTCSTATS_OBJECTS_H_ + +#include + +#include "webrtc/api/rtcstats.h" + +namespace webrtc { + +class RTCPeerConnectionStats : public RTCStats { + public: + RTCPeerConnectionStats(const std::string& id, double timestamp); + RTCPeerConnectionStats(std::string&& id, double timestamp); + + WEBRTC_RTCSTATS_IMPL(RTCStats, RTCPeerConnectionStats, + &data_channels_opened, + &data_channels_closed); + + RTCStatsMember data_channels_opened; + RTCStatsMember data_channels_closed; +}; + +} // namespace webrtc + +#endif // WEBRTC_API_RTCSTATS_OBJECTS_H_ diff --git a/webrtc/api/test/mock_datachannel.h b/webrtc/api/test/mock_datachannel.h new file mode 100644 index 0000000000..5828e4c343 --- /dev/null +++ b/webrtc/api/test/mock_datachannel.h @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_API_TEST_MOCK_DATACHANNEL_H_ +#define WEBRTC_API_TEST_MOCK_DATACHANNEL_H_ + +#include "webrtc/api/datachannel.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace webrtc { + +class MockDataChannel : public rtc::RefCountedObject { + public: + explicit MockDataChannel(DataState state) + : rtc::RefCountedObject( + nullptr, cricket::DCT_NONE, "MockDataChannel") { + EXPECT_CALL(*this, state()).WillRepeatedly(testing::Return(state)); + } + MOCK_CONST_METHOD0(state, DataState()); +}; + +} // namespace webrtc + +#endif // WEBRTC_API_TEST_MOCK_DATACHANNEL_H_ diff --git a/webrtc/stats/BUILD.gn b/webrtc/stats/BUILD.gn index 2f69216772..d1c08bdd51 100644 --- a/webrtc/stats/BUILD.gn +++ b/webrtc/stats/BUILD.gn @@ -20,12 +20,20 @@ source_set("rtc_stats") { cflags = [] sources = [ "rtcstats.cc", + "rtcstats_objects.cc", + "rtcstatscollector.cc", + "rtcstatscollector.h", "rtcstatsreport.cc", ] configs += [ "..:common_config" ] public_configs = [ "..:common_inherited_config" ] + if (is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + configs -= [ "//build/config/clang:find_bad_constructs" ] + } + deps = [ "../api:libjingle_peerconnection", ] @@ -37,12 +45,18 @@ if (rtc_include_tests) { testonly = true sources = [ "rtcstats_unittest.cc", + "rtcstatscollector_unittest.cc", "rtcstatsreport_unittest.cc", ] configs += [ "..:common_config" ] public_configs = [ "..:common_inherited_config" ] + if (is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + configs -= [ "//build/config/clang:find_bad_constructs" ] + } + deps = [ ":rtc_stats", "../base:rtc_base_tests_utils", @@ -53,11 +67,5 @@ if (rtc_include_tests) { if (is_android) { deps += [ "//testing/android/native_test:native_test_native_code" ] } - - if (is_clang) { - # Suppress warnings from Chrome's Clang plugins. - # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. - configs -= [ "//build/config/clang:find_bad_constructs" ] - } } } diff --git a/webrtc/stats/DEPS b/webrtc/stats/DEPS index e4fbd6b2d2..6431936921 100644 --- a/webrtc/stats/DEPS +++ b/webrtc/stats/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+webrtc/api", "+webrtc/base", + "+webrtc/media", ] diff --git a/webrtc/stats/rtcstats_objects.cc b/webrtc/stats/rtcstats_objects.cc new file mode 100644 index 0000000000..1cfe85f036 --- /dev/null +++ b/webrtc/stats/rtcstats_objects.cc @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/api/rtcstats_objects.h" + +namespace webrtc { + +const char RTCPeerConnectionStats::kType[] = "peer-connection"; + +RTCPeerConnectionStats::RTCPeerConnectionStats( + const std::string& id, double timestamp) + : RTCPeerConnectionStats(std::string(id), timestamp) { +} + +RTCPeerConnectionStats::RTCPeerConnectionStats( + std::string&& id, double timestamp) + : RTCStats(std::move(id), timestamp), + data_channels_opened("dataChannelsOpened"), + data_channels_closed("dataChannelsClosed") { +} + +} // namespace webrtc diff --git a/webrtc/stats/rtcstatscollector.cc b/webrtc/stats/rtcstatscollector.cc new file mode 100644 index 0000000000..6cb2a31b44 --- /dev/null +++ b/webrtc/stats/rtcstatscollector.cc @@ -0,0 +1,81 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/stats/rtcstatscollector.h" + +#include +#include +#include + +#include "webrtc/api/peerconnection.h" +#include "webrtc/base/checks.h" + +namespace webrtc { + +RTCStatsCollector::RTCStatsCollector( + PeerConnection* pc, + double cache_lifetime, + std::unique_ptr timing) + : pc_(pc), + timing_(std::move(timing)), + cache_timestamp_(0.0), + cache_lifetime_(cache_lifetime) { + RTC_DCHECK(pc_); + RTC_DCHECK(timing_); + RTC_DCHECK(IsOnSignalingThread()); + RTC_DCHECK_GE(cache_lifetime_, 0.0); +} + +rtc::scoped_refptr RTCStatsCollector::GetStatsReport() { + RTC_DCHECK(IsOnSignalingThread()); + double now = timing_->TimerNow(); + if (cached_report_ && now - cache_timestamp_ <= cache_lifetime_) + return cached_report_; + cache_timestamp_ = now; + + rtc::scoped_refptr report = RTCStatsReport::Create(); + report->AddStats(ProducePeerConnectionStats()); + + cached_report_ = report; + return cached_report_; +} + +void RTCStatsCollector::ClearCachedStatsReport() { + RTC_DCHECK(IsOnSignalingThread()); + cached_report_ = nullptr; +} + +bool RTCStatsCollector::IsOnSignalingThread() const { + return pc_->session()->signaling_thread()->IsCurrent(); +} + +std::unique_ptr +RTCStatsCollector::ProducePeerConnectionStats() const { + // TODO(hbos): If data channels are removed from the peer connection this will + // yield incorrect counts. Address before closing crbug.com/636818. See + // https://w3c.github.io/webrtc-stats/webrtc-stats.html#pcstats-dict*. + uint32_t data_channels_opened = 0; + const std::vector>& data_channels = + pc_->sctp_data_channels(); + for (const rtc::scoped_refptr& data_channel : data_channels) { + if (data_channel->state() == DataChannelInterface::kOpen) + ++data_channels_opened; + } + // There is always just one |RTCPeerConnectionStats| so its |id| can be a + // constant. + std::unique_ptr stats( + new RTCPeerConnectionStats("RTCPeerConnection", cache_timestamp_)); + stats->data_channels_opened = data_channels_opened; + stats->data_channels_closed = static_cast(data_channels.size()) - + data_channels_opened; + return stats; +} + +} // namespace webrtc diff --git a/webrtc/stats/rtcstatscollector.h b/webrtc/stats/rtcstatscollector.h new file mode 100644 index 0000000000..1e4d6c3c26 --- /dev/null +++ b/webrtc/stats/rtcstatscollector.h @@ -0,0 +1,59 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_STATS_RTCSTATSCOLLECTOR_H_ +#define WEBRTC_STATS_RTCSTATSCOLLECTOR_H_ + +#include + +#include "webrtc/api/rtcstats_objects.h" +#include "webrtc/api/rtcstatsreport.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/timing.h" + +namespace webrtc { + +class PeerConnection; + +// All calls to the collector and gathering of stats is performed on the +// signaling thread. A stats report is cached for |cache_lifetime_| ms. +class RTCStatsCollector { + public: + explicit RTCStatsCollector( + PeerConnection* pc, + double cache_lifetime = 0.05, + std::unique_ptr timing = std::unique_ptr( + new rtc::Timing())); + + // Gets a recent stats report. If there is a report cached that is still fresh + // 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. + rtc::scoped_refptr GetStatsReport(); + // Clears the cache's reference to the most recent stats report. Subsequently + // calling |GetStatsReport| guarantees fresh stats. + void ClearCachedStatsReport(); + + private: + bool IsOnSignalingThread() const; + + std::unique_ptr ProducePeerConnectionStats() const; + + PeerConnection* const pc_; + mutable std::unique_ptr timing_; + // Time relative to the UNIX epoch (Jan 1, 1970, UTC), in seconds. + double cache_timestamp_; + double cache_lifetime_; // In seconds. + rtc::scoped_refptr cached_report_; +}; + +} // namespace webrtc + +#endif // WEBRTC_STATS_RTCSTATSCOLLECTOR_H_ diff --git a/webrtc/stats/rtcstatscollector_unittest.cc b/webrtc/stats/rtcstatscollector_unittest.cc new file mode 100644 index 0000000000..a3b0572c07 --- /dev/null +++ b/webrtc/stats/rtcstatscollector_unittest.cc @@ -0,0 +1,152 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/stats/rtcstatscollector.h" + +#include +#include +#include + +#include "webrtc/api/jsepsessiondescription.h" +#include "webrtc/api/rtcstats_objects.h" +#include "webrtc/api/rtcstatsreport.h" +#include "webrtc/api/test/mock_datachannel.h" +#include "webrtc/api/test/mock_peerconnection.h" +#include "webrtc/api/test/mock_webrtcsession.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/test/faketiming.h" +#include "webrtc/media/base/fakemediaengine.h" + +using testing::Return; +using testing::ReturnRef; + +namespace webrtc { + +class RTCStatsCollectorTester : public SetSessionDescriptionObserver { + public: + RTCStatsCollectorTester() + : worker_thread_(rtc::Thread::Current()), + network_thread_(rtc::Thread::Current()), + channel_manager_(new cricket::ChannelManager( + new cricket::FakeMediaEngine(), + worker_thread_, + network_thread_)), + media_controller_( + MediaControllerInterface::Create(cricket::MediaConfig(), + worker_thread_, + channel_manager_.get())), + session_(media_controller_.get()), + pc_() { + EXPECT_CALL(pc_, session()).WillRepeatedly(Return(&session_)); + EXPECT_CALL(pc_, sctp_data_channels()).WillRepeatedly( + ReturnRef(data_channels_)); + } + + MockWebRtcSession& session() { return session_; } + MockPeerConnection& pc() { return pc_; } + std::vector>& data_channels() { + return data_channels_; + } + + // SetSessionDescriptionObserver overrides. + void OnSuccess() override {} + void OnFailure(const std::string& error) override { + RTC_NOTREACHED() << error; + } + + private: + rtc::Thread* const worker_thread_; + rtc::Thread* const network_thread_; + std::unique_ptr channel_manager_; + std::unique_ptr media_controller_; + MockWebRtcSession session_; + MockPeerConnection pc_; + + std::vector> data_channels_; +}; + +class RTCStatsCollectorTest : public testing::Test { + public: + RTCStatsCollectorTest() + : test_(new rtc::RefCountedObject()), + timing_(new rtc::FakeTiming()), + collector_(&test_->pc(), 0.05, std::unique_ptr(timing_)) { + } + + protected: + rtc::scoped_refptr test_; + rtc::FakeTiming* timing_; // Owned by |collector_|. + RTCStatsCollector collector_; +}; + +TEST_F(RTCStatsCollectorTest, CachedStatsReport) { + // Caching should ensure |a| and |b| are the same report. + rtc::scoped_refptr a = collector_.GetStatsReport(); + rtc::scoped_refptr b = collector_.GetStatsReport(); + EXPECT_TRUE(a); + EXPECT_EQ(a.get(), b.get()); + // Invalidate cache by clearing it. + collector_.ClearCachedStatsReport(); + rtc::scoped_refptr c = collector_.GetStatsReport(); + EXPECT_TRUE(c); + EXPECT_NE(b.get(), c.get()); + // Invalidate cache by advancing time. + timing_->AdvanceTimeMillisecs(51.0); + rtc::scoped_refptr d = collector_.GetStatsReport(); + EXPECT_TRUE(d); + EXPECT_NE(c.get(), d.get()); +} + +TEST_F(RTCStatsCollectorTest, CollectRTCPeerConnectionStats) { + rtc::scoped_refptr report = collector_.GetStatsReport(); + EXPECT_EQ(report->GetStatsOfType().size(), + static_cast(1)) << "Expecting 1 RTCPeerConnectionStats."; + const RTCStats* stats = report->Get("RTCPeerConnection"); + EXPECT_TRUE(stats); + EXPECT_EQ(stats->timestamp(), timing_->TimerNow()); + { + // Expected stats with no data channels + const RTCPeerConnectionStats& pcstats = + stats->cast_to(); + EXPECT_EQ(*pcstats.data_channels_opened, static_cast(0)); + EXPECT_EQ(*pcstats.data_channels_closed, static_cast(0)); + } + + test_->data_channels().push_back( + new MockDataChannel(DataChannelInterface::kConnecting)); + test_->data_channels().push_back( + new MockDataChannel(DataChannelInterface::kOpen)); + test_->data_channels().push_back( + new MockDataChannel(DataChannelInterface::kClosing)); + test_->data_channels().push_back( + new MockDataChannel(DataChannelInterface::kClosed)); + + collector_.ClearCachedStatsReport(); + report = collector_.GetStatsReport(); + EXPECT_EQ(report->GetStatsOfType().size(), + static_cast(1)) << "Expecting 1 RTCPeerConnectionStats."; + stats = report->Get("RTCPeerConnection"); + EXPECT_TRUE(stats); + { + // Expected stats with the above four data channels + // TODO(hbos): When the |RTCPeerConnectionStats| is the number of data + // channels that have been opened and closed, not the numbers currently + // open/closed, we would expect opened >= closed and (opened - closed) to be + // the number currently open. crbug.com/636818. + const RTCPeerConnectionStats& pcstats = + stats->cast_to(); + EXPECT_EQ(*pcstats.data_channels_opened, static_cast(1)); + EXPECT_EQ(*pcstats.data_channels_closed, static_cast(3)); + } +} + +} // namespace webrtc diff --git a/webrtc/stats/stats.gyp b/webrtc/stats/stats.gyp index 4c2255711c..b2b99ccb27 100644 --- a/webrtc/stats/stats.gyp +++ b/webrtc/stats/stats.gyp @@ -18,6 +18,9 @@ ], 'sources': [ 'rtcstats.cc', + 'rtcstats_objects.cc', + 'rtcstatscollector.cc', + 'rtcstatscollector.h', 'rtcstatsreport.cc', ], }, @@ -37,6 +40,7 @@ ], 'sources': [ 'rtcstats_unittest.cc', + 'rtcstatscollector_unittest.cc', 'rtcstatsreport_unittest.cc', ], },