Update StatsReport and by extension StatsCollector to reduce data copying.

Summary of changes:
* We're now using an enum for types instead of strings which both eliminates unecessary string creations+copies and further restricts the type to a known set at compile time.
* IDs are now a separate type instead of a string, copying of Values is not possible and values are const to allow grabbing references outside of the statscollector.
* StatsReport member variables are no longer public.
* Consolidated code in StatsCollector (e.g. merged PrepareLocalReport and PrepareRemoteReport).
* Refactored methods that forced copies of string (e.g. ExtractValueFromReport).
* More asserts for thread correctness.
* Using std::list for the StatsSet instead of a set since order is not important and updates are more efficient in list<>.

BUG=2822
R=hta@webrtc.org, perkj@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/40439004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@8110 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc
index 935fef5..125e52c 100644
--- a/talk/app/webrtc/statscollector.cc
+++ b/talk/app/webrtc/statscollector.cc
@@ -35,6 +35,8 @@
 #include "webrtc/base/scoped_ptr.h"
 #include "webrtc/base/timing.h"
 
+using rtc::scoped_ptr;
+
 namespace webrtc {
 namespace {
 
@@ -73,55 +75,26 @@
     return false;
   }
 
-  std::ostringstream ost;
   // Component 1 is always used for RTP.
-  ost << "Channel-" << found->second << "-1";
-  *transport = ost.str();
+  scoped_ptr<StatsReport::Id> id(
+      StatsReport::NewComponentId(found->second, 1));
+  // TODO(tommi): Should |transport| simply be of type StatsReport::Id?
+  // When we support more value types than string (e.g. int, double, vector etc)
+  // we should also support a value type for Id.
+  *transport = id->ToString();
   return true;
 }
 
-std::string StatsId(const std::string& type, const std::string& id) {
-  return type + "_" + id;
-}
-
-std::string StatsId(const std::string& type, const std::string& id,
-                    StatsCollector::TrackDirection direction) {
-  ASSERT(direction == StatsCollector::kSending ||
-         direction == StatsCollector::kReceiving);
-
-  // Strings for the direction of the track.
-  const char kSendDirection[] = "send";
-  const char kRecvDirection[] = "recv";
-
-  const std::string direction_id = (direction == StatsCollector::kSending) ?
-      kSendDirection : kRecvDirection;
-  return type + "_" + id + "_" + direction_id;
-}
-
-bool ExtractValueFromReport(
-    const StatsReport& report,
-    StatsReport::StatsValueName name,
-    std::string* value) {
-  StatsReport::Values::const_iterator it = report.values().begin();
-  for (; it != report.values().end(); ++it) {
-    if ((*it)->name == name) {
-      *value = (*it)->value;
-      return true;
-    }
-  }
-  return false;
-}
-
-void AddTrackReport(StatsSet* reports, const std::string& track_id) {
+void AddTrackReport(StatsCollection* reports, const std::string& track_id) {
   // Adds an empty track report.
-  StatsReport* report = reports->ReplaceOrAddNew(
-      StatsId(StatsReport::kStatsReportTypeTrack, track_id));
-  report->type = StatsReport::kStatsReportTypeTrack;
+  rtc::scoped_ptr<StatsReport::Id> id(
+      StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack, track_id));
+  StatsReport* report = reports->ReplaceOrAddNew(id.Pass());
   report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
 }
 
 template <class TrackVector>
-void CreateTrackReports(const TrackVector& tracks, StatsSet* reports) {
+void CreateTrackReports(const TrackVector& tracks, StatsCollection* reports) {
   for (size_t j = 0; j < tracks.size(); ++j) {
     webrtc::MediaStreamTrackInterface* track = tracks[j];
     AddTrackReport(reports, track->id());
@@ -284,7 +257,7 @@
                   double stats_gathering_started,
                   PeerConnectionInterface::StatsOutputLevel level,
                   StatsReport* report) {
-  report->type = StatsReport::kStatsReportTypeBwe;
+  ASSERT(report->type() == StatsReport::kStatsReportTypeBwe);
 
   // Clear out stats from previous GatherStats calls if any.
   if (report->timestamp() != stats_gathering_started) {
@@ -341,25 +314,20 @@
 void ExtractStatsFromList(const std::vector<T>& data,
                           const std::string& transport_id,
                           StatsCollector* collector,
-                          StatsCollector::TrackDirection direction) {
-  typename std::vector<T>::const_iterator it = data.begin();
-  for (; it != data.end(); ++it) {
-    std::string id;
-    uint32 ssrc = it->ssrc();
+                          StatsReport::Direction direction) {
+  for (const auto& d : data) {
+    uint32 ssrc = d.ssrc();
     // Each track can have stats for both local and remote objects.
     // TODO(hta): Handle the case of multiple SSRCs per object.
-    StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id,
-                                                        direction);
+    StatsReport* report = collector->PrepareReport(true, ssrc, transport_id,
+                                                   direction);
     if (report)
-      ExtractStats(*it, report);
+      ExtractStats(d, report);
 
-    if (it->remote_stats.size() > 0) {
-      report = collector->PrepareRemoteReport(ssrc, transport_id,
-                                              direction);
-      if (!report) {
-        continue;
-      }
-      ExtractRemoteStats(*it, report);
+    if (!d.remote_stats.empty()) {
+      report = collector->PrepareReport(false, ssrc, transport_id, direction);
+      if (report)
+        ExtractRemoteStats(d, report);
     }
   }
 }
@@ -436,10 +404,14 @@
 
   // Create the kStatsReportTypeTrack report for the new track if there is no
   // report yet.
-  StatsReport* found = reports_.Find(
-      StatsId(StatsReport::kStatsReportTypeTrack, audio_track->id()));
-  if (!found)
-    AddTrackReport(&reports_, audio_track->id());
+  rtc::scoped_ptr<StatsReport::Id> id(
+      StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack,
+                              audio_track->id()));
+  StatsReport* report = reports_.Find(*id.get());
+  if (!report) {
+    report = reports_.InsertNew(id.Pass());
+    report->AddValue(StatsReport::kStatsValueNameTrackId, audio_track->id());
+  }
 }
 
 void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
@@ -463,20 +435,19 @@
   ASSERT(reports->empty());
 
   if (!track) {
-    StatsSet::const_iterator it;
-    for (it = reports_.begin(); it != reports_.end(); ++it)
-      reports->push_back(&(*it));
+    reports->reserve(reports_.size());
+    for (auto* r : reports_)
+      reports->push_back(r);
     return;
   }
 
-  StatsReport* report =
-      reports_.Find(StatsId(StatsReport::kStatsReportTypeSession,
-                            session_->id()));
+  StatsReport* report = reports_.Find(StatsReport::NewTypedId(
+      StatsReport::kStatsReportTypeSession, session_->id()));
   if (report)
     reports->push_back(report);
 
-  report = reports_.Find(
-      StatsId(StatsReport::kStatsReportTypeTrack, track->id()));
+  report = reports_.Find(StatsReport::NewTypedId(
+      StatsReport::kStatsReportTypeTrack, track->id()));
 
   if (!report)
     return;
@@ -484,18 +455,14 @@
   reports->push_back(report);
 
   std::string track_id;
-  for (StatsSet::const_iterator it = reports_.begin(); it != reports_.end();
-       ++it) {
-    if (it->type != StatsReport::kStatsReportTypeSsrc)
+  for (const auto* r : reports_) {
+    if (r->type() != StatsReport::kStatsReportTypeSsrc)
       continue;
 
-    if (ExtractValueFromReport(*it,
-                               StatsReport::kStatsValueNameTrackId,
-                               &track_id)) {
-      if (track_id == track->id()) {
-        reports->push_back(&(*it));
-      }
-    }
+    const StatsReport::Value* v =
+        r->FindValue(StatsReport::kStatsValueNameTrackId);
+    if (v && v->value == track->id())
+      reports->push_back(r);
   }
 }
 
@@ -520,14 +487,18 @@
   }
 }
 
-StatsReport* StatsCollector::PrepareLocalReport(
+StatsReport* StatsCollector::PrepareReport(
+    bool local,
     uint32 ssrc,
     const std::string& transport_id,
-    TrackDirection direction) {
+    StatsReport::Direction direction) {
   ASSERT(session_->signaling_thread()->IsCurrent());
   const std::string ssrc_id = rtc::ToString<uint32>(ssrc);
-  StatsReport* report = reports_.Find(
-      StatsId(StatsReport::kStatsReportTypeSsrc, ssrc_id, direction));
+  rtc::scoped_ptr<StatsReport::Id> id(StatsReport::NewIdWithDirection(
+      local ? StatsReport::kStatsReportTypeSsrc :
+              StatsReport::kStatsReportTypeRemoteSsrc,
+      ssrc_id, direction));
+  StatsReport* report = reports_.Find(*id.get());
 
   // Use the ID of the track that is currently mapped to the SSRC, if any.
   std::string track_id;
@@ -540,20 +511,28 @@
 
     // The ssrc is not used by any existing track. Keeps the old track id
     // since we want to report the stats for inactive ssrc.
-    ExtractValueFromReport(*report,
-                           StatsReport::kStatsValueNameTrackId,
-                           &track_id);
+    const StatsReport::Value* v =
+        report->FindValue(StatsReport::kStatsValueNameTrackId);
+    if (v)
+      track_id = v->value;
   }
 
-  report = GetOrCreateReport(
-      StatsReport::kStatsReportTypeSsrc, ssrc_id, direction);
+  if (!report) {
+    report = reports_.InsertNew(id.Pass());
+  } else {
+    // Clear out stats from previous GatherStats calls if any.
+    // This is required since the report will be returned for the new values.
+    // Having the old values in the report will lead to multiple values with
+    // the same name.
+    // TODO(tommi): This seems to be pretty wasteful if some of these values
+    // have not changed (we basically throw them away just to recreate them).
+    // Figure out a way to not have to do this while not breaking the existing
+    // functionality.
+    report->ResetValues();
+  }
 
-  // Clear out stats from previous GatherStats calls if any.
-  // This is required since the report will be returned for the new values.
-  // Having the old values in the report will lead to multiple values with
-  // the same name.
-  // TODO(xians): Consider changing StatsReport to use map instead of vector.
-  report->ResetValues();
+  ASSERT(report->values().empty());
+  // FYI - for remote reports, the timestamp will be overwritten later.
   report->set_timestamp(stats_gathering_started_);
 
   report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
@@ -564,49 +543,10 @@
   return report;
 }
 
-StatsReport* StatsCollector::PrepareRemoteReport(
-    uint32 ssrc,
-    const std::string& transport_id,
-    TrackDirection direction) {
-  ASSERT(session_->signaling_thread()->IsCurrent());
-  const std::string ssrc_id = rtc::ToString<uint32>(ssrc);
-  StatsReport* report = reports_.Find(
-      StatsId(StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction));
-
-  // Use the ID of the track that is currently mapped to the SSRC, if any.
-  std::string track_id;
-  if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) {
-    if (!report) {
-      // The ssrc is not used by any track or existing report, return NULL
-      // in such case to indicate no report is prepared for the ssrc.
-      return NULL;
-    }
-
-    // The ssrc is not used by any existing track. Keeps the old track id
-    // since we want to report the stats for inactive ssrc.
-    ExtractValueFromReport(*report,
-                           StatsReport::kStatsValueNameTrackId,
-                           &track_id);
-  }
-
-  report = GetOrCreateReport(
-      StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction);
-
-  // Clear out stats from previous GatherStats calls if any.
-  // The timestamp will be added later. Zero it for debugging.
-  report->ResetValues();
-  report->set_timestamp(0);
-
-  report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
-  report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
-  // Add the mapping of SSRC to transport.
-  report->AddValue(StatsReport::kStatsValueNameTransportId,
-                   transport_id);
-  return report;
-}
-
 std::string StatsCollector::AddOneCertificateReport(
     const rtc::SSLCertificate* cert, const std::string& issuer_id) {
+  ASSERT(session_->signaling_thread()->IsCurrent());
+
   // TODO(bemasc): Move this computation to a helper class that caches these
   // values to reduce CPU use in GetStats.  This will require adding a fast
   // SSLCertificate::Equals() method to detect certificate changes.
@@ -633,9 +573,10 @@
   rtc::Base64::EncodeFromArray(
       der_buffer.data(), der_buffer.length(), &der_base64);
 
-  StatsReport* report = reports_.ReplaceOrAddNew(
-      StatsId(StatsReport::kStatsReportTypeCertificate, fingerprint));
-  report->type = StatsReport::kStatsReportTypeCertificate;
+  rtc::scoped_ptr<StatsReport::Id> id(
+      StatsReport::NewTypedId(
+          StatsReport::kStatsReportTypeCertificate, fingerprint));
+  StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
   report->set_timestamp(stats_gathering_started_);
   report->AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint);
   report->AddValue(StatsReport::kStatsValueNameFingerprintAlgorithm,
@@ -643,11 +584,13 @@
   report->AddValue(StatsReport::kStatsValueNameDer, der_base64);
   if (!issuer_id.empty())
     report->AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id);
+  // TODO(tommi): Can we avoid this?
   return report->id().ToString();
 }
 
 std::string StatsCollector::AddCertificateReports(
     const rtc::SSLCertificate* cert) {
+  ASSERT(session_->signaling_thread()->IsCurrent());
   // Produces a chain of StatsReports representing this certificate and the rest
   // of its chain, and adds those reports to |reports_|.  The return value is
   // the id of the leaf report.  The provided cert must be non-null, so at least
@@ -673,20 +616,17 @@
 
 std::string StatsCollector::AddCandidateReport(
     const cricket::Candidate& candidate,
-    const std::string& report_type) {
-  std::ostringstream ost;
-  ost << "Cand-" << candidate.id();
-  StatsReport* report = reports_.Find(ost.str());
+    bool local) {
+  scoped_ptr<StatsReport::Id> id(
+      StatsReport::NewCandidateId(local, candidate.id()));
+  StatsReport* report = reports_.Find(*id.get());
   if (!report) {
-    report = reports_.InsertNew(ost.str());
-    DCHECK(StatsReport::kStatsReportTypeIceLocalCandidate == report_type ||
-           StatsReport::kStatsReportTypeIceRemoteCandidate == report_type);
-    report->type = report_type;
-    if (report_type == StatsReport::kStatsReportTypeIceLocalCandidate) {
+    report = reports_.InsertNew(id.Pass());
+    report->set_timestamp(stats_gathering_started_);
+    if (local) {
       report->AddValue(StatsReport::kStatsValueNameCandidateNetworkType,
                        AdapterTypeToStatsType(candidate.network_type()));
     }
-    report->set_timestamp(stats_gathering_started_);
     report->AddValue(StatsReport::kStatsValueNameCandidateIPAddress,
                      candidate.address().ipaddr().ToString());
     report->AddValue(StatsReport::kStatsValueNameCandidatePortNumber,
@@ -699,15 +639,17 @@
                      candidate.protocol());
   }
 
-  return ost.str();
+  // TODO(tommi): Necessary?
+  return report->id().ToString();
 }
 
 void StatsCollector::ExtractSessionInfo() {
   ASSERT(session_->signaling_thread()->IsCurrent());
   // Extract information from the base session.
-  StatsReport* report = reports_.ReplaceOrAddNew(
-      StatsId(StatsReport::kStatsReportTypeSession, session_->id()));
-  report->type = StatsReport::kStatsReportTypeSession;
+  rtc::scoped_ptr<StatsReport::Id> id(
+      StatsReport::NewTypedId(
+          StatsReport::kStatsReportTypeSession, session_->id()));
+  StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
   report->set_timestamp(stats_gathering_started_);
   report->ResetValues();
   report->AddBoolean(StatsReport::kStatsValueNameInitiator,
@@ -749,11 +691,10 @@
                = transport_iter->second.channel_stats.begin();
            channel_iter != transport_iter->second.channel_stats.end();
            ++channel_iter) {
-        std::ostringstream ostc;
-        ostc << "Channel-" << transport_iter->second.content_name
-             << "-" << channel_iter->component;
-        StatsReport* channel_report = reports_.ReplaceOrAddNew(ostc.str());
-        channel_report->type = StatsReport::kStatsReportTypeComponent;
+        rtc::scoped_ptr<StatsReport::Id> id(
+            StatsReport::NewComponentId(transport_iter->second.content_name,
+                channel_iter->component));
+        StatsReport* channel_report = reports_.ReplaceOrAddNew(id.Pass());
         channel_report->set_timestamp(stats_gathering_started_);
         channel_report->AddValue(StatsReport::kStatsValueNameComponent,
                                  channel_iter->component);
@@ -770,13 +711,13 @@
         for (size_t i = 0;
              i < channel_iter->connection_infos.size();
              ++i) {
-          std::ostringstream ost;
-          ost << "Conn-" << transport_iter->first << "-"
-              << channel_iter->component << "-" << i;
-          StatsReport* report = reports_.ReplaceOrAddNew(ost.str());
-          report->type = StatsReport::kStatsReportTypeCandidatePair;
+          rtc::scoped_ptr<StatsReport::Id> id(
+              StatsReport::NewCandidatePairId(transport_iter->first,
+                  channel_iter->component, static_cast<int>(i)));
+          StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
           report->set_timestamp(stats_gathering_started_);
           // Link from connection to its containing channel.
+          // TODO(tommi): Any way to avoid ToString here?
           report->AddValue(StatsReport::kStatsValueNameChannelId,
                            channel_report->id().ToString());
 
@@ -797,14 +738,10 @@
           report->AddBoolean(StatsReport::kStatsValueNameActiveConnection,
                              info.best_connection);
           report->AddValue(StatsReport::kStatsValueNameLocalCandidateId,
-                           AddCandidateReport(
-                               info.local_candidate,
-                               StatsReport::kStatsReportTypeIceLocalCandidate));
+                           AddCandidateReport(info.local_candidate, true));
           report->AddValue(
               StatsReport::kStatsValueNameRemoteCandidateId,
-              AddCandidateReport(
-                  info.remote_candidate,
-                  StatsReport::kStatsReportTypeIceRemoteCandidate));
+              AddCandidateReport(info.remote_candidate, false));
           report->AddValue(StatsReport::kStatsValueNameLocalAddress,
                            info.local_candidate.address().ToString());
           report->AddValue(StatsReport::kStatsValueNameRemoteAddress,
@@ -841,8 +778,10 @@
                   << session_->voice_channel()->content_name();
     return;
   }
-  ExtractStatsFromList(voice_info.receivers, transport_id, this, kReceiving);
-  ExtractStatsFromList(voice_info.senders, transport_id, this, kSending);
+  ExtractStatsFromList(voice_info.receivers, transport_id, this,
+      StatsReport::kReceive);
+  ExtractStatsFromList(voice_info.senders, transport_id, this,
+      StatsReport::kSend);
 
   UpdateStatsFromExistingLocalAudioTracks();
 }
@@ -871,13 +810,16 @@
                   << session_->video_channel()->content_name();
     return;
   }
-  ExtractStatsFromList(video_info.receivers, transport_id, this, kReceiving);
-  ExtractStatsFromList(video_info.senders, transport_id, this, kSending);
+  ExtractStatsFromList(video_info.receivers, transport_id, this,
+      StatsReport::kReceive);
+  ExtractStatsFromList(video_info.senders, transport_id, this,
+      StatsReport::kSend);
   if (video_info.bw_estimations.size() != 1) {
     LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size();
   } else {
-    StatsReport* report =
-        reports_.FindOrAddNew(StatsReport::kStatsReportVideoBweId);
+    rtc::scoped_ptr<StatsReport::Id> report_id(
+        StatsReport::NewBandwidthEstimationId());
+    StatsReport* report = reports_.FindOrAddNew(report_id.Pass());
     ExtractStats(
         video_info.bw_estimations[0], stats_gathering_started_, level, report);
   }
@@ -888,9 +830,10 @@
 
   for (const auto& dc :
            session_->mediastream_signaling()->sctp_data_channels()) {
-    StatsReport* report = reports_.ReplaceOrAddNew(
-        StatsId(StatsReport::kStatsReportTypeDataChannel, dc->label()));
-    report->type = StatsReport::kStatsReportTypeDataChannel;
+    rtc::scoped_ptr<StatsReport::Id> id(
+        StatsReport::NewTypedId(StatsReport::kStatsReportTypeDataChannel,
+                                dc->label()));
+    StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
     report->AddValue(StatsReport::kStatsValueNameLabel, dc->label());
     report->AddValue(StatsReport::kStatsValueNameDataChannelId, dc->id());
     report->AddValue(StatsReport::kStatsValueNameProtocol, dc->protocol());
@@ -899,26 +842,27 @@
   }
 }
 
-StatsReport* StatsCollector::GetReport(const std::string& type,
+StatsReport* StatsCollector::GetReport(const StatsReport::StatsType& type,
                                        const std::string& id,
-                                       TrackDirection direction) {
+                                       StatsReport::Direction direction) {
   ASSERT(session_->signaling_thread()->IsCurrent());
   ASSERT(type == StatsReport::kStatsReportTypeSsrc ||
          type == StatsReport::kStatsReportTypeRemoteSsrc);
-  return reports_.Find(StatsId(type, id, direction));
+  return reports_.Find(StatsReport::NewIdWithDirection(type, id, direction));
 }
 
-StatsReport* StatsCollector::GetOrCreateReport(const std::string& type,
-                                               const std::string& id,
-                                               TrackDirection direction) {
+StatsReport* StatsCollector::GetOrCreateReport(
+    const StatsReport::StatsType& type,
+    const std::string& id,
+    StatsReport::Direction direction) {
   ASSERT(session_->signaling_thread()->IsCurrent());
   ASSERT(type == StatsReport::kStatsReportTypeSsrc ||
          type == StatsReport::kStatsReportTypeRemoteSsrc);
   StatsReport* report = GetReport(type, id, direction);
   if (report == NULL) {
-    std::string statsid = StatsId(type, id, direction);
-    report = reports_.FindOrAddNew(statsid);
-    report->type = type;
+    rtc::scoped_ptr<StatsReport::Id> report_id(
+        StatsReport::NewIdWithDirection(type, id, direction));
+    report = reports_.InsertNew(report_id.Pass());
   }
 
   return report;
@@ -934,7 +878,7 @@
     std::string ssrc_id = rtc::ToString<uint32>(ssrc);
     StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc,
                                     ssrc_id,
-                                    kSending);
+                                    StatsReport::kSend);
     if (report == NULL) {
       // This can happen if a local audio track is added to a stream on the
       // fly and the report has not been set up yet. Do nothing in this case.
@@ -943,13 +887,10 @@
     }
 
     // The same ssrc can be used by both local and remote audio tracks.
-    std::string track_id;
-    if (!ExtractValueFromReport(*report,
-                                StatsReport::kStatsValueNameTrackId,
-                                &track_id) ||
-        track_id != track->id()) {
+    const StatsReport::Value* v =
+        report->FindValue(StatsReport::kStatsValueNameTrackId);
+    if (!v || v->value != track->id())
       continue;
-    }
 
     UpdateReportFromAudioTrack(track, report);
   }
@@ -991,16 +932,16 @@
 }
 
 bool StatsCollector::GetTrackIdBySsrc(uint32 ssrc, std::string* track_id,
-                                      TrackDirection direction) {
+                                      StatsReport::Direction direction) {
   ASSERT(session_->signaling_thread()->IsCurrent());
-  if (direction == kSending) {
+  if (direction == StatsReport::kSend) {
     if (!session_->GetLocalTrackIdBySsrc(ssrc, track_id)) {
       LOG(LS_WARNING) << "The SSRC " << ssrc
                       << " is not associated with a sending track";
       return false;
     }
   } else {
-    ASSERT(direction == kReceiving);
+    ASSERT(direction == StatsReport::kReceive);
     if (!session_->GetRemoteTrackIdBySsrc(ssrc, track_id)) {
       LOG(LS_WARNING) << "The SSRC " << ssrc
                       << " is not associated with a receiving track";
diff --git a/talk/app/webrtc/statscollector.h b/talk/app/webrtc/statscollector.h
index 1140d92..499da44 100644
--- a/talk/app/webrtc/statscollector.h
+++ b/talk/app/webrtc/statscollector.h
@@ -54,14 +54,9 @@
 
 class StatsCollector {
  public:
-  enum TrackDirection {
-    kSending = 0,
-    kReceiving,
-  };
-
   // The caller is responsible for ensuring that the session outlives the
   // StatsCollector instance.
-  StatsCollector(WebRtcSession* session);
+  explicit StatsCollector(WebRtcSession* session);
   virtual ~StatsCollector();
 
   // Adds a MediaStream with tracks that can be used as a |selector| in a call
@@ -89,13 +84,10 @@
   void GetStats(MediaStreamTrackInterface* track,
                 StatsReports* reports);
 
-  // Prepare an SSRC report for the given ssrc. Used internally
+  // Prepare a local or remote SSRC report for the given ssrc. Used internally
   // in the ExtractStatsFromList template.
-  StatsReport* PrepareLocalReport(uint32 ssrc, const std::string& transport,
-                                  TrackDirection direction);
-  // Prepare an SSRC report for the given remote ssrc. Used internally.
-  StatsReport* PrepareRemoteReport(uint32 ssrc, const std::string& transport,
-                                   TrackDirection direction);
+  StatsReport* PrepareReport(bool local, uint32 ssrc,
+      const std::string& transport_id, StatsReport::Direction direction);
 
   // Method used by the unittest to force a update of stats since UpdateStats()
   // that occur less than kMinGatherStatsPeriod number of ms apart will be
@@ -114,7 +106,7 @@
   // Helper method for creating IceCandidate report. |is_local| indicates
   // whether this candidate is local or remote.
   std::string AddCandidateReport(const cricket::Candidate& candidate,
-                                 const std::string& report_type);
+                                 bool local);
 
   // Adds a report for this certificate and every certificate in its chain, and
   // returns the leaf certificate's report's ID.
@@ -125,12 +117,12 @@
   void ExtractVoiceInfo();
   void ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level);
   void BuildSsrcToTransportId();
-  webrtc::StatsReport* GetOrCreateReport(const std::string& type,
+  webrtc::StatsReport* GetOrCreateReport(const StatsReport::StatsType& type,
                                          const std::string& id,
-                                         TrackDirection direction);
-  webrtc::StatsReport* GetReport(const std::string& type,
+                                         StatsReport::Direction direction);
+  webrtc::StatsReport* GetReport(const StatsReport::StatsType& type,
                                  const std::string& id,
-                                 TrackDirection direction);
+                                 StatsReport::Direction direction);
 
   // Helper method to get stats from the local audio tracks.
   void UpdateStatsFromExistingLocalAudioTracks();
@@ -140,10 +132,10 @@
   // Helper method to get the id for the track identified by ssrc.
   // |direction| tells if the track is for sending or receiving.
   bool GetTrackIdBySsrc(uint32 ssrc, std::string* track_id,
-                        TrackDirection direction);
+                        StatsReport::Direction direction);
 
-  // A map from the report id to the report.
-  StatsSet reports_;
+  // A collection for all of our stats reports.
+  StatsCollection reports_;
   // Raw pointer to the session the statistics are gathered from.
   WebRtcSession* const session_;
   double stats_gathering_started_;
diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc
index b70fe7f..b8c983b 100644
--- a/talk/app/webrtc/statscollector_unittest.cc
+++ b/talk/app/webrtc/statscollector_unittest.cc
@@ -48,6 +48,7 @@
 #include "webrtc/p2p/base/fakesession.h"
 
 using cricket::StatsOptions;
+using rtc::scoped_ptr;
 using testing::_;
 using testing::DoAll;
 using testing::Field;
@@ -69,7 +70,6 @@
 
 // Error return values
 const char kNotFound[] = "NOT FOUND";
-const char kNoReports[] = "NO REPORTS";
 
 // Constant names for track identification.
 const char kLocalTrackId[] = "local_track_id";
@@ -152,6 +152,7 @@
   rtc::scoped_refptr<FakeAudioProcessor> processor_;
 };
 
+// TODO(tommi): Use FindValue().
 bool GetValue(const StatsReport* report,
               StatsReport::StatsValueName name,
               std::string* value) {
@@ -164,52 +165,69 @@
   return false;
 }
 
-std::string ExtractStatsValue(const std::string& type,
+std::string ExtractStatsValue(const StatsReport::StatsType& type,
                               const StatsReports& reports,
                               StatsReport::StatsValueName name) {
-  if (reports.empty()) {
-    return kNoReports;
-  }
-  for (size_t i = 0; i < reports.size(); ++i) {
-    if (reports[i]->type != type)
-      continue;
+  for (const auto* r : reports) {
     std::string ret;
-    if (GetValue(reports[i], name, &ret)) {
+    if (r->type() == type && GetValue(r, name, &ret))
       return ret;
-    }
   }
 
   return kNotFound;
 }
 
+scoped_ptr<StatsReport::Id> TypedIdFromIdString(StatsReport::StatsType type,
+                                                const std::string& value) {
+  EXPECT_FALSE(value.empty());
+  scoped_ptr<StatsReport::Id> id;
+  if (value.empty())
+    return id.Pass();
+
+  // This has assumptions about how the ID is constructed.  As is, this is
+  // OK since this is for testing purposes only, but if we ever need this
+  // in production, we should add a generic method that does this.
+  size_t index = value.find('_');
+  EXPECT_NE(index, std::string::npos);
+  if (index == std::string::npos || index == (value.length() - 1))
+    return id.Pass();
+
+  id = StatsReport::NewTypedId(type, value.substr(index + 1));
+  EXPECT_EQ(id->ToString(), value);
+  return id.Pass();
+}
+
+scoped_ptr<StatsReport::Id> IdFromCertIdString(const std::string& cert_id) {
+  return TypedIdFromIdString(StatsReport::kStatsReportTypeCertificate, cert_id)
+      .Pass();
+}
+
 // Finds the |n|-th report of type |type| in |reports|.
 // |n| starts from 1 for finding the first report.
 const StatsReport* FindNthReportByType(
-    const StatsReports& reports, const std::string& type, int n) {
+    const StatsReports& reports, const StatsReport::StatsType& type, int n) {
   for (size_t i = 0; i < reports.size(); ++i) {
-    if (reports[i]->type == type) {
+    if (reports[i]->type() == type) {
       n--;
       if (n == 0)
         return reports[i];
     }
   }
-  return NULL;
+  return nullptr;
 }
 
 const StatsReport* FindReportById(const StatsReports& reports,
-                                  const std::string& id) {
+                                  const StatsReport::Id& id) {
   for (const auto* r : reports) {
-    if (r->id().ToString() == id) {
+    if (r->id().Equals(id))
       return r;
-    }
   }
   return nullptr;
 }
 
 std::string ExtractSsrcStatsValue(StatsReports reports,
                                   StatsReport::StatsValueName name) {
-  return ExtractStatsValue(
-      StatsReport::kStatsReportTypeSsrc, reports, name);
+  return ExtractStatsValue(StatsReport::kStatsReportTypeSsrc, reports, name);
 }
 
 std::string ExtractBweStatsValue(StatsReports reports,
@@ -234,18 +252,18 @@
 
 void CheckCertChainReports(const StatsReports& reports,
                            const std::vector<std::string>& ders,
-                           const std::string& start_id) {
-  std::string certificate_id = start_id;
+                           const StatsReport::Id& start_id) {
+  scoped_ptr<StatsReport::Id> cert_id;
+  const StatsReport::Id* certificate_id = &start_id;
   size_t i = 0;
   while (true) {
-    const StatsReport* report = FindReportById(reports, certificate_id);
+    const StatsReport* report = FindReportById(reports, *certificate_id);
     ASSERT_TRUE(report != NULL);
 
     std::string der_base64;
     EXPECT_TRUE(GetValue(
         report, StatsReport::kStatsValueNameDer, &der_base64));
-    std::string der = rtc::Base64::Decode(der_base64,
-                                                rtc::Base64::DO_STRICT);
+    std::string der = rtc::Base64::Decode(der_base64, rtc::Base64::DO_STRICT);
     EXPECT_EQ(ders[i], der);
 
     std::string fingerprint_algorithm;
@@ -257,16 +275,20 @@
     std::string sha_1_str = rtc::DIGEST_SHA_1;
     EXPECT_EQ(sha_1_str, fingerprint_algorithm);
 
-    std::string dummy_fingerprint;  // Value is not checked.
-    EXPECT_TRUE(GetValue(
-        report,
-        StatsReport::kStatsValueNameFingerprint,
-        &dummy_fingerprint));
+    std::string fingerprint;
+    EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameFingerprint,
+                         &fingerprint));
+    EXPECT_FALSE(fingerprint.empty());
 
     ++i;
-    if (!GetValue(
-        report, StatsReport::kStatsValueNameIssuerId, &certificate_id))
+    std::string issuer_id;
+    if (!GetValue(report, StatsReport::kStatsValueNameIssuerId,
+                  &issuer_id)) {
       break;
+    }
+
+    cert_id = IdFromCertIdString(issuer_id).Pass();
+    certificate_id = cert_id.get();
   }
   EXPECT_EQ(ders.size(), i);
 }
@@ -510,8 +532,8 @@
 
   std::string AddCandidateReport(StatsCollector* collector,
                                  const cricket::Candidate& candidate,
-                                 const std::string& report_type) {
-    return collector->AddCandidateReport(candidate, report_type);
+                                 bool local) {
+    return collector->AddCandidateReport(candidate, local);
   }
 
   void SetupAndVerifyAudioTrackStats(
@@ -651,7 +673,8 @@
         StatsReport::kStatsValueNameLocalCertificateId);
     if (local_ders.size() > 0) {
       EXPECT_NE(kNotFound, local_certificate_id);
-      CheckCertChainReports(reports, local_ders, local_certificate_id);
+      scoped_ptr<StatsReport::Id> id(IdFromCertIdString(local_certificate_id));
+      CheckCertChainReports(reports, local_ders, *id.get());
     } else {
       EXPECT_EQ(kNotFound, local_certificate_id);
     }
@@ -663,7 +686,8 @@
         StatsReport::kStatsValueNameRemoteCertificateId);
     if (remote_ders.size() > 0) {
       EXPECT_NE(kNotFound, remote_certificate_id);
-      CheckCertChainReports(reports, remote_ders, remote_certificate_id);
+      scoped_ptr<StatsReport::Id> id(IdFromCertIdString(remote_certificate_id));
+      CheckCertChainReports(reports, remote_ders, *id.get());
     } else {
       EXPECT_EQ(kNotFound, remote_certificate_id);
     }
@@ -842,8 +866,7 @@
   StatsReports reports;
   stats.GetStats(NULL, &reports);
   EXPECT_EQ((size_t)1, reports.size());
-  EXPECT_EQ(std::string(StatsReport::kStatsReportTypeTrack),
-            reports[0]->type);
+  EXPECT_EQ(StatsReport::kStatsReportTypeTrack, reports[0]->type());
 
   std::string trackValue =
       ExtractStatsValue(StatsReport::kStatsReportTypeTrack,
@@ -953,8 +976,19 @@
       reports,
       StatsReport::kStatsValueNameTransportId);
   ASSERT_NE(kNotFound, transport_id);
-  const StatsReport* transport_report = FindReportById(reports,
-                                                       transport_id);
+  // Transport id component ID will always be 1.
+  // This has assumptions about how the ID is constructed.  As is, this is
+  // OK since this is for testing purposes only, but if we ever need this
+  // in production, we should add a generic method that does this.
+  size_t index = transport_id.find('-');
+  ASSERT_NE(std::string::npos, index);
+  std::string content = transport_id.substr(index + 1);
+  index = content.rfind('-');
+  ASSERT_NE(std::string::npos, index);
+  content = content.substr(0, index);
+  scoped_ptr<StatsReport::Id> id(StatsReport::NewComponentId(content, 1));
+  ASSERT_EQ(transport_id, id->ToString());
+  const StatsReport* transport_report = FindReportById(reports, *id.get());
   ASSERT_FALSE(transport_report == NULL);
 }
 
@@ -1101,8 +1135,7 @@
   c.set_address(local_address);
   c.set_priority(priority);
   c.set_network_type(network_type);
-  std::string report_id = AddCandidateReport(
-      &stats, c, StatsReport::kStatsReportTypeIceLocalCandidate);
+  std::string report_id = AddCandidateReport(&stats, c, true);
   EXPECT_EQ("Cand-" + c.id(), report_id);
 
   c = cricket::Candidate();
@@ -1112,8 +1145,7 @@
   c.set_address(remote_address);
   c.set_priority(priority);
   c.set_network_type(network_type);
-  report_id = AddCandidateReport(
-      &stats, c, StatsReport::kStatsReportTypeIceRemoteCandidate);
+  report_id = AddCandidateReport(&stats, c, false);
   EXPECT_EQ("Cand-" + c.id(), report_id);
 
   stats.GetStats(NULL, &reports);
diff --git a/talk/app/webrtc/statstypes.cc b/talk/app/webrtc/statstypes.cc
index 6ce2e45..a9f30e5 100644
--- a/talk/app/webrtc/statstypes.cc
+++ b/talk/app/webrtc/statstypes.cc
@@ -30,89 +30,179 @@
 using rtc::scoped_ptr;
 
 namespace webrtc {
+namespace {
 
-const char StatsReport::kStatsReportTypeSession[] = "googLibjingleSession";
-const char StatsReport::kStatsReportTypeBwe[] = "VideoBwe";
-const char StatsReport::kStatsReportTypeRemoteSsrc[] = "remoteSsrc";
-const char StatsReport::kStatsReportTypeSsrc[] = "ssrc";
-const char StatsReport::kStatsReportTypeTrack[] = "googTrack";
-const char StatsReport::kStatsReportTypeIceLocalCandidate[] = "localcandidate";
-const char StatsReport::kStatsReportTypeIceRemoteCandidate[] =
-    "remotecandidate";
-const char StatsReport::kStatsReportTypeTransport[] = "googTransport";
-const char StatsReport::kStatsReportTypeComponent[] = "googComponent";
-const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair";
-const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate";
-const char StatsReport::kStatsReportTypeDataChannel[] = "datachannel";
+// The id of StatsReport of type kStatsReportTypeBwe.
+const char kStatsReportVideoBweId[] = "bweforvideo";
 
-const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo";
-
-StatsReport::StatsReport(const StatsReport& src)
-  : id_(src.id_),
-    type(src.type),
-    timestamp_(src.timestamp_),
-    values_(src.values_) {
+// NOTE: These names need to be consistent with an external
+// specification (W3C Stats Identifiers).
+const char* InternalTypeToString(StatsReport::StatsType type) {
+  switch (type) {
+    case StatsReport::kStatsReportTypeSession:
+      return "googLibjingleSession";
+    case StatsReport::kStatsReportTypeBwe:
+      return "VideoBwe";
+    case StatsReport::kStatsReportTypeRemoteSsrc:
+      return "remoteSsrc";
+    case StatsReport::kStatsReportTypeSsrc:
+      return "ssrc";
+    case StatsReport::kStatsReportTypeTrack:
+      return "googTrack";
+    case StatsReport::kStatsReportTypeIceLocalCandidate:
+      return "localcandidate";
+    case StatsReport::kStatsReportTypeIceRemoteCandidate:
+      return "remotecandidate";
+    case StatsReport::kStatsReportTypeTransport:
+      return "googTransport";
+    case StatsReport::kStatsReportTypeComponent:
+      return "googComponent";
+    case StatsReport::kStatsReportTypeCandidatePair:
+      return "googCandidatePair";
+    case StatsReport::kStatsReportTypeCertificate:
+      return "googCertificate";
+    case StatsReport::kStatsReportTypeDataChannel:
+      return "datachannel";
+  }
+  ASSERT(false);
+  return nullptr;
 }
 
-StatsReport::StatsReport(const std::string& id)
-    : id_(id), timestamp_(0) {
-}
+class BandwidthEstimationId : public StatsReport::Id {
+ public:
+  BandwidthEstimationId() : StatsReport::Id(StatsReport::kStatsReportTypeBwe) {}
+  std::string ToString() const override { return kStatsReportVideoBweId; }
+};
 
-StatsReport::StatsReport(scoped_ptr<StatsReport::Id> id)
-    : id_(id->ToString()), timestamp_(0) {
-}
+class TypedId : public StatsReport::Id {
+ public:
+  static const char kSeparator = '_';
+  TypedId(StatsReport::StatsType type, const std::string& id)
+      : StatsReport::Id(type), id_(id) {}
 
-// static
-scoped_ptr<StatsReport::Id> StatsReport::NewTypedId(
-    StatsReport::StatsType type, const std::string& id) {
-  std::string internal_id(type);
-  internal_id += '_';
-  internal_id += id;
-  return scoped_ptr<Id>(new Id(internal_id)).Pass();
-}
+  bool Equals(const Id& other) const override {
+    return Id::Equals(other) &&
+           static_cast<const TypedId&>(other).id_ == id_;
+  }
 
-StatsReport& StatsReport::operator=(const StatsReport& src) {
-  ASSERT(id_ == src.id_);
-  type = src.type;
-  timestamp_ = src.timestamp_;
-  values_ = src.values_;
-  return *this;
-}
+  std::string ToString() const override {
+    return std::string(InternalTypeToString(type_)) + kSeparator + id_;
+  }
 
-// Operators provided for STL container/algorithm support.
-bool StatsReport::operator<(const StatsReport& other) const {
-  return id_ < other.id_;
-}
+ protected:
+  const std::string id_;
+};
 
-bool StatsReport::operator==(const StatsReport& other) const {
-  return id_ == other.id_;
-}
+class IdWithDirection : public TypedId {
+ public:
+  IdWithDirection(StatsReport::StatsType type, const std::string& id,
+                  StatsReport::Direction direction)
+      : TypedId(type, id), direction_(direction) {}
 
-// Special support for being able to use std::find on a container
-// without requiring a new StatsReport instance.
-bool StatsReport::operator==(const std::string& other_id) const {
-  return id_ == other_id;
-}
+  bool Equals(const Id& other) const override {
+    return TypedId::Equals(other) &&
+           static_cast<const IdWithDirection&>(other).direction_ == direction_;
+  }
 
-// The copy ctor can't be declared as explicit due to problems with STL.
-StatsReport::Value::Value(const Value& other)
-    : name(other.name), value(other.value) {
-}
+  std::string ToString() const override {
+    std::string ret(TypedId::ToString());
+    ret += '_';
+    ret += direction_ == StatsReport::kSend ? "send" : "recv";
+    return ret;
+  }
 
-StatsReport::Value::Value(StatsValueName name)
-    : name(name) {
+ private:
+  const StatsReport::Direction direction_;
+};
+
+class CandidateId : public TypedId {
+ public:
+  CandidateId(bool local, const std::string& id)
+      : TypedId(local ?
+                    StatsReport::kStatsReportTypeIceLocalCandidate :
+                    StatsReport::kStatsReportTypeIceRemoteCandidate,
+                id) {
+  }
+
+  std::string ToString() const override {
+    return "Cand-" + id_;
+  }
+};
+
+class ComponentId : public StatsReport::Id {
+ public:
+  ComponentId(const std::string& content_name, int component)
+      : ComponentId(StatsReport::kStatsReportTypeComponent, content_name,
+            component) {}
+
+  bool Equals(const Id& other) const override {
+    return Id::Equals(other) &&
+        static_cast<const ComponentId&>(other).component_ == component_ &&
+        static_cast<const ComponentId&>(other).content_name_ == content_name_;
+  }
+
+  std::string ToString() const override {
+    return ToString("Channel-");
+  }
+
+ protected:
+  ComponentId(StatsReport::StatsType type, const std::string& content_name,
+              int component)
+      : Id(type),
+        content_name_(content_name),
+        component_(component) {}
+
+  std::string ToString(const char* prefix) const {
+    std::string ret(prefix);
+    ret += content_name_;
+    ret += '-';
+    ret += rtc::ToString<>(component_);
+    return ret;
+  }
+
+ private:
+  const std::string content_name_;
+  const int component_;
+};
+
+class CandidatePairId : public ComponentId {
+ public:
+  CandidatePairId(const std::string& content_name, int component, int index)
+      : ComponentId(StatsReport::kStatsReportTypeCandidatePair, content_name,
+            component),
+        index_(index) {}
+
+  bool Equals(const Id& other) const override {
+    return ComponentId::Equals(other) &&
+        static_cast<const CandidatePairId&>(other).index_ == index_;
+  }
+
+  std::string ToString() const override {
+    std::string ret(ComponentId::ToString("Conn-"));
+    ret += '-';
+    ret += rtc::ToString<>(index_);
+    return ret;
+  }
+
+ private:
+  const int index_;
+};
+
+}  // namespace
+
+StatsReport::Id::Id(StatsType type) : type_(type) {}
+StatsReport::Id::~Id() {}
+
+StatsReport::StatsType StatsReport::Id::type() const { return type_; }
+
+bool StatsReport::Id::Equals(const Id& other) const {
+  return other.type_ == type_;
 }
 
 StatsReport::Value::Value(StatsValueName name, const std::string& value)
     : name(name), value(value) {
 }
 
-StatsReport::Value& StatsReport::Value::operator=(const Value& other) {
-  const_cast<StatsValueName&>(name) = other.name;
-  value = other.value;
-  return *this;
-}
-
 const char* StatsReport::Value::display_name() const {
   switch (name) {
     case kStatsValueNameAudioOutputLevel:
@@ -331,6 +421,50 @@
   return nullptr;
 }
 
+StatsReport::StatsReport(scoped_ptr<Id> id) : id_(id.Pass()), timestamp_(0.0) {
+  ASSERT(id_.get());
+}
+
+// static
+scoped_ptr<StatsReport::Id> StatsReport::NewBandwidthEstimationId() {
+  return scoped_ptr<Id>(new BandwidthEstimationId()).Pass();
+}
+
+// static
+scoped_ptr<StatsReport::Id> StatsReport::NewTypedId(
+    StatsType type, const std::string& id) {
+  return scoped_ptr<Id>(new TypedId(type, id)).Pass();
+}
+
+// static
+scoped_ptr<StatsReport::Id> StatsReport::NewIdWithDirection(
+    StatsType type, const std::string& id, StatsReport::Direction direction) {
+  return scoped_ptr<Id>(new IdWithDirection(type, id, direction)).Pass();
+}
+
+// static
+scoped_ptr<StatsReport::Id> StatsReport::NewCandidateId(
+    bool local, const std::string& id) {
+  return scoped_ptr<Id>(new CandidateId(local, id)).Pass();
+}
+
+// static
+scoped_ptr<StatsReport::Id> StatsReport::NewComponentId(
+    const std::string& content_name, int component) {
+  return scoped_ptr<Id>(new ComponentId(content_name, component)).Pass();
+}
+
+// static
+scoped_ptr<StatsReport::Id> StatsReport::NewCandidatePairId(
+    const std::string& content_name, int component, int index) {
+  return scoped_ptr<Id>(new CandidatePairId(content_name, component, index))
+      .Pass();
+}
+
+const char* StatsReport::TypeToString() const {
+  return InternalTypeToString(id_->type());
+}
+
 void StatsReport::AddValue(StatsReport::StatsValueName name,
                            const std::string& value) {
   values_.push_back(ValuePtr(new Value(name, value)));
@@ -340,6 +474,7 @@
   AddValue(name, rtc::ToString<int64>(value));
 }
 
+// TODO(tommi): Change the way we store vector values.
 template <typename T>
 void StatsReport::AddValue(StatsReport::StatsValueName name,
                            const std::vector<T>& value) {
@@ -370,6 +505,7 @@
     StatsReport::StatsValueName, const std::vector<int64_t>&);
 
 void StatsReport::AddBoolean(StatsReport::StatsValueName name, bool value) {
+  // TODO(tommi): Store bools as bool.
   AddValue(name, value ? "true" : "false");
 }
 
@@ -392,42 +528,67 @@
   values_.clear();
 }
 
-StatsSet::StatsSet() {
+const StatsReport::Value* StatsReport::FindValue(StatsValueName name) const {
+  Values::const_iterator it = std::find_if(values_.begin(), values_.end(),
+      [&name](const ValuePtr& v)->bool { return v->name == name; });
+  return it == values_.end() ? nullptr : (*it).get();
 }
 
-StatsSet::~StatsSet() {
+StatsCollection::StatsCollection() {
 }
 
-StatsSet::const_iterator StatsSet::begin() const {
+StatsCollection::~StatsCollection() {
+  for (auto* r : list_)
+    delete r;
+}
+
+StatsCollection::const_iterator StatsCollection::begin() const {
   return list_.begin();
 }
 
-StatsSet::const_iterator StatsSet::end() const {
+StatsCollection::const_iterator StatsCollection::end() const {
   return list_.end();
 }
 
-StatsReport* StatsSet::InsertNew(const std::string& id) {
-  ASSERT(Find(id) == NULL);
-  const StatsReport* ret = &(*list_.insert(StatsReportCopyable(id)).first);
-  return const_cast<StatsReport*>(ret);
+size_t StatsCollection::size() const {
+  return list_.size();
 }
 
-StatsReport* StatsSet::FindOrAddNew(const std::string& id) {
-  StatsReport* ret = Find(id);
-  return ret ? ret : InsertNew(id);
+StatsReport* StatsCollection::InsertNew(scoped_ptr<StatsReport::Id> id) {
+  ASSERT(Find(*id.get()) == NULL);
+  StatsReport* report = new StatsReport(id.Pass());
+  list_.push_back(report);
+  return report;
 }
 
-StatsReport* StatsSet::ReplaceOrAddNew(const std::string& id) {
-  list_.erase(id);
-  return InsertNew(id);
+StatsReport* StatsCollection::FindOrAddNew(scoped_ptr<StatsReport::Id> id) {
+  StatsReport* ret = Find(*id.get());
+  return ret ? ret : InsertNew(id.Pass());
+}
+
+StatsReport* StatsCollection::ReplaceOrAddNew(scoped_ptr<StatsReport::Id> id) {
+  ASSERT(id.get());
+  Container::iterator it = std::find_if(list_.begin(), list_.end(),
+      [&id](const StatsReport* r)->bool { return r->id().Equals(*id.get()); });
+  if (it != end()) {
+    delete *it;
+    StatsReport* report = new StatsReport(id.Pass());
+    *it = report;
+    return report;
+  }
+  return InsertNew(id.Pass());
 }
 
 // Looks for a report with the given |id|.  If one is not found, NULL
 // will be returned.
-StatsReport* StatsSet::Find(const std::string& id) {
-  const_iterator it = std::find(begin(), end(), id);
-  return it == end() ? NULL :
-      const_cast<StatsReport*>(static_cast<const StatsReport*>(&(*it)));
+StatsReport* StatsCollection::Find(const StatsReport::Id& id) {
+  Container::iterator it = std::find_if(list_.begin(), list_.end(),
+      [&id](const StatsReport* r)->bool { return r->id().Equals(id); });
+  return it == list_.end() ? nullptr : *it;
+}
+
+StatsReport* StatsCollection::Find(const scoped_ptr<StatsReport::Id>& id) {
+  return Find(*id.get());
 }
 
 }  // namespace webrtc
diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h
index 2bc0f33..8f132db 100644
--- a/talk/app/webrtc/statstypes.h
+++ b/talk/app/webrtc/statstypes.h
@@ -32,11 +32,13 @@
 #define TALK_APP_WEBRTC_STATSTYPES_H_
 
 #include <algorithm>
-#include <set>
+#include <list>
 #include <string>
 #include <vector>
 
 #include "webrtc/base/basictypes.h"
+#include "webrtc/base/linked_ptr.h"
+#include "webrtc/base/scoped_ptr.h"
 #include "webrtc/base/common.h"
 #include "webrtc/base/linked_ptr.h"
 #include "webrtc/base/scoped_ptr.h"
@@ -46,35 +48,71 @@
 
 class StatsReport {
  public:
-  // TODO(tommi): Remove this ctor after removing reliance upon it in Chromium
-  // (mock_peer_connection_impl.cc).
-  StatsReport() : timestamp_(0) {}
+  // Indicates whether a track is for sending or receiving.
+  // Used in reports for audio/video tracks.
+  enum Direction {
+    kSend = 0,
+    kReceive,
+  };
 
-  // TODO(tommi): Make protected and disallow copy completely once not needed.
-  StatsReport(const StatsReport& src);
+  enum StatsType {
+    // StatsReport types.
+    // A StatsReport of |type| = "googSession" contains overall information
+    // about the thing libjingle calls a session (which may contain one
+    // or more RTP sessions.
+    kStatsReportTypeSession,
 
-  // Constructor is protected to force use of StatsSet.
-  // TODO(tommi): Make this ctor protected.
-  explicit StatsReport(const std::string& id);
+    // A StatsReport of |type| = "googTransport" contains information
+    // about a libjingle "transport".
+    kStatsReportTypeTransport,
 
-  // TODO(tommi): Make this protected.
-  StatsReport& operator=(const StatsReport& src);
+    // A StatsReport of |type| = "googComponent" contains information
+    // about a libjingle "channel" (typically, RTP or RTCP for a transport).
+    // This is intended to be the same thing as an ICE "Component".
+    kStatsReportTypeComponent,
 
-  // Operators provided for STL container/algorithm support.
-  bool operator<(const StatsReport& other) const;
-  bool operator==(const StatsReport& other) const;
-  // Special support for being able to use std::find on a container
-  // without requiring a new StatsReport instance.
-  bool operator==(const std::string& other_id) const;
+    // A StatsReport of |type| = "googCandidatePair" contains information
+    // about a libjingle "connection" - a single source/destination port pair.
+    // This is intended to be the same thing as an ICE "candidate pair".
+    kStatsReportTypeCandidatePair,
 
-  // The unique identifier for this object.
-  // This is used as a key for this report in ordered containers,
-  // so it must never be changed.
-  // TODO(tommi): Make this member variable const.
-  std::string id_;  // See below for contents.
-  std::string type;  // See below for contents.
+    // A StatsReport of |type| = "VideoBWE" is statistics for video Bandwidth
+    // Estimation, which is global per-session.  The |id| field is "bweforvideo"
+    // (will probably change in the future).
+    kStatsReportTypeBwe,
 
-  // StatsValue names.
+    // A StatsReport of |type| = "ssrc" is statistics for a specific rtp stream.
+    // The |id| field is the SSRC in decimal form of the rtp stream.
+    kStatsReportTypeSsrc,
+
+    // A StatsReport of |type| = "remoteSsrc" is statistics for a specific
+    // rtp stream, generated by the remote end of the connection.
+    kStatsReportTypeRemoteSsrc,
+
+    // A StatsReport of |type| = "googTrack" is statistics for a specific media
+    // track. The |id| field is the track id.
+    kStatsReportTypeTrack,
+
+    // A StatsReport of |type| = "localcandidate" or "remotecandidate" is
+    // attributes on a specific ICE Candidate. It links to its connection pair
+    // by candidate id. The string value is taken from
+    // http://w3c.github.io/webrtc-stats/#rtcstatstype-enum*.
+    kStatsReportTypeIceLocalCandidate,
+    kStatsReportTypeIceRemoteCandidate,
+
+    // A StatsReport of |type| = "googCertificate" contains an SSL certificate
+    // transmitted by one of the endpoints of this connection.  The |id| is
+    // controlled by the fingerprint, and is used to identify the certificate in
+    // the Channel stats (as "googLocalCertificateId" or
+    // "googRemoteCertificateId") and in any child certificates (as
+    // "googIssuerId").
+    kStatsReportTypeCertificate,
+
+    // A StatsReport of |type| = "datachannel" with statistics for a
+    // particular DataChannel.
+    kStatsReportTypeDataChannel,
+  };
+
   enum StatsValueName {
     kStatsValueNameActiveConnection,
     kStatsValueNameAudioInputLevel,
@@ -183,40 +221,54 @@
 
   class Id {
    public:
-    Id(const std::string& id) : id_(id) {}
-    Id(const Id& id) : id_(id.id_) {}
-    const std::string& ToString() const { return id_; }
-   private:
-    const std::string id_;
+    virtual ~Id();
+    StatsType type() const;
+    virtual bool Equals(const Id& other) const;
+    virtual std::string ToString() const = 0;
+
+   protected:
+    Id(StatsType type);  // Only meant for derived classes.
+    const StatsType type_;
   };
 
   struct Value {
-    // The copy ctor can't be declared as explicit due to problems with STL.
-    Value(const Value& other);
-    explicit Value(StatsValueName name);
     Value(StatsValueName name, const std::string& value);
 
-    // TODO(tommi): Remove this operator once we don't need it.
-    // The operator is provided for compatibility with STL containers.
-    // The public |name| member variable is otherwise meant to be read-only.
-    Value& operator=(const Value& other);
-
     // Returns the string representation of |name|.
     const char* display_name() const;
 
     const StatsValueName name;
+    // TODO(tommi): Support more value types than string.
+    const std::string value;
 
-    std::string value;
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Value);
   };
 
   typedef rtc::linked_ptr<Value> ValuePtr;
   typedef std::vector<ValuePtr> Values;
-  typedef const char* StatsType;
 
   // Ownership of |id| is passed to |this|.
   explicit StatsReport(rtc::scoped_ptr<Id> id);
 
+  // Factory functions for various types of stats IDs.
+  static rtc::scoped_ptr<Id> NewBandwidthEstimationId();
   static rtc::scoped_ptr<Id> NewTypedId(StatsType type, const std::string& id);
+  static rtc::scoped_ptr<Id> NewIdWithDirection(
+      StatsType type, const std::string& id, Direction direction);
+  static rtc::scoped_ptr<Id> NewCandidateId(bool local, const std::string& id);
+  static rtc::scoped_ptr<Id> NewComponentId(
+      const std::string& content_name, int component);
+  static rtc::scoped_ptr<Id> NewCandidatePairId(
+      const std::string& content_name, int component, int index);
+
+  const Id& id() const { return *id_.get(); }
+  StatsType type() const { return id_->type(); }
+  double timestamp() const { return timestamp_; }
+  void set_timestamp(double t) { timestamp_ = t; }
+  const Values& values() const { return values_; }
+
+  const char* TypeToString() const;
 
   void AddValue(StatsValueName name, const std::string& value);
   void AddValue(StatsValueName name, int64 value);
@@ -228,122 +280,55 @@
 
   void ResetValues();
 
-  const Id id() const { return Id(id_); }
-  double timestamp() const { return timestamp_; }
-  void set_timestamp(double t) { timestamp_ = t; }
-  const Values& values() const { return values_; }
+  const Value* FindValue(StatsValueName name) const;
 
-  const char* TypeToString() const { return type.c_str(); }
-
+ private:
+  // The unique identifier for this object.
+  // This is used as a key for this report in ordered containers,
+  // so it must never be changed.
+  const rtc::scoped_ptr<Id> id_;
   double timestamp_;  // Time since 1970-01-01T00:00:00Z in milliseconds.
   Values values_;
 
-  // TODO(tommi): These should all be enum values.
-
-  // StatsReport types.
-  // A StatsReport of |type| = "googSession" contains overall information
-  // about the thing libjingle calls a session (which may contain one
-  // or more RTP sessions.
-  static const char kStatsReportTypeSession[];
-
-  // A StatsReport of |type| = "googTransport" contains information
-  // about a libjingle "transport".
-  static const char kStatsReportTypeTransport[];
-
-  // A StatsReport of |type| = "googComponent" contains information
-  // about a libjingle "channel" (typically, RTP or RTCP for a transport).
-  // This is intended to be the same thing as an ICE "Component".
-  static const char kStatsReportTypeComponent[];
-
-  // A StatsReport of |type| = "googCandidatePair" contains information
-  // about a libjingle "connection" - a single source/destination port pair.
-  // This is intended to be the same thing as an ICE "candidate pair".
-  static const char kStatsReportTypeCandidatePair[];
-
-  // StatsReport of |type| = "VideoBWE" is statistics for video Bandwidth
-  // Estimation, which is global per-session.  The |id| field is "bweforvideo"
-  // (will probably change in the future).
-  static const char kStatsReportTypeBwe[];
-
-  // StatsReport of |type| = "ssrc" is statistics for a specific rtp stream.
-  // The |id| field is the SSRC in decimal form of the rtp stream.
-  static const char kStatsReportTypeSsrc[];
-
-  // StatsReport of |type| = "remoteSsrc" is statistics for a specific
-  // rtp stream, generated by the remote end of the connection.
-  static const char kStatsReportTypeRemoteSsrc[];
-
-  // StatsReport of |type| = "googTrack" is statistics for a specific media
-  // track. The |id| field is the track id.
-  static const char kStatsReportTypeTrack[];
-
-  // StatsReport of |type| = "localcandidate" or "remotecandidate" is attributes
-  // on a specific ICE Candidate. It links to its connection pair by candidate
-  // id. The string value is taken from
-  // http://w3c.github.io/webrtc-stats/#rtcstatstype-enum*.
-  static const char kStatsReportTypeIceLocalCandidate[];
-  static const char kStatsReportTypeIceRemoteCandidate[];
-
-  // A StatsReport of |type| = "googCertificate" contains an SSL certificate
-  // transmitted by one of the endpoints of this connection.  The |id| is
-  // controlled by the fingerprint, and is used to identify the certificate in
-  // the Channel stats (as "googLocalCertificateId" or
-  // "googRemoteCertificateId") and in any child certificates (as
-  // "googIssuerId").
-  static const char kStatsReportTypeCertificate[];
-
-  // The id of StatsReport of type VideoBWE.
-  static const char kStatsReportVideoBweId[];
-
-  // A StatsReport of |type| = "datachannel" with statistics for a
-  // particular DataChannel.
-  static const char kStatsReportTypeDataChannel[];
-};
-
-// This class is provided for the cases where we need to keep
-// snapshots of reports around.  This is an edge case.
-// TODO(tommi): Move into the private section of StatsSet.
-class StatsReportCopyable : public StatsReport {
- public:
-  StatsReportCopyable(const std::string& id) : StatsReport(id) {}
-  explicit StatsReportCopyable(const StatsReport& src)
-      : StatsReport(src) {}
-
-  using StatsReport::operator=;
+  DISALLOW_COPY_AND_ASSIGN(StatsReport);
 };
 
 // Typedef for an array of const StatsReport pointers.
 // Ownership of the pointers held by this implementation is assumed to lie
 // elsewhere and lifetime guarantees are made by the implementation that uses
-// this type.  In the StatsCollector, object ownership lies with the StatsSet
-// class.
+// this type.  In the StatsCollector, object ownership lies with the
+// StatsCollection class.
 typedef std::vector<const StatsReport*> StatsReports;
 
 // A map from the report id to the report.
 // This class wraps an STL container and provides a limited set of
 // functionality in order to keep things simple.
 // TODO(tommi): Use a thread checker here (currently not in libjingle).
-class StatsSet {
+class StatsCollection {
  public:
-  StatsSet();
-  ~StatsSet();
+  StatsCollection();
+  ~StatsCollection();
 
-  typedef std::set<StatsReportCopyable> Container;
+  // TODO(tommi): shared_ptr (or linked_ptr)?
+  typedef std::list<StatsReport*> Container;
   typedef Container::iterator iterator;
   typedef Container::const_iterator const_iterator;
 
   const_iterator begin() const;
   const_iterator end() const;
+  size_t size() const;
 
   // Creates a new report object with |id| that does not already
   // exist in the list of reports.
-  StatsReport* InsertNew(const std::string& id);
-  StatsReport* FindOrAddNew(const std::string& id);
-  StatsReport* ReplaceOrAddNew(const std::string& id);
+  StatsReport* InsertNew(rtc::scoped_ptr<StatsReport::Id> id);
+  StatsReport* FindOrAddNew(rtc::scoped_ptr<StatsReport::Id> id);
+  StatsReport* ReplaceOrAddNew(rtc::scoped_ptr<StatsReport::Id> id);
 
   // Looks for a report with the given |id|.  If one is not found, NULL
   // will be returned.
-  StatsReport* Find(const std::string& id);
+  StatsReport* Find(const StatsReport::Id& id);
+  // TODO(tommi): we should only need one of these.
+  StatsReport* Find(const rtc::scoped_ptr<StatsReport::Id>& id);
 
  private:
   Container list_;
diff --git a/talk/app/webrtc/test/mockpeerconnectionobservers.h b/talk/app/webrtc/test/mockpeerconnectionobservers.h
index 226e439..606d485 100644
--- a/talk/app/webrtc/test/mockpeerconnectionobservers.h
+++ b/talk/app/webrtc/test/mockpeerconnectionobservers.h
@@ -126,7 +126,7 @@
     memset(&stats_, sizeof(stats_), 0);
     stats_.number_of_reports = reports.size();
     for (const auto* r : reports) {
-      if (r->type == StatsReport::kStatsReportTypeSsrc) {
+      if (r->type() == StatsReport::kStatsReportTypeSsrc) {
         GetIntValue(r, StatsReport::kStatsValueNameAudioOutputLevel,
             &stats_.audio_output_level);
         GetIntValue(r, StatsReport::kStatsValueNameAudioInputLevel,
@@ -135,7 +135,7 @@
             &stats_.bytes_received);
         GetIntValue(r, StatsReport::kStatsValueNameBytesSent,
             &stats_.bytes_sent);
-      } else if (r->type == StatsReport::kStatsReportTypeBwe) {
+      } else if (r->type() == StatsReport::kStatsReportTypeBwe) {
         GetIntValue(r, StatsReport::kStatsValueNameAvailableReceiveBandwidth,
             &stats_.available_receive_bandwidth);
       }