| /* | 
 |  *  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 "api/statstypes.h" | 
 |  | 
 | #include <string.h> | 
 |  | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/refcountedobject.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"; | 
 |   } | 
 |   RTC_NOTREACHED(); | 
 |   return nullptr; | 
 | } | 
 |  | 
 | class BandwidthEstimationId : public StatsReport::IdBase { | 
 |  public: | 
 |   BandwidthEstimationId() | 
 |       : StatsReport::IdBase(StatsReport::kStatsReportTypeBwe) {} | 
 |   std::string ToString() const override { return kStatsReportVideoBweId; } | 
 | }; | 
 |  | 
 | class TypedId : public StatsReport::IdBase { | 
 |  public: | 
 |   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_; | 
 |   } | 
 |  | 
 |  protected: | 
 |   const std::string id_; | 
 | }; | 
 |  | 
 | class TypedIntId : public StatsReport::IdBase { | 
 |  public: | 
 |   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 + | 
 |            rtc::ToString(id_); | 
 |   } | 
 |  | 
 |  protected: | 
 |   const int id_; | 
 | }; | 
 |  | 
 | class IdWithDirection : public TypedId { | 
 |  public: | 
 |   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; | 
 |   } | 
 |  | 
 |  private: | 
 |   const StatsReport::Direction direction_; | 
 | }; | 
 |  | 
 | class CandidateId : public TypedId { | 
 |  public: | 
 |   CandidateId(bool local, const std::string& id) | 
 |       : TypedId(local ? StatsReport::kStatsReportTypeIceLocalCandidate | 
 |                       : StatsReport::kStatsReportTypeIceRemoteCandidate, | 
 |                 id) {} | 
 |  | 
 |   std::string ToString() const override { return "Cand-" + id_; } | 
 | }; | 
 |  | 
 | class ComponentId : public StatsReport::IdBase { | 
 |  public: | 
 |   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-"); } | 
 |  | 
 |  protected: | 
 |   ComponentId(StatsReport::StatsType type, | 
 |               const std::string& content_name, | 
 |               int component) | 
 |       : IdBase(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 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; | 
 |   } | 
 |  | 
 |  private: | 
 |   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: | 
 |       break; | 
 |     case kString: | 
 |       delete value_.string_; | 
 |       break; | 
 |     case kId: | 
 |       delete value_.id_; | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | bool StatsReport::Value::Equals(const Value& other) const { | 
 |   if (name != other.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 RTC_DCHECK_IS_ON | 
 |       if (value_.static_string_ != other.value_.static_string_) { | 
 |         RTC_DCHECK(strcmp(value_.static_string_, other.value_.static_string_) != | 
 |                    0) | 
 |             << "Duplicate global?"; | 
 |       } | 
 | #endif | 
 |       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_); | 
 |   } | 
 |   RTC_NOTREACHED(); | 
 |   return false; | 
 | } | 
 |  | 
 | bool StatsReport::Value::operator==(const std::string& value) const { | 
 |   return (type_ == kString && value_.string_->compare(value) == 0) || | 
 |          (type_ == kStaticString && value.compare(value_.static_string_) == 0); | 
 | } | 
 |  | 
 | bool StatsReport::Value::operator==(const char* value) const { | 
 |   if (type_ == kString) | 
 |     return value_.string_->compare(value) == 0; | 
 |   if (type_ != kStaticString) | 
 |     return false; | 
 | #if RTC_DCHECK_IS_ON | 
 |   if (value_.static_string_ != value) | 
 |     RTC_DCHECK(strcmp(value_.static_string_, value) != 0) | 
 |         << "Duplicate global?"; | 
 | #endif | 
 |   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 kStatsValueNameConcealedSamples: | 
 |       return "concealedSamples"; | 
 |     case kStatsValueNameConcealmentEvents: | 
 |       return "concealmentEvents"; | 
 |     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 kStatsValueNameTotalSamplesReceived: | 
 |       return "totalSamplesReceived"; | 
 |     case kStatsValueNameTransportId: | 
 |       return "transportId"; | 
 |     case kStatsValueNameSelectedCandidatePairId: | 
 |       return "selectedCandidatePairId"; | 
 |     case kStatsValueNameSsrc: | 
 |       return "ssrc"; | 
 |     case kStatsValueNameState: | 
 |       return "state"; | 
 |     case kStatsValueNameDataChannelId: | 
 |       return "datachannelid"; | 
 |     case kStatsValueNameFramesDecoded: | 
 |       return "framesDecoded"; | 
 |     case kStatsValueNameFramesEncoded: | 
 |       return "framesEncoded"; | 
 |     case kStatsValueNameJitterBufferDelay: | 
 |       return "jitterBufferDelay"; | 
 |     case kStatsValueNameCodecImplementationName: | 
 |       return "codecImplementationName"; | 
 |     case kStatsValueNameMediaType: | 
 |       return "mediaType"; | 
 |     case kStatsValueNameQpSum: | 
 |       return "qpSum"; | 
 |     // '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. | 
 |     // Connectivity checks. | 
 |     case kStatsValueNameSentPingRequestsTotal: | 
 |       return "requestsSent"; | 
 |     case kStatsValueNameSentPingRequestsBeforeFirstResponse: | 
 |       return "consentRequestsSent"; | 
 |     case kStatsValueNameSentPingResponses: | 
 |       return "responsesSent"; | 
 |     case kStatsValueNameRecvPingRequests: | 
 |       return "requestsReceived"; | 
 |     case kStatsValueNameRecvPingResponses: | 
 |       return "responsesReceived"; | 
 |     // STUN Keepalive pings. | 
 |     case kStatsValueNameSentStunKeepaliveRequests: | 
 |       return "stunKeepaliveRequestsSent"; | 
 |     case kStatsValueNameRecvStunKeepaliveResponses: | 
 |       return "stunKeepaliveResponsesReceived"; | 
 |     case kStatsValueNameStunKeepaliveRttTotal: | 
 |       return "stunKeepaliveRttTotal"; | 
 |     case kStatsValueNameStunKeepaliveRttSquaredTotal: | 
 |       return "stunKeepaliveRttSquaredTotal"; | 
 |  | 
 |     // Candidate related attributes. Values are taken from | 
 |     // http://w3c.github.io/webrtc-stats/#rtcstatstype-enum*. | 
 |     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 kStatsValueNameContentType: | 
 |       return "googContentType"; | 
 |     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 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 kStatsValueNameHasEnteredLowResolution: | 
 |       return "googHasEnteredLowResolution"; | 
 |     case kStatsValueNameHugeFramesSent: | 
 |       return "hugeFramesSent"; | 
 |     case kStatsValueNameInitiator: | 
 |       return "googInitiator"; | 
 |     case kStatsValueNameInterframeDelayMaxMs: | 
 |       return "googInterframeDelayMax"; | 
 |     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 kStatsValueNameResidualEchoLikelihood: | 
 |       return "googResidualEchoLikelihood"; | 
 |     case kStatsValueNameResidualEchoLikelihoodRecentMax: | 
 |       return "googResidualEchoLikelihoodRecentMax"; | 
 |     case kStatsValueNameAnaBitrateActionCounter: | 
 |       return "googAnaBitrateActionCounter"; | 
 |     case kStatsValueNameAnaChannelActionCounter: | 
 |       return "googAnaChannelActionCounter"; | 
 |     case kStatsValueNameAnaDtxActionCounter: | 
 |       return "googAnaDtxActionCounter"; | 
 |     case kStatsValueNameAnaFecActionCounter: | 
 |       return "googAnaFecActionCounter"; | 
 |     case kStatsValueNameAnaFrameLengthIncreaseCounter: | 
 |       return "googAnaFrameLengthIncreaseCounter"; | 
 |     case kStatsValueNameAnaFrameLengthDecreaseCounter: | 
 |       return "googAnaFrameLengthDecreaseCounter"; | 
 |     case kStatsValueNameAnaUplinkPacketLossFraction: | 
 |       return "googAnaUplinkPacketLossFraction"; | 
 |     case kStatsValueNameRetransmitBitrate: | 
 |       return "googRetransmitBitrate"; | 
 |     case kStatsValueNameRtt: | 
 |       return "googRtt"; | 
 |     case kStatsValueNameSecondaryDecodedRate: | 
 |       return "googSecondaryDecodedRate"; | 
 |     case kStatsValueNameSecondaryDiscardedRate: | 
 |       return "googSecondaryDiscardedRate"; | 
 |     case kStatsValueNameSendPacketsDiscarded: | 
 |       return "packetsDiscardedOnSend"; | 
 |     case kStatsValueNameSpeechExpandRate: | 
 |       return "googSpeechExpandRate"; | 
 |     case kStatsValueNameSrtpCipher: | 
 |       return "srtpCipher"; | 
 |     case kStatsValueNameTargetEncBitrate: | 
 |       return "googTargetEncBitrate"; | 
 |     case kStatsValueNameTotalAudioEnergy: | 
 |       return "totalAudioEnergy"; | 
 |     case kStatsValueNameTotalSamplesDuration: | 
 |       return "totalSamplesDuration"; | 
 |     case kStatsValueNameTransmitBitrate: | 
 |       return "googTransmitBitrate"; | 
 |     case kStatsValueNameTransportType: | 
 |       return "googTransportType"; | 
 |     case kStatsValueNameTrackId: | 
 |       return "googTrackId"; | 
 |     case kStatsValueNameTimingFrameInfo: | 
 |       return "googTimingFrameInfo"; | 
 |     case kStatsValueNameTypingNoiseState: | 
 |       return "googTypingNoiseState"; | 
 |     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(); | 
 |   } | 
 |   RTC_NOTREACHED(); | 
 |   return std::string(); | 
 | } | 
 |  | 
 | StatsReport::StatsReport(const Id& id) : id_(id), timestamp_(0.0) { | 
 |   RTC_DCHECK(id_.get()); | 
 | } | 
 |  | 
 | StatsReport::~StatsReport() = default; | 
 |  | 
 | // 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() { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   for (auto* r : list_) | 
 |     delete r; | 
 | } | 
 |  | 
 | StatsCollection::const_iterator StatsCollection::begin() const { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   return list_.begin(); | 
 | } | 
 |  | 
 | StatsCollection::const_iterator StatsCollection::end() const { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   return list_.end(); | 
 | } | 
 |  | 
 | size_t StatsCollection::size() const { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   return list_.size(); | 
 | } | 
 |  | 
 | StatsReport* StatsCollection::InsertNew(const StatsReport::Id& id) { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   RTC_DCHECK(Find(id) == nullptr); | 
 |   StatsReport* report = new StatsReport(id); | 
 |   list_.push_back(report); | 
 |   return report; | 
 | } | 
 |  | 
 | StatsReport* StatsCollection::FindOrAddNew(const StatsReport::Id& id) { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   StatsReport* ret = Find(id); | 
 |   return ret ? ret : InsertNew(id); | 
 | } | 
 |  | 
 | StatsReport* StatsCollection::ReplaceOrAddNew(const StatsReport::Id& id) { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   RTC_DCHECK(id.get()); | 
 |   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) { | 
 |   RTC_DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   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 |