Home | History | Annotate | Download | only in webrtc
      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 }
    181 
    182 void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) {
    183   ExtractCommonSendProperties(info, report);
    184 
    185   SetAudioProcessingStats(
    186       report, info.typing_noise_detected, info.echo_return_loss,
    187       info.echo_return_loss_enhancement, info.echo_delay_median_ms,
    188       info.aec_quality_min, info.echo_delay_std_ms);
    189 
    190   RTC_DCHECK_GE(info.audio_level, 0);
    191   const IntForAdd ints[] = {
    192     { StatsReport::kStatsValueNameAudioInputLevel, info.audio_level},
    193     { StatsReport::kStatsValueNameJitterReceived, info.jitter_ms },
    194     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
    195     { StatsReport::kStatsValueNamePacketsSent, info.packets_sent },
    196   };
    197 
    198   for (const auto& i : ints)
    199     report->AddInt(i.name, i.value);
    200 }
    201 
    202 void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) {
    203   ExtractCommonReceiveProperties(info, report);
    204   report->AddString(StatsReport::kStatsValueNameCodecImplementationName,
    205                     info.decoder_implementation_name);
    206   report->AddInt64(StatsReport::kStatsValueNameBytesReceived,
    207                    info.bytes_rcvd);
    208   report->AddInt64(StatsReport::kStatsValueNameCaptureStartNtpTimeMs,
    209                    info.capture_start_ntp_time_ms);
    210   const IntForAdd ints[] = {
    211     { StatsReport::kStatsValueNameCurrentDelayMs, info.current_delay_ms },
    212     { StatsReport::kStatsValueNameDecodeMs, info.decode_ms },
    213     { StatsReport::kStatsValueNameFirsSent, info.firs_sent },
    214     { StatsReport::kStatsValueNameFrameHeightReceived, info.frame_height },
    215     { StatsReport::kStatsValueNameFrameRateDecoded, info.framerate_decoded },
    216     { StatsReport::kStatsValueNameFrameRateOutput, info.framerate_output },
    217     { StatsReport::kStatsValueNameFrameRateReceived, info.framerate_rcvd },
    218     { StatsReport::kStatsValueNameFrameWidthReceived, info.frame_width },
    219     { StatsReport::kStatsValueNameJitterBufferMs, info.jitter_buffer_ms },
    220     { StatsReport::kStatsValueNameMaxDecodeMs, info.max_decode_ms },
    221     { StatsReport::kStatsValueNameMinPlayoutDelayMs,
    222       info.min_playout_delay_ms },
    223     { StatsReport::kStatsValueNameNacksSent, info.nacks_sent },
    224     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
    225     { StatsReport::kStatsValueNamePacketsReceived, info.packets_rcvd },
    226     { StatsReport::kStatsValueNamePlisSent, info.plis_sent },
    227     { StatsReport::kStatsValueNameRenderDelayMs, info.render_delay_ms },
    228     { StatsReport::kStatsValueNameTargetDelayMs, info.target_delay_ms },
    229   };
    230 
    231   for (const auto& i : ints)
    232     report->AddInt(i.name, i.value);
    233 }
    234 
    235 void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) {
    236   ExtractCommonSendProperties(info, report);
    237 
    238   report->AddString(StatsReport::kStatsValueNameCodecImplementationName,
    239                     info.encoder_implementation_name);
    240   report->AddBoolean(StatsReport::kStatsValueNameBandwidthLimitedResolution,
    241                      (info.adapt_reason & 0x2) > 0);
    242   report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution,
    243                      (info.adapt_reason & 0x1) > 0);
    244   report->AddBoolean(StatsReport::kStatsValueNameViewLimitedResolution,
    245                      (info.adapt_reason & 0x4) > 0);
    246 
    247   const IntForAdd ints[] = {
    248     { StatsReport::kStatsValueNameAdaptationChanges, info.adapt_changes },
    249     { StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms },
    250     { StatsReport::kStatsValueNameEncodeUsagePercent,
    251       info.encode_usage_percent },
    252     { StatsReport::kStatsValueNameFirsReceived, info.firs_rcvd },
    253     { StatsReport::kStatsValueNameFrameHeightInput, info.input_frame_height },
    254     { StatsReport::kStatsValueNameFrameHeightSent, info.send_frame_height },
    255     { StatsReport::kStatsValueNameFrameRateInput, info.framerate_input },
    256     { StatsReport::kStatsValueNameFrameRateSent, info.framerate_sent },
    257     { StatsReport::kStatsValueNameFrameWidthInput, info.input_frame_width },
    258     { StatsReport::kStatsValueNameFrameWidthSent, info.send_frame_width },
    259     { StatsReport::kStatsValueNameNacksReceived, info.nacks_rcvd },
    260     { StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
    261     { StatsReport::kStatsValueNamePacketsSent, info.packets_sent },
    262     { StatsReport::kStatsValueNamePlisReceived, info.plis_rcvd },
    263   };
    264 
    265   for (const auto& i : ints)
    266     report->AddInt(i.name, i.value);
    267 }
    268 
    269 void ExtractStats(const cricket::BandwidthEstimationInfo& info,
    270                   double stats_gathering_started,
    271                   PeerConnectionInterface::StatsOutputLevel level,
    272                   StatsReport* report) {
    273   RTC_DCHECK(report->type() == StatsReport::kStatsReportTypeBwe);
    274 
    275   report->set_timestamp(stats_gathering_started);
    276   const IntForAdd ints[] = {
    277     { StatsReport::kStatsValueNameAvailableSendBandwidth,
    278       info.available_send_bandwidth },
    279     { StatsReport::kStatsValueNameAvailableReceiveBandwidth,
    280       info.available_recv_bandwidth },
    281     { StatsReport::kStatsValueNameTargetEncBitrate, info.target_enc_bitrate },
    282     { StatsReport::kStatsValueNameActualEncBitrate, info.actual_enc_bitrate },
    283     { StatsReport::kStatsValueNameRetransmitBitrate, info.retransmit_bitrate },
    284     { StatsReport::kStatsValueNameTransmitBitrate, info.transmit_bitrate },
    285   };
    286   for (const auto& i : ints)
    287     report->AddInt(i.name, i.value);
    288   report->AddInt64(StatsReport::kStatsValueNameBucketDelay, info.bucket_delay);
    289 }
    290 
    291 void ExtractRemoteStats(const cricket::MediaSenderInfo& info,
    292                         StatsReport* report) {
    293   report->set_timestamp(info.remote_stats[0].timestamp);
    294   // TODO(hta): Extract some stats here.
    295 }
    296 
    297 void ExtractRemoteStats(const cricket::MediaReceiverInfo& info,
    298                         StatsReport* report) {
    299   report->set_timestamp(info.remote_stats[0].timestamp);
    300   // TODO(hta): Extract some stats here.
    301 }
    302 
    303 // Template to extract stats from a data vector.
    304 // In order to use the template, the functions that are called from it,
    305 // ExtractStats and ExtractRemoteStats, must be defined and overloaded
    306 // for each type.
    307 template<typename T>
    308 void ExtractStatsFromList(const std::vector<T>& data,
    309                           const StatsReport::Id& transport_id,
    310                           StatsCollector* collector,
    311                           StatsReport::Direction direction) {
    312   for (const auto& d : data) {
    313     uint32_t ssrc = d.ssrc();
    314     // Each track can have stats for both local and remote objects.
    315     // TODO(hta): Handle the case of multiple SSRCs per object.
    316     StatsReport* report = collector->PrepareReport(true, ssrc, transport_id,
    317                                                    direction);
    318     if (report)
    319       ExtractStats(d, report);
    320 
    321     if (!d.remote_stats.empty()) {
    322       report = collector->PrepareReport(false, ssrc, transport_id, direction);
    323       if (report)
    324         ExtractRemoteStats(d, report);
    325     }
    326   }
    327 }
    328 
    329 }  // namespace
    330 
    331 const char* IceCandidateTypeToStatsType(const std::string& candidate_type) {
    332   if (candidate_type == cricket::LOCAL_PORT_TYPE) {
    333     return STATSREPORT_LOCAL_PORT_TYPE;
    334   }
    335   if (candidate_type == cricket::STUN_PORT_TYPE) {
    336     return STATSREPORT_STUN_PORT_TYPE;
    337   }
    338   if (candidate_type == cricket::PRFLX_PORT_TYPE) {
    339     return STATSREPORT_PRFLX_PORT_TYPE;
    340   }
    341   if (candidate_type == cricket::RELAY_PORT_TYPE) {
    342     return STATSREPORT_RELAY_PORT_TYPE;
    343   }
    344   RTC_DCHECK(false);
    345   return "unknown";
    346 }
    347 
    348 const char* AdapterTypeToStatsType(rtc::AdapterType type) {
    349   switch (type) {
    350     case rtc::ADAPTER_TYPE_UNKNOWN:
    351       return "unknown";
    352     case rtc::ADAPTER_TYPE_ETHERNET:
    353       return STATSREPORT_ADAPTER_TYPE_ETHERNET;
    354     case rtc::ADAPTER_TYPE_WIFI:
    355       return STATSREPORT_ADAPTER_TYPE_WIFI;
    356     case rtc::ADAPTER_TYPE_CELLULAR:
    357       return STATSREPORT_ADAPTER_TYPE_WWAN;
    358     case rtc::ADAPTER_TYPE_VPN:
    359       return STATSREPORT_ADAPTER_TYPE_VPN;
    360     case rtc::ADAPTER_TYPE_LOOPBACK:
    361       return STATSREPORT_ADAPTER_TYPE_LOOPBACK;
    362     default:
    363       RTC_DCHECK(false);
    364       return "";
    365   }
    366 }
    367 
    368 StatsCollector::StatsCollector(PeerConnection* pc)
    369     : pc_(pc), stats_gathering_started_(0) {
    370   RTC_DCHECK(pc_);
    371 }
    372 
    373 StatsCollector::~StatsCollector() {
    374   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    375 }
    376 
    377 double StatsCollector::GetTimeNow() {
    378   return rtc::Timing::WallTimeNow() * rtc::kNumMillisecsPerSec;
    379 }
    380 
    381 // Adds a MediaStream with tracks that can be used as a |selector| in a call
    382 // to GetStats.
    383 void StatsCollector::AddStream(MediaStreamInterface* stream) {
    384   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    385   RTC_DCHECK(stream != NULL);
    386 
    387   CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(),
    388                                        &reports_, track_ids_);
    389   CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(),
    390                                        &reports_, track_ids_);
    391 }
    392 
    393 void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track,
    394                                         uint32_t ssrc) {
    395   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    396   RTC_DCHECK(audio_track != NULL);
    397 #if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
    398   for (const auto& track : local_audio_tracks_)
    399     RTC_DCHECK(track.first != audio_track || track.second != ssrc);
    400 #endif
    401 
    402   local_audio_tracks_.push_back(std::make_pair(audio_track, ssrc));
    403 
    404   // Create the kStatsReportTypeTrack report for the new track if there is no
    405   // report yet.
    406   StatsReport::Id id(StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack,
    407                                              audio_track->id()));
    408   StatsReport* report = reports_.Find(id);
    409   if (!report) {
    410     report = reports_.InsertNew(id);
    411     report->AddString(StatsReport::kStatsValueNameTrackId, audio_track->id());
    412   }
    413 }
    414 
    415 void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
    416                                            uint32_t ssrc) {
    417   RTC_DCHECK(audio_track != NULL);
    418   local_audio_tracks_.erase(std::remove_if(local_audio_tracks_.begin(),
    419       local_audio_tracks_.end(),
    420       [audio_track, ssrc](const LocalAudioTrackVector::value_type& track) {
    421         return track.first == audio_track && track.second == ssrc;
    422       }));
    423 }
    424 
    425 void StatsCollector::GetStats(MediaStreamTrackInterface* track,
    426                               StatsReports* reports) {
    427   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    428   RTC_DCHECK(reports != NULL);
    429   RTC_DCHECK(reports->empty());
    430 
    431   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
    432 
    433   if (!track) {
    434     reports->reserve(reports_.size());
    435     for (auto* r : reports_)
    436       reports->push_back(r);
    437     return;
    438   }
    439 
    440   StatsReport* report = reports_.Find(StatsReport::NewTypedId(
    441       StatsReport::kStatsReportTypeSession, pc_->session()->id()));
    442   if (report)
    443     reports->push_back(report);
    444 
    445   report = reports_.Find(StatsReport::NewTypedId(
    446       StatsReport::kStatsReportTypeTrack, track->id()));
    447 
    448   if (!report)
    449     return;
    450 
    451   reports->push_back(report);
    452 
    453   std::string track_id;
    454   for (const auto* r : reports_) {
    455     if (r->type() != StatsReport::kStatsReportTypeSsrc)
    456       continue;
    457 
    458     const StatsReport::Value* v =
    459         r->FindValue(StatsReport::kStatsValueNameTrackId);
    460     if (v && v->string_val() == track->id())
    461       reports->push_back(r);
    462   }
    463 }
    464 
    465 void
    466 StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
    467   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    468   double time_now = GetTimeNow();
    469   // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of
    470   // ms apart will be ignored.
    471   const double kMinGatherStatsPeriod = 50;
    472   if (stats_gathering_started_ != 0 &&
    473       stats_gathering_started_ + kMinGatherStatsPeriod > time_now) {
    474     return;
    475   }
    476   stats_gathering_started_ = time_now;
    477 
    478   if (pc_->session()) {
    479     // TODO(tommi): All of these hop over to the worker thread to fetch
    480     // information.  We could use an AsyncInvoker to run all of these and post
    481     // the information back to the signaling thread where we can create and
    482     // update stats reports.  That would also clean up the threading story a bit
    483     // since we'd be creating/updating the stats report objects consistently on
    484     // the same thread (this class has no locks right now).
    485     ExtractSessionInfo();
    486     ExtractVoiceInfo();
    487     ExtractVideoInfo(level);
    488     ExtractDataInfo();
    489     UpdateTrackReports();
    490   }
    491 }
    492 
    493 StatsReport* StatsCollector::PrepareReport(
    494     bool local,
    495     uint32_t ssrc,
    496     const StatsReport::Id& transport_id,
    497     StatsReport::Direction direction) {
    498   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    499   StatsReport::Id id(StatsReport::NewIdWithDirection(
    500       local ? StatsReport::kStatsReportTypeSsrc
    501             : StatsReport::kStatsReportTypeRemoteSsrc,
    502       rtc::ToString<uint32_t>(ssrc), direction));
    503   StatsReport* report = reports_.Find(id);
    504 
    505   // Use the ID of the track that is currently mapped to the SSRC, if any.
    506   std::string track_id;
    507   if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) {
    508     if (!report) {
    509       // The ssrc is not used by any track or existing report, return NULL
    510       // in such case to indicate no report is prepared for the ssrc.
    511       return NULL;
    512     }
    513 
    514     // The ssrc is not used by any existing track. Keeps the old track id
    515     // since we want to report the stats for inactive ssrc.
    516     const StatsReport::Value* v =
    517         report->FindValue(StatsReport::kStatsValueNameTrackId);
    518     if (v)
    519       track_id = v->string_val();
    520   }
    521 
    522   if (!report)
    523     report = reports_.InsertNew(id);
    524 
    525   // FYI - for remote reports, the timestamp will be overwritten later.
    526   report->set_timestamp(stats_gathering_started_);
    527 
    528   report->AddInt64(StatsReport::kStatsValueNameSsrc, ssrc);
    529   report->AddString(StatsReport::kStatsValueNameTrackId, track_id);
    530   // Add the mapping of SSRC to transport.
    531   report->AddId(StatsReport::kStatsValueNameTransportId, transport_id);
    532   return report;
    533 }
    534 
    535 StatsReport* StatsCollector::AddOneCertificateReport(
    536     const rtc::SSLCertificate* cert, const StatsReport* issuer) {
    537   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    538 
    539   // TODO(bemasc): Move this computation to a helper class that caches these
    540   // values to reduce CPU use in GetStats.  This will require adding a fast
    541   // SSLCertificate::Equals() method to detect certificate changes.
    542 
    543   std::string digest_algorithm;
    544   if (!cert->GetSignatureDigestAlgorithm(&digest_algorithm))
    545     return nullptr;
    546 
    547   rtc::scoped_ptr<rtc::SSLFingerprint> ssl_fingerprint(
    548       rtc::SSLFingerprint::Create(digest_algorithm, cert));
    549 
    550   // SSLFingerprint::Create can fail if the algorithm returned by
    551   // SSLCertificate::GetSignatureDigestAlgorithm is not supported by the
    552   // implementation of SSLCertificate::ComputeDigest.  This currently happens
    553   // with MD5- and SHA-224-signed certificates when linked to libNSS.
    554   if (!ssl_fingerprint)
    555     return nullptr;
    556 
    557   std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint();
    558 
    559   rtc::Buffer der_buffer;
    560   cert->ToDER(&der_buffer);
    561   std::string der_base64;
    562   rtc::Base64::EncodeFromArray(der_buffer.data(), der_buffer.size(),
    563                                &der_base64);
    564 
    565   StatsReport::Id id(StatsReport::NewTypedId(
    566       StatsReport::kStatsReportTypeCertificate, fingerprint));
    567   StatsReport* report = reports_.ReplaceOrAddNew(id);
    568   report->set_timestamp(stats_gathering_started_);
    569   report->AddString(StatsReport::kStatsValueNameFingerprint, fingerprint);
    570   report->AddString(StatsReport::kStatsValueNameFingerprintAlgorithm,
    571                     digest_algorithm);
    572   report->AddString(StatsReport::kStatsValueNameDer, der_base64);
    573   if (issuer)
    574     report->AddId(StatsReport::kStatsValueNameIssuerId, issuer->id());
    575   return report;
    576 }
    577 
    578 StatsReport* StatsCollector::AddCertificateReports(
    579     const rtc::SSLCertificate* cert) {
    580   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    581   // Produces a chain of StatsReports representing this certificate and the rest
    582   // of its chain, and adds those reports to |reports_|.  The return value is
    583   // the id of the leaf report.  The provided cert must be non-null, so at least
    584   // one report will always be provided and the returned string will never be
    585   // empty.
    586   RTC_DCHECK(cert != NULL);
    587 
    588   StatsReport* issuer = nullptr;
    589   rtc::scoped_ptr<rtc::SSLCertChain> chain;
    590   if (cert->GetChain(chain.accept())) {
    591     // This loop runs in reverse, i.e. from root to leaf, so that each
    592     // certificate's issuer's report ID is known before the child certificate's
    593     // report is generated.  The root certificate does not have an issuer ID
    594     // value.
    595     for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) {
    596       const rtc::SSLCertificate& cert_i = chain->Get(i);
    597       issuer = AddOneCertificateReport(&cert_i, issuer);
    598     }
    599   }
    600   // Add the leaf certificate.
    601   return AddOneCertificateReport(cert, issuer);
    602 }
    603 
    604 StatsReport* StatsCollector::AddConnectionInfoReport(
    605     const std::string& content_name, int component, int connection_id,
    606     const StatsReport::Id& channel_report_id,
    607     const cricket::ConnectionInfo& info) {
    608   StatsReport::Id id(StatsReport::NewCandidatePairId(content_name, component,
    609                                                      connection_id));
    610   StatsReport* report = reports_.ReplaceOrAddNew(id);
    611   report->set_timestamp(stats_gathering_started_);
    612 
    613   const BoolForAdd bools[] = {
    614     {StatsReport::kStatsValueNameActiveConnection, info.best_connection},
    615     {StatsReport::kStatsValueNameReceiving, info.receiving},
    616     {StatsReport::kStatsValueNameWritable, info.writable},
    617   };
    618   for (const auto& b : bools)
    619     report->AddBoolean(b.name, b.value);
    620 
    621   report->AddId(StatsReport::kStatsValueNameChannelId, channel_report_id);
    622   report->AddId(StatsReport::kStatsValueNameLocalCandidateId,
    623                 AddCandidateReport(info.local_candidate, true)->id());
    624   report->AddId(StatsReport::kStatsValueNameRemoteCandidateId,
    625                 AddCandidateReport(info.remote_candidate, false)->id());
    626 
    627   const Int64ForAdd int64s[] = {
    628     { StatsReport::kStatsValueNameBytesReceived, info.recv_total_bytes },
    629     { StatsReport::kStatsValueNameBytesSent, info.sent_total_bytes },
    630     { StatsReport::kStatsValueNamePacketsSent, info.sent_total_packets },
    631     { StatsReport::kStatsValueNameRtt, info.rtt },
    632     { StatsReport::kStatsValueNameSendPacketsDiscarded,
    633       info.sent_discarded_packets },
    634   };
    635   for (const auto& i : int64s)
    636     report->AddInt64(i.name, i.value);
    637 
    638   report->AddString(StatsReport::kStatsValueNameLocalAddress,
    639                     info.local_candidate.address().ToString());
    640   report->AddString(StatsReport::kStatsValueNameLocalCandidateType,
    641                     info.local_candidate.type());
    642   report->AddString(StatsReport::kStatsValueNameRemoteAddress,
    643                     info.remote_candidate.address().ToString());
    644   report->AddString(StatsReport::kStatsValueNameRemoteCandidateType,
    645                     info.remote_candidate.type());
    646   report->AddString(StatsReport::kStatsValueNameTransportType,
    647                     info.local_candidate.protocol());
    648 
    649   return report;
    650 }
    651 
    652 StatsReport* StatsCollector::AddCandidateReport(
    653     const cricket::Candidate& candidate,
    654     bool local) {
    655   StatsReport::Id id(StatsReport::NewCandidateId(local, candidate.id()));
    656   StatsReport* report = reports_.Find(id);
    657   if (!report) {
    658     report = reports_.InsertNew(id);
    659     report->set_timestamp(stats_gathering_started_);
    660     if (local) {
    661       report->AddString(StatsReport::kStatsValueNameCandidateNetworkType,
    662                         AdapterTypeToStatsType(candidate.network_type()));
    663     }
    664     report->AddString(StatsReport::kStatsValueNameCandidateIPAddress,
    665                       candidate.address().ipaddr().ToString());
    666     report->AddString(StatsReport::kStatsValueNameCandidatePortNumber,
    667                       candidate.address().PortAsString());
    668     report->AddInt(StatsReport::kStatsValueNameCandidatePriority,
    669                    candidate.priority());
    670     report->AddString(StatsReport::kStatsValueNameCandidateType,
    671                       IceCandidateTypeToStatsType(candidate.type()));
    672     report->AddString(StatsReport::kStatsValueNameCandidateTransportType,
    673                       candidate.protocol());
    674   }
    675 
    676   return report;
    677 }
    678 
    679 void StatsCollector::ExtractSessionInfo() {
    680   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    681 
    682   // Extract information from the base session.
    683   StatsReport::Id id(StatsReport::NewTypedId(
    684       StatsReport::kStatsReportTypeSession, pc_->session()->id()));
    685   StatsReport* report = reports_.ReplaceOrAddNew(id);
    686   report->set_timestamp(stats_gathering_started_);
    687   report->AddBoolean(StatsReport::kStatsValueNameInitiator,
    688                      pc_->session()->initial_offerer());
    689 
    690   SessionStats stats;
    691   if (!pc_->session()->GetTransportStats(&stats)) {
    692     return;
    693   }
    694 
    695   // Store the proxy map away for use in SSRC reporting.
    696   // TODO(tommi): This shouldn't be necessary if we post the stats back to the
    697   // signaling thread after fetching them on the worker thread, then just use
    698   // the proxy map directly from the session stats.
    699   // As is, if GetStats() failed, we could be using old (incorrect?) proxy
    700   // data.
    701   proxy_to_transport_ = stats.proxy_to_transport;
    702 
    703   for (const auto& transport_iter : stats.transport_stats) {
    704     // Attempt to get a copy of the certificates from the transport and
    705     // expose them in stats reports.  All channels in a transport share the
    706     // same local and remote certificates.
    707     //
    708     StatsReport::Id local_cert_report_id, remote_cert_report_id;
    709     rtc::scoped_refptr<rtc::RTCCertificate> certificate;
    710     if (pc_->session()->GetLocalCertificate(
    711             transport_iter.second.transport_name, &certificate)) {
    712       StatsReport* r = AddCertificateReports(&(certificate->ssl_certificate()));
    713       if (r)
    714         local_cert_report_id = r->id();
    715     }
    716 
    717     rtc::scoped_ptr<rtc::SSLCertificate> cert;
    718     if (pc_->session()->GetRemoteSSLCertificate(
    719             transport_iter.second.transport_name, cert.accept())) {
    720       StatsReport* r = AddCertificateReports(cert.get());
    721       if (r)
    722         remote_cert_report_id = r->id();
    723     }
    724 
    725     for (const auto& channel_iter : transport_iter.second.channel_stats) {
    726       StatsReport::Id id(StatsReport::NewComponentId(
    727           transport_iter.second.transport_name, channel_iter.component));
    728       StatsReport* channel_report = reports_.ReplaceOrAddNew(id);
    729       channel_report->set_timestamp(stats_gathering_started_);
    730       channel_report->AddInt(StatsReport::kStatsValueNameComponent,
    731                              channel_iter.component);
    732       if (local_cert_report_id.get()) {
    733         channel_report->AddId(StatsReport::kStatsValueNameLocalCertificateId,
    734                               local_cert_report_id);
    735       }
    736       if (remote_cert_report_id.get()) {
    737         channel_report->AddId(StatsReport::kStatsValueNameRemoteCertificateId,
    738                               remote_cert_report_id);
    739       }
    740       int srtp_crypto_suite = channel_iter.srtp_crypto_suite;
    741       if (srtp_crypto_suite != rtc::SRTP_INVALID_CRYPTO_SUITE &&
    742           rtc::SrtpCryptoSuiteToName(srtp_crypto_suite).length()) {
    743         channel_report->AddString(
    744             StatsReport::kStatsValueNameSrtpCipher,
    745             rtc::SrtpCryptoSuiteToName(srtp_crypto_suite));
    746       }
    747       int ssl_cipher_suite = channel_iter.ssl_cipher_suite;
    748       if (ssl_cipher_suite != rtc::TLS_NULL_WITH_NULL_NULL &&
    749           rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite)
    750               .length()) {
    751         channel_report->AddString(
    752             StatsReport::kStatsValueNameDtlsCipher,
    753             rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite));
    754       }
    755 
    756       int connection_id = 0;
    757       for (const cricket::ConnectionInfo& info :
    758                channel_iter.connection_infos) {
    759         StatsReport* connection_report = AddConnectionInfoReport(
    760             transport_iter.first, channel_iter.component, connection_id++,
    761             channel_report->id(), info);
    762         if (info.best_connection) {
    763           channel_report->AddId(
    764               StatsReport::kStatsValueNameSelectedCandidatePairId,
    765               connection_report->id());
    766         }
    767       }
    768     }
    769   }
    770 }
    771 
    772 void StatsCollector::ExtractVoiceInfo() {
    773   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    774 
    775   if (!pc_->session()->voice_channel()) {
    776     return;
    777   }
    778   cricket::VoiceMediaInfo voice_info;
    779   if (!pc_->session()->voice_channel()->GetStats(&voice_info)) {
    780     LOG(LS_ERROR) << "Failed to get voice channel stats.";
    781     return;
    782   }
    783 
    784   // TODO(tommi): The above code should run on the worker thread and post the
    785   // results back to the signaling thread, where we can add data to the reports.
    786   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
    787 
    788   StatsReport::Id transport_id(GetTransportIdFromProxy(
    789       proxy_to_transport_, pc_->session()->voice_channel()->content_name()));
    790   if (!transport_id.get()) {
    791     LOG(LS_ERROR) << "Failed to get transport name for proxy "
    792                   << pc_->session()->voice_channel()->content_name();
    793     return;
    794   }
    795 
    796   ExtractStatsFromList(voice_info.receivers, transport_id, this,
    797       StatsReport::kReceive);
    798   ExtractStatsFromList(voice_info.senders, transport_id, this,
    799       StatsReport::kSend);
    800 
    801   UpdateStatsFromExistingLocalAudioTracks();
    802 }
    803 
    804 void StatsCollector::ExtractVideoInfo(
    805     PeerConnectionInterface::StatsOutputLevel level) {
    806   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    807 
    808   if (!pc_->session()->video_channel())
    809     return;
    810 
    811   cricket::VideoMediaInfo video_info;
    812   if (!pc_->session()->video_channel()->GetStats(&video_info)) {
    813     LOG(LS_ERROR) << "Failed to get video channel stats.";
    814     return;
    815   }
    816 
    817   // TODO(tommi): The above code should run on the worker thread and post the
    818   // results back to the signaling thread, where we can add data to the reports.
    819   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
    820 
    821   StatsReport::Id transport_id(GetTransportIdFromProxy(
    822       proxy_to_transport_, pc_->session()->video_channel()->content_name()));
    823   if (!transport_id.get()) {
    824     LOG(LS_ERROR) << "Failed to get transport name for proxy "
    825                   << pc_->session()->video_channel()->content_name();
    826     return;
    827   }
    828   ExtractStatsFromList(video_info.receivers, transport_id, this,
    829       StatsReport::kReceive);
    830   ExtractStatsFromList(video_info.senders, transport_id, this,
    831       StatsReport::kSend);
    832   if (video_info.bw_estimations.size() != 1) {
    833     LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size();
    834   } else {
    835     StatsReport::Id report_id(StatsReport::NewBandwidthEstimationId());
    836     StatsReport* report = reports_.FindOrAddNew(report_id);
    837     ExtractStats(
    838         video_info.bw_estimations[0], stats_gathering_started_, level, report);
    839   }
    840 }
    841 
    842 void StatsCollector::ExtractDataInfo() {
    843   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    844 
    845   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
    846 
    847   for (const auto& dc : pc_->sctp_data_channels()) {
    848     StatsReport::Id id(StatsReport::NewTypedIntId(
    849         StatsReport::kStatsReportTypeDataChannel, dc->id()));
    850     StatsReport* report = reports_.ReplaceOrAddNew(id);
    851     report->set_timestamp(stats_gathering_started_);
    852     report->AddString(StatsReport::kStatsValueNameLabel, dc->label());
    853     report->AddInt(StatsReport::kStatsValueNameDataChannelId, dc->id());
    854     report->AddString(StatsReport::kStatsValueNameProtocol, dc->protocol());
    855     report->AddString(StatsReport::kStatsValueNameState,
    856                       DataChannelInterface::DataStateString(dc->state()));
    857   }
    858 }
    859 
    860 StatsReport* StatsCollector::GetReport(const StatsReport::StatsType& type,
    861                                        const std::string& id,
    862                                        StatsReport::Direction direction) {
    863   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    864   RTC_DCHECK(type == StatsReport::kStatsReportTypeSsrc ||
    865              type == StatsReport::kStatsReportTypeRemoteSsrc);
    866   return reports_.Find(StatsReport::NewIdWithDirection(type, id, direction));
    867 }
    868 
    869 void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() {
    870   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    871   // Loop through the existing local audio tracks.
    872   for (const auto& it : local_audio_tracks_) {
    873     AudioTrackInterface* track = it.first;
    874     uint32_t ssrc = it.second;
    875     StatsReport* report =
    876         GetReport(StatsReport::kStatsReportTypeSsrc,
    877                   rtc::ToString<uint32_t>(ssrc), StatsReport::kSend);
    878     if (report == NULL) {
    879       // This can happen if a local audio track is added to a stream on the
    880       // fly and the report has not been set up yet. Do nothing in this case.
    881       LOG(LS_ERROR) << "Stats report does not exist for ssrc " << ssrc;
    882       continue;
    883     }
    884 
    885     // The same ssrc can be used by both local and remote audio tracks.
    886     const StatsReport::Value* v =
    887         report->FindValue(StatsReport::kStatsValueNameTrackId);
    888     if (!v || v->string_val() != track->id())
    889       continue;
    890 
    891     report->set_timestamp(stats_gathering_started_);
    892     UpdateReportFromAudioTrack(track, report);
    893   }
    894 }
    895 
    896 void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track,
    897                                                 StatsReport* report) {
    898   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    899   RTC_DCHECK(track != NULL);
    900 
    901   // Don't overwrite report values if they're not available.
    902   int signal_level;
    903   if (track->GetSignalLevel(&signal_level)) {
    904     RTC_DCHECK_GE(signal_level, 0);
    905     report->AddInt(StatsReport::kStatsValueNameAudioInputLevel, signal_level);
    906   }
    907 
    908   auto audio_processor(track->GetAudioProcessor());
    909 
    910   if (audio_processor.get()) {
    911     AudioProcessorInterface::AudioProcessorStats stats;
    912     audio_processor->GetStats(&stats);
    913 
    914     SetAudioProcessingStats(
    915         report, stats.typing_noise_detected, stats.echo_return_loss,
    916         stats.echo_return_loss_enhancement, stats.echo_delay_median_ms,
    917         stats.aec_quality_min, stats.echo_delay_std_ms);
    918   }
    919 }
    920 
    921 bool StatsCollector::GetTrackIdBySsrc(uint32_t ssrc,
    922                                       std::string* track_id,
    923                                       StatsReport::Direction direction) {
    924   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    925   if (direction == StatsReport::kSend) {
    926     if (!pc_->session()->GetLocalTrackIdBySsrc(ssrc, track_id)) {
    927       LOG(LS_WARNING) << "The SSRC " << ssrc
    928                       << " is not associated with a sending track";
    929       return false;
    930     }
    931   } else {
    932     RTC_DCHECK(direction == StatsReport::kReceive);
    933     if (!pc_->session()->GetRemoteTrackIdBySsrc(ssrc, track_id)) {
    934       LOG(LS_WARNING) << "The SSRC " << ssrc
    935                       << " is not associated with a receiving track";
    936       return false;
    937     }
    938   }
    939 
    940   return true;
    941 }
    942 
    943 void StatsCollector::UpdateTrackReports() {
    944   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
    945 
    946   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
    947 
    948   for (const auto& entry : track_ids_) {
    949     StatsReport* report = entry.second;
    950     report->set_timestamp(stats_gathering_started_);
    951   }
    952 }
    953 
    954 void StatsCollector::ClearUpdateStatsCacheForTest() {
    955   stats_gathering_started_ = 0;
    956 }
    957 
    958 }  // namespace webrtc
    959