• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetTransportIdFromProxy(const ProxyTransportMap & map,const std::string & proxy)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 
AddTrackReport(StatsCollection * reports,const std::string & track_id)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>
CreateTrackReports(const TrackVector & tracks,StatsCollection * reports,TrackIdMap & track_ids)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 
ExtractCommonSendProperties(const cricket::MediaSenderInfo & info,StatsReport * report)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 
ExtractCommonReceiveProperties(const cricket::MediaReceiverInfo & info,StatsReport * report)113 void ExtractCommonReceiveProperties(const cricket::MediaReceiverInfo& info,
114                                     StatsReport* report) {
115   report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name);
116 }
117 
SetAudioProcessingStats(StatsReport * report,bool typing_noise_detected,int echo_return_loss,int echo_return_loss_enhancement,int echo_delay_median_ms,float aec_quality_min,int echo_delay_std_ms)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 
ExtractStats(const cricket::VoiceReceiverInfo & info,StatsReport * report)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 
ExtractStats(const cricket::VoiceSenderInfo & info,StatsReport * report)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 
ExtractStats(const cricket::VideoReceiverInfo & info,StatsReport * report)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 
ExtractStats(const cricket::VideoSenderInfo & info,StatsReport * report)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 
ExtractStats(const cricket::BandwidthEstimationInfo & info,double stats_gathering_started,PeerConnectionInterface::StatsOutputLevel level,StatsReport * report)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 
ExtractRemoteStats(const cricket::MediaSenderInfo & info,StatsReport * report)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 
ExtractRemoteStats(const cricket::MediaReceiverInfo & info,StatsReport * report)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>
ExtractStatsFromList(const std::vector<T> & data,const StatsReport::Id & transport_id,StatsCollector * collector,StatsReport::Direction direction)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 
IceCandidateTypeToStatsType(const std::string & candidate_type)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 
AdapterTypeToStatsType(rtc::AdapterType type)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 
StatsCollector(PeerConnection * pc)368 StatsCollector::StatsCollector(PeerConnection* pc)
369     : pc_(pc), stats_gathering_started_(0) {
370   RTC_DCHECK(pc_);
371 }
372 
~StatsCollector()373 StatsCollector::~StatsCollector() {
374   RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
375 }
376 
GetTimeNow()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.
AddStream(MediaStreamInterface * stream)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 
AddLocalAudioTrack(AudioTrackInterface * audio_track,uint32_t ssrc)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 
RemoveLocalAudioTrack(AudioTrackInterface * audio_track,uint32_t ssrc)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 
GetStats(MediaStreamTrackInterface * track,StatsReports * reports)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
UpdateStats(PeerConnectionInterface::StatsOutputLevel level)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 
PrepareReport(bool local,uint32_t ssrc,const StatsReport::Id & transport_id,StatsReport::Direction direction)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 
AddOneCertificateReport(const rtc::SSLCertificate * cert,const StatsReport * issuer)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 
AddCertificateReports(const rtc::SSLCertificate * cert)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 
AddConnectionInfoReport(const std::string & content_name,int component,int connection_id,const StatsReport::Id & channel_report_id,const cricket::ConnectionInfo & info)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 
AddCandidateReport(const cricket::Candidate & candidate,bool local)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 
ExtractSessionInfo()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 
ExtractVoiceInfo()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 
ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level)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 
ExtractDataInfo()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 
GetReport(const StatsReport::StatsType & type,const std::string & id,StatsReport::Direction direction)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 
UpdateStatsFromExistingLocalAudioTracks()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 
UpdateReportFromAudioTrack(AudioTrackInterface * track,StatsReport * report)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 
GetTrackIdBySsrc(uint32_t ssrc,std::string * track_id,StatsReport::Direction direction)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 
UpdateTrackReports()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 
ClearUpdateStatsCacheForTest()954 void StatsCollector::ClearUpdateStatsCacheForTest() {
955   stats_gathering_started_ = 0;
956 }
957 
958 }  // namespace webrtc
959