Move the PeerConnection's usage pattern concept to its own file.

This makes it easier to use it from multiple other modules.

Bug: webrtc:11995
Change-Id: Id23843ae4600ebe46aed7465e873d107089fd50b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/187347
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32361}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 047fc22..3fb1729 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -230,6 +230,7 @@
     ":rtp_sender",
     ":rtp_transceiver",
     ":stats_collector_interface",
+    ":usage_pattern",
     ":video_track_source",
     "../api:array_view",
     "../api:audio_options_api",
@@ -305,6 +306,18 @@
   ]
 }
 
+rtc_library("usage_pattern") {
+  sources = [
+    "usage_pattern.cc",
+    "usage_pattern.h",
+  ]
+  deps = [
+    "../api:libjingle_peerconnection_api",
+    "../rtc_base:logging",
+    "../system_wrappers:metrics",
+  ]
+}
+
 rtc_library("rtp_transceiver") {
   sources = [
     "rtp_transceiver.cc",
@@ -745,6 +758,7 @@
       ":rtp_receiver",
       ":rtp_sender",
       ":rtp_transceiver",
+      ":usage_pattern",
       ":video_track_source",
       "../api:array_view",
       "../api:audio_options_api",
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index e43a447..6b7028e 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -3066,33 +3066,11 @@
 
 void PeerConnection::NoteUsageEvent(UsageEvent event) {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  usage_event_accumulator_ |= static_cast<int>(event);
+  usage_pattern_.NoteUsageEvent(event);
 }
 
 void PeerConnection::ReportUsagePattern() const {
-  RTC_DLOG(LS_INFO) << "Usage signature is " << usage_event_accumulator_;
-  RTC_HISTOGRAM_ENUMERATION_SPARSE("WebRTC.PeerConnection.UsagePattern",
-                                   usage_event_accumulator_,
-                                   static_cast<int>(UsageEvent::MAX_VALUE));
-  const int bad_bits =
-      static_cast<int>(UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED) |
-      static_cast<int>(UsageEvent::CANDIDATE_COLLECTED);
-  const int good_bits =
-      static_cast<int>(UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED) |
-      static_cast<int>(UsageEvent::REMOTE_CANDIDATE_ADDED) |
-      static_cast<int>(UsageEvent::ICE_STATE_CONNECTED);
-  if ((usage_event_accumulator_ & bad_bits) == bad_bits &&
-      (usage_event_accumulator_ & good_bits) == 0) {
-    // If called after close(), we can't report, because observer may have
-    // been deallocated, and therefore pointer is null. Write to log instead.
-    if (observer_) {
-      Observer()->OnInterestingUsage(usage_event_accumulator_);
-    } else {
-      RTC_LOG(LS_INFO) << "Interesting usage signature "
-                       << usage_event_accumulator_
-                       << " observed after observer shutdown";
-    }
-  }
+  usage_pattern_.ReportUsagePattern(observer_);
 }
 
 bool PeerConnection::SrtpRequired() const {
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index c073b59..8698c30 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -36,6 +36,7 @@
 #include "pc/stats_collector.h"
 #include "pc/stream_collection.h"
 #include "pc/transceiver_list.h"
+#include "pc/usage_pattern.h"
 #include "pc/webrtc_session_description_factory.h"
 #include "rtc_base/experiments/field_trial_parser.h"
 #include "rtc_base/operations_chain.h"
@@ -70,53 +71,6 @@
                        public RtpSenderBase::SetStreamsObserver,
                        public sigslot::has_slots<> {
  public:
-  // A bit in the usage pattern is registered when its defining event occurs at
-  // least once.
-  enum class UsageEvent : int {
-    TURN_SERVER_ADDED = 0x01,
-    STUN_SERVER_ADDED = 0x02,
-    DATA_ADDED = 0x04,
-    AUDIO_ADDED = 0x08,
-    VIDEO_ADDED = 0x10,
-    // |SetLocalDescription| returns successfully.
-    SET_LOCAL_DESCRIPTION_SUCCEEDED = 0x20,
-    // |SetRemoteDescription| returns successfully.
-    SET_REMOTE_DESCRIPTION_SUCCEEDED = 0x40,
-    // A local candidate (with type host, server-reflexive, or relay) is
-    // collected.
-    CANDIDATE_COLLECTED = 0x80,
-    // A remote candidate is successfully added via |AddIceCandidate|.
-    ADD_ICE_CANDIDATE_SUCCEEDED = 0x100,
-    ICE_STATE_CONNECTED = 0x200,
-    CLOSE_CALLED = 0x400,
-    // A local candidate with private IP is collected.
-    PRIVATE_CANDIDATE_COLLECTED = 0x800,
-    // A remote candidate with private IP is added, either via AddiceCandidate
-    // or from the remote description.
-    REMOTE_PRIVATE_CANDIDATE_ADDED = 0x1000,
-    // A local mDNS candidate is collected.
-    MDNS_CANDIDATE_COLLECTED = 0x2000,
-    // A remote mDNS candidate is added, either via AddIceCandidate or from the
-    // remote description.
-    REMOTE_MDNS_CANDIDATE_ADDED = 0x4000,
-    // A local candidate with IPv6 address is collected.
-    IPV6_CANDIDATE_COLLECTED = 0x8000,
-    // A remote candidate with IPv6 address is added, either via AddIceCandidate
-    // or from the remote description.
-    REMOTE_IPV6_CANDIDATE_ADDED = 0x10000,
-    // A remote candidate (with type host, server-reflexive, or relay) is
-    // successfully added, either via AddIceCandidate or from the remote
-    // description.
-    REMOTE_CANDIDATE_ADDED = 0x20000,
-    // An explicit host-host candidate pair is selected, i.e. both the local and
-    // the remote candidates have the host type. This does not include candidate
-    // pairs formed with equivalent prflx remote candidates, e.g. a host-prflx
-    // pair where the prflx candidate has the same base as a host candidate of
-    // the remote peer.
-    DIRECT_CONNECTION_SELECTED = 0x40000,
-    MAX_VALUE = 0x80000,
-  };
-
   explicit PeerConnection(PeerConnectionFactory* factory,
                           std::unique_ptr<RtcEventLog> event_log,
                           std::unique_ptr<Call> call);
@@ -873,7 +827,7 @@
   cricket::AudioOptions audio_options_ RTC_GUARDED_BY(signaling_thread());
   cricket::VideoOptions video_options_ RTC_GUARDED_BY(signaling_thread());
 
-  int usage_event_accumulator_ RTC_GUARDED_BY(signaling_thread()) = 0;
+  UsagePattern usage_pattern_ RTC_GUARDED_BY(signaling_thread());
   bool return_histogram_very_quickly_ RTC_GUARDED_BY(signaling_thread()) =
       false;
 
diff --git a/pc/peer_connection_histogram_unittest.cc b/pc/peer_connection_histogram_unittest.cc
index e36b29a..8fcb87a 100644
--- a/pc/peer_connection_histogram_unittest.cc
+++ b/pc/peer_connection_histogram_unittest.cc
@@ -32,6 +32,7 @@
 #include "pc/peer_connection_wrapper.h"
 #include "pc/sdp_utils.h"
 #include "pc/test/mock_peer_connection_observers.h"
+#include "pc/usage_pattern.h"
 #include "pc/webrtc_sdp.h"
 #include "rtc_base/arraysize.h"
 #include "rtc_base/checks.h"
@@ -61,7 +62,7 @@
 static const rtc::SocketAddress kPrivateIpv6LocalAddress("fd12:3456:789a:1::1",
                                                          0);
 
-int MakeUsageFingerprint(std::set<PeerConnection::UsageEvent> events) {
+int MakeUsageFingerprint(std::set<UsageEvent> events) {
   int signature = 0;
   for (const auto it : events) {
     signature |= static_cast<int>(it);
@@ -418,16 +419,12 @@
   caller->pc()->Close();
   callee->pc()->Close();
   int expected_fingerprint = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::VIDEO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
+       UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
   // In this case, we may or may not have PRIVATE_CANDIDATE_COLLECTED,
   // depending on the machine configuration.
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
@@ -437,9 +434,7 @@
       webrtc::metrics::NumEvents(
           kUsagePatternMetric,
           expected_fingerprint |
-              static_cast<int>(
-                  PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
-          2);
+              static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == 2);
 }
 
 // Test getting the usage fingerprint when the caller collects an mDNS
@@ -458,32 +453,24 @@
   callee->pc()->Close();
 
   int expected_fingerprint_caller = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::VIDEO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::MDNS_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
+       UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED,
+       UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
 
   // Without a resolver, the callee cannot resolve the received mDNS candidate
   // but can still connect with the caller via a prflx candidate. As a result,
   // the bit for the direct connection should not be logged.
   int expected_fingerprint_callee = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::VIDEO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
+       UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(kUsagePatternMetric,
                                                  expected_fingerprint_caller));
@@ -509,29 +496,21 @@
   // Similar to the test above, the caller connects with the callee via a prflx
   // candidate.
   int expected_fingerprint_caller = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::VIDEO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
+       UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::CLOSE_CALLED});
 
   int expected_fingerprint_callee = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::VIDEO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::MDNS_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
+       UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED,
+       UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(kUsagePatternMetric,
                                                  expected_fingerprint_caller));
@@ -549,15 +528,11 @@
   caller->pc()->Close();
   callee->pc()->Close();
   int expected_fingerprint = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::DATA_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_TRUE(
       webrtc::metrics::NumEvents(kUsagePatternMetric, expected_fingerprint) ==
@@ -565,9 +540,7 @@
       webrtc::metrics::NumEvents(
           kUsagePatternMetric,
           expected_fingerprint |
-              static_cast<int>(
-                  PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
-          2);
+              static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == 2);
 }
 #endif  // HAVE_SCTP
 #endif  // WEBRTC_ANDROID
@@ -584,10 +557,9 @@
   auto caller = CreatePeerConnection(configuration);
   ASSERT_TRUE(caller);
   caller->pc()->Close();
-  int expected_fingerprint =
-      MakeUsageFingerprint({PeerConnection::UsageEvent::STUN_SERVER_ADDED,
-                            PeerConnection::UsageEvent::TURN_SERVER_ADDED,
-                            PeerConnection::UsageEvent::CLOSE_CALLED});
+  int expected_fingerprint = MakeUsageFingerprint(
+      {UsageEvent::STUN_SERVER_ADDED, UsageEvent::TURN_SERVER_ADDED,
+       UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(
       1, webrtc::metrics::NumEvents(kUsagePatternMetric, expected_fingerprint));
@@ -606,10 +578,9 @@
   ASSERT_TRUE(caller);
   ASSERT_TRUE(caller->pc()->SetConfiguration(configuration).ok());
   caller->pc()->Close();
-  int expected_fingerprint =
-      MakeUsageFingerprint({PeerConnection::UsageEvent::STUN_SERVER_ADDED,
-                            PeerConnection::UsageEvent::TURN_SERVER_ADDED,
-                            PeerConnection::UsageEvent::CLOSE_CALLED});
+  int expected_fingerprint = MakeUsageFingerprint(
+      {UsageEvent::STUN_SERVER_ADDED, UsageEvent::TURN_SERVER_ADDED,
+       UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(
       1, webrtc::metrics::NumEvents(kUsagePatternMetric, expected_fingerprint));
@@ -624,28 +595,20 @@
   callee->pc()->Close();
 
   int expected_fingerprint_caller = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
+       UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
 
   int expected_fingerprint_callee = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
+       UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(kUsagePatternMetric,
                                                  expected_fingerprint_caller));
@@ -662,30 +625,22 @@
   callee->pc()->Close();
 
   int expected_fingerprint_caller = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
+       UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
 
   int expected_fingerprint_callee = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::AUDIO_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::IPV6_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
+       UsageEvent::IPV6_CANDIDATE_COLLECTED,
+       UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(kUsagePatternMetric,
                                                  expected_fingerprint_caller));
@@ -747,27 +702,20 @@
   // with the callee via a prflx candidate and hence no direct connection bit
   // should be set.
   int expected_fingerprint_caller = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::DATA_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::IPV6_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
+       UsageEvent::IPV6_CANDIDATE_COLLECTED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::CLOSE_CALLED});
 
   int expected_fingerprint_callee = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::DATA_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::MDNS_CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED,
-       PeerConnection::UsageEvent::ICE_STATE_CONNECTED,
-       PeerConnection::UsageEvent::DIRECT_CONNECTION_SELECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED,
+       UsageEvent::REMOTE_CANDIDATE_ADDED,
+       UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
+       UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
+       UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(2, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumEvents(kUsagePatternMetric,
                                                  expected_fingerprint_caller));
@@ -781,16 +729,13 @@
   caller->GenerateOfferAndCollectCandidates();
   caller->pc()->Close();
   int expected_fingerprint = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::DATA_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumSamples(kUsagePatternMetric));
   EXPECT_METRIC_TRUE(
       expected_fingerprint == ObservedFingerprint() ||
       (expected_fingerprint |
-       static_cast<int>(
-           PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
+       static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
           ObservedFingerprint());
   EXPECT_METRIC_EQ(absl::make_optional(ObservedFingerprint()),
                    caller->observer()->interesting_usage_detected());
@@ -801,9 +746,8 @@
   caller->CreateDataChannel("foo");
   caller->GenerateOfferAndCollectCandidates();
   int expected_fingerprint = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::DATA_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED});
+      {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED});
   EXPECT_METRIC_EQ(0, webrtc::metrics::NumSamples(kUsagePatternMetric));
   caller->GetInternalPeerConnection()->RequestUsagePatternReportForTesting();
   EXPECT_METRIC_EQ_WAIT(1, webrtc::metrics::NumSamples(kUsagePatternMetric),
@@ -811,8 +755,7 @@
   EXPECT_METRIC_TRUE(
       expected_fingerprint == ObservedFingerprint() ||
       (expected_fingerprint |
-       static_cast<int>(
-           PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
+       static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
           ObservedFingerprint());
   EXPECT_METRIC_EQ(absl::make_optional(ObservedFingerprint()),
                    caller->observer()->interesting_usage_detected());
@@ -824,10 +767,8 @@
   caller->CreateDataChannel("foo");
   caller->GenerateOfferAndCollectCandidates();
   int expected_fingerprint = MakeUsageFingerprint(
-      {PeerConnection::UsageEvent::DATA_ADDED,
-       PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
-       PeerConnection::UsageEvent::CANDIDATE_COLLECTED,
-       PeerConnection::UsageEvent::CLOSE_CALLED});
+      {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
+       UsageEvent::CANDIDATE_COLLECTED, UsageEvent::CLOSE_CALLED});
   EXPECT_METRIC_EQ(0, webrtc::metrics::NumSamples(kUsagePatternMetric));
   caller->pc()->Close();
   EXPECT_METRIC_EQ(1, webrtc::metrics::NumSamples(kUsagePatternMetric));
@@ -838,8 +779,7 @@
   EXPECT_METRIC_TRUE(
       expected_fingerprint == ObservedFingerprint() ||
       (expected_fingerprint |
-       static_cast<int>(
-           PeerConnection::UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
+       static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
           ObservedFingerprint());
   // After close, the usage-detection callback should NOT have been called.
   EXPECT_METRIC_FALSE(caller->observer()->interesting_usage_detected());
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 8b2d5a0..5dc5571 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -1802,8 +1802,7 @@
   }
 
   observer->OnSetLocalDescriptionComplete(RTCError::OK());
-  pc_->NoteUsageEvent(
-      PeerConnection::UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED);
+  pc_->NoteUsageEvent(UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED);
 
   // Check if negotiation is needed. We must do this after informing the
   // observer that SetLocalDescription() has completed to ensure negotiation is
@@ -2062,8 +2061,7 @@
   }
 
   observer->OnSetRemoteDescriptionComplete(RTCError::OK());
-  pc_->NoteUsageEvent(
-      PeerConnection::UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED);
+  pc_->NoteUsageEvent(UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED);
 
   // Check if negotiation is needed. We must do this after informing the
   // observer that SetRemoteDescription() has completed to ensure negotiation is
@@ -2162,8 +2160,7 @@
   if (ready) {
     bool result = UseCandidate(ice_candidate);
     if (result) {
-      pc_->NoteUsageEvent(
-          PeerConnection::UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED);
+      pc_->NoteUsageEvent(UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED);
       NoteAddIceCandidateResult(kAddIceCandidateSuccess);
     } else {
       NoteAddIceCandidateResult(kAddIceCandidateFailNotUsable);
@@ -4235,18 +4232,15 @@
 
 void SdpOfferAnswerHandler::ReportRemoteIceCandidateAdded(
     const cricket::Candidate& candidate) {
-  pc_->NoteUsageEvent(PeerConnection::UsageEvent::REMOTE_CANDIDATE_ADDED);
+  pc_->NoteUsageEvent(UsageEvent::REMOTE_CANDIDATE_ADDED);
   if (candidate.address().IsPrivateIP()) {
-    pc_->NoteUsageEvent(
-        PeerConnection::UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED);
+    pc_->NoteUsageEvent(UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED);
   }
   if (candidate.address().IsUnresolvedIP()) {
-    pc_->NoteUsageEvent(
-        PeerConnection::UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED);
+    pc_->NoteUsageEvent(UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED);
   }
   if (candidate.address().family() == AF_INET6) {
-    pc_->NoteUsageEvent(
-        PeerConnection::UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED);
+    pc_->NoteUsageEvent(UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED);
   }
 }
 
diff --git a/pc/usage_pattern.cc b/pc/usage_pattern.cc
new file mode 100644
index 0000000..8484721
--- /dev/null
+++ b/pc/usage_pattern.cc
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 2020 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 "pc/usage_pattern.h"
+
+#include "api/peer_connection_interface.h"
+#include "rtc_base/logging.h"
+#include "system_wrappers/include/metrics.h"
+
+namespace webrtc {
+
+void UsagePattern::NoteUsageEvent(UsageEvent event) {
+  usage_event_accumulator_ |= static_cast<int>(event);
+}
+
+void UsagePattern::ReportUsagePattern(PeerConnectionObserver* observer) const {
+  RTC_DLOG(LS_INFO) << "Usage signature is " << usage_event_accumulator_;
+  RTC_HISTOGRAM_ENUMERATION_SPARSE("WebRTC.PeerConnection.UsagePattern",
+                                   usage_event_accumulator_,
+                                   static_cast<int>(UsageEvent::MAX_VALUE));
+  const int bad_bits =
+      static_cast<int>(UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED) |
+      static_cast<int>(UsageEvent::CANDIDATE_COLLECTED);
+  const int good_bits =
+      static_cast<int>(UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED) |
+      static_cast<int>(UsageEvent::REMOTE_CANDIDATE_ADDED) |
+      static_cast<int>(UsageEvent::ICE_STATE_CONNECTED);
+  if ((usage_event_accumulator_ & bad_bits) == bad_bits &&
+      (usage_event_accumulator_ & good_bits) == 0) {
+    // If called after close(), we can't report, because observer may have
+    // been deallocated, and therefore pointer is null. Write to log instead.
+    if (observer) {
+      observer->OnInterestingUsage(usage_event_accumulator_);
+    } else {
+      RTC_LOG(LS_INFO) << "Interesting usage signature "
+                       << usage_event_accumulator_
+                       << " observed after observer shutdown";
+    }
+  }
+}
+
+}  // namespace webrtc
diff --git a/pc/usage_pattern.h b/pc/usage_pattern.h
new file mode 100644
index 0000000..c4a8918
--- /dev/null
+++ b/pc/usage_pattern.h
@@ -0,0 +1,75 @@
+/*
+ *  Copyright 2020 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 PC_USAGE_PATTERN_H_
+#define PC_USAGE_PATTERN_H_
+
+namespace webrtc {
+
+class PeerConnectionObserver;
+
+// A bit in the usage pattern is registered when its defining event occurs
+// at least once.
+enum class UsageEvent : int {
+  TURN_SERVER_ADDED = 0x01,
+  STUN_SERVER_ADDED = 0x02,
+  DATA_ADDED = 0x04,
+  AUDIO_ADDED = 0x08,
+  VIDEO_ADDED = 0x10,
+  // |SetLocalDescription| returns successfully.
+  SET_LOCAL_DESCRIPTION_SUCCEEDED = 0x20,
+  // |SetRemoteDescription| returns successfully.
+  SET_REMOTE_DESCRIPTION_SUCCEEDED = 0x40,
+  // A local candidate (with type host, server-reflexive, or relay) is
+  // collected.
+  CANDIDATE_COLLECTED = 0x80,
+  // A remote candidate is successfully added via |AddIceCandidate|.
+  ADD_ICE_CANDIDATE_SUCCEEDED = 0x100,
+  ICE_STATE_CONNECTED = 0x200,
+  CLOSE_CALLED = 0x400,
+  // A local candidate with private IP is collected.
+  PRIVATE_CANDIDATE_COLLECTED = 0x800,
+  // A remote candidate with private IP is added, either via AddiceCandidate
+  // or from the remote description.
+  REMOTE_PRIVATE_CANDIDATE_ADDED = 0x1000,
+  // A local mDNS candidate is collected.
+  MDNS_CANDIDATE_COLLECTED = 0x2000,
+  // A remote mDNS candidate is added, either via AddIceCandidate or from the
+  // remote description.
+  REMOTE_MDNS_CANDIDATE_ADDED = 0x4000,
+  // A local candidate with IPv6 address is collected.
+  IPV6_CANDIDATE_COLLECTED = 0x8000,
+  // A remote candidate with IPv6 address is added, either via AddIceCandidate
+  // or from the remote description.
+  REMOTE_IPV6_CANDIDATE_ADDED = 0x10000,
+  // A remote candidate (with type host, server-reflexive, or relay) is
+  // successfully added, either via AddIceCandidate or from the remote
+  // description.
+  REMOTE_CANDIDATE_ADDED = 0x20000,
+  // An explicit host-host candidate pair is selected, i.e. both the local and
+  // the remote candidates have the host type. This does not include candidate
+  // pairs formed with equivalent prflx remote candidates, e.g. a host-prflx
+  // pair where the prflx candidate has the same base as a host candidate of
+  // the remote peer.
+  DIRECT_CONNECTION_SELECTED = 0x40000,
+  MAX_VALUE = 0x80000,
+};
+
+class UsagePattern {
+ public:
+  void NoteUsageEvent(UsageEvent event);
+  void ReportUsagePattern(PeerConnectionObserver* observer) const;
+
+ private:
+  int usage_event_accumulator_ = 0;
+};
+
+}  // namespace webrtc
+#endif  // PC_USAGE_PATTERN_H_