blob: eb6b30cca61cd76b92b3034997e364fb09b9cec7 [file] [log] [blame]
* Copyright 2014 The WebRTC project authors. All Rights Reserved.
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
#include "webrtc/api/statstypes.h"
#include <string.h>
#include "webrtc/base/checks.h"
// TODO(tommi): Could we have a static map of value name -> expected type
// and use this to RTC_DCHECK on correct usage (somewhat strongly typed values)?
// Alternatively, we could define the names+type in a separate document and
// generate strongly typed inline C++ code that forces the correct type to be
// used for a given name at compile time.
using rtc::RefCountedObject;
namespace webrtc {
namespace {
// The id of StatsReport of type kStatsReportTypeBwe.
const char kStatsReportVideoBweId[] = "bweforvideo";
// 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 "transport";
case StatsReport::kStatsReportTypeComponent:
return "googComponent";
case StatsReport::kStatsReportTypeCandidatePair:
return "googCandidatePair";
case StatsReport::kStatsReportTypeCertificate:
return "googCertificate";
case StatsReport::kStatsReportTypeDataChannel:
return "datachannel";
return nullptr;
class BandwidthEstimationId : public StatsReport::IdBase {
: StatsReport::IdBase(StatsReport::kStatsReportTypeBwe) {}
std::string ToString() const override { return kStatsReportVideoBweId; }
class TypedId : public StatsReport::IdBase {
TypedId(StatsReport::StatsType type, const std::string& id)
: StatsReport::IdBase(type), id_(id) {}
bool Equals(const IdBase& other) const override {
return IdBase::Equals(other) &&
static_cast<const TypedId&>(other).id_ == id_;
std::string ToString() const override {
return std::string(InternalTypeToString(type_)) + kSeparator + id_;
const std::string id_;
class TypedIntId : public StatsReport::IdBase {
TypedIntId(StatsReport::StatsType type, int id)
: StatsReport::IdBase(type), id_(id) {}
bool Equals(const IdBase& other) const override {
return IdBase::Equals(other) &&
static_cast<const TypedIntId&>(other).id_ == id_;
std::string ToString() const override {
return std::string(InternalTypeToString(type_)) +
kSeparator +
const int id_;
class IdWithDirection : public TypedId {
IdWithDirection(StatsReport::StatsType type, const std::string& id,
StatsReport::Direction direction)
: TypedId(type, id), direction_(direction) {}
bool Equals(const IdBase& other) const override {
return TypedId::Equals(other) &&
static_cast<const IdWithDirection&>(other).direction_ == direction_;
std::string ToString() const override {
std::string ret(TypedId::ToString());
ret += kSeparator;
ret += direction_ == StatsReport::kSend ? "send" : "recv";
return ret;
const StatsReport::Direction direction_;
class CandidateId : public TypedId {
CandidateId(bool local, const std::string& id)
: TypedId(local ?
StatsReport::kStatsReportTypeIceLocalCandidate :
id) {
std::string ToString() const override {
return "Cand-" + id_;
class ComponentId : public StatsReport::IdBase {
ComponentId(const std::string& content_name, int component)
: ComponentId(StatsReport::kStatsReportTypeComponent, content_name,
component) {}
bool Equals(const IdBase& other) const override {
return IdBase::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-");
ComponentId(StatsReport::StatsType type, const std::string& content_name,
int component)
: IdBase(type),
component_(component) {}
std::string ToString(const char* prefix) const {
std::string ret(prefix);
ret += content_name_;
ret += '-';
ret += rtc::ToString<>(component_);
return ret;
const std::string content_name_;
const int component_;
class CandidatePairId : public ComponentId {
CandidatePairId(const std::string& content_name, int component, int index)
: ComponentId(StatsReport::kStatsReportTypeCandidatePair, content_name,
index_(index) {}
bool Equals(const IdBase& 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;
const int index_;
} // namespace
StatsReport::IdBase::IdBase(StatsType type) : type_(type) {}
StatsReport::IdBase::~IdBase() {}
StatsReport::StatsType StatsReport::IdBase::type() const { return type_; }
bool StatsReport::IdBase::Equals(const IdBase& other) const {
return other.type_ == type_;
StatsReport::Value::Value(StatsValueName name, int64_t value, Type int_type)
: name(name), type_(int_type) {
RTC_DCHECK(type_ == kInt || type_ == kInt64);
type_ == kInt ? value_.int_ = static_cast<int>(value) : value_.int64_ = value;
StatsReport::Value::Value(StatsValueName name, float f)
: name(name), type_(kFloat) {
value_.float_ = f;
StatsReport::Value::Value(StatsValueName name, const std::string& value)
: name(name), type_(kString) {
value_.string_ = new std::string(value);
StatsReport::Value::Value(StatsValueName name, const char* value)
: name(name), type_(kStaticString) {
value_.static_string_ = value;
StatsReport::Value::Value(StatsValueName name, bool b)
: name(name), type_(kBool) {
value_.bool_ = b;
StatsReport::Value::Value(StatsValueName name, const Id& value)
: name(name), type_(kId) {
value_.id_ = new Id(value);
StatsReport::Value::~Value() {
switch (type_) {
case kInt:
case kInt64:
case kFloat:
case kBool:
case kStaticString:
case kString:
delete value_.string_;
case kId:
delete value_.id_;
bool StatsReport::Value::Equals(const Value& other) const {
if (name !=
return false;
// There's a 1:1 relation between a name and a type, so we don't have to
// check that.
RTC_DCHECK_EQ(type_, other.type_);
switch (type_) {
case kInt:
return value_.int_ == other.value_.int_;
case kInt64:
return value_.int64_ == other.value_.int64_;
case kFloat:
return value_.float_ == other.value_.float_;
case kStaticString: {
#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
if (value_.static_string_ != other.value_.static_string_) {
RTC_DCHECK(strcmp(value_.static_string_, other.value_.static_string_) !=
<< "Duplicate global?";
return value_.static_string_ == other.value_.static_string_;
case kString:
return *value_.string_ == *other.value_.string_;
case kBool:
return value_.bool_ == other.value_.bool_;
case kId:
return (*value_.id_)->Equals(*other.value_.id_);
return false;
bool StatsReport::Value::operator==(const std::string& value) const {
return (type_ == kString && value_.string_->compare(value) == 0) ||
(type_ == kStaticString && == 0);
bool StatsReport::Value::operator==(const char* value) const {
if (type_ == kString)
return value_.string_->compare(value) == 0;
if (type_ != kStaticString)
return false;
#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
if (value_.static_string_ != value)
RTC_DCHECK(strcmp(value_.static_string_, value) != 0)
<< "Duplicate global?";
return value == value_.static_string_;
bool StatsReport::Value::operator==(int64_t value) const {
return type_ == kInt ? value_.int_ == static_cast<int>(value) :
(type_ == kInt64 ? value_.int64_ == value : false);
bool StatsReport::Value::operator==(bool value) const {
return type_ == kBool && value_.bool_ == value;
bool StatsReport::Value::operator==(float value) const {
return type_ == kFloat && value_.float_ == value;
bool StatsReport::Value::operator==(const Id& value) const {
return type_ == kId && (*value_.id_)->Equals(value);
int StatsReport::Value::int_val() const {
RTC_DCHECK(type_ == kInt);
return value_.int_;
int64_t StatsReport::Value::int64_val() const {
RTC_DCHECK(type_ == kInt64);
return value_.int64_;
float StatsReport::Value::float_val() const {
RTC_DCHECK(type_ == kFloat);
return value_.float_;
const char* StatsReport::Value::static_string_val() const {
RTC_DCHECK(type_ == kStaticString);
return value_.static_string_;
const std::string& StatsReport::Value::string_val() const {
RTC_DCHECK(type_ == kString);
return *value_.string_;
bool StatsReport::Value::bool_val() const {
RTC_DCHECK(type_ == kBool);
return value_.bool_;
const char* StatsReport::Value::display_name() const {
switch (name) {
case kStatsValueNameAecDivergentFilterFraction:
return "aecDivergentFilterFraction";
case kStatsValueNameAudioOutputLevel:
return "audioOutputLevel";
case kStatsValueNameAudioInputLevel:
return "audioInputLevel";
case kStatsValueNameBytesSent:
return "bytesSent";
case kStatsValueNamePacketsSent:
return "packetsSent";
case kStatsValueNameBytesReceived:
return "bytesReceived";
case kStatsValueNameLabel:
return "label";
case kStatsValueNamePacketsReceived:
return "packetsReceived";
case kStatsValueNamePacketsLost:
return "packetsLost";
case kStatsValueNameProtocol:
return "protocol";
case kStatsValueNameTransportId:
return "transportId";
case kStatsValueNameSelectedCandidatePairId:
return "selectedCandidatePairId";
case kStatsValueNameSsrc:
return "ssrc";
case kStatsValueNameState:
return "state";
case kStatsValueNameDataChannelId:
return "datachannelid";
case kStatsValueNameCodecImplementationName:
return "codecImplementationName";
case kStatsValueNameMediaType:
return "mediaType";
// 'goog' prefixed constants.
case kStatsValueNameAccelerateRate:
return "googAccelerateRate";
case kStatsValueNameActiveConnection:
return "googActiveConnection";
case kStatsValueNameActualEncBitrate:
return "googActualEncBitrate";
case kStatsValueNameAvailableReceiveBandwidth:
return "googAvailableReceiveBandwidth";
case kStatsValueNameAvailableSendBandwidth:
return "googAvailableSendBandwidth";
case kStatsValueNameAvgEncodeMs:
return "googAvgEncodeMs";
case kStatsValueNameBucketDelay:
return "googBucketDelay";
case kStatsValueNameBandwidthLimitedResolution:
return "googBandwidthLimitedResolution";
// STUN ping related attributes.
// TODO(zhihuang) Rename these stats to follow the standards.
case kStatsValueNameSentPingRequestsTotal:
return "requestsSent";
case kStatsValueNameSentPingRequestsBeforeFirstResponse:
return "consentRequestsSent";
case kStatsValueNameSentPingResponses:
return "responsesSent";
case kStatsValueNameRecvPingRequests:
return "requestsReceived";
case kStatsValueNameRecvPingResponses:
return "responsesReceived";
// Candidate related attributes. Values are taken from
case kStatsValueNameCandidateIPAddress:
return "ipAddress";
case kStatsValueNameCandidateNetworkType:
return "networkType";
case kStatsValueNameCandidatePortNumber:
return "portNumber";
case kStatsValueNameCandidatePriority:
return "priority";
case kStatsValueNameCandidateTransportType:
return "transport";
case kStatsValueNameCandidateType:
return "candidateType";
case kStatsValueNameChannelId:
return "googChannelId";
case kStatsValueNameCodecName:
return "googCodecName";
case kStatsValueNameComponent:
return "googComponent";
case kStatsValueNameContentName:
return "googContentName";
case kStatsValueNameCpuLimitedResolution:
return "googCpuLimitedResolution";
case kStatsValueNameDecodingCTSG:
return "googDecodingCTSG";
case kStatsValueNameDecodingCTN:
return "googDecodingCTN";
case kStatsValueNameDecodingMutedOutput:
return "googDecodingMuted";
case kStatsValueNameDecodingNormal:
return "googDecodingNormal";
case kStatsValueNameDecodingPLC:
return "googDecodingPLC";
case kStatsValueNameDecodingCNG:
return "googDecodingCNG";
case kStatsValueNameDecodingPLCCNG:
return "googDecodingPLCCNG";
case kStatsValueNameDer:
return "googDerBase64";
case kStatsValueNameDtlsCipher:
return "dtlsCipher";
case kStatsValueNameEchoCancellationQualityMin:
return "googEchoCancellationQualityMin";
case kStatsValueNameEchoDelayMedian:
return "googEchoCancellationEchoDelayMedian";
case kStatsValueNameEchoDelayStdDev:
return "googEchoCancellationEchoDelayStdDev";
case kStatsValueNameEchoReturnLoss:
return "googEchoCancellationReturnLoss";
case kStatsValueNameEchoReturnLossEnhancement:
return "googEchoCancellationReturnLossEnhancement";
case kStatsValueNameEncodeUsagePercent:
return "googEncodeUsagePercent";
case kStatsValueNameExpandRate:
return "googExpandRate";
case kStatsValueNameFingerprint:
return "googFingerprint";
case kStatsValueNameFingerprintAlgorithm:
return "googFingerprintAlgorithm";
case kStatsValueNameFirsReceived:
return "googFirsReceived";
case kStatsValueNameFirsSent:
return "googFirsSent";
case kStatsValueNameFrameHeightInput:
return "googFrameHeightInput";
case kStatsValueNameFrameHeightReceived:
return "googFrameHeightReceived";
case kStatsValueNameFrameHeightSent:
return "googFrameHeightSent";
case kStatsValueNameFrameRateReceived:
return "googFrameRateReceived";
case kStatsValueNameFrameRateDecoded:
return "googFrameRateDecoded";
case kStatsValueNameFrameRateOutput:
return "googFrameRateOutput";
case kStatsValueNameDecodeMs:
return "googDecodeMs";
case kStatsValueNameMaxDecodeMs:
return "googMaxDecodeMs";
case kStatsValueNameCurrentDelayMs:
return "googCurrentDelayMs";
case kStatsValueNameTargetDelayMs:
return "googTargetDelayMs";
case kStatsValueNameJitterBufferMs:
return "googJitterBufferMs";
case kStatsValueNameMinPlayoutDelayMs:
return "googMinPlayoutDelayMs";
case kStatsValueNameRenderDelayMs:
return "googRenderDelayMs";
case kStatsValueNameCaptureStartNtpTimeMs:
return "googCaptureStartNtpTimeMs";
case kStatsValueNameFrameRateInput:
return "googFrameRateInput";
case kStatsValueNameFrameRateSent:
return "googFrameRateSent";
case kStatsValueNameFrameWidthInput:
return "googFrameWidthInput";
case kStatsValueNameFrameWidthReceived:
return "googFrameWidthReceived";
case kStatsValueNameFrameWidthSent:
return "googFrameWidthSent";
case kStatsValueNameInitiator:
return "googInitiator";
case kStatsValueNameIssuerId:
return "googIssuerId";
case kStatsValueNameJitterReceived:
return "googJitterReceived";
case kStatsValueNameLocalAddress:
return "googLocalAddress";
case kStatsValueNameLocalCandidateId:
return "localCandidateId";
case kStatsValueNameLocalCandidateType:
return "googLocalCandidateType";
case kStatsValueNameLocalCertificateId:
return "localCertificateId";
case kStatsValueNameAdaptationChanges:
return "googAdaptationChanges";
case kStatsValueNameNacksReceived:
return "googNacksReceived";
case kStatsValueNameNacksSent:
return "googNacksSent";
case kStatsValueNamePreemptiveExpandRate:
return "googPreemptiveExpandRate";
case kStatsValueNamePlisReceived:
return "googPlisReceived";
case kStatsValueNamePlisSent:
return "googPlisSent";
case kStatsValueNamePreferredJitterBufferMs:
return "googPreferredJitterBufferMs";
case kStatsValueNameReceiving:
return "googReadable";
case kStatsValueNameRemoteAddress:
return "googRemoteAddress";
case kStatsValueNameRemoteCandidateId:
return "remoteCandidateId";
case kStatsValueNameRemoteCandidateType:
return "googRemoteCandidateType";
case kStatsValueNameRemoteCertificateId:
return "remoteCertificateId";
case kStatsValueNameRetransmitBitrate:
return "googRetransmitBitrate";
case kStatsValueNameRtt:
return "googRtt";
case kStatsValueNameSecondaryDecodedRate:
return "googSecondaryDecodedRate";
case kStatsValueNameSendPacketsDiscarded:
return "packetsDiscardedOnSend";
case kStatsValueNameSpeechExpandRate:
return "googSpeechExpandRate";
case kStatsValueNameSrtpCipher:
return "srtpCipher";
case kStatsValueNameTargetEncBitrate:
return "googTargetEncBitrate";
case kStatsValueNameTransmitBitrate:
return "googTransmitBitrate";
case kStatsValueNameTransportType:
return "googTransportType";
case kStatsValueNameTrackId:
return "googTrackId";
case kStatsValueNameTypingNoiseState:
return "googTypingNoiseState";
case kStatsValueNameViewLimitedResolution:
return "googViewLimitedResolution";
case kStatsValueNameWritable:
return "googWritable";
return nullptr;
std::string StatsReport::Value::ToString() const {
switch (type_) {
case kInt:
return rtc::ToString(value_.int_);
case kInt64:
return rtc::ToString(value_.int64_);
case kFloat:
return rtc::ToString(value_.float_);
case kStaticString:
return std::string(value_.static_string_);
case kString:
return *value_.string_;
case kBool:
return value_.bool_ ? "true" : "false";
case kId:
return (*value_.id_)->ToString();
return std::string();
StatsReport::StatsReport(const Id& id) : id_(id), timestamp_(0.0) {
// static
StatsReport::Id StatsReport::NewBandwidthEstimationId() {
return Id(new RefCountedObject<BandwidthEstimationId>());
// static
StatsReport::Id StatsReport::NewTypedId(StatsType type, const std::string& id) {
return Id(new RefCountedObject<TypedId>(type, id));
// static
StatsReport::Id StatsReport::NewTypedIntId(StatsType type, int id) {
return Id(new RefCountedObject<TypedIntId>(type, id));
// static
StatsReport::Id StatsReport::NewIdWithDirection(
StatsType type, const std::string& id, StatsReport::Direction direction) {
return Id(new RefCountedObject<IdWithDirection>(type, id, direction));
// static
StatsReport::Id StatsReport::NewCandidateId(bool local, const std::string& id) {
return Id(new RefCountedObject<CandidateId>(local, id));
// static
StatsReport::Id StatsReport::NewComponentId(
const std::string& content_name, int component) {
return Id(new RefCountedObject<ComponentId>(content_name, component));
// static
StatsReport::Id StatsReport::NewCandidatePairId(
const std::string& content_name, int component, int index) {
return Id(new RefCountedObject<CandidatePairId>(
content_name, component, index));
const char* StatsReport::TypeToString() const {
return InternalTypeToString(id_->type());
void StatsReport::AddString(StatsReport::StatsValueName name,
const std::string& value) {
const Value* found = FindValue(name);
if (!found || !(*found == value))
values_[name] = ValuePtr(new Value(name, value));
void StatsReport::AddString(StatsReport::StatsValueName name,
const char* value) {
const Value* found = FindValue(name);
if (!found || !(*found == value))
values_[name] = ValuePtr(new Value(name, value));
void StatsReport::AddInt64(StatsReport::StatsValueName name, int64_t value) {
const Value* found = FindValue(name);
if (!found || !(*found == value))
values_[name] = ValuePtr(new Value(name, value, Value::kInt64));
void StatsReport::AddInt(StatsReport::StatsValueName name, int value) {
const Value* found = FindValue(name);
if (!found || !(*found == static_cast<int64_t>(value)))
values_[name] = ValuePtr(new Value(name, value, Value::kInt));
void StatsReport::AddFloat(StatsReport::StatsValueName name, float value) {
const Value* found = FindValue(name);
if (!found || !(*found == value))
values_[name] = ValuePtr(new Value(name, value));
void StatsReport::AddBoolean(StatsReport::StatsValueName name, bool value) {
const Value* found = FindValue(name);
if (!found || !(*found == value))
values_[name] = ValuePtr(new Value(name, value));
void StatsReport::AddId(StatsReport::StatsValueName name,
const Id& value) {
const Value* found = FindValue(name);
if (!found || !(*found == value))
values_[name] = ValuePtr(new Value(name, value));
const StatsReport::Value* StatsReport::FindValue(StatsValueName name) const {
Values::const_iterator it = values_.find(name);
return it == values_.end() ? nullptr : it->second.get();
StatsCollection::StatsCollection() {
StatsCollection::~StatsCollection() {
for (auto* r : list_)
delete r;
StatsCollection::const_iterator StatsCollection::begin() const {
return list_.begin();
StatsCollection::const_iterator StatsCollection::end() const {
return list_.end();
size_t StatsCollection::size() const {
return list_.size();
StatsReport* StatsCollection::InsertNew(const StatsReport::Id& id) {
RTC_DCHECK(Find(id) == nullptr);
StatsReport* report = new StatsReport(id);
return report;
StatsReport* StatsCollection::FindOrAddNew(const StatsReport::Id& id) {
StatsReport* ret = Find(id);
return ret ? ret : InsertNew(id);
StatsReport* StatsCollection::ReplaceOrAddNew(const StatsReport::Id& id) {
Container::iterator it = std::find_if(list_.begin(), list_.end(),
[&id](const StatsReport* r)->bool { return r->id()->Equals(id); });
if (it != end()) {
StatsReport* report = new StatsReport((*it)->id());
delete *it;
*it = report;
return report;
return InsertNew(id);
// Looks for a report with the given |id|. If one is not found, NULL
// will be returned.
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;
} // namespace webrtc