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', ], },