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-Original-Commit-Position: refs/heads/master@{#13979}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: d565b73121b1b7672fb7d1f115bbbbb137a838eb
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 2c26724..68c6c8e 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -70,6 +70,7 @@
     "remoteaudiosource.cc",
     "remoteaudiosource.h",
     "rtcstats.h",
+    "rtcstats_objects.h",
     "rtcstatsreport.h",
     "rtpparameters.h",
     "rtpreceiver.cc",
@@ -341,6 +342,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/api/api.gyp b/api/api.gyp
index f9f846b..b0f76ba 100644
--- a/api/api.gyp
+++ b/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/api/api_tests.gyp b/api/api_tests.gyp
index 73aebaa..51fb63d 100644
--- a/api/api_tests.gyp
+++ b/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/api/rtcstats_objects.h b/api/rtcstats_objects.h
new file mode 100644
index 0000000..8c03edf
--- /dev/null
+++ b/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 <string>
+
+#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<uint32_t> data_channels_opened;
+  RTCStatsMember<uint32_t> data_channels_closed;
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_API_RTCSTATS_OBJECTS_H_
diff --git a/api/test/mock_datachannel.h b/api/test/mock_datachannel.h
new file mode 100644
index 0000000..5828e4c
--- /dev/null
+++ b/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<DataChannel> {
+ public:
+  explicit MockDataChannel(DataState state)
+      : rtc::RefCountedObject<DataChannel>(
+            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/stats/BUILD.gn b/stats/BUILD.gn
index 2f69216..d1c08bd 100644
--- a/stats/BUILD.gn
+++ b/stats/BUILD.gn
@@ -20,12 +20,20 @@
   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 @@
     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 (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/stats/DEPS b/stats/DEPS
index e4fbd6b..6431936 100644
--- a/stats/DEPS
+++ b/stats/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+webrtc/api",
   "+webrtc/base",
+  "+webrtc/media",
 ]
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
new file mode 100644
index 0000000..1cfe85f
--- /dev/null
+++ b/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/stats/rtcstatscollector.cc b/stats/rtcstatscollector.cc
new file mode 100644
index 0000000..6cb2a31
--- /dev/null
+++ b/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 <memory>
+#include <utility>
+#include <vector>
+
+#include "webrtc/api/peerconnection.h"
+#include "webrtc/base/checks.h"
+
+namespace webrtc {
+
+RTCStatsCollector::RTCStatsCollector(
+    PeerConnection* pc,
+    double cache_lifetime,
+    std::unique_ptr<rtc::Timing> 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<const RTCStatsReport> 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<RTCStatsReport> 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<RTCPeerConnectionStats>
+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<rtc::scoped_refptr<DataChannel>>& data_channels =
+      pc_->sctp_data_channels();
+  for (const rtc::scoped_refptr<DataChannel>& 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<RTCPeerConnectionStats> stats(
+    new RTCPeerConnectionStats("RTCPeerConnection", cache_timestamp_));
+  stats->data_channels_opened = data_channels_opened;
+  stats->data_channels_closed = static_cast<uint32_t>(data_channels.size()) -
+                                data_channels_opened;
+  return stats;
+}
+
+}  // namespace webrtc
diff --git a/stats/rtcstatscollector.h b/stats/rtcstatscollector.h
new file mode 100644
index 0000000..1e4d6c3
--- /dev/null
+++ b/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 <memory>
+
+#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<rtc::Timing> timing = std::unique_ptr<rtc::Timing>(
+          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<const RTCStatsReport> 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<RTCPeerConnectionStats> ProducePeerConnectionStats() const;
+
+  PeerConnection* const pc_;
+  mutable std::unique_ptr<rtc::Timing> timing_;
+  // Time relative to the UNIX epoch (Jan 1, 1970, UTC), in seconds.
+  double cache_timestamp_;
+  double cache_lifetime_;  // In seconds.
+  rtc::scoped_refptr<const RTCStatsReport> cached_report_;
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_STATS_RTCSTATSCOLLECTOR_H_
diff --git a/stats/rtcstatscollector_unittest.cc b/stats/rtcstatscollector_unittest.cc
new file mode 100644
index 0000000..a3b0572
--- /dev/null
+++ b/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 <memory>
+#include <string>
+#include <vector>
+
+#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<rtc::scoped_refptr<DataChannel>>& 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<cricket::ChannelManager> channel_manager_;
+  std::unique_ptr<webrtc::MediaControllerInterface> media_controller_;
+  MockWebRtcSession session_;
+  MockPeerConnection pc_;
+
+  std::vector<rtc::scoped_refptr<DataChannel>> data_channels_;
+};
+
+class RTCStatsCollectorTest : public testing::Test {
+ public:
+  RTCStatsCollectorTest()
+    : test_(new rtc::RefCountedObject<RTCStatsCollectorTester>()),
+      timing_(new rtc::FakeTiming()),
+      collector_(&test_->pc(), 0.05, std::unique_ptr<rtc::Timing>(timing_)) {
+  }
+
+ protected:
+  rtc::scoped_refptr<RTCStatsCollectorTester> 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<const RTCStatsReport> a = collector_.GetStatsReport();
+  rtc::scoped_refptr<const RTCStatsReport> b = collector_.GetStatsReport();
+  EXPECT_TRUE(a);
+  EXPECT_EQ(a.get(), b.get());
+  // Invalidate cache by clearing it.
+  collector_.ClearCachedStatsReport();
+  rtc::scoped_refptr<const RTCStatsReport> c = collector_.GetStatsReport();
+  EXPECT_TRUE(c);
+  EXPECT_NE(b.get(), c.get());
+  // Invalidate cache by advancing time.
+  timing_->AdvanceTimeMillisecs(51.0);
+  rtc::scoped_refptr<const RTCStatsReport> d = collector_.GetStatsReport();
+  EXPECT_TRUE(d);
+  EXPECT_NE(c.get(), d.get());
+}
+
+TEST_F(RTCStatsCollectorTest, CollectRTCPeerConnectionStats) {
+  rtc::scoped_refptr<const RTCStatsReport> report = collector_.GetStatsReport();
+  EXPECT_EQ(report->GetStatsOfType<RTCPeerConnectionStats>().size(),
+            static_cast<size_t>(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<RTCPeerConnectionStats>();
+    EXPECT_EQ(*pcstats.data_channels_opened, static_cast<uint32_t>(0));
+    EXPECT_EQ(*pcstats.data_channels_closed, static_cast<uint32_t>(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<RTCPeerConnectionStats>().size(),
+            static_cast<size_t>(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<RTCPeerConnectionStats>();
+    EXPECT_EQ(*pcstats.data_channels_opened, static_cast<uint32_t>(1));
+    EXPECT_EQ(*pcstats.data_channels_closed, static_cast<uint32_t>(3));
+  }
+}
+
+}  // namespace webrtc
diff --git a/stats/stats.gyp b/stats/stats.gyp
index 4c22557..b2b99cc 100644
--- a/stats/stats.gyp
+++ b/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',
           ],
         },