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
40using rtc::scoped_ptr;
41
42namespace webrtc {
43namespace {
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.
48const char STATSREPORT_LOCAL_PORT_TYPE[] = "host";
49const char STATSREPORT_STUN_PORT_TYPE[] = "serverreflexive";
50const char STATSREPORT_PRFLX_PORT_TYPE[] = "peerreflexive";
51const 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.
56const char* STATSREPORT_ADAPTER_TYPE_ETHERNET = "lan";
57const char* STATSREPORT_ADAPTER_TYPE_WIFI = "wlan";
58const char* STATSREPORT_ADAPTER_TYPE_WWAN = "wwan";
59const char* STATSREPORT_ADAPTER_TYPE_VPN = "vpn";
60const char* STATSREPORT_ADAPTER_TYPE_LOOPBACK = "loopback";
61
62template<typename ValueType>
63struct TypeForAdd {
64  const StatsReport::StatsValueName name;
65  const ValueType& value;
66};
67
68typedef TypeForAdd<bool> BoolForAdd;
69typedef TypeForAdd<float> FloatForAdd;
70typedef TypeForAdd<int64_t> Int64ForAdd;
71typedef TypeForAdd<int> IntForAdd;
72
73StatsReport::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
85StatsReport* 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
95template <class TrackVector>
96void 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
106void 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
113void ExtractCommonReceiveProperties(const cricket::MediaReceiverInfo& info,
114                                    StatsReport* report) {
115  report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name);
116}
117
118void 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
140void 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
182void 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
202void 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
235void 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
269void 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
291void 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
297void 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.
307template<typename T>
308void 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
331const 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
348const 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
368StatsCollector::StatsCollector(PeerConnection* pc)
369    : pc_(pc), stats_gathering_started_(0) {
370  RTC_DCHECK(pc_);
371}
372
373StatsCollector::~StatsCollector() {
374  RTC_DCHECK(pc_->session()->signaling_thread()->IsCurrent());
375}
376
377double 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.
383void 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
393void 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
415void 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
425void 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
465void
466StatsCollector::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
493StatsReport* 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
535StatsReport* 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
578StatsReport* 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
604StatsReport* 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
652StatsReport* 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
679void 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
772void 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
804void 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
842void 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
860StatsReport* 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
869void 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
896void 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
921bool 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
943void 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
954void StatsCollector::ClearUpdateStatsCacheForTest() {
955  stats_gathering_started_ = 0;
956}
957
958}  // namespace webrtc
959