| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 /* |  | 
| 2  * libjingle |  | 
| 3  * Copyright 2012 Google Inc. |  | 
| 4  * |  | 
| 5  * Redistribution and use in source and binary forms, with or without |  | 
| 6  * modification, are permitted provided that the following conditions are met: |  | 
| 7  * |  | 
| 8  *  1. Redistributions of source code must retain the above copyright notice, |  | 
| 9  *     this list of conditions and the following disclaimer. |  | 
| 10  *  2. Redistributions in binary form must reproduce the above copyright notice, |  | 
| 11  *     this list of conditions and the following disclaimer in the documentation |  | 
| 12  *     and/or other materials provided with the distribution. |  | 
| 13  *  3. The name of the author may not be used to endorse or promote products |  | 
| 14  *     derived from this software without specific prior written permission. |  | 
| 15  * |  | 
| 16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |  | 
| 17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |  | 
| 18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |  | 
| 19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |  | 
| 20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |  | 
| 21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |  | 
| 22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |  | 
| 23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |  | 
| 24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |  | 
| 25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | 
| 26  */ |  | 
| 27 |  | 
| 28 #include "talk/app/webrtc/statscollector.h" |  | 
| 29 |  | 
| 30 #include <utility> |  | 
| 31 #include <vector> |  | 
| 32 |  | 
| 33 #include "talk/app/webrtc/peerconnection.h" |  | 
| 34 #include "talk/session/media/channel.h" |  | 
| 35 #include "webrtc/base/base64.h" |  | 
| 36 #include "webrtc/base/checks.h" |  | 
| 37 #include "webrtc/base/scoped_ptr.h" |  | 
| 38 #include "webrtc/base/timing.h" |  | 
| 39 |  | 
| 40 using rtc::scoped_ptr; |  | 
| 41 |  | 
| 42 namespace webrtc { |  | 
| 43 namespace { |  | 
| 44 |  | 
| 45 // The following is the enum RTCStatsIceCandidateType from |  | 
| 46 // http://w3c.github.io/webrtc-stats/#rtcstatsicecandidatetype-enum such that |  | 
| 47 // our stats report for ice candidate type could conform to that. |  | 
| 48 const char STATSREPORT_LOCAL_PORT_TYPE[] = "host"; |  | 
| 49 const char STATSREPORT_STUN_PORT_TYPE[] = "serverreflexive"; |  | 
| 50 const char STATSREPORT_PRFLX_PORT_TYPE[] = "peerreflexive"; |  | 
| 51 const char STATSREPORT_RELAY_PORT_TYPE[] = "relayed"; |  | 
| 52 |  | 
| 53 // Strings used by the stats collector to report adapter types. This fits the |  | 
| 54 // general stype of http://w3c.github.io/webrtc-stats than what |  | 
| 55 // AdapterTypeToString does. |  | 
| 56 const char* STATSREPORT_ADAPTER_TYPE_ETHERNET = "lan"; |  | 
| 57 const char* STATSREPORT_ADAPTER_TYPE_WIFI = "wlan"; |  | 
| 58 const char* STATSREPORT_ADAPTER_TYPE_WWAN = "wwan"; |  | 
| 59 const char* STATSREPORT_ADAPTER_TYPE_VPN = "vpn"; |  | 
| 60 const char* STATSREPORT_ADAPTER_TYPE_LOOPBACK = "loopback"; |  | 
| 61 |  | 
| 62 template<typename ValueType> |  | 
| 63 struct TypeForAdd { |  | 
| 64   const StatsReport::StatsValueName name; |  | 
| 65   const ValueType& value; |  | 
| 66 }; |  | 
| 67 |  | 
| 68 typedef TypeForAdd<bool> BoolForAdd; |  | 
| 69 typedef TypeForAdd<float> FloatForAdd; |  | 
| 70 typedef TypeForAdd<int64_t> Int64ForAdd; |  | 
| 71 typedef TypeForAdd<int> IntForAdd; |  | 
| 72 |  | 
| 73 StatsReport::Id GetTransportIdFromProxy(const ProxyTransportMap& map, |  | 
| 74                                         const std::string& proxy) { |  | 
| 75   RTC_DCHECK(!proxy.empty()); |  | 
| 76   auto found = map.find(proxy); |  | 
| 77   if (found == map.end()) { |  | 
| 78     return StatsReport::Id(); |  | 
| 79   } |  | 
| 80 |  | 
| 81   return StatsReport::NewComponentId( |  | 
| 82       found->second, cricket::ICE_CANDIDATE_COMPONENT_RTP); |  | 
| 83 } |  | 
| 84 |  | 
| 85 StatsReport* AddTrackReport(StatsCollection* reports, |  | 
| 86                             const std::string& track_id) { |  | 
| 87   // Adds an empty track report. |  | 
| 88   StatsReport::Id id( |  | 
| 89       StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack, track_id)); |  | 
| 90   StatsReport* report = reports->ReplaceOrAddNew(id); |  | 
| 91   report->AddString(StatsReport::kStatsValueNameTrackId, track_id); |  | 
| 92   return report; |  | 
| 93 } |  | 
| 94 |  | 
| 95 template <class TrackVector> |  | 
| 96 void CreateTrackReports(const TrackVector& tracks, StatsCollection* reports, |  | 
| 97                         TrackIdMap& track_ids) { |  | 
| 98   for (const auto& track : tracks) { |  | 
| 99     const std::string& track_id = track->id(); |  | 
| 100     StatsReport* report = AddTrackReport(reports, track_id); |  | 
| 101     RTC_DCHECK(report != nullptr); |  | 
| 102     track_ids[track_id] = report; |  | 
| 103   } |  | 
| 104 } |  | 
| 105 |  | 
| 106 void ExtractCommonSendProperties(const cricket::MediaSenderInfo& info, |  | 
| 107                                  StatsReport* report) { |  | 
| 108   report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name); |  | 
| 109   report->AddInt64(StatsReport::kStatsValueNameBytesSent, info.bytes_sent); |  | 
| 110   report->AddInt64(StatsReport::kStatsValueNameRtt, info.rtt_ms); |  | 
| 111 } |  | 
| 112 |  | 
| 113 void ExtractCommonReceiveProperties(const cricket::MediaReceiverInfo& info, |  | 
| 114                                     StatsReport* report) { |  | 
| 115   report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name); |  | 
| 116 } |  | 
| 117 |  | 
| 118 void SetAudioProcessingStats(StatsReport* report, |  | 
| 119                              bool typing_noise_detected, |  | 
| 120                              int echo_return_loss, |  | 
| 121                              int echo_return_loss_enhancement, |  | 
| 122                              int echo_delay_median_ms, |  | 
| 123                              float aec_quality_min, |  | 
| 124                              int echo_delay_std_ms) { |  | 
| 125   report->AddBoolean(StatsReport::kStatsValueNameTypingNoiseState, |  | 
| 126                      typing_noise_detected); |  | 
| 127   report->AddFloat(StatsReport::kStatsValueNameEchoCancellationQualityMin, |  | 
| 128                    aec_quality_min); |  | 
| 129   const IntForAdd ints[] = { |  | 
| 130     { StatsReport::kStatsValueNameEchoReturnLoss, echo_return_loss }, |  | 
| 131     { StatsReport::kStatsValueNameEchoReturnLossEnhancement, |  | 
| 132       echo_return_loss_enhancement }, |  | 
| 133     { StatsReport::kStatsValueNameEchoDelayMedian, echo_delay_median_ms }, |  | 
| 134     { StatsReport::kStatsValueNameEchoDelayStdDev, echo_delay_std_ms }, |  | 
| 135   }; |  | 
| 136   for (const auto& i : ints) |  | 
| 137     report->AddInt(i.name, i.value); |  | 
| 138 } |  | 
| 139 |  | 
| 140 void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) { |  | 
| 141   ExtractCommonReceiveProperties(info, report); |  | 
| 142   const FloatForAdd floats[] = { |  | 
| 143     { StatsReport::kStatsValueNameExpandRate, info.expand_rate }, |  | 
| 144     { StatsReport::kStatsValueNameSecondaryDecodedRate, |  | 
| 145       info.secondary_decoded_rate }, |  | 
| 146     { StatsReport::kStatsValueNameSpeechExpandRate, info.speech_expand_rate }, |  | 
| 147     { StatsReport::kStatsValueNameAccelerateRate, info.accelerate_rate }, |  | 
| 148     { StatsReport::kStatsValueNamePreemptiveExpandRate, |  | 
| 149       info.preemptive_expand_rate }, |  | 
| 150   }; |  | 
| 151 |  | 
| 152   const IntForAdd ints[] = { |  | 
| 153     { StatsReport::kStatsValueNameAudioOutputLevel, info.audio_level }, |  | 
| 154     { StatsReport::kStatsValueNameCurrentDelayMs, info.delay_estimate_ms }, |  | 
| 155     { StatsReport::kStatsValueNameDecodingCNG, info.decoding_cng }, |  | 
| 156     { StatsReport::kStatsValueNameDecodingCTN, info.decoding_calls_to_neteq }, |  | 
| 157     { StatsReport::kStatsValueNameDecodingCTSG, |  | 
| 158       info.decoding_calls_to_silence_generator }, |  | 
| 159     { StatsReport::kStatsValueNameDecodingNormal, info.decoding_normal }, |  | 
| 160     { StatsReport::kStatsValueNameDecodingPLC, info.decoding_plc }, |  | 
| 161     { StatsReport::kStatsValueNameDecodingPLCCNG, info.decoding_plc_cng }, |  | 
| 162     { StatsReport::kStatsValueNameJitterBufferMs, info.jitter_buffer_ms }, |  | 
| 163     { StatsReport::kStatsValueNameJitterReceived, info.jitter_ms }, |  | 
| 164     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost }, |  | 
| 165     { StatsReport::kStatsValueNamePacketsReceived, info.packets_rcvd }, |  | 
| 166     { StatsReport::kStatsValueNamePreferredJitterBufferMs, |  | 
| 167       info.jitter_buffer_preferred_ms }, |  | 
| 168   }; |  | 
| 169 |  | 
| 170   for (const auto& f : floats) |  | 
| 171     report->AddFloat(f.name, f.value); |  | 
| 172 |  | 
| 173   for (const auto& i : ints) |  | 
| 174     report->AddInt(i.name, i.value); |  | 
| 175 |  | 
| 176   report->AddInt64(StatsReport::kStatsValueNameBytesReceived, |  | 
| 177                    info.bytes_rcvd); |  | 
| 178   report->AddInt64(StatsReport::kStatsValueNameCaptureStartNtpTimeMs, |  | 
| 179                    info.capture_start_ntp_time_ms); |  | 
| 180   report->AddString(StatsReport::kStatsValueNameMediaType, "audio"); |  | 
| 181 } |  | 
| 182 |  | 
| 183 void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) { |  | 
| 184   ExtractCommonSendProperties(info, report); |  | 
| 185 |  | 
| 186   SetAudioProcessingStats( |  | 
| 187       report, info.typing_noise_detected, info.echo_return_loss, |  | 
| 188       info.echo_return_loss_enhancement, info.echo_delay_median_ms, |  | 
| 189       info.aec_quality_min, info.echo_delay_std_ms); |  | 
| 190 |  | 
| 191   RTC_DCHECK_GE(info.audio_level, 0); |  | 
| 192   const IntForAdd ints[] = { |  | 
| 193     { StatsReport::kStatsValueNameAudioInputLevel, info.audio_level}, |  | 
| 194     { StatsReport::kStatsValueNameJitterReceived, info.jitter_ms }, |  | 
| 195     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost }, |  | 
| 196     { StatsReport::kStatsValueNamePacketsSent, info.packets_sent }, |  | 
| 197   }; |  | 
| 198 |  | 
| 199   for (const auto& i : ints) |  | 
| 200     report->AddInt(i.name, i.value); |  | 
| 201   report->AddString(StatsReport::kStatsValueNameMediaType, "audio"); |  | 
| 202 } |  | 
| 203 |  | 
| 204 void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) { |  | 
| 205   ExtractCommonReceiveProperties(info, report); |  | 
| 206   report->AddString(StatsReport::kStatsValueNameCodecImplementationName, |  | 
| 207                     info.decoder_implementation_name); |  | 
| 208   report->AddInt64(StatsReport::kStatsValueNameBytesReceived, |  | 
| 209                    info.bytes_rcvd); |  | 
| 210   report->AddInt64(StatsReport::kStatsValueNameCaptureStartNtpTimeMs, |  | 
| 211                    info.capture_start_ntp_time_ms); |  | 
| 212   const IntForAdd ints[] = { |  | 
| 213     { StatsReport::kStatsValueNameCurrentDelayMs, info.current_delay_ms }, |  | 
| 214     { StatsReport::kStatsValueNameDecodeMs, info.decode_ms }, |  | 
| 215     { StatsReport::kStatsValueNameFirsSent, info.firs_sent }, |  | 
| 216     { StatsReport::kStatsValueNameFrameHeightReceived, info.frame_height }, |  | 
| 217     { StatsReport::kStatsValueNameFrameRateDecoded, info.framerate_decoded }, |  | 
| 218     { StatsReport::kStatsValueNameFrameRateOutput, info.framerate_output }, |  | 
| 219     { StatsReport::kStatsValueNameFrameRateReceived, info.framerate_rcvd }, |  | 
| 220     { StatsReport::kStatsValueNameFrameWidthReceived, info.frame_width }, |  | 
| 221     { StatsReport::kStatsValueNameJitterBufferMs, info.jitter_buffer_ms }, |  | 
| 222     { StatsReport::kStatsValueNameMaxDecodeMs, info.max_decode_ms }, |  | 
| 223     { StatsReport::kStatsValueNameMinPlayoutDelayMs, |  | 
| 224       info.min_playout_delay_ms }, |  | 
| 225     { StatsReport::kStatsValueNameNacksSent, info.nacks_sent }, |  | 
| 226     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost }, |  | 
| 227     { StatsReport::kStatsValueNamePacketsReceived, info.packets_rcvd }, |  | 
| 228     { StatsReport::kStatsValueNamePlisSent, info.plis_sent }, |  | 
| 229     { StatsReport::kStatsValueNameRenderDelayMs, info.render_delay_ms }, |  | 
| 230     { StatsReport::kStatsValueNameTargetDelayMs, info.target_delay_ms }, |  | 
| 231   }; |  | 
| 232 |  | 
| 233   for (const auto& i : ints) |  | 
| 234     report->AddInt(i.name, i.value); |  | 
| 235   report->AddString(StatsReport::kStatsValueNameMediaType, "video"); |  | 
| 236 } |  | 
| 237 |  | 
| 238 void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) { |  | 
| 239   ExtractCommonSendProperties(info, report); |  | 
| 240 |  | 
| 241   report->AddString(StatsReport::kStatsValueNameCodecImplementationName, |  | 
| 242                     info.encoder_implementation_name); |  | 
| 243   report->AddBoolean(StatsReport::kStatsValueNameBandwidthLimitedResolution, |  | 
| 244                      (info.adapt_reason & 0x2) > 0); |  | 
| 245   report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution, |  | 
| 246                      (info.adapt_reason & 0x1) > 0); |  | 
| 247   report->AddBoolean(StatsReport::kStatsValueNameViewLimitedResolution, |  | 
| 248                      (info.adapt_reason & 0x4) > 0); |  | 
| 249 |  | 
| 250   const IntForAdd ints[] = { |  | 
| 251     { StatsReport::kStatsValueNameAdaptationChanges, info.adapt_changes }, |  | 
| 252     { StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms }, |  | 
| 253     { StatsReport::kStatsValueNameEncodeUsagePercent, |  | 
| 254       info.encode_usage_percent }, |  | 
| 255     { StatsReport::kStatsValueNameFirsReceived, info.firs_rcvd }, |  | 
| 256     { StatsReport::kStatsValueNameFrameHeightInput, info.input_frame_height }, |  | 
| 257     { StatsReport::kStatsValueNameFrameHeightSent, info.send_frame_height }, |  | 
| 258     { StatsReport::kStatsValueNameFrameRateInput, info.framerate_input }, |  | 
| 259     { StatsReport::kStatsValueNameFrameRateSent, info.framerate_sent }, |  | 
| 260     { StatsReport::kStatsValueNameFrameWidthInput, info.input_frame_width }, |  | 
| 261     { StatsReport::kStatsValueNameFrameWidthSent, info.send_frame_width }, |  | 
| 262     { StatsReport::kStatsValueNameNacksReceived, info.nacks_rcvd }, |  | 
| 263     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost }, |  | 
| 264     { StatsReport::kStatsValueNamePacketsSent, info.packets_sent }, |  | 
| 265     { StatsReport::kStatsValueNamePlisReceived, info.plis_rcvd }, |  | 
| 266   }; |  | 
| 267 |  | 
| 268   for (const auto& i : ints) |  | 
| 269     report->AddInt(i.name, i.value); |  | 
| 270   report->AddString(StatsReport::kStatsValueNameMediaType, "video"); |  | 
| 271 } |  | 
| 272 |  | 
| 273 void ExtractStats(const cricket::BandwidthEstimationInfo& info, |  | 
| 274                   double stats_gathering_started, |  | 
| 275                   PeerConnectionInterface::StatsOutputLevel level, |  | 
| 276                   StatsReport* report) { |  | 
| 277   RTC_DCHECK(report->type() == StatsReport::kStatsReportTypeBwe); |  | 
| 278 |  | 
| 279   report->set_timestamp(stats_gathering_started); |  | 
| 280   const IntForAdd ints[] = { |  | 
| 281     { StatsReport::kStatsValueNameAvailableSendBandwidth, |  | 
| 282       info.available_send_bandwidth }, |  | 
| 283     { StatsReport::kStatsValueNameAvailableReceiveBandwidth, |  | 
| 284       info.available_recv_bandwidth }, |  | 
| 285     { StatsReport::kStatsValueNameTargetEncBitrate, info.target_enc_bitrate }, |  | 
| 286     { StatsReport::kStatsValueNameActualEncBitrate, info.actual_enc_bitrate }, |  | 
| 287     { StatsReport::kStatsValueNameRetransmitBitrate, info.retransmit_bitrate }, |  | 
| 288     { StatsReport::kStatsValueNameTransmitBitrate, info.transmit_bitrate }, |  | 
| 289   }; |  | 
| 290   for (const auto& i : ints) |  | 
| 291     report->AddInt(i.name, i.value); |  | 
| 292   report->AddInt64(StatsReport::kStatsValueNameBucketDelay, info.bucket_delay); |  | 
| 293 } |  | 
| 294 |  | 
| 295 void ExtractRemoteStats(const cricket::MediaSenderInfo& info, |  | 
| 296                         StatsReport* report) { |  | 
| 297   report->set_timestamp(info.remote_stats[0].timestamp); |  | 
| 298   // TODO(hta): Extract some stats here. |  | 
| 299 } |  | 
| 300 |  | 
| 301 void ExtractRemoteStats(const cricket::MediaReceiverInfo& info, |  | 
| 302                         StatsReport* report) { |  | 
| 303   report->set_timestamp(info.remote_stats[0].timestamp); |  | 
| 304   // TODO(hta): Extract some stats here. |  | 
| 305 } |  | 
| 306 |  | 
| 307 // Template to extract stats from a data vector. |  | 
| 308 // In order to use the template, the functions that are called from it, |  | 
| 309 // ExtractStats and ExtractRemoteStats, must be defined and overloaded |  | 
| 310 // for each type. |  | 
| 311 template<typename T> |  | 
| 312 void ExtractStatsFromList(const std::vector<T>& data, |  | 
| 313                           const StatsReport::Id& transport_id, |  | 
| 314                           StatsCollector* collector, |  | 
| 315                           StatsReport::Direction direction) { |  | 
| 316   for (const auto& d : data) { |  | 
| 317     uint32_t ssrc = d.ssrc(); |  | 
| 318     // Each track can have stats for both local and remote objects. |  | 
| 319     // TODO(hta): Handle the case of multiple SSRCs per object. |  | 
| 320     StatsReport* report = collector->PrepareReport(true, ssrc, transport_id, |  | 
| 321                                                    direction); |  | 
| 322     if (report) |  | 
| 323       ExtractStats(d, report); |  | 
| 324 |  | 
| 325     if (!d.remote_stats.empty()) { |  | 
| 326       report = collector->PrepareReport(false, ssrc, transport_id, direction); |  | 
| 327       if (report) |  | 
| 328         ExtractRemoteStats(d, report); |  | 
| 329     } |  | 
| 330   } |  | 
| 331 } |  | 
| 332 |  | 
| 333 }  // namespace |  | 
| 334 |  | 
| 335 const char* IceCandidateTypeToStatsType(const std::string& candidate_type) { |  | 
| 336   if (candidate_type == cricket::LOCAL_PORT_TYPE) { |  | 
| 337     return STATSREPORT_LOCAL_PORT_TYPE; |  | 
| 338   } |  | 
| 339   if (candidate_type == cricket::STUN_PORT_TYPE) { |  | 
| 340     return STATSREPORT_STUN_PORT_TYPE; |  | 
| 341   } |  | 
| 342   if (candidate_type == cricket::PRFLX_PORT_TYPE) { |  | 
| 343     return STATSREPORT_PRFLX_PORT_TYPE; |  | 
| 344   } |  | 
| 345   if (candidate_type == cricket::RELAY_PORT_TYPE) { |  | 
| 346     return STATSREPORT_RELAY_PORT_TYPE; |  | 
| 347   } |  | 
| 348   RTC_DCHECK(false); |  | 
| 349   return "unknown"; |  | 
| 350 } |  | 
| 351 |  | 
| 352 const char* AdapterTypeToStatsType(rtc::AdapterType type) { |  | 
| 353   switch (type) { |  | 
| 354     case rtc::ADAPTER_TYPE_UNKNOWN: |  | 
| 355       return "unknown"; |  | 
| 356     case rtc::ADAPTER_TYPE_ETHERNET: |  | 
| 357       return STATSREPORT_ADAPTER_TYPE_ETHERNET; |  | 
| 358     case rtc::ADAPTER_TYPE_WIFI: |  | 
| 359       return STATSREPORT_ADAPTER_TYPE_WIFI; |  | 
| 360     case rtc::ADAPTER_TYPE_CELLULAR: |  | 
| 361       return STATSREPORT_ADAPTER_TYPE_WWAN; |  | 
| 362     case rtc::ADAPTER_TYPE_VPN: |  | 
| 363       return STATSREPORT_ADAPTER_TYPE_VPN; |  | 
| 364     case rtc::ADAPTER_TYPE_LOOPBACK: |  | 
| 365       return STATSREPORT_ADAPTER_TYPE_LOOPBACK; |  | 
| 366     default: |  | 
| 367       RTC_DCHECK(false); |  | 
| 368       return ""; |  | 
| 369   } |  | 
| 370 } |  | 
| 371 |  | 
| 372 StatsCollector::StatsCollector(PeerConnection* pc) |  | 
| 373     : pc_(pc), stats_gathering_started_(0) { |  | 
| 374   RTC_DCHECK(pc_); |  | 
| 375 } |  | 
| 376 |  | 
| 377 StatsCollector::~StatsCollector() { |  | 
| 378   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 379 } |  | 
| 380 |  | 
| 381 double StatsCollector::GetTimeNow() { |  | 
| 382   return rtc::Timing::WallTimeNow() * rtc::kNumMillisecsPerSec; |  | 
| 383 } |  | 
| 384 |  | 
| 385 // Adds a MediaStream with tracks that can be used as a |selector| in a call |  | 
| 386 // to GetStats. |  | 
| 387 void StatsCollector::AddStream(MediaStreamInterface* stream) { |  | 
| 388   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 389   RTC_DCHECK(stream != NULL); |  | 
| 390 |  | 
| 391   CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(), |  | 
| 392                                        &reports_, track_ids_); |  | 
| 393   CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(), |  | 
| 394                                        &reports_, track_ids_); |  | 
| 395 } |  | 
| 396 |  | 
| 397 void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track, |  | 
| 398                                         uint32_t ssrc) { |  | 
| 399   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 400   RTC_DCHECK(audio_track != NULL); |  | 
| 401 #if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) |  | 
| 402   for (const auto& track : local_audio_tracks_) |  | 
| 403     RTC_DCHECK(track.first != audio_track || track.second != ssrc); |  | 
| 404 #endif |  | 
| 405 |  | 
| 406   local_audio_tracks_.push_back(std::make_pair(audio_track, ssrc)); |  | 
| 407 |  | 
| 408   // Create the kStatsReportTypeTrack report for the new track if there is no |  | 
| 409   // report yet. |  | 
| 410   StatsReport::Id id(StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack, |  | 
| 411                                              audio_track->id())); |  | 
| 412   StatsReport* report = reports_.Find(id); |  | 
| 413   if (!report) { |  | 
| 414     report = reports_.InsertNew(id); |  | 
| 415     report->AddString(StatsReport::kStatsValueNameTrackId, audio_track->id()); |  | 
| 416   } |  | 
| 417 } |  | 
| 418 |  | 
| 419 void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track, |  | 
| 420                                            uint32_t ssrc) { |  | 
| 421   RTC_DCHECK(audio_track != NULL); |  | 
| 422   local_audio_tracks_.erase(std::remove_if(local_audio_tracks_.begin(), |  | 
| 423       local_audio_tracks_.end(), |  | 
| 424       [audio_track, ssrc](const LocalAudioTrackVector::value_type& track) { |  | 
| 425         return track.first == audio_track && track.second == ssrc; |  | 
| 426       })); |  | 
| 427 } |  | 
| 428 |  | 
| 429 void StatsCollector::GetStats(MediaStreamTrackInterface* track, |  | 
| 430                               StatsReports* reports) { |  | 
| 431   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 432   RTC_DCHECK(reports != NULL); |  | 
| 433   RTC_DCHECK(reports->empty()); |  | 
| 434 |  | 
| 435   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; |  | 
| 436 |  | 
| 437   if (!track) { |  | 
| 438     reports->reserve(reports_.size()); |  | 
| 439     for (auto* r : reports_) |  | 
| 440       reports->push_back(r); |  | 
| 441     return; |  | 
| 442   } |  | 
| 443 |  | 
| 444   StatsReport* report = reports_.Find(StatsReport::NewTypedId( |  | 
| 445       StatsReport::kStatsReportTypeSession, pc_->session()->id())); |  | 
| 446   if (report) |  | 
| 447     reports->push_back(report); |  | 
| 448 |  | 
| 449   report = reports_.Find(StatsReport::NewTypedId( |  | 
| 450       StatsReport::kStatsReportTypeTrack, track->id())); |  | 
| 451 |  | 
| 452   if (!report) |  | 
| 453     return; |  | 
| 454 |  | 
| 455   reports->push_back(report); |  | 
| 456 |  | 
| 457   std::string track_id; |  | 
| 458   for (const auto* r : reports_) { |  | 
| 459     if (r->type() != StatsReport::kStatsReportTypeSsrc) |  | 
| 460       continue; |  | 
| 461 |  | 
| 462     const StatsReport::Value* v = |  | 
| 463         r->FindValue(StatsReport::kStatsValueNameTrackId); |  | 
| 464     if (v && v->string_val() == track->id()) |  | 
| 465       reports->push_back(r); |  | 
| 466   } |  | 
| 467 } |  | 
| 468 |  | 
| 469 void |  | 
| 470 StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) { |  | 
| 471   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 472   double time_now = GetTimeNow(); |  | 
| 473   // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of |  | 
| 474   // ms apart will be ignored. |  | 
| 475   const double kMinGatherStatsPeriod = 50; |  | 
| 476   if (stats_gathering_started_ != 0 && |  | 
| 477       stats_gathering_started_ + kMinGatherStatsPeriod > time_now) { |  | 
| 478     return; |  | 
| 479   } |  | 
| 480   stats_gathering_started_ = time_now; |  | 
| 481 |  | 
| 482   if (pc_->session()) { |  | 
| 483     // TODO(tommi): All of these hop over to the worker thread to fetch |  | 
| 484     // information.  We could use an AsyncInvoker to run all of these and post |  | 
| 485     // the information back to the signaling thread where we can create and |  | 
| 486     // update stats reports.  That would also clean up the threading story a bit |  | 
| 487     // since we'd be creating/updating the stats report objects consistently on |  | 
| 488     // the same thread (this class has no locks right now). |  | 
| 489     ExtractSessionInfo(); |  | 
| 490     ExtractVoiceInfo(); |  | 
| 491     ExtractVideoInfo(level); |  | 
| 492     ExtractDataInfo(); |  | 
| 493     UpdateTrackReports(); |  | 
| 494   } |  | 
| 495 } |  | 
| 496 |  | 
| 497 StatsReport* StatsCollector::PrepareReport( |  | 
| 498     bool local, |  | 
| 499     uint32_t ssrc, |  | 
| 500     const StatsReport::Id& transport_id, |  | 
| 501     StatsReport::Direction direction) { |  | 
| 502   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 503   StatsReport::Id id(StatsReport::NewIdWithDirection( |  | 
| 504       local ? StatsReport::kStatsReportTypeSsrc |  | 
| 505             : StatsReport::kStatsReportTypeRemoteSsrc, |  | 
| 506       rtc::ToString<uint32_t>(ssrc), direction)); |  | 
| 507   StatsReport* report = reports_.Find(id); |  | 
| 508 |  | 
| 509   // Use the ID of the track that is currently mapped to the SSRC, if any. |  | 
| 510   std::string track_id; |  | 
| 511   if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) { |  | 
| 512     if (!report) { |  | 
| 513       // The ssrc is not used by any track or existing report, return NULL |  | 
| 514       // in such case to indicate no report is prepared for the ssrc. |  | 
| 515       return NULL; |  | 
| 516     } |  | 
| 517 |  | 
| 518     // The ssrc is not used by any existing track. Keeps the old track id |  | 
| 519     // since we want to report the stats for inactive ssrc. |  | 
| 520     const StatsReport::Value* v = |  | 
| 521         report->FindValue(StatsReport::kStatsValueNameTrackId); |  | 
| 522     if (v) |  | 
| 523       track_id = v->string_val(); |  | 
| 524   } |  | 
| 525 |  | 
| 526   if (!report) |  | 
| 527     report = reports_.InsertNew(id); |  | 
| 528 |  | 
| 529   // FYI - for remote reports, the timestamp will be overwritten later. |  | 
| 530   report->set_timestamp(stats_gathering_started_); |  | 
| 531 |  | 
| 532   report->AddInt64(StatsReport::kStatsValueNameSsrc, ssrc); |  | 
| 533   report->AddString(StatsReport::kStatsValueNameTrackId, track_id); |  | 
| 534   // Add the mapping of SSRC to transport. |  | 
| 535   report->AddId(StatsReport::kStatsValueNameTransportId, transport_id); |  | 
| 536   return report; |  | 
| 537 } |  | 
| 538 |  | 
| 539 StatsReport* StatsCollector::AddOneCertificateReport( |  | 
| 540     const rtc::SSLCertificate* cert, const StatsReport* issuer) { |  | 
| 541   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 542 |  | 
| 543   // TODO(bemasc): Move this computation to a helper class that caches these |  | 
| 544   // values to reduce CPU use in GetStats.  This will require adding a fast |  | 
| 545   // SSLCertificate::Equals() method to detect certificate changes. |  | 
| 546 |  | 
| 547   std::string digest_algorithm; |  | 
| 548   if (!cert->GetSignatureDigestAlgorithm(&digest_algorithm)) |  | 
| 549     return nullptr; |  | 
| 550 |  | 
| 551   rtc::scoped_ptr<rtc::SSLFingerprint> ssl_fingerprint( |  | 
| 552       rtc::SSLFingerprint::Create(digest_algorithm, cert)); |  | 
| 553 |  | 
| 554   // SSLFingerprint::Create can fail if the algorithm returned by |  | 
| 555   // SSLCertificate::GetSignatureDigestAlgorithm is not supported by the |  | 
| 556   // implementation of SSLCertificate::ComputeDigest.  This currently happens |  | 
| 557   // with MD5- and SHA-224-signed certificates when linked to libNSS. |  | 
| 558   if (!ssl_fingerprint) |  | 
| 559     return nullptr; |  | 
| 560 |  | 
| 561   std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint(); |  | 
| 562 |  | 
| 563   rtc::Buffer der_buffer; |  | 
| 564   cert->ToDER(&der_buffer); |  | 
| 565   std::string der_base64; |  | 
| 566   rtc::Base64::EncodeFromArray(der_buffer.data(), der_buffer.size(), |  | 
| 567                                &der_base64); |  | 
| 568 |  | 
| 569   StatsReport::Id id(StatsReport::NewTypedId( |  | 
| 570       StatsReport::kStatsReportTypeCertificate, fingerprint)); |  | 
| 571   StatsReport* report = reports_.ReplaceOrAddNew(id); |  | 
| 572   report->set_timestamp(stats_gathering_started_); |  | 
| 573   report->AddString(StatsReport::kStatsValueNameFingerprint, fingerprint); |  | 
| 574   report->AddString(StatsReport::kStatsValueNameFingerprintAlgorithm, |  | 
| 575                     digest_algorithm); |  | 
| 576   report->AddString(StatsReport::kStatsValueNameDer, der_base64); |  | 
| 577   if (issuer) |  | 
| 578     report->AddId(StatsReport::kStatsValueNameIssuerId, issuer->id()); |  | 
| 579   return report; |  | 
| 580 } |  | 
| 581 |  | 
| 582 StatsReport* StatsCollector::AddCertificateReports( |  | 
| 583     const rtc::SSLCertificate* cert) { |  | 
| 584   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 585   // Produces a chain of StatsReports representing this certificate and the rest |  | 
| 586   // of its chain, and adds those reports to |reports_|.  The return value is |  | 
| 587   // the id of the leaf report.  The provided cert must be non-null, so at least |  | 
| 588   // one report will always be provided and the returned string will never be |  | 
| 589   // empty. |  | 
| 590   RTC_DCHECK(cert != NULL); |  | 
| 591 |  | 
| 592   StatsReport* issuer = nullptr; |  | 
| 593   rtc::scoped_ptr<rtc::SSLCertChain> chain; |  | 
| 594   if (cert->GetChain(chain.accept())) { |  | 
| 595     // This loop runs in reverse, i.e. from root to leaf, so that each |  | 
| 596     // certificate's issuer's report ID is known before the child certificate's |  | 
| 597     // report is generated.  The root certificate does not have an issuer ID |  | 
| 598     // value. |  | 
| 599     for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) { |  | 
| 600       const rtc::SSLCertificate& cert_i = chain->Get(i); |  | 
| 601       issuer = AddOneCertificateReport(&cert_i, issuer); |  | 
| 602     } |  | 
| 603   } |  | 
| 604   // Add the leaf certificate. |  | 
| 605   return AddOneCertificateReport(cert, issuer); |  | 
| 606 } |  | 
| 607 |  | 
| 608 StatsReport* StatsCollector::AddConnectionInfoReport( |  | 
| 609     const std::string& content_name, int component, int connection_id, |  | 
| 610     const StatsReport::Id& channel_report_id, |  | 
| 611     const cricket::ConnectionInfo& info) { |  | 
| 612   StatsReport::Id id(StatsReport::NewCandidatePairId(content_name, component, |  | 
| 613                                                      connection_id)); |  | 
| 614   StatsReport* report = reports_.ReplaceOrAddNew(id); |  | 
| 615   report->set_timestamp(stats_gathering_started_); |  | 
| 616 |  | 
| 617   const BoolForAdd bools[] = { |  | 
| 618     {StatsReport::kStatsValueNameActiveConnection, info.best_connection}, |  | 
| 619     {StatsReport::kStatsValueNameReceiving, info.receiving}, |  | 
| 620     {StatsReport::kStatsValueNameWritable, info.writable}, |  | 
| 621   }; |  | 
| 622   for (const auto& b : bools) |  | 
| 623     report->AddBoolean(b.name, b.value); |  | 
| 624 |  | 
| 625   report->AddId(StatsReport::kStatsValueNameChannelId, channel_report_id); |  | 
| 626   report->AddId(StatsReport::kStatsValueNameLocalCandidateId, |  | 
| 627                 AddCandidateReport(info.local_candidate, true)->id()); |  | 
| 628   report->AddId(StatsReport::kStatsValueNameRemoteCandidateId, |  | 
| 629                 AddCandidateReport(info.remote_candidate, false)->id()); |  | 
| 630 |  | 
| 631   const Int64ForAdd int64s[] = { |  | 
| 632     { StatsReport::kStatsValueNameBytesReceived, info.recv_total_bytes }, |  | 
| 633     { StatsReport::kStatsValueNameBytesSent, info.sent_total_bytes }, |  | 
| 634     { StatsReport::kStatsValueNamePacketsSent, info.sent_total_packets }, |  | 
| 635     { StatsReport::kStatsValueNameRtt, info.rtt }, |  | 
| 636     { StatsReport::kStatsValueNameSendPacketsDiscarded, |  | 
| 637       info.sent_discarded_packets }, |  | 
| 638   }; |  | 
| 639   for (const auto& i : int64s) |  | 
| 640     report->AddInt64(i.name, i.value); |  | 
| 641 |  | 
| 642   report->AddString(StatsReport::kStatsValueNameLocalAddress, |  | 
| 643                     info.local_candidate.address().ToString()); |  | 
| 644   report->AddString(StatsReport::kStatsValueNameLocalCandidateType, |  | 
| 645                     info.local_candidate.type()); |  | 
| 646   report->AddString(StatsReport::kStatsValueNameRemoteAddress, |  | 
| 647                     info.remote_candidate.address().ToString()); |  | 
| 648   report->AddString(StatsReport::kStatsValueNameRemoteCandidateType, |  | 
| 649                     info.remote_candidate.type()); |  | 
| 650   report->AddString(StatsReport::kStatsValueNameTransportType, |  | 
| 651                     info.local_candidate.protocol()); |  | 
| 652 |  | 
| 653   return report; |  | 
| 654 } |  | 
| 655 |  | 
| 656 StatsReport* StatsCollector::AddCandidateReport( |  | 
| 657     const cricket::Candidate& candidate, |  | 
| 658     bool local) { |  | 
| 659   StatsReport::Id id(StatsReport::NewCandidateId(local, candidate.id())); |  | 
| 660   StatsReport* report = reports_.Find(id); |  | 
| 661   if (!report) { |  | 
| 662     report = reports_.InsertNew(id); |  | 
| 663     report->set_timestamp(stats_gathering_started_); |  | 
| 664     if (local) { |  | 
| 665       report->AddString(StatsReport::kStatsValueNameCandidateNetworkType, |  | 
| 666                         AdapterTypeToStatsType(candidate.network_type())); |  | 
| 667     } |  | 
| 668     report->AddString(StatsReport::kStatsValueNameCandidateIPAddress, |  | 
| 669                       candidate.address().ipaddr().ToString()); |  | 
| 670     report->AddString(StatsReport::kStatsValueNameCandidatePortNumber, |  | 
| 671                       candidate.address().PortAsString()); |  | 
| 672     report->AddInt(StatsReport::kStatsValueNameCandidatePriority, |  | 
| 673                    candidate.priority()); |  | 
| 674     report->AddString(StatsReport::kStatsValueNameCandidateType, |  | 
| 675                       IceCandidateTypeToStatsType(candidate.type())); |  | 
| 676     report->AddString(StatsReport::kStatsValueNameCandidateTransportType, |  | 
| 677                       candidate.protocol()); |  | 
| 678   } |  | 
| 679 |  | 
| 680   return report; |  | 
| 681 } |  | 
| 682 |  | 
| 683 void StatsCollector::ExtractSessionInfo() { |  | 
| 684   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 685 |  | 
| 686   // Extract information from the base session. |  | 
| 687   StatsReport::Id id(StatsReport::NewTypedId( |  | 
| 688       StatsReport::kStatsReportTypeSession, pc_->session()->id())); |  | 
| 689   StatsReport* report = reports_.ReplaceOrAddNew(id); |  | 
| 690   report->set_timestamp(stats_gathering_started_); |  | 
| 691   report->AddBoolean(StatsReport::kStatsValueNameInitiator, |  | 
| 692                      pc_->session()->initial_offerer()); |  | 
| 693 |  | 
| 694   SessionStats stats; |  | 
| 695   if (!pc_->session()->GetTransportStats(&stats)) { |  | 
| 696     return; |  | 
| 697   } |  | 
| 698 |  | 
| 699   // Store the proxy map away for use in SSRC reporting. |  | 
| 700   // TODO(tommi): This shouldn't be necessary if we post the stats back to the |  | 
| 701   // signaling thread after fetching them on the worker thread, then just use |  | 
| 702   // the proxy map directly from the session stats. |  | 
| 703   // As is, if GetStats() failed, we could be using old (incorrect?) proxy |  | 
| 704   // data. |  | 
| 705   proxy_to_transport_ = stats.proxy_to_transport; |  | 
| 706 |  | 
| 707   for (const auto& transport_iter : stats.transport_stats) { |  | 
| 708     // Attempt to get a copy of the certificates from the transport and |  | 
| 709     // expose them in stats reports.  All channels in a transport share the |  | 
| 710     // same local and remote certificates. |  | 
| 711     // |  | 
| 712     StatsReport::Id local_cert_report_id, remote_cert_report_id; |  | 
| 713     rtc::scoped_refptr<rtc::RTCCertificate> certificate; |  | 
| 714     if (pc_->session()->GetLocalCertificate( |  | 
| 715             transport_iter.second.transport_name, &certificate)) { |  | 
| 716       StatsReport* r = AddCertificateReports(&(certificate->ssl_certificate())); |  | 
| 717       if (r) |  | 
| 718         local_cert_report_id = r->id(); |  | 
| 719     } |  | 
| 720 |  | 
| 721     rtc::scoped_ptr<rtc::SSLCertificate> cert; |  | 
| 722     if (pc_->session()->GetRemoteSSLCertificate( |  | 
| 723             transport_iter.second.transport_name, cert.accept())) { |  | 
| 724       StatsReport* r = AddCertificateReports(cert.get()); |  | 
| 725       if (r) |  | 
| 726         remote_cert_report_id = r->id(); |  | 
| 727     } |  | 
| 728 |  | 
| 729     for (const auto& channel_iter : transport_iter.second.channel_stats) { |  | 
| 730       StatsReport::Id id(StatsReport::NewComponentId( |  | 
| 731           transport_iter.second.transport_name, channel_iter.component)); |  | 
| 732       StatsReport* channel_report = reports_.ReplaceOrAddNew(id); |  | 
| 733       channel_report->set_timestamp(stats_gathering_started_); |  | 
| 734       channel_report->AddInt(StatsReport::kStatsValueNameComponent, |  | 
| 735                              channel_iter.component); |  | 
| 736       if (local_cert_report_id.get()) { |  | 
| 737         channel_report->AddId(StatsReport::kStatsValueNameLocalCertificateId, |  | 
| 738                               local_cert_report_id); |  | 
| 739       } |  | 
| 740       if (remote_cert_report_id.get()) { |  | 
| 741         channel_report->AddId(StatsReport::kStatsValueNameRemoteCertificateId, |  | 
| 742                               remote_cert_report_id); |  | 
| 743       } |  | 
| 744       int srtp_crypto_suite = channel_iter.srtp_crypto_suite; |  | 
| 745       if (srtp_crypto_suite != rtc::SRTP_INVALID_CRYPTO_SUITE && |  | 
| 746           rtc::SrtpCryptoSuiteToName(srtp_crypto_suite).length()) { |  | 
| 747         channel_report->AddString( |  | 
| 748             StatsReport::kStatsValueNameSrtpCipher, |  | 
| 749             rtc::SrtpCryptoSuiteToName(srtp_crypto_suite)); |  | 
| 750       } |  | 
| 751       int ssl_cipher_suite = channel_iter.ssl_cipher_suite; |  | 
| 752       if (ssl_cipher_suite != rtc::TLS_NULL_WITH_NULL_NULL && |  | 
| 753           rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite) |  | 
| 754               .length()) { |  | 
| 755         channel_report->AddString( |  | 
| 756             StatsReport::kStatsValueNameDtlsCipher, |  | 
| 757             rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite)); |  | 
| 758       } |  | 
| 759 |  | 
| 760       int connection_id = 0; |  | 
| 761       for (const cricket::ConnectionInfo& info : |  | 
| 762                channel_iter.connection_infos) { |  | 
| 763         StatsReport* connection_report = AddConnectionInfoReport( |  | 
| 764             transport_iter.first, channel_iter.component, connection_id++, |  | 
| 765             channel_report->id(), info); |  | 
| 766         if (info.best_connection) { |  | 
| 767           channel_report->AddId( |  | 
| 768               StatsReport::kStatsValueNameSelectedCandidatePairId, |  | 
| 769               connection_report->id()); |  | 
| 770         } |  | 
| 771       } |  | 
| 772     } |  | 
| 773   } |  | 
| 774 } |  | 
| 775 |  | 
| 776 void StatsCollector::ExtractVoiceInfo() { |  | 
| 777   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 778 |  | 
| 779   if (!pc_->session()->voice_channel()) { |  | 
| 780     return; |  | 
| 781   } |  | 
| 782   cricket::VoiceMediaInfo voice_info; |  | 
| 783   if (!pc_->session()->voice_channel()->GetStats(&voice_info)) { |  | 
| 784     LOG(LS_ERROR) << "Failed to get voice channel stats."; |  | 
| 785     return; |  | 
| 786   } |  | 
| 787 |  | 
| 788   // TODO(tommi): The above code should run on the worker thread and post the |  | 
| 789   // results back to the signaling thread, where we can add data to the reports. |  | 
| 790   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; |  | 
| 791 |  | 
| 792   StatsReport::Id transport_id(GetTransportIdFromProxy( |  | 
| 793       proxy_to_transport_, pc_->session()->voice_channel()->content_name())); |  | 
| 794   if (!transport_id.get()) { |  | 
| 795     LOG(LS_ERROR) << "Failed to get transport name for proxy " |  | 
| 796                   << pc_->session()->voice_channel()->content_name(); |  | 
| 797     return; |  | 
| 798   } |  | 
| 799 |  | 
| 800   ExtractStatsFromList(voice_info.receivers, transport_id, this, |  | 
| 801       StatsReport::kReceive); |  | 
| 802   ExtractStatsFromList(voice_info.senders, transport_id, this, |  | 
| 803       StatsReport::kSend); |  | 
| 804 |  | 
| 805   UpdateStatsFromExistingLocalAudioTracks(); |  | 
| 806 } |  | 
| 807 |  | 
| 808 void StatsCollector::ExtractVideoInfo( |  | 
| 809     PeerConnectionInterface::StatsOutputLevel level) { |  | 
| 810   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 811 |  | 
| 812   if (!pc_->session()->video_channel()) |  | 
| 813     return; |  | 
| 814 |  | 
| 815   cricket::VideoMediaInfo video_info; |  | 
| 816   if (!pc_->session()->video_channel()->GetStats(&video_info)) { |  | 
| 817     LOG(LS_ERROR) << "Failed to get video channel stats."; |  | 
| 818     return; |  | 
| 819   } |  | 
| 820 |  | 
| 821   // TODO(tommi): The above code should run on the worker thread and post the |  | 
| 822   // results back to the signaling thread, where we can add data to the reports. |  | 
| 823   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; |  | 
| 824 |  | 
| 825   StatsReport::Id transport_id(GetTransportIdFromProxy( |  | 
| 826       proxy_to_transport_, pc_->session()->video_channel()->content_name())); |  | 
| 827   if (!transport_id.get()) { |  | 
| 828     LOG(LS_ERROR) << "Failed to get transport name for proxy " |  | 
| 829                   << pc_->session()->video_channel()->content_name(); |  | 
| 830     return; |  | 
| 831   } |  | 
| 832   ExtractStatsFromList(video_info.receivers, transport_id, this, |  | 
| 833       StatsReport::kReceive); |  | 
| 834   ExtractStatsFromList(video_info.senders, transport_id, this, |  | 
| 835       StatsReport::kSend); |  | 
| 836   if (video_info.bw_estimations.size() != 1) { |  | 
| 837     LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size(); |  | 
| 838   } else { |  | 
| 839     StatsReport::Id report_id(StatsReport::NewBandwidthEstimationId()); |  | 
| 840     StatsReport* report = reports_.FindOrAddNew(report_id); |  | 
| 841     ExtractStats( |  | 
| 842         video_info.bw_estimations[0], stats_gathering_started_, level, report); |  | 
| 843   } |  | 
| 844 } |  | 
| 845 |  | 
| 846 void StatsCollector::ExtractDataInfo() { |  | 
| 847   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 848 |  | 
| 849   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; |  | 
| 850 |  | 
| 851   for (const auto& dc : pc_->sctp_data_channels()) { |  | 
| 852     StatsReport::Id id(StatsReport::NewTypedIntId( |  | 
| 853         StatsReport::kStatsReportTypeDataChannel, dc->id())); |  | 
| 854     StatsReport* report = reports_.ReplaceOrAddNew(id); |  | 
| 855     report->set_timestamp(stats_gathering_started_); |  | 
| 856     report->AddString(StatsReport::kStatsValueNameLabel, dc->label()); |  | 
| 857     report->AddInt(StatsReport::kStatsValueNameDataChannelId, dc->id()); |  | 
| 858     report->AddString(StatsReport::kStatsValueNameProtocol, dc->protocol()); |  | 
| 859     report->AddString(StatsReport::kStatsValueNameState, |  | 
| 860                       DataChannelInterface::DataStateString(dc->state())); |  | 
| 861   } |  | 
| 862 } |  | 
| 863 |  | 
| 864 StatsReport* StatsCollector::GetReport(const StatsReport::StatsType& type, |  | 
| 865                                        const std::string& id, |  | 
| 866                                        StatsReport::Direction direction) { |  | 
| 867   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 868   RTC_DCHECK(type == StatsReport::kStatsReportTypeSsrc || |  | 
| 869              type == StatsReport::kStatsReportTypeRemoteSsrc); |  | 
| 870   return reports_.Find(StatsReport::NewIdWithDirection(type, id, direction)); |  | 
| 871 } |  | 
| 872 |  | 
| 873 void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() { |  | 
| 874   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 875   // Loop through the existing local audio tracks. |  | 
| 876   for (const auto& it : local_audio_tracks_) { |  | 
| 877     AudioTrackInterface* track = it.first; |  | 
| 878     uint32_t ssrc = it.second; |  | 
| 879     StatsReport* report = |  | 
| 880         GetReport(StatsReport::kStatsReportTypeSsrc, |  | 
| 881                   rtc::ToString<uint32_t>(ssrc), StatsReport::kSend); |  | 
| 882     if (report == NULL) { |  | 
| 883       // This can happen if a local audio track is added to a stream on the |  | 
| 884       // fly and the report has not been set up yet. Do nothing in this case. |  | 
| 885       LOG(LS_ERROR) << "Stats report does not exist for ssrc " << ssrc; |  | 
| 886       continue; |  | 
| 887     } |  | 
| 888 |  | 
| 889     // The same ssrc can be used by both local and remote audio tracks. |  | 
| 890     const StatsReport::Value* v = |  | 
| 891         report->FindValue(StatsReport::kStatsValueNameTrackId); |  | 
| 892     if (!v || v->string_val() != track->id()) |  | 
| 893       continue; |  | 
| 894 |  | 
| 895     report->set_timestamp(stats_gathering_started_); |  | 
| 896     UpdateReportFromAudioTrack(track, report); |  | 
| 897   } |  | 
| 898 } |  | 
| 899 |  | 
| 900 void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, |  | 
| 901                                                 StatsReport* report) { |  | 
| 902   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 903   RTC_DCHECK(track != NULL); |  | 
| 904 |  | 
| 905   // Don't overwrite report values if they're not available. |  | 
| 906   int signal_level; |  | 
| 907   if (track->GetSignalLevel(&signal_level)) { |  | 
| 908     RTC_DCHECK_GE(signal_level, 0); |  | 
| 909     report->AddInt(StatsReport::kStatsValueNameAudioInputLevel, signal_level); |  | 
| 910   } |  | 
| 911 |  | 
| 912   auto audio_processor(track->GetAudioProcessor()); |  | 
| 913 |  | 
| 914   if (audio_processor.get()) { |  | 
| 915     AudioProcessorInterface::AudioProcessorStats stats; |  | 
| 916     audio_processor->GetStats(&stats); |  | 
| 917 |  | 
| 918     SetAudioProcessingStats( |  | 
| 919         report, stats.typing_noise_detected, stats.echo_return_loss, |  | 
| 920         stats.echo_return_loss_enhancement, stats.echo_delay_median_ms, |  | 
| 921         stats.aec_quality_min, stats.echo_delay_std_ms); |  | 
| 922   } |  | 
| 923 } |  | 
| 924 |  | 
| 925 bool StatsCollector::GetTrackIdBySsrc(uint32_t ssrc, |  | 
| 926                                       std::string* track_id, |  | 
| 927                                       StatsReport::Direction direction) { |  | 
| 928   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 929   if (direction == StatsReport::kSend) { |  | 
| 930     if (!pc_->session()->GetLocalTrackIdBySsrc(ssrc, track_id)) { |  | 
| 931       LOG(LS_WARNING) << "The SSRC " << ssrc |  | 
| 932                       << " is not associated with a sending track"; |  | 
| 933       return false; |  | 
| 934     } |  | 
| 935   } else { |  | 
| 936     RTC_DCHECK(direction == StatsReport::kReceive); |  | 
| 937     if (!pc_->session()->GetRemoteTrackIdBySsrc(ssrc, track_id)) { |  | 
| 938       LOG(LS_WARNING) << "The SSRC " << ssrc |  | 
| 939                       << " is not associated with a receiving track"; |  | 
| 940       return false; |  | 
| 941     } |  | 
| 942   } |  | 
| 943 |  | 
| 944   return true; |  | 
| 945 } |  | 
| 946 |  | 
| 947 void StatsCollector::UpdateTrackReports() { |  | 
| 948   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent()); |  | 
| 949 |  | 
| 950   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; |  | 
| 951 |  | 
| 952   for (const auto& entry : track_ids_) { |  | 
| 953     StatsReport* report = entry.second; |  | 
| 954     report->set_timestamp(stats_gathering_started_); |  | 
| 955   } |  | 
| 956 } |  | 
| 957 |  | 
| 958 void StatsCollector::ClearUpdateStatsCacheForTest() { |  | 
| 959   stats_gathering_started_ = 0; |  | 
| 960 } |  | 
| 961 |  | 
| 962 }  // namespace webrtc |  | 
| OLD | NEW | 
|---|