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
This commit is contained in:
tommi@webrtc.org 2015-01-21 11:36:18 +00:00
parent f66a6b2a00
commit 4fb7e25843
6 changed files with 580 additions and 469 deletions

View File

@ -35,6 +35,8 @@
#include "webrtc/base/scoped_ptr.h" #include "webrtc/base/scoped_ptr.h"
#include "webrtc/base/timing.h" #include "webrtc/base/timing.h"
using rtc::scoped_ptr;
namespace webrtc { namespace webrtc {
namespace { namespace {
@ -73,55 +75,26 @@ bool GetTransportIdFromProxy(const cricket::ProxyTransportMap& map,
return false; return false;
} }
std::ostringstream ost;
// Component 1 is always used for RTP. // Component 1 is always used for RTP.
ost << "Channel-" << found->second << "-1"; scoped_ptr<StatsReport::Id> id(
*transport = ost.str(); 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; return true;
} }
std::string StatsId(const std::string& type, const std::string& id) { void AddTrackReport(StatsCollection* reports, const std::string& track_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) {
// Adds an empty track report. // Adds an empty track report.
StatsReport* report = reports->ReplaceOrAddNew( rtc::scoped_ptr<StatsReport::Id> id(
StatsId(StatsReport::kStatsReportTypeTrack, track_id)); StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack, track_id));
report->type = StatsReport::kStatsReportTypeTrack; StatsReport* report = reports->ReplaceOrAddNew(id.Pass());
report->AddValue(StatsReport::kStatsValueNameTrackId, track_id); report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
} }
template <class TrackVector> 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) { for (size_t j = 0; j < tracks.size(); ++j) {
webrtc::MediaStreamTrackInterface* track = tracks[j]; webrtc::MediaStreamTrackInterface* track = tracks[j];
AddTrackReport(reports, track->id()); AddTrackReport(reports, track->id());
@ -284,7 +257,7 @@ void ExtractStats(const cricket::BandwidthEstimationInfo& info,
double stats_gathering_started, double stats_gathering_started,
PeerConnectionInterface::StatsOutputLevel level, PeerConnectionInterface::StatsOutputLevel level,
StatsReport* report) { StatsReport* report) {
report->type = StatsReport::kStatsReportTypeBwe; ASSERT(report->type() == StatsReport::kStatsReportTypeBwe);
// Clear out stats from previous GatherStats calls if any. // Clear out stats from previous GatherStats calls if any.
if (report->timestamp() != stats_gathering_started) { if (report->timestamp() != stats_gathering_started) {
@ -341,25 +314,20 @@ template<typename T>
void ExtractStatsFromList(const std::vector<T>& data, void ExtractStatsFromList(const std::vector<T>& data,
const std::string& transport_id, const std::string& transport_id,
StatsCollector* collector, StatsCollector* collector,
StatsCollector::TrackDirection direction) { StatsReport::Direction direction) {
typename std::vector<T>::const_iterator it = data.begin(); for (const auto& d : data) {
for (; it != data.end(); ++it) { uint32 ssrc = d.ssrc();
std::string id;
uint32 ssrc = it->ssrc();
// Each track can have stats for both local and remote objects. // Each track can have stats for both local and remote objects.
// TODO(hta): Handle the case of multiple SSRCs per object. // TODO(hta): Handle the case of multiple SSRCs per object.
StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id, StatsReport* report = collector->PrepareReport(true, ssrc, transport_id,
direction); direction);
if (report) if (report)
ExtractStats(*it, report); ExtractStats(d, report);
if (it->remote_stats.size() > 0) { if (!d.remote_stats.empty()) {
report = collector->PrepareRemoteReport(ssrc, transport_id, report = collector->PrepareReport(false, ssrc, transport_id, direction);
direction); if (report)
if (!report) { ExtractRemoteStats(d, report);
continue;
}
ExtractRemoteStats(*it, report);
} }
} }
} }
@ -436,10 +404,14 @@ void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track,
// Create the kStatsReportTypeTrack report for the new track if there is no // Create the kStatsReportTypeTrack report for the new track if there is no
// report yet. // report yet.
StatsReport* found = reports_.Find( rtc::scoped_ptr<StatsReport::Id> id(
StatsId(StatsReport::kStatsReportTypeTrack, audio_track->id())); StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack,
if (!found) audio_track->id()));
AddTrackReport(&reports_, 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, void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
@ -463,20 +435,19 @@ void StatsCollector::GetStats(MediaStreamTrackInterface* track,
ASSERT(reports->empty()); ASSERT(reports->empty());
if (!track) { if (!track) {
StatsSet::const_iterator it; reports->reserve(reports_.size());
for (it = reports_.begin(); it != reports_.end(); ++it) for (auto* r : reports_)
reports->push_back(&(*it)); reports->push_back(r);
return; return;
} }
StatsReport* report = StatsReport* report = reports_.Find(StatsReport::NewTypedId(
reports_.Find(StatsId(StatsReport::kStatsReportTypeSession, StatsReport::kStatsReportTypeSession, session_->id()));
session_->id()));
if (report) if (report)
reports->push_back(report); reports->push_back(report);
report = reports_.Find( report = reports_.Find(StatsReport::NewTypedId(
StatsId(StatsReport::kStatsReportTypeTrack, track->id())); StatsReport::kStatsReportTypeTrack, track->id()));
if (!report) if (!report)
return; return;
@ -484,18 +455,14 @@ void StatsCollector::GetStats(MediaStreamTrackInterface* track,
reports->push_back(report); reports->push_back(report);
std::string track_id; std::string track_id;
for (StatsSet::const_iterator it = reports_.begin(); it != reports_.end(); for (const auto* r : reports_) {
++it) { if (r->type() != StatsReport::kStatsReportTypeSsrc)
if (it->type != StatsReport::kStatsReportTypeSsrc)
continue; continue;
if (ExtractValueFromReport(*it, const StatsReport::Value* v =
StatsReport::kStatsValueNameTrackId, r->FindValue(StatsReport::kStatsValueNameTrackId);
&track_id)) { if (v && v->value == track->id())
if (track_id == track->id()) { reports->push_back(r);
reports->push_back(&(*it));
}
}
} }
} }
@ -520,14 +487,18 @@ StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
} }
} }
StatsReport* StatsCollector::PrepareLocalReport( StatsReport* StatsCollector::PrepareReport(
bool local,
uint32 ssrc, uint32 ssrc,
const std::string& transport_id, const std::string& transport_id,
TrackDirection direction) { StatsReport::Direction direction) {
ASSERT(session_->signaling_thread()->IsCurrent()); ASSERT(session_->signaling_thread()->IsCurrent());
const std::string ssrc_id = rtc::ToString<uint32>(ssrc); const std::string ssrc_id = rtc::ToString<uint32>(ssrc);
StatsReport* report = reports_.Find( rtc::scoped_ptr<StatsReport::Id> id(StatsReport::NewIdWithDirection(
StatsId(StatsReport::kStatsReportTypeSsrc, ssrc_id, direction)); 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. // Use the ID of the track that is currently mapped to the SSRC, if any.
std::string track_id; std::string track_id;
@ -540,20 +511,28 @@ StatsReport* StatsCollector::PrepareLocalReport(
// The ssrc is not used by any existing track. Keeps the old track id // The ssrc is not used by any existing track. Keeps the old track id
// since we want to report the stats for inactive ssrc. // since we want to report the stats for inactive ssrc.
ExtractValueFromReport(*report, const StatsReport::Value* v =
StatsReport::kStatsValueNameTrackId, report->FindValue(StatsReport::kStatsValueNameTrackId);
&track_id); if (v)
track_id = v->value;
} }
report = GetOrCreateReport( if (!report) {
StatsReport::kStatsReportTypeSsrc, ssrc_id, direction); 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. ASSERT(report->values().empty());
// This is required since the report will be returned for the new values. // FYI - for remote reports, the timestamp will be overwritten later.
// 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();
report->set_timestamp(stats_gathering_started_); report->set_timestamp(stats_gathering_started_);
report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id); report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
@ -564,49 +543,10 @@ StatsReport* StatsCollector::PrepareLocalReport(
return report; 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( std::string StatsCollector::AddOneCertificateReport(
const rtc::SSLCertificate* cert, const std::string& issuer_id) { 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 // 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 // values to reduce CPU use in GetStats. This will require adding a fast
// SSLCertificate::Equals() method to detect certificate changes. // SSLCertificate::Equals() method to detect certificate changes.
@ -633,9 +573,10 @@ std::string StatsCollector::AddOneCertificateReport(
rtc::Base64::EncodeFromArray( rtc::Base64::EncodeFromArray(
der_buffer.data(), der_buffer.length(), &der_base64); der_buffer.data(), der_buffer.length(), &der_base64);
StatsReport* report = reports_.ReplaceOrAddNew( rtc::scoped_ptr<StatsReport::Id> id(
StatsId(StatsReport::kStatsReportTypeCertificate, fingerprint)); StatsReport::NewTypedId(
report->type = StatsReport::kStatsReportTypeCertificate; StatsReport::kStatsReportTypeCertificate, fingerprint));
StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
report->set_timestamp(stats_gathering_started_); report->set_timestamp(stats_gathering_started_);
report->AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint); report->AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint);
report->AddValue(StatsReport::kStatsValueNameFingerprintAlgorithm, report->AddValue(StatsReport::kStatsValueNameFingerprintAlgorithm,
@ -643,11 +584,13 @@ std::string StatsCollector::AddOneCertificateReport(
report->AddValue(StatsReport::kStatsValueNameDer, der_base64); report->AddValue(StatsReport::kStatsValueNameDer, der_base64);
if (!issuer_id.empty()) if (!issuer_id.empty())
report->AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id); report->AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id);
// TODO(tommi): Can we avoid this?
return report->id().ToString(); return report->id().ToString();
} }
std::string StatsCollector::AddCertificateReports( std::string StatsCollector::AddCertificateReports(
const rtc::SSLCertificate* cert) { const rtc::SSLCertificate* cert) {
ASSERT(session_->signaling_thread()->IsCurrent());
// Produces a chain of StatsReports representing this certificate and the rest // Produces a chain of StatsReports representing this certificate and the rest
// of its chain, and adds those reports to |reports_|. The return value is // 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 // the id of the leaf report. The provided cert must be non-null, so at least
@ -673,20 +616,17 @@ std::string StatsCollector::AddCertificateReports(
std::string StatsCollector::AddCandidateReport( std::string StatsCollector::AddCandidateReport(
const cricket::Candidate& candidate, const cricket::Candidate& candidate,
const std::string& report_type) { bool local) {
std::ostringstream ost; scoped_ptr<StatsReport::Id> id(
ost << "Cand-" << candidate.id(); StatsReport::NewCandidateId(local, candidate.id()));
StatsReport* report = reports_.Find(ost.str()); StatsReport* report = reports_.Find(*id.get());
if (!report) { if (!report) {
report = reports_.InsertNew(ost.str()); report = reports_.InsertNew(id.Pass());
DCHECK(StatsReport::kStatsReportTypeIceLocalCandidate == report_type || report->set_timestamp(stats_gathering_started_);
StatsReport::kStatsReportTypeIceRemoteCandidate == report_type); if (local) {
report->type = report_type;
if (report_type == StatsReport::kStatsReportTypeIceLocalCandidate) {
report->AddValue(StatsReport::kStatsValueNameCandidateNetworkType, report->AddValue(StatsReport::kStatsValueNameCandidateNetworkType,
AdapterTypeToStatsType(candidate.network_type())); AdapterTypeToStatsType(candidate.network_type()));
} }
report->set_timestamp(stats_gathering_started_);
report->AddValue(StatsReport::kStatsValueNameCandidateIPAddress, report->AddValue(StatsReport::kStatsValueNameCandidateIPAddress,
candidate.address().ipaddr().ToString()); candidate.address().ipaddr().ToString());
report->AddValue(StatsReport::kStatsValueNameCandidatePortNumber, report->AddValue(StatsReport::kStatsValueNameCandidatePortNumber,
@ -699,15 +639,17 @@ std::string StatsCollector::AddCandidateReport(
candidate.protocol()); candidate.protocol());
} }
return ost.str(); // TODO(tommi): Necessary?
return report->id().ToString();
} }
void StatsCollector::ExtractSessionInfo() { void StatsCollector::ExtractSessionInfo() {
ASSERT(session_->signaling_thread()->IsCurrent()); ASSERT(session_->signaling_thread()->IsCurrent());
// Extract information from the base session. // Extract information from the base session.
StatsReport* report = reports_.ReplaceOrAddNew( rtc::scoped_ptr<StatsReport::Id> id(
StatsId(StatsReport::kStatsReportTypeSession, session_->id())); StatsReport::NewTypedId(
report->type = StatsReport::kStatsReportTypeSession; StatsReport::kStatsReportTypeSession, session_->id()));
StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
report->set_timestamp(stats_gathering_started_); report->set_timestamp(stats_gathering_started_);
report->ResetValues(); report->ResetValues();
report->AddBoolean(StatsReport::kStatsValueNameInitiator, report->AddBoolean(StatsReport::kStatsValueNameInitiator,
@ -749,11 +691,10 @@ void StatsCollector::ExtractSessionInfo() {
= transport_iter->second.channel_stats.begin(); = transport_iter->second.channel_stats.begin();
channel_iter != transport_iter->second.channel_stats.end(); channel_iter != transport_iter->second.channel_stats.end();
++channel_iter) { ++channel_iter) {
std::ostringstream ostc; rtc::scoped_ptr<StatsReport::Id> id(
ostc << "Channel-" << transport_iter->second.content_name StatsReport::NewComponentId(transport_iter->second.content_name,
<< "-" << channel_iter->component; channel_iter->component));
StatsReport* channel_report = reports_.ReplaceOrAddNew(ostc.str()); StatsReport* channel_report = reports_.ReplaceOrAddNew(id.Pass());
channel_report->type = StatsReport::kStatsReportTypeComponent;
channel_report->set_timestamp(stats_gathering_started_); channel_report->set_timestamp(stats_gathering_started_);
channel_report->AddValue(StatsReport::kStatsValueNameComponent, channel_report->AddValue(StatsReport::kStatsValueNameComponent,
channel_iter->component); channel_iter->component);
@ -770,13 +711,13 @@ void StatsCollector::ExtractSessionInfo() {
for (size_t i = 0; for (size_t i = 0;
i < channel_iter->connection_infos.size(); i < channel_iter->connection_infos.size();
++i) { ++i) {
std::ostringstream ost; rtc::scoped_ptr<StatsReport::Id> id(
ost << "Conn-" << transport_iter->first << "-" StatsReport::NewCandidatePairId(transport_iter->first,
<< channel_iter->component << "-" << i; channel_iter->component, static_cast<int>(i)));
StatsReport* report = reports_.ReplaceOrAddNew(ost.str()); StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
report->type = StatsReport::kStatsReportTypeCandidatePair;
report->set_timestamp(stats_gathering_started_); report->set_timestamp(stats_gathering_started_);
// Link from connection to its containing channel. // Link from connection to its containing channel.
// TODO(tommi): Any way to avoid ToString here?
report->AddValue(StatsReport::kStatsValueNameChannelId, report->AddValue(StatsReport::kStatsValueNameChannelId,
channel_report->id().ToString()); channel_report->id().ToString());
@ -797,14 +738,10 @@ void StatsCollector::ExtractSessionInfo() {
report->AddBoolean(StatsReport::kStatsValueNameActiveConnection, report->AddBoolean(StatsReport::kStatsValueNameActiveConnection,
info.best_connection); info.best_connection);
report->AddValue(StatsReport::kStatsValueNameLocalCandidateId, report->AddValue(StatsReport::kStatsValueNameLocalCandidateId,
AddCandidateReport( AddCandidateReport(info.local_candidate, true));
info.local_candidate,
StatsReport::kStatsReportTypeIceLocalCandidate));
report->AddValue( report->AddValue(
StatsReport::kStatsValueNameRemoteCandidateId, StatsReport::kStatsValueNameRemoteCandidateId,
AddCandidateReport( AddCandidateReport(info.remote_candidate, false));
info.remote_candidate,
StatsReport::kStatsReportTypeIceRemoteCandidate));
report->AddValue(StatsReport::kStatsValueNameLocalAddress, report->AddValue(StatsReport::kStatsValueNameLocalAddress,
info.local_candidate.address().ToString()); info.local_candidate.address().ToString());
report->AddValue(StatsReport::kStatsValueNameRemoteAddress, report->AddValue(StatsReport::kStatsValueNameRemoteAddress,
@ -841,8 +778,10 @@ void StatsCollector::ExtractVoiceInfo() {
<< session_->voice_channel()->content_name(); << session_->voice_channel()->content_name();
return; return;
} }
ExtractStatsFromList(voice_info.receivers, transport_id, this, kReceiving); ExtractStatsFromList(voice_info.receivers, transport_id, this,
ExtractStatsFromList(voice_info.senders, transport_id, this, kSending); StatsReport::kReceive);
ExtractStatsFromList(voice_info.senders, transport_id, this,
StatsReport::kSend);
UpdateStatsFromExistingLocalAudioTracks(); UpdateStatsFromExistingLocalAudioTracks();
} }
@ -871,13 +810,16 @@ void StatsCollector::ExtractVideoInfo(
<< session_->video_channel()->content_name(); << session_->video_channel()->content_name();
return; return;
} }
ExtractStatsFromList(video_info.receivers, transport_id, this, kReceiving); ExtractStatsFromList(video_info.receivers, transport_id, this,
ExtractStatsFromList(video_info.senders, transport_id, this, kSending); StatsReport::kReceive);
ExtractStatsFromList(video_info.senders, transport_id, this,
StatsReport::kSend);
if (video_info.bw_estimations.size() != 1) { if (video_info.bw_estimations.size() != 1) {
LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size(); LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size();
} else { } else {
StatsReport* report = rtc::scoped_ptr<StatsReport::Id> report_id(
reports_.FindOrAddNew(StatsReport::kStatsReportVideoBweId); StatsReport::NewBandwidthEstimationId());
StatsReport* report = reports_.FindOrAddNew(report_id.Pass());
ExtractStats( ExtractStats(
video_info.bw_estimations[0], stats_gathering_started_, level, report); video_info.bw_estimations[0], stats_gathering_started_, level, report);
} }
@ -888,9 +830,10 @@ void StatsCollector::ExtractDataInfo() {
for (const auto& dc : for (const auto& dc :
session_->mediastream_signaling()->sctp_data_channels()) { session_->mediastream_signaling()->sctp_data_channels()) {
StatsReport* report = reports_.ReplaceOrAddNew( rtc::scoped_ptr<StatsReport::Id> id(
StatsId(StatsReport::kStatsReportTypeDataChannel, dc->label())); StatsReport::NewTypedId(StatsReport::kStatsReportTypeDataChannel,
report->type = StatsReport::kStatsReportTypeDataChannel; dc->label()));
StatsReport* report = reports_.ReplaceOrAddNew(id.Pass());
report->AddValue(StatsReport::kStatsValueNameLabel, dc->label()); report->AddValue(StatsReport::kStatsValueNameLabel, dc->label());
report->AddValue(StatsReport::kStatsValueNameDataChannelId, dc->id()); report->AddValue(StatsReport::kStatsValueNameDataChannelId, dc->id());
report->AddValue(StatsReport::kStatsValueNameProtocol, dc->protocol()); report->AddValue(StatsReport::kStatsValueNameProtocol, dc->protocol());
@ -899,26 +842,27 @@ void StatsCollector::ExtractDataInfo() {
} }
} }
StatsReport* StatsCollector::GetReport(const std::string& type, StatsReport* StatsCollector::GetReport(const StatsReport::StatsType& type,
const std::string& id, const std::string& id,
TrackDirection direction) { StatsReport::Direction direction) {
ASSERT(session_->signaling_thread()->IsCurrent()); ASSERT(session_->signaling_thread()->IsCurrent());
ASSERT(type == StatsReport::kStatsReportTypeSsrc || ASSERT(type == StatsReport::kStatsReportTypeSsrc ||
type == StatsReport::kStatsReportTypeRemoteSsrc); 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, StatsReport* StatsCollector::GetOrCreateReport(
const std::string& id, const StatsReport::StatsType& type,
TrackDirection direction) { const std::string& id,
StatsReport::Direction direction) {
ASSERT(session_->signaling_thread()->IsCurrent()); ASSERT(session_->signaling_thread()->IsCurrent());
ASSERT(type == StatsReport::kStatsReportTypeSsrc || ASSERT(type == StatsReport::kStatsReportTypeSsrc ||
type == StatsReport::kStatsReportTypeRemoteSsrc); type == StatsReport::kStatsReportTypeRemoteSsrc);
StatsReport* report = GetReport(type, id, direction); StatsReport* report = GetReport(type, id, direction);
if (report == NULL) { if (report == NULL) {
std::string statsid = StatsId(type, id, direction); rtc::scoped_ptr<StatsReport::Id> report_id(
report = reports_.FindOrAddNew(statsid); StatsReport::NewIdWithDirection(type, id, direction));
report->type = type; report = reports_.InsertNew(report_id.Pass());
} }
return report; return report;
@ -934,7 +878,7 @@ void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() {
std::string ssrc_id = rtc::ToString<uint32>(ssrc); std::string ssrc_id = rtc::ToString<uint32>(ssrc);
StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc, StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc,
ssrc_id, ssrc_id,
kSending); StatsReport::kSend);
if (report == NULL) { if (report == NULL) {
// This can happen if a local audio track is added to a stream on the // 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. // fly and the report has not been set up yet. Do nothing in this case.
@ -943,13 +887,10 @@ void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() {
} }
// The same ssrc can be used by both local and remote audio tracks. // The same ssrc can be used by both local and remote audio tracks.
std::string track_id; const StatsReport::Value* v =
if (!ExtractValueFromReport(*report, report->FindValue(StatsReport::kStatsValueNameTrackId);
StatsReport::kStatsValueNameTrackId, if (!v || v->value != track->id())
&track_id) ||
track_id != track->id()) {
continue; continue;
}
UpdateReportFromAudioTrack(track, report); UpdateReportFromAudioTrack(track, report);
} }
@ -991,16 +932,16 @@ void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track,
} }
bool StatsCollector::GetTrackIdBySsrc(uint32 ssrc, std::string* track_id, bool StatsCollector::GetTrackIdBySsrc(uint32 ssrc, std::string* track_id,
TrackDirection direction) { StatsReport::Direction direction) {
ASSERT(session_->signaling_thread()->IsCurrent()); ASSERT(session_->signaling_thread()->IsCurrent());
if (direction == kSending) { if (direction == StatsReport::kSend) {
if (!session_->GetLocalTrackIdBySsrc(ssrc, track_id)) { if (!session_->GetLocalTrackIdBySsrc(ssrc, track_id)) {
LOG(LS_WARNING) << "The SSRC " << ssrc LOG(LS_WARNING) << "The SSRC " << ssrc
<< " is not associated with a sending track"; << " is not associated with a sending track";
return false; return false;
} }
} else { } else {
ASSERT(direction == kReceiving); ASSERT(direction == StatsReport::kReceive);
if (!session_->GetRemoteTrackIdBySsrc(ssrc, track_id)) { if (!session_->GetRemoteTrackIdBySsrc(ssrc, track_id)) {
LOG(LS_WARNING) << "The SSRC " << ssrc LOG(LS_WARNING) << "The SSRC " << ssrc
<< " is not associated with a receiving track"; << " is not associated with a receiving track";

View File

@ -54,14 +54,9 @@ const char* AdapterTypeToStatsType(rtc::AdapterType type);
class StatsCollector { class StatsCollector {
public: public:
enum TrackDirection {
kSending = 0,
kReceiving,
};
// The caller is responsible for ensuring that the session outlives the // The caller is responsible for ensuring that the session outlives the
// StatsCollector instance. // StatsCollector instance.
StatsCollector(WebRtcSession* session); explicit StatsCollector(WebRtcSession* session);
virtual ~StatsCollector(); virtual ~StatsCollector();
// Adds a MediaStream with tracks that can be used as a |selector| in a call // Adds a MediaStream with tracks that can be used as a |selector| in a call
@ -89,13 +84,10 @@ class StatsCollector {
void GetStats(MediaStreamTrackInterface* track, void GetStats(MediaStreamTrackInterface* track,
StatsReports* reports); 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. // in the ExtractStatsFromList template.
StatsReport* PrepareLocalReport(uint32 ssrc, const std::string& transport, StatsReport* PrepareReport(bool local, uint32 ssrc,
TrackDirection direction); const std::string& transport_id, StatsReport::Direction direction);
// Prepare an SSRC report for the given remote ssrc. Used internally.
StatsReport* PrepareRemoteReport(uint32 ssrc, const std::string& transport,
TrackDirection direction);
// Method used by the unittest to force a update of stats since UpdateStats() // Method used by the unittest to force a update of stats since UpdateStats()
// that occur less than kMinGatherStatsPeriod number of ms apart will be // that occur less than kMinGatherStatsPeriod number of ms apart will be
@ -114,7 +106,7 @@ class StatsCollector {
// Helper method for creating IceCandidate report. |is_local| indicates // Helper method for creating IceCandidate report. |is_local| indicates
// whether this candidate is local or remote. // whether this candidate is local or remote.
std::string AddCandidateReport(const cricket::Candidate& candidate, 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 // Adds a report for this certificate and every certificate in its chain, and
// returns the leaf certificate's report's ID. // returns the leaf certificate's report's ID.
@ -125,12 +117,12 @@ class StatsCollector {
void ExtractVoiceInfo(); void ExtractVoiceInfo();
void ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level); void ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level);
void BuildSsrcToTransportId(); void BuildSsrcToTransportId();
webrtc::StatsReport* GetOrCreateReport(const std::string& type, webrtc::StatsReport* GetOrCreateReport(const StatsReport::StatsType& type,
const std::string& id, const std::string& id,
TrackDirection direction); StatsReport::Direction direction);
webrtc::StatsReport* GetReport(const std::string& type, webrtc::StatsReport* GetReport(const StatsReport::StatsType& type,
const std::string& id, const std::string& id,
TrackDirection direction); StatsReport::Direction direction);
// Helper method to get stats from the local audio tracks. // Helper method to get stats from the local audio tracks.
void UpdateStatsFromExistingLocalAudioTracks(); void UpdateStatsFromExistingLocalAudioTracks();
@ -140,10 +132,10 @@ class StatsCollector {
// Helper method to get the id for the track identified by ssrc. // Helper method to get the id for the track identified by ssrc.
// |direction| tells if the track is for sending or receiving. // |direction| tells if the track is for sending or receiving.
bool GetTrackIdBySsrc(uint32 ssrc, std::string* track_id, bool GetTrackIdBySsrc(uint32 ssrc, std::string* track_id,
TrackDirection direction); StatsReport::Direction direction);
// A map from the report id to the report. // A collection for all of our stats reports.
StatsSet reports_; StatsCollection reports_;
// Raw pointer to the session the statistics are gathered from. // Raw pointer to the session the statistics are gathered from.
WebRtcSession* const session_; WebRtcSession* const session_;
double stats_gathering_started_; double stats_gathering_started_;

View File

@ -48,6 +48,7 @@
#include "webrtc/p2p/base/fakesession.h" #include "webrtc/p2p/base/fakesession.h"
using cricket::StatsOptions; using cricket::StatsOptions;
using rtc::scoped_ptr;
using testing::_; using testing::_;
using testing::DoAll; using testing::DoAll;
using testing::Field; using testing::Field;
@ -69,7 +70,6 @@ namespace webrtc {
// Error return values // Error return values
const char kNotFound[] = "NOT FOUND"; const char kNotFound[] = "NOT FOUND";
const char kNoReports[] = "NO REPORTS";
// Constant names for track identification. // Constant names for track identification.
const char kLocalTrackId[] = "local_track_id"; const char kLocalTrackId[] = "local_track_id";
@ -152,6 +152,7 @@ class FakeAudioTrack
rtc::scoped_refptr<FakeAudioProcessor> processor_; rtc::scoped_refptr<FakeAudioProcessor> processor_;
}; };
// TODO(tommi): Use FindValue().
bool GetValue(const StatsReport* report, bool GetValue(const StatsReport* report,
StatsReport::StatsValueName name, StatsReport::StatsValueName name,
std::string* value) { std::string* value) {
@ -164,52 +165,69 @@ bool GetValue(const StatsReport* report,
return false; return false;
} }
std::string ExtractStatsValue(const std::string& type, std::string ExtractStatsValue(const StatsReport::StatsType& type,
const StatsReports& reports, const StatsReports& reports,
StatsReport::StatsValueName name) { StatsReport::StatsValueName name) {
if (reports.empty()) { for (const auto* r : reports) {
return kNoReports;
}
for (size_t i = 0; i < reports.size(); ++i) {
if (reports[i]->type != type)
continue;
std::string ret; std::string ret;
if (GetValue(reports[i], name, &ret)) { if (r->type() == type && GetValue(r, name, &ret))
return ret; return ret;
}
} }
return kNotFound; 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|. // Finds the |n|-th report of type |type| in |reports|.
// |n| starts from 1 for finding the first report. // |n| starts from 1 for finding the first report.
const StatsReport* FindNthReportByType( 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) { for (size_t i = 0; i < reports.size(); ++i) {
if (reports[i]->type == type) { if (reports[i]->type() == type) {
n--; n--;
if (n == 0) if (n == 0)
return reports[i]; return reports[i];
} }
} }
return NULL; return nullptr;
} }
const StatsReport* FindReportById(const StatsReports& reports, const StatsReport* FindReportById(const StatsReports& reports,
const std::string& id) { const StatsReport::Id& id) {
for (const auto* r : reports) { for (const auto* r : reports) {
if (r->id().ToString() == id) { if (r->id().Equals(id))
return r; return r;
}
} }
return nullptr; return nullptr;
} }
std::string ExtractSsrcStatsValue(StatsReports reports, std::string ExtractSsrcStatsValue(StatsReports reports,
StatsReport::StatsValueName name) { StatsReport::StatsValueName name) {
return ExtractStatsValue( return ExtractStatsValue(StatsReport::kStatsReportTypeSsrc, reports, name);
StatsReport::kStatsReportTypeSsrc, reports, name);
} }
std::string ExtractBweStatsValue(StatsReports reports, std::string ExtractBweStatsValue(StatsReports reports,
@ -234,18 +252,18 @@ std::vector<std::string> DersToPems(
void CheckCertChainReports(const StatsReports& reports, void CheckCertChainReports(const StatsReports& reports,
const std::vector<std::string>& ders, const std::vector<std::string>& ders,
const std::string& start_id) { const StatsReport::Id& start_id) {
std::string certificate_id = start_id; scoped_ptr<StatsReport::Id> cert_id;
const StatsReport::Id* certificate_id = &start_id;
size_t i = 0; size_t i = 0;
while (true) { while (true) {
const StatsReport* report = FindReportById(reports, certificate_id); const StatsReport* report = FindReportById(reports, *certificate_id);
ASSERT_TRUE(report != NULL); ASSERT_TRUE(report != NULL);
std::string der_base64; std::string der_base64;
EXPECT_TRUE(GetValue( EXPECT_TRUE(GetValue(
report, StatsReport::kStatsValueNameDer, &der_base64)); report, StatsReport::kStatsValueNameDer, &der_base64));
std::string der = rtc::Base64::Decode(der_base64, std::string der = rtc::Base64::Decode(der_base64, rtc::Base64::DO_STRICT);
rtc::Base64::DO_STRICT);
EXPECT_EQ(ders[i], der); EXPECT_EQ(ders[i], der);
std::string fingerprint_algorithm; std::string fingerprint_algorithm;
@ -257,16 +275,20 @@ void CheckCertChainReports(const StatsReports& reports,
std::string sha_1_str = rtc::DIGEST_SHA_1; std::string sha_1_str = rtc::DIGEST_SHA_1;
EXPECT_EQ(sha_1_str, fingerprint_algorithm); EXPECT_EQ(sha_1_str, fingerprint_algorithm);
std::string dummy_fingerprint; // Value is not checked. std::string fingerprint;
EXPECT_TRUE(GetValue( EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameFingerprint,
report, &fingerprint));
StatsReport::kStatsValueNameFingerprint, EXPECT_FALSE(fingerprint.empty());
&dummy_fingerprint));
++i; ++i;
if (!GetValue( std::string issuer_id;
report, StatsReport::kStatsValueNameIssuerId, &certificate_id)) if (!GetValue(report, StatsReport::kStatsValueNameIssuerId,
&issuer_id)) {
break; break;
}
cert_id = IdFromCertIdString(issuer_id).Pass();
certificate_id = cert_id.get();
} }
EXPECT_EQ(ders.size(), i); EXPECT_EQ(ders.size(), i);
} }
@ -510,8 +532,8 @@ class StatsCollectorTest : public testing::Test {
std::string AddCandidateReport(StatsCollector* collector, std::string AddCandidateReport(StatsCollector* collector,
const cricket::Candidate& candidate, const cricket::Candidate& candidate,
const std::string& report_type) { bool local) {
return collector->AddCandidateReport(candidate, report_type); return collector->AddCandidateReport(candidate, local);
} }
void SetupAndVerifyAudioTrackStats( void SetupAndVerifyAudioTrackStats(
@ -651,7 +673,8 @@ class StatsCollectorTest : public testing::Test {
StatsReport::kStatsValueNameLocalCertificateId); StatsReport::kStatsValueNameLocalCertificateId);
if (local_ders.size() > 0) { if (local_ders.size() > 0) {
EXPECT_NE(kNotFound, local_certificate_id); 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 { } else {
EXPECT_EQ(kNotFound, local_certificate_id); EXPECT_EQ(kNotFound, local_certificate_id);
} }
@ -663,7 +686,8 @@ class StatsCollectorTest : public testing::Test {
StatsReport::kStatsValueNameRemoteCertificateId); StatsReport::kStatsValueNameRemoteCertificateId);
if (remote_ders.size() > 0) { if (remote_ders.size() > 0) {
EXPECT_NE(kNotFound, remote_certificate_id); 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 { } else {
EXPECT_EQ(kNotFound, remote_certificate_id); EXPECT_EQ(kNotFound, remote_certificate_id);
} }
@ -842,8 +866,7 @@ TEST_F(StatsCollectorTest, TrackObjectExistsWithoutUpdateStats) {
StatsReports reports; StatsReports reports;
stats.GetStats(NULL, &reports); stats.GetStats(NULL, &reports);
EXPECT_EQ((size_t)1, reports.size()); EXPECT_EQ((size_t)1, reports.size());
EXPECT_EQ(std::string(StatsReport::kStatsReportTypeTrack), EXPECT_EQ(StatsReport::kStatsReportTypeTrack, reports[0]->type());
reports[0]->type);
std::string trackValue = std::string trackValue =
ExtractStatsValue(StatsReport::kStatsReportTypeTrack, ExtractStatsValue(StatsReport::kStatsReportTypeTrack,
@ -953,8 +976,19 @@ TEST_F(StatsCollectorTest, TransportObjectLinkedFromSsrcObject) {
reports, reports,
StatsReport::kStatsValueNameTransportId); StatsReport::kStatsValueNameTransportId);
ASSERT_NE(kNotFound, transport_id); ASSERT_NE(kNotFound, transport_id);
const StatsReport* transport_report = FindReportById(reports, // Transport id component ID will always be 1.
transport_id); // 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); ASSERT_FALSE(transport_report == NULL);
} }
@ -1101,8 +1135,7 @@ TEST_F(StatsCollectorTest, IceCandidateReport) {
c.set_address(local_address); c.set_address(local_address);
c.set_priority(priority); c.set_priority(priority);
c.set_network_type(network_type); c.set_network_type(network_type);
std::string report_id = AddCandidateReport( std::string report_id = AddCandidateReport(&stats, c, true);
&stats, c, StatsReport::kStatsReportTypeIceLocalCandidate);
EXPECT_EQ("Cand-" + c.id(), report_id); EXPECT_EQ("Cand-" + c.id(), report_id);
c = cricket::Candidate(); c = cricket::Candidate();
@ -1112,8 +1145,7 @@ TEST_F(StatsCollectorTest, IceCandidateReport) {
c.set_address(remote_address); c.set_address(remote_address);
c.set_priority(priority); c.set_priority(priority);
c.set_network_type(network_type); c.set_network_type(network_type);
report_id = AddCandidateReport( report_id = AddCandidateReport(&stats, c, false);
&stats, c, StatsReport::kStatsReportTypeIceRemoteCandidate);
EXPECT_EQ("Cand-" + c.id(), report_id); EXPECT_EQ("Cand-" + c.id(), report_id);
stats.GetStats(NULL, &reports); stats.GetStats(NULL, &reports);

View File

@ -30,89 +30,179 @@
using rtc::scoped_ptr; using rtc::scoped_ptr;
namespace webrtc { namespace webrtc {
namespace {
const char StatsReport::kStatsReportTypeSession[] = "googLibjingleSession"; // The id of StatsReport of type kStatsReportTypeBwe.
const char StatsReport::kStatsReportTypeBwe[] = "VideoBwe"; const char kStatsReportVideoBweId[] = "bweforvideo";
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";
const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo"; // NOTE: These names need to be consistent with an external
// specification (W3C Stats Identifiers).
StatsReport::StatsReport(const StatsReport& src) const char* InternalTypeToString(StatsReport::StatsType type) {
: id_(src.id_), switch (type) {
type(src.type), case StatsReport::kStatsReportTypeSession:
timestamp_(src.timestamp_), return "googLibjingleSession";
values_(src.values_) { 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) class BandwidthEstimationId : public StatsReport::Id {
: id_(id), timestamp_(0) { public:
} BandwidthEstimationId() : StatsReport::Id(StatsReport::kStatsReportTypeBwe) {}
std::string ToString() const override { return kStatsReportVideoBweId; }
};
StatsReport::StatsReport(scoped_ptr<StatsReport::Id> id) class TypedId : public StatsReport::Id {
: id_(id->ToString()), timestamp_(0) { public:
} static const char kSeparator = '_';
TypedId(StatsReport::StatsType type, const std::string& id)
: StatsReport::Id(type), id_(id) {}
// static bool Equals(const Id& other) const override {
scoped_ptr<StatsReport::Id> StatsReport::NewTypedId( return Id::Equals(other) &&
StatsReport::StatsType type, const std::string& id) { static_cast<const TypedId&>(other).id_ == id_;
std::string internal_id(type); }
internal_id += '_';
internal_id += id;
return scoped_ptr<Id>(new Id(internal_id)).Pass();
}
StatsReport& StatsReport::operator=(const StatsReport& src) { std::string ToString() const override {
ASSERT(id_ == src.id_); return std::string(InternalTypeToString(type_)) + kSeparator + id_;
type = src.type; }
timestamp_ = src.timestamp_;
values_ = src.values_;
return *this;
}
// Operators provided for STL container/algorithm support. protected:
bool StatsReport::operator<(const StatsReport& other) const { const std::string id_;
return id_ < other.id_; };
}
bool StatsReport::operator==(const StatsReport& other) const { class IdWithDirection : public TypedId {
return id_ == other.id_; 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 bool Equals(const Id& other) const override {
// without requiring a new StatsReport instance. return TypedId::Equals(other) &&
bool StatsReport::operator==(const std::string& other_id) const { static_cast<const IdWithDirection&>(other).direction_ == direction_;
return id_ == other_id; }
}
// The copy ctor can't be declared as explicit due to problems with STL. std::string ToString() const override {
StatsReport::Value::Value(const Value& other) std::string ret(TypedId::ToString());
: name(other.name), value(other.value) { ret += '_';
} ret += direction_ == StatsReport::kSend ? "send" : "recv";
return ret;
}
StatsReport::Value::Value(StatsValueName name) private:
: name(name) { 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) StatsReport::Value::Value(StatsValueName name, const std::string& value)
: name(name), value(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 { const char* StatsReport::Value::display_name() const {
switch (name) { switch (name) {
case kStatsValueNameAudioOutputLevel: case kStatsValueNameAudioOutputLevel:
@ -331,6 +421,50 @@ const char* StatsReport::Value::display_name() const {
return nullptr; 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, void StatsReport::AddValue(StatsReport::StatsValueName name,
const std::string& value) { const std::string& value) {
values_.push_back(ValuePtr(new Value(name, value))); values_.push_back(ValuePtr(new Value(name, value)));
@ -340,6 +474,7 @@ void StatsReport::AddValue(StatsReport::StatsValueName name, int64 value) {
AddValue(name, rtc::ToString<int64>(value)); AddValue(name, rtc::ToString<int64>(value));
} }
// TODO(tommi): Change the way we store vector values.
template <typename T> template <typename T>
void StatsReport::AddValue(StatsReport::StatsValueName name, void StatsReport::AddValue(StatsReport::StatsValueName name,
const std::vector<T>& value) { const std::vector<T>& value) {
@ -370,6 +505,7 @@ void StatsReport::AddValue<int64_t>(
StatsReport::StatsValueName, const std::vector<int64_t>&); StatsReport::StatsValueName, const std::vector<int64_t>&);
void StatsReport::AddBoolean(StatsReport::StatsValueName name, bool value) { void StatsReport::AddBoolean(StatsReport::StatsValueName name, bool value) {
// TODO(tommi): Store bools as bool.
AddValue(name, value ? "true" : "false"); AddValue(name, value ? "true" : "false");
} }
@ -392,42 +528,67 @@ void StatsReport::ResetValues() {
values_.clear(); 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(); return list_.begin();
} }
StatsSet::const_iterator StatsSet::end() const { StatsCollection::const_iterator StatsCollection::end() const {
return list_.end(); return list_.end();
} }
StatsReport* StatsSet::InsertNew(const std::string& id) { size_t StatsCollection::size() const {
ASSERT(Find(id) == NULL); return list_.size();
const StatsReport* ret = &(*list_.insert(StatsReportCopyable(id)).first);
return const_cast<StatsReport*>(ret);
} }
StatsReport* StatsSet::FindOrAddNew(const std::string& id) { StatsReport* StatsCollection::InsertNew(scoped_ptr<StatsReport::Id> id) {
StatsReport* ret = Find(id); ASSERT(Find(*id.get()) == NULL);
return ret ? ret : InsertNew(id); StatsReport* report = new StatsReport(id.Pass());
list_.push_back(report);
return report;
} }
StatsReport* StatsSet::ReplaceOrAddNew(const std::string& id) { StatsReport* StatsCollection::FindOrAddNew(scoped_ptr<StatsReport::Id> id) {
list_.erase(id); StatsReport* ret = Find(*id.get());
return InsertNew(id); 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 // Looks for a report with the given |id|. If one is not found, NULL
// will be returned. // will be returned.
StatsReport* StatsSet::Find(const std::string& id) { StatsReport* StatsCollection::Find(const StatsReport::Id& id) {
const_iterator it = std::find(begin(), end(), id); Container::iterator it = std::find_if(list_.begin(), list_.end(),
return it == end() ? NULL : [&id](const StatsReport* r)->bool { return r->id().Equals(id); });
const_cast<StatsReport*>(static_cast<const StatsReport*>(&(*it))); return it == list_.end() ? nullptr : *it;
}
StatsReport* StatsCollection::Find(const scoped_ptr<StatsReport::Id>& id) {
return Find(*id.get());
} }
} // namespace webrtc } // namespace webrtc

View File

@ -32,11 +32,13 @@
#define TALK_APP_WEBRTC_STATSTYPES_H_ #define TALK_APP_WEBRTC_STATSTYPES_H_
#include <algorithm> #include <algorithm>
#include <set> #include <list>
#include <string> #include <string>
#include <vector> #include <vector>
#include "webrtc/base/basictypes.h" #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/common.h"
#include "webrtc/base/linked_ptr.h" #include "webrtc/base/linked_ptr.h"
#include "webrtc/base/scoped_ptr.h" #include "webrtc/base/scoped_ptr.h"
@ -46,35 +48,71 @@ namespace webrtc {
class StatsReport { class StatsReport {
public: public:
// TODO(tommi): Remove this ctor after removing reliance upon it in Chromium // Indicates whether a track is for sending or receiving.
// (mock_peer_connection_impl.cc). // Used in reports for audio/video tracks.
StatsReport() : timestamp_(0) {} enum Direction {
kSend = 0,
kReceive,
};
// TODO(tommi): Make protected and disallow copy completely once not needed. enum StatsType {
StatsReport(const StatsReport& src); // 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. // A StatsReport of |type| = "googTransport" contains information
// TODO(tommi): Make this ctor protected. // about a libjingle "transport".
explicit StatsReport(const std::string& id); kStatsReportTypeTransport,
// TODO(tommi): Make this protected. // A StatsReport of |type| = "googComponent" contains information
StatsReport& operator=(const StatsReport& src); // 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. // A StatsReport of |type| = "googCandidatePair" contains information
bool operator<(const StatsReport& other) const; // about a libjingle "connection" - a single source/destination port pair.
bool operator==(const StatsReport& other) const; // This is intended to be the same thing as an ICE "candidate pair".
// Special support for being able to use std::find on a container kStatsReportTypeCandidatePair,
// without requiring a new StatsReport instance.
bool operator==(const std::string& other_id) const;
// The unique identifier for this object. // A StatsReport of |type| = "VideoBWE" is statistics for video Bandwidth
// This is used as a key for this report in ordered containers, // Estimation, which is global per-session. The |id| field is "bweforvideo"
// so it must never be changed. // (will probably change in the future).
// TODO(tommi): Make this member variable const. kStatsReportTypeBwe,
std::string id_; // See below for contents.
std::string type; // See below for contents. // 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,
};
// StatsValue names.
enum StatsValueName { enum StatsValueName {
kStatsValueNameActiveConnection, kStatsValueNameActiveConnection,
kStatsValueNameAudioInputLevel, kStatsValueNameAudioInputLevel,
@ -183,40 +221,54 @@ class StatsReport {
class Id { class Id {
public: public:
Id(const std::string& id) : id_(id) {} virtual ~Id();
Id(const Id& id) : id_(id.id_) {} StatsType type() const;
const std::string& ToString() const { return id_; } virtual bool Equals(const Id& other) const;
private: virtual std::string ToString() const = 0;
const std::string id_;
protected:
Id(StatsType type); // Only meant for derived classes.
const StatsType type_;
}; };
struct Value { 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); 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|. // Returns the string representation of |name|.
const char* display_name() const; const char* display_name() const;
const StatsValueName name; 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 rtc::linked_ptr<Value> ValuePtr;
typedef std::vector<ValuePtr> Values; typedef std::vector<ValuePtr> Values;
typedef const char* StatsType;
// Ownership of |id| is passed to |this|. // Ownership of |id| is passed to |this|.
explicit StatsReport(rtc::scoped_ptr<Id> id); 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> 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, const std::string& value);
void AddValue(StatsValueName name, int64 value); void AddValue(StatsValueName name, int64 value);
@ -228,122 +280,55 @@ class StatsReport {
void ResetValues(); void ResetValues();
const Id id() const { return Id(id_); } const Value* FindValue(StatsValueName name) const;
double timestamp() const { return timestamp_; }
void set_timestamp(double t) { timestamp_ = t; }
const Values& values() const { return values_; }
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. double timestamp_; // Time since 1970-01-01T00:00:00Z in milliseconds.
Values values_; Values values_;
// TODO(tommi): These should all be enum values. DISALLOW_COPY_AND_ASSIGN(StatsReport);
// 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=;
}; };
// Typedef for an array of const StatsReport pointers. // Typedef for an array of const StatsReport pointers.
// Ownership of the pointers held by this implementation is assumed to lie // Ownership of the pointers held by this implementation is assumed to lie
// elsewhere and lifetime guarantees are made by the implementation that uses // elsewhere and lifetime guarantees are made by the implementation that uses
// this type. In the StatsCollector, object ownership lies with the StatsSet // this type. In the StatsCollector, object ownership lies with the
// class. // StatsCollection class.
typedef std::vector<const StatsReport*> StatsReports; typedef std::vector<const StatsReport*> StatsReports;
// A map from the report id to the report. // A map from the report id to the report.
// This class wraps an STL container and provides a limited set of // This class wraps an STL container and provides a limited set of
// functionality in order to keep things simple. // functionality in order to keep things simple.
// TODO(tommi): Use a thread checker here (currently not in libjingle). // TODO(tommi): Use a thread checker here (currently not in libjingle).
class StatsSet { class StatsCollection {
public: public:
StatsSet(); StatsCollection();
~StatsSet(); ~StatsCollection();
typedef std::set<StatsReportCopyable> Container; // TODO(tommi): shared_ptr (or linked_ptr)?
typedef std::list<StatsReport*> Container;
typedef Container::iterator iterator; typedef Container::iterator iterator;
typedef Container::const_iterator const_iterator; typedef Container::const_iterator const_iterator;
const_iterator begin() const; const_iterator begin() const;
const_iterator end() const; const_iterator end() const;
size_t size() const;
// Creates a new report object with |id| that does not already // Creates a new report object with |id| that does not already
// exist in the list of reports. // exist in the list of reports.
StatsReport* InsertNew(const std::string& id); StatsReport* InsertNew(rtc::scoped_ptr<StatsReport::Id> id);
StatsReport* FindOrAddNew(const std::string& id); StatsReport* FindOrAddNew(rtc::scoped_ptr<StatsReport::Id> id);
StatsReport* ReplaceOrAddNew(const std::string& id); StatsReport* ReplaceOrAddNew(rtc::scoped_ptr<StatsReport::Id> id);
// Looks for a report with the given |id|. If one is not found, NULL // Looks for a report with the given |id|. If one is not found, NULL
// will be returned. // 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: private:
Container list_; Container list_;

View File

@ -126,7 +126,7 @@ class MockStatsObserver : public webrtc::StatsObserver {
memset(&stats_, sizeof(stats_), 0); memset(&stats_, sizeof(stats_), 0);
stats_.number_of_reports = reports.size(); stats_.number_of_reports = reports.size();
for (const auto* r : reports) { for (const auto* r : reports) {
if (r->type == StatsReport::kStatsReportTypeSsrc) { if (r->type() == StatsReport::kStatsReportTypeSsrc) {
GetIntValue(r, StatsReport::kStatsValueNameAudioOutputLevel, GetIntValue(r, StatsReport::kStatsValueNameAudioOutputLevel,
&stats_.audio_output_level); &stats_.audio_output_level);
GetIntValue(r, StatsReport::kStatsValueNameAudioInputLevel, GetIntValue(r, StatsReport::kStatsValueNameAudioInputLevel,
@ -135,7 +135,7 @@ class MockStatsObserver : public webrtc::StatsObserver {
&stats_.bytes_received); &stats_.bytes_received);
GetIntValue(r, StatsReport::kStatsValueNameBytesSent, GetIntValue(r, StatsReport::kStatsValueNameBytesSent,
&stats_.bytes_sent); &stats_.bytes_sent);
} else if (r->type == StatsReport::kStatsReportTypeBwe) { } else if (r->type() == StatsReport::kStatsReportTypeBwe) {
GetIntValue(r, StatsReport::kStatsValueNameAvailableReceiveBandwidth, GetIntValue(r, StatsReport::kStatsValueNameAvailableReceiveBandwidth,
&stats_.available_receive_bandwidth); &stats_.available_receive_bandwidth);
} }