3 * Copyright 2012, Google Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
28 #include "talk/app/webrtc/statscollector.h"
33 #include "talk/base/base64.h"
34 #include "talk/base/scoped_ptr.h"
35 #include "talk/session/media/channel.h"
39 // The items below are in alphabetical order.
40 const char StatsReport::kStatsValueNameActiveConnection[] =
41 "googActiveConnection";
42 const char StatsReport::kStatsValueNameActualEncBitrate[] =
43 "googActualEncBitrate";
44 const char StatsReport::kStatsValueNameAudioOutputLevel[] = "audioOutputLevel";
45 const char StatsReport::kStatsValueNameAudioInputLevel[] = "audioInputLevel";
46 const char StatsReport::kStatsValueNameAvailableReceiveBandwidth[] =
47 "googAvailableReceiveBandwidth";
48 const char StatsReport::kStatsValueNameAvailableSendBandwidth[] =
49 "googAvailableSendBandwidth";
50 const char StatsReport::kStatsValueNameAvgEncodeMs[] = "googAvgEncodeMs";
51 const char StatsReport::kStatsValueNameBucketDelay[] = "googBucketDelay";
52 const char StatsReport::kStatsValueNameBytesReceived[] = "bytesReceived";
53 const char StatsReport::kStatsValueNameBytesSent[] = "bytesSent";
54 const char StatsReport::kStatsValueNameBandwidthLimitedResolution[] =
55 "googBandwidthLimitedResolution";
56 const char StatsReport::kStatsValueNameCaptureJitterMs[] =
57 "googCaptureJitterMs";
58 const char StatsReport::kStatsValueNameCaptureQueueDelayMsPerS[] =
59 "googCaptureQueueDelayMsPerS";
60 const char StatsReport::kStatsValueNameChannelId[] = "googChannelId";
61 const char StatsReport::kStatsValueNameCodecName[] = "googCodecName";
62 const char StatsReport::kStatsValueNameComponent[] = "googComponent";
63 const char StatsReport::kStatsValueNameContentName[] = "googContentName";
64 const char StatsReport::kStatsValueNameCpuLimitedResolution[] =
65 "googCpuLimitedResolution";
66 const char StatsReport::kStatsValueNameDer[] = "googDerBase64";
67 // Echo metrics from the audio processing module.
68 const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] =
69 "googEchoCancellationQualityMin";
70 const char StatsReport::kStatsValueNameEchoDelayMedian[] =
71 "googEchoCancellationEchoDelayMedian";
72 const char StatsReport::kStatsValueNameEchoDelayStdDev[] =
73 "googEchoCancellationEchoDelayStdDev";
74 const char StatsReport::kStatsValueNameEchoReturnLoss[] =
75 "googEchoCancellationReturnLoss";
76 const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] =
77 "googEchoCancellationReturnLossEnhancement";
79 const char StatsReport::kStatsValueNameEncodeUsagePercent[] =
80 "googEncodeUsagePercent";
81 const char StatsReport::kStatsValueNameExpandRate[] = "googExpandRate";
82 const char StatsReport::kStatsValueNameFingerprint[] = "googFingerprint";
83 const char StatsReport::kStatsValueNameFingerprintAlgorithm[] =
84 "googFingerprintAlgorithm";
85 const char StatsReport::kStatsValueNameFirsReceived[] = "googFirsReceived";
86 const char StatsReport::kStatsValueNameFirsSent[] = "googFirsSent";
87 const char StatsReport::kStatsValueNameFrameHeightInput[] =
88 "googFrameHeightInput";
89 const char StatsReport::kStatsValueNameFrameHeightReceived[] =
90 "googFrameHeightReceived";
91 const char StatsReport::kStatsValueNameFrameHeightSent[] =
92 "googFrameHeightSent";
93 const char StatsReport::kStatsValueNameFrameRateReceived[] =
94 "googFrameRateReceived";
95 const char StatsReport::kStatsValueNameFrameRateDecoded[] =
96 "googFrameRateDecoded";
97 const char StatsReport::kStatsValueNameFrameRateOutput[] =
98 "googFrameRateOutput";
99 const char StatsReport::kStatsValueNameDecodeMs[] = "googDecodeMs";
100 const char StatsReport::kStatsValueNameMaxDecodeMs[] = "googMaxDecodeMs";
101 const char StatsReport::kStatsValueNameCurrentDelayMs[] = "googCurrentDelayMs";
102 const char StatsReport::kStatsValueNameTargetDelayMs[] = "googTargetDelayMs";
103 const char StatsReport::kStatsValueNameJitterBufferMs[] = "googJitterBufferMs";
104 const char StatsReport::kStatsValueNameMinPlayoutDelayMs[] =
105 "googMinPlayoutDelayMs";
106 const char StatsReport::kStatsValueNameRenderDelayMs[] = "googRenderDelayMs";
108 const char StatsReport::kStatsValueNameFrameRateInput[] = "googFrameRateInput";
109 const char StatsReport::kStatsValueNameFrameRateSent[] = "googFrameRateSent";
110 const char StatsReport::kStatsValueNameFrameWidthInput[] =
111 "googFrameWidthInput";
112 const char StatsReport::kStatsValueNameFrameWidthReceived[] =
113 "googFrameWidthReceived";
114 const char StatsReport::kStatsValueNameFrameWidthSent[] = "googFrameWidthSent";
115 const char StatsReport::kStatsValueNameInitiator[] = "googInitiator";
116 const char StatsReport::kStatsValueNameIssuerId[] = "googIssuerId";
117 const char StatsReport::kStatsValueNameJitterReceived[] = "googJitterReceived";
118 const char StatsReport::kStatsValueNameLocalAddress[] = "googLocalAddress";
119 const char StatsReport::kStatsValueNameLocalCandidateType[] =
120 "googLocalCandidateType";
121 const char StatsReport::kStatsValueNameLocalCertificateId[] =
122 "googLocalCertificateId";
123 const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived";
124 const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent";
125 const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived";
126 const char StatsReport::kStatsValueNamePacketsSent[] = "packetsSent";
127 const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost";
128 const char StatsReport::kStatsValueNameReadable[] = "googReadable";
129 const char StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug[] =
130 "googReceivedPacketGroupArrivalTimeDebug";
131 const char StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug[] =
132 "googReceivedPacketGroupPropagationDeltaDebug";
134 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug[] =
135 "googReceivedPacketGroupPropagationDeltaSumDebug";
136 const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress";
137 const char StatsReport::kStatsValueNameRemoteCandidateType[] =
138 "googRemoteCandidateType";
139 const char StatsReport::kStatsValueNameRemoteCertificateId[] =
140 "googRemoteCertificateId";
141 const char StatsReport::kStatsValueNameRetransmitBitrate[] =
142 "googRetransmitBitrate";
143 const char StatsReport::kStatsValueNameRtt[] = "googRtt";
144 const char StatsReport::kStatsValueNameSsrc[] = "ssrc";
145 const char StatsReport::kStatsValueNameTargetEncBitrate[] =
146 "googTargetEncBitrate";
147 const char StatsReport::kStatsValueNameTransmitBitrate[] =
148 "googTransmitBitrate";
149 const char StatsReport::kStatsValueNameTransportId[] = "transportId";
150 const char StatsReport::kStatsValueNameTransportType[] = "googTransportType";
151 const char StatsReport::kStatsValueNameTrackId[] = "googTrackId";
152 const char StatsReport::kStatsValueNameTypingNoiseState[] =
153 "googTypingNoiseState";
154 const char StatsReport::kStatsValueNameViewLimitedResolution[] =
155 "googViewLimitedResolution";
156 const char StatsReport::kStatsValueNameWritable[] = "googWritable";
158 const char StatsReport::kStatsReportTypeSession[] = "googLibjingleSession";
159 const char StatsReport::kStatsReportTypeBwe[] = "VideoBwe";
160 const char StatsReport::kStatsReportTypeRemoteSsrc[] = "remoteSsrc";
161 const char StatsReport::kStatsReportTypeSsrc[] = "ssrc";
162 const char StatsReport::kStatsReportTypeTrack[] = "googTrack";
163 const char StatsReport::kStatsReportTypeIceCandidate[] = "iceCandidate";
164 const char StatsReport::kStatsReportTypeTransport[] = "googTransport";
165 const char StatsReport::kStatsReportTypeComponent[] = "googComponent";
166 const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair";
167 const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate";
169 const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo";
172 // Implementations of functions in statstypes.h
173 void StatsReport::AddValue(const std::string& name, const std::string& value) {
177 values.push_back(temp);
180 void StatsReport::AddValue(const std::string& name, int64 value) {
181 AddValue(name, talk_base::ToString<int64>(value));
184 template <typename T>
185 void StatsReport::AddValue(const std::string& name,
186 const std::vector<T>& value) {
187 std::ostringstream oss;
189 for (size_t i = 0; i < value.size(); ++i) {
190 oss << talk_base::ToString<T>(value[i]);
191 if (i != value.size() - 1)
195 AddValue(name, oss.str());
198 void StatsReport::AddBoolean(const std::string& name, bool value) {
199 AddValue(name, value ? "true" : "false");
203 typedef std::map<std::string, StatsReport> StatsMap;
205 std::string StatsId(const std::string& type, const std::string& id) {
206 return type + "_" + id;
209 bool ExtractValueFromReport(
210 const StatsReport& report,
211 const std::string& name,
212 std::string* value) {
213 StatsReport::Values::const_iterator it = report.values.begin();
214 for (; it != report.values.end(); ++it) {
215 if (it->name == name) {
223 template <class TrackVector>
224 void CreateTrackReports(const TrackVector& tracks, StatsMap* reports) {
225 for (size_t j = 0; j < tracks.size(); ++j) {
226 webrtc::MediaStreamTrackInterface* track = tracks[j];
227 // Adds an empty track report.
229 report.type = StatsReport::kStatsReportTypeTrack;
230 report.id = StatsId(StatsReport::kStatsReportTypeTrack, track->id());
231 report.AddValue(StatsReport::kStatsValueNameTrackId,
233 (*reports)[report.id] = report;
237 void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) {
238 report->AddValue(StatsReport::kStatsValueNameAudioOutputLevel,
240 report->AddValue(StatsReport::kStatsValueNameBytesReceived,
242 report->AddValue(StatsReport::kStatsValueNameJitterReceived,
244 report->AddValue(StatsReport::kStatsValueNameExpandRate,
245 talk_base::ToString<float>(info.expand_rate));
246 report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
248 report->AddValue(StatsReport::kStatsValueNamePacketsLost,
252 void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) {
253 report->AddValue(StatsReport::kStatsValueNameAudioInputLevel,
255 report->AddValue(StatsReport::kStatsValueNameBytesSent,
257 report->AddValue(StatsReport::kStatsValueNamePacketsSent,
259 report->AddValue(StatsReport::kStatsValueNameJitterReceived,
261 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms);
262 report->AddValue(StatsReport::kStatsValueNameEchoCancellationQualityMin,
263 talk_base::ToString<float>(info.aec_quality_min));
264 report->AddValue(StatsReport::kStatsValueNameEchoDelayMedian,
265 info.echo_delay_median_ms);
266 report->AddValue(StatsReport::kStatsValueNameEchoDelayStdDev,
267 info.echo_delay_std_ms);
268 report->AddValue(StatsReport::kStatsValueNameEchoReturnLoss,
269 info.echo_return_loss);
270 report->AddValue(StatsReport::kStatsValueNameEchoReturnLossEnhancement,
271 info.echo_return_loss_enhancement);
272 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
273 report->AddBoolean(StatsReport::kStatsValueNameTypingNoiseState,
274 info.typing_noise_detected);
277 void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) {
278 report->AddValue(StatsReport::kStatsValueNameBytesReceived,
280 report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
282 report->AddValue(StatsReport::kStatsValueNamePacketsLost,
285 report->AddValue(StatsReport::kStatsValueNameFirsSent,
287 report->AddValue(StatsReport::kStatsValueNameNacksSent,
289 report->AddValue(StatsReport::kStatsValueNameFrameWidthReceived,
291 report->AddValue(StatsReport::kStatsValueNameFrameHeightReceived,
293 report->AddValue(StatsReport::kStatsValueNameFrameRateReceived,
294 info.framerate_rcvd);
295 report->AddValue(StatsReport::kStatsValueNameFrameRateDecoded,
296 info.framerate_decoded);
297 report->AddValue(StatsReport::kStatsValueNameFrameRateOutput,
298 info.framerate_output);
300 report->AddValue(StatsReport::kStatsValueNameDecodeMs,
302 report->AddValue(StatsReport::kStatsValueNameMaxDecodeMs,
304 report->AddValue(StatsReport::kStatsValueNameCurrentDelayMs,
305 info.current_delay_ms);
306 report->AddValue(StatsReport::kStatsValueNameTargetDelayMs,
307 info.target_delay_ms);
308 report->AddValue(StatsReport::kStatsValueNameJitterBufferMs,
309 info.jitter_buffer_ms);
310 report->AddValue(StatsReport::kStatsValueNameMinPlayoutDelayMs,
311 info.min_playout_delay_ms);
312 report->AddValue(StatsReport::kStatsValueNameRenderDelayMs,
313 info.render_delay_ms);
316 void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) {
317 report->AddValue(StatsReport::kStatsValueNameBytesSent,
319 report->AddValue(StatsReport::kStatsValueNamePacketsSent,
322 report->AddValue(StatsReport::kStatsValueNameFirsReceived,
324 report->AddValue(StatsReport::kStatsValueNameNacksReceived,
326 report->AddValue(StatsReport::kStatsValueNameFrameWidthInput,
327 info.input_frame_width);
328 report->AddValue(StatsReport::kStatsValueNameFrameHeightInput,
329 info.input_frame_height);
330 report->AddValue(StatsReport::kStatsValueNameFrameWidthSent,
331 info.send_frame_width);
332 report->AddValue(StatsReport::kStatsValueNameFrameHeightSent,
333 info.send_frame_height);
334 report->AddValue(StatsReport::kStatsValueNameFrameRateInput,
335 info.framerate_input);
336 report->AddValue(StatsReport::kStatsValueNameFrameRateSent,
337 info.framerate_sent);
338 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms);
339 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
340 report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution,
341 (info.adapt_reason & 0x1) > 0);
342 report->AddBoolean(StatsReport::kStatsValueNameBandwidthLimitedResolution,
343 (info.adapt_reason & 0x2) > 0);
344 report->AddBoolean(StatsReport::kStatsValueNameViewLimitedResolution,
345 (info.adapt_reason & 0x4) > 0);
346 report->AddValue(StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms);
347 report->AddValue(StatsReport::kStatsValueNameCaptureJitterMs,
348 info.capture_jitter_ms);
349 report->AddValue(StatsReport::kStatsValueNameCaptureQueueDelayMsPerS,
350 info.capture_queue_delay_ms_per_s);
351 report->AddValue(StatsReport::kStatsValueNameEncodeUsagePercent,
352 info.encode_usage_percent);
355 void ExtractStats(const cricket::BandwidthEstimationInfo& info,
356 double stats_gathering_started,
357 PeerConnectionInterface::StatsOutputLevel level,
358 StatsReport* report) {
359 report->id = StatsReport::kStatsReportVideoBweId;
360 report->type = StatsReport::kStatsReportTypeBwe;
362 // Clear out stats from previous GatherStats calls if any.
363 if (report->timestamp != stats_gathering_started) {
364 report->values.clear();
365 report->timestamp = stats_gathering_started;
368 report->AddValue(StatsReport::kStatsValueNameAvailableSendBandwidth,
369 info.available_send_bandwidth);
370 report->AddValue(StatsReport::kStatsValueNameAvailableReceiveBandwidth,
371 info.available_recv_bandwidth);
372 report->AddValue(StatsReport::kStatsValueNameTargetEncBitrate,
373 info.target_enc_bitrate);
374 report->AddValue(StatsReport::kStatsValueNameActualEncBitrate,
375 info.actual_enc_bitrate);
376 report->AddValue(StatsReport::kStatsValueNameRetransmitBitrate,
377 info.retransmit_bitrate);
378 report->AddValue(StatsReport::kStatsValueNameTransmitBitrate,
379 info.transmit_bitrate);
380 report->AddValue(StatsReport::kStatsValueNameBucketDelay,
382 if (level >= PeerConnectionInterface::kStatsOutputLevelDebug) {
384 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug,
385 info.total_received_propagation_delta_ms);
386 if (info.recent_received_propagation_delta_ms.size() > 0) {
388 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug,
389 info.recent_received_propagation_delta_ms);
391 StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug,
392 info.recent_received_packet_group_arrival_time_ms);
397 void ExtractRemoteStats(const cricket::MediaSenderInfo& info,
398 StatsReport* report) {
399 report->timestamp = info.remote_stats[0].timestamp;
400 // TODO(hta): Extract some stats here.
403 void ExtractRemoteStats(const cricket::MediaReceiverInfo& info,
404 StatsReport* report) {
405 report->timestamp = info.remote_stats[0].timestamp;
406 // TODO(hta): Extract some stats here.
409 // Template to extract stats from a data vector.
410 // In order to use the template, the functions that are called from it,
411 // ExtractStats and ExtractRemoteStats, must be defined and overloaded
414 void ExtractStatsFromList(const std::vector<T>& data,
415 const std::string& transport_id,
416 StatsCollector* collector) {
417 typename std::vector<T>::const_iterator it = data.begin();
418 for (; it != data.end(); ++it) {
420 uint32 ssrc = it->ssrc();
421 // Each object can result in 2 objects, a local and a remote object.
422 // TODO(hta): Handle the case of multiple SSRCs per object.
423 StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id);
427 ExtractStats(*it, report);
428 if (it->remote_stats.size() > 0) {
429 report = collector->PrepareRemoteReport(ssrc, transport_id);
433 ExtractRemoteStats(*it, report);
440 StatsCollector::StatsCollector()
441 : session_(NULL), stats_gathering_started_(0) {
444 // Adds a MediaStream with tracks that can be used as a |selector| in a call
446 void StatsCollector::AddStream(MediaStreamInterface* stream) {
447 ASSERT(stream != NULL);
449 CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(),
451 CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(),
455 bool StatsCollector::GetStats(MediaStreamTrackInterface* track,
456 StatsReports* reports) {
457 ASSERT(reports != NULL);
460 StatsMap::iterator it;
462 for (it = reports_.begin(); it != reports_.end(); ++it) {
463 reports->push_back(it->second);
468 it = reports_.find(StatsId(StatsReport::kStatsReportTypeSession,
470 if (it != reports_.end()) {
471 reports->push_back(it->second);
474 it = reports_.find(StatsId(StatsReport::kStatsReportTypeTrack, track->id()));
476 if (it == reports_.end()) {
477 LOG(LS_WARNING) << "No StatsReport is available for "<< track->id();
481 reports->push_back(it->second);
483 std::string track_id;
484 for (it = reports_.begin(); it != reports_.end(); ++it) {
485 if (it->second.type != StatsReport::kStatsReportTypeSsrc) {
488 if (ExtractValueFromReport(it->second,
489 StatsReport::kStatsValueNameTrackId,
491 if (track_id == track->id()) {
492 reports->push_back(it->second);
501 StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
502 double time_now = GetTimeNow();
503 // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of
504 // ms apart will be ignored.
505 const double kMinGatherStatsPeriod = 50;
506 if (stats_gathering_started_ + kMinGatherStatsPeriod > time_now) {
509 stats_gathering_started_ = time_now;
512 ExtractSessionInfo();
514 ExtractVideoInfo(level);
518 StatsReport* StatsCollector::PrepareLocalReport(
520 const std::string& transport_id) {
521 std::string ssrc_id = talk_base::ToString<uint32>(ssrc);
522 StatsMap::iterator it = reports_.find(StatsId(
523 StatsReport::kStatsReportTypeSsrc, ssrc_id));
525 std::string track_id;
526 if (it == reports_.end()) {
527 if (!session()->GetTrackIdBySsrc(ssrc, &track_id)) {
528 LOG(LS_WARNING) << "The SSRC " << ssrc
529 << " is not associated with a track";
533 // Keeps the old track id since we want to report the stats for inactive
535 ExtractValueFromReport(it->second,
536 StatsReport::kStatsValueNameTrackId,
540 StatsReport* report = GetOrCreateReport(StatsReport::kStatsReportTypeSsrc,
543 // Clear out stats from previous GatherStats calls if any.
544 if (report->timestamp != stats_gathering_started_) {
545 report->values.clear();
546 report->timestamp = stats_gathering_started_;
549 report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
550 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
551 // Add the mapping of SSRC to transport.
552 report->AddValue(StatsReport::kStatsValueNameTransportId,
557 StatsReport* StatsCollector::PrepareRemoteReport(
559 const std::string& transport_id) {
560 std::string ssrc_id = talk_base::ToString<uint32>(ssrc);
561 StatsMap::iterator it = reports_.find(StatsId(
562 StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id));
564 std::string track_id;
565 if (it == reports_.end()) {
566 if (!session()->GetTrackIdBySsrc(ssrc, &track_id)) {
567 LOG(LS_WARNING) << "The SSRC " << ssrc
568 << " is not associated with a track";
572 // Keeps the old track id since we want to report the stats for inactive
574 ExtractValueFromReport(it->second,
575 StatsReport::kStatsValueNameTrackId,
579 StatsReport* report = GetOrCreateReport(
580 StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id);
582 // Clear out stats from previous GatherStats calls if any.
583 // The timestamp will be added later. Zero it for debugging.
584 report->values.clear();
585 report->timestamp = 0;
587 report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
588 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
589 // Add the mapping of SSRC to transport.
590 report->AddValue(StatsReport::kStatsValueNameTransportId,
595 std::string StatsCollector::AddOneCertificateReport(
596 const talk_base::SSLCertificate* cert, const std::string& issuer_id) {
597 // TODO(bemasc): Move this computation to a helper class that caches these
598 // values to reduce CPU use in GetStats. This will require adding a fast
599 // SSLCertificate::Equals() method to detect certificate changes.
601 std::string digest_algorithm;
602 if (!cert->GetSignatureDigestAlgorithm(&digest_algorithm))
603 return std::string();
605 talk_base::scoped_ptr<talk_base::SSLFingerprint> ssl_fingerprint(
606 talk_base::SSLFingerprint::Create(digest_algorithm, cert));
608 // SSLFingerprint::Create can fail if the algorithm returned by
609 // SSLCertificate::GetSignatureDigestAlgorithm is not supported by the
610 // implementation of SSLCertificate::ComputeDigest. This currently happens
611 // with MD5- and SHA-224-signed certificates when linked to libNSS.
612 if (!ssl_fingerprint)
613 return std::string();
615 std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint();
617 talk_base::Buffer der_buffer;
618 cert->ToDER(&der_buffer);
619 std::string der_base64;
620 talk_base::Base64::EncodeFromArray(
621 der_buffer.data(), der_buffer.length(), &der_base64);
624 report.type = StatsReport::kStatsReportTypeCertificate;
625 report.id = StatsId(report.type, fingerprint);
626 report.timestamp = stats_gathering_started_;
627 report.AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint);
628 report.AddValue(StatsReport::kStatsValueNameFingerprintAlgorithm,
630 report.AddValue(StatsReport::kStatsValueNameDer, der_base64);
631 if (!issuer_id.empty())
632 report.AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id);
633 reports_[report.id] = report;
637 std::string StatsCollector::AddCertificateReports(
638 const talk_base::SSLCertificate* cert) {
639 // Produces a chain of StatsReports representing this certificate and the rest
640 // of its chain, and adds those reports to |reports_|. The return value is
641 // the id of the leaf report. The provided cert must be non-null, so at least
642 // one report will always be provided and the returned string will never be
644 ASSERT(cert != NULL);
646 std::string issuer_id;
647 talk_base::scoped_ptr<talk_base::SSLCertChain> chain;
648 if (cert->GetChain(chain.accept())) {
649 // This loop runs in reverse, i.e. from root to leaf, so that each
650 // certificate's issuer's report ID is known before the child certificate's
651 // report is generated. The root certificate does not have an issuer ID
653 for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) {
654 const talk_base::SSLCertificate& cert_i = chain->Get(i);
655 issuer_id = AddOneCertificateReport(&cert_i, issuer_id);
658 // Add the leaf certificate.
659 return AddOneCertificateReport(cert, issuer_id);
662 void StatsCollector::ExtractSessionInfo() {
663 // Extract information from the base session.
665 report.id = StatsId(StatsReport::kStatsReportTypeSession, session_->id());
666 report.type = StatsReport::kStatsReportTypeSession;
667 report.timestamp = stats_gathering_started_;
668 report.values.clear();
669 report.AddBoolean(StatsReport::kStatsValueNameInitiator,
670 session_->initiator());
672 reports_[report.id] = report;
674 cricket::SessionStats stats;
675 if (session_->GetStats(&stats)) {
676 // Store the proxy map away for use in SSRC reporting.
677 proxy_to_transport_ = stats.proxy_to_transport;
679 for (cricket::TransportStatsMap::iterator transport_iter
680 = stats.transport_stats.begin();
681 transport_iter != stats.transport_stats.end(); ++transport_iter) {
682 // Attempt to get a copy of the certificates from the transport and
683 // expose them in stats reports. All channels in a transport share the
684 // same local and remote certificates.
685 std::string local_cert_report_id, remote_cert_report_id;
686 cricket::Transport* transport =
687 session_->GetTransport(transport_iter->second.content_name);
689 talk_base::scoped_ptr<talk_base::SSLIdentity> identity;
690 if (transport->GetIdentity(identity.accept()))
691 local_cert_report_id = AddCertificateReports(
692 &(identity->certificate()));
694 talk_base::scoped_ptr<talk_base::SSLCertificate> cert;
695 if (transport->GetRemoteCertificate(cert.accept()))
696 remote_cert_report_id = AddCertificateReports(cert.get());
698 for (cricket::TransportChannelStatsList::iterator channel_iter
699 = transport_iter->second.channel_stats.begin();
700 channel_iter != transport_iter->second.channel_stats.end();
702 StatsReport channel_report;
703 std::ostringstream ostc;
704 ostc << "Channel-" << transport_iter->second.content_name
705 << "-" << channel_iter->component;
706 channel_report.id = ostc.str();
707 channel_report.type = StatsReport::kStatsReportTypeComponent;
708 channel_report.timestamp = stats_gathering_started_;
709 channel_report.AddValue(StatsReport::kStatsValueNameComponent,
710 channel_iter->component);
711 if (!local_cert_report_id.empty())
712 channel_report.AddValue(
713 StatsReport::kStatsValueNameLocalCertificateId,
714 local_cert_report_id);
715 if (!remote_cert_report_id.empty())
716 channel_report.AddValue(
717 StatsReport::kStatsValueNameRemoteCertificateId,
718 remote_cert_report_id);
719 reports_[channel_report.id] = channel_report;
721 i < channel_iter->connection_infos.size();
724 const cricket::ConnectionInfo& info
725 = channel_iter->connection_infos[i];
726 std::ostringstream ost;
727 ost << "Conn-" << transport_iter->first << "-"
728 << channel_iter->component << "-" << i;
729 report.id = ost.str();
730 report.type = StatsReport::kStatsReportTypeCandidatePair;
731 report.timestamp = stats_gathering_started_;
732 // Link from connection to its containing channel.
733 report.AddValue(StatsReport::kStatsValueNameChannelId,
735 report.AddValue(StatsReport::kStatsValueNameBytesSent,
736 info.sent_total_bytes);
737 report.AddValue(StatsReport::kStatsValueNameBytesReceived,
738 info.recv_total_bytes);
739 report.AddBoolean(StatsReport::kStatsValueNameWritable,
741 report.AddBoolean(StatsReport::kStatsValueNameReadable,
743 report.AddBoolean(StatsReport::kStatsValueNameActiveConnection,
744 info.best_connection);
745 report.AddValue(StatsReport::kStatsValueNameLocalAddress,
746 info.local_candidate.address().ToString());
747 report.AddValue(StatsReport::kStatsValueNameRemoteAddress,
748 info.remote_candidate.address().ToString());
749 report.AddValue(StatsReport::kStatsValueNameRtt, info.rtt);
750 report.AddValue(StatsReport::kStatsValueNameTransportType,
751 info.local_candidate.protocol());
752 report.AddValue(StatsReport::kStatsValueNameLocalCandidateType,
753 info.local_candidate.type());
754 report.AddValue(StatsReport::kStatsValueNameRemoteCandidateType,
755 info.remote_candidate.type());
756 reports_[report.id] = report;
763 void StatsCollector::ExtractVoiceInfo() {
764 if (!session_->voice_channel()) {
767 cricket::VoiceMediaInfo voice_info;
768 if (!session_->voice_channel()->GetStats(&voice_info)) {
769 LOG(LS_ERROR) << "Failed to get voice channel stats.";
772 std::string transport_id;
773 if (!GetTransportIdFromProxy(session_->voice_channel()->content_name(),
775 LOG(LS_ERROR) << "Failed to get transport name for proxy "
776 << session_->voice_channel()->content_name();
779 ExtractStatsFromList(voice_info.receivers, transport_id, this);
780 ExtractStatsFromList(voice_info.senders, transport_id, this);
783 void StatsCollector::ExtractVideoInfo(
784 PeerConnectionInterface::StatsOutputLevel level) {
785 if (!session_->video_channel()) {
788 cricket::StatsOptions options;
789 options.include_received_propagation_stats =
790 (level >= PeerConnectionInterface::kStatsOutputLevelDebug) ?
792 cricket::VideoMediaInfo video_info;
793 if (!session_->video_channel()->GetStats(options, &video_info)) {
794 LOG(LS_ERROR) << "Failed to get video channel stats.";
797 std::string transport_id;
798 if (!GetTransportIdFromProxy(session_->video_channel()->content_name(),
800 LOG(LS_ERROR) << "Failed to get transport name for proxy "
801 << session_->video_channel()->content_name();
804 ExtractStatsFromList(video_info.receivers, transport_id, this);
805 ExtractStatsFromList(video_info.senders, transport_id, this);
806 if (video_info.bw_estimations.size() != 1) {
807 LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size();
809 StatsReport* report = &reports_[StatsReport::kStatsReportVideoBweId];
811 video_info.bw_estimations[0], stats_gathering_started_, level, report);
815 double StatsCollector::GetTimeNow() {
816 return timing_.WallTimeNow() * talk_base::kNumMillisecsPerSec;
819 bool StatsCollector::GetTransportIdFromProxy(const std::string& proxy,
820 std::string* transport) {
821 // TODO(hta): Remove handling of empty proxy name once tests do not use it.
826 if (proxy_to_transport_.find(proxy) == proxy_to_transport_.end()) {
827 LOG(LS_ERROR) << "No transport ID mapping for " << proxy;
830 std::ostringstream ost;
831 // Component 1 is always used for RTP.
832 ost << "Channel-" << proxy_to_transport_[proxy] << "-1";
833 *transport = ost.str();
837 StatsReport* StatsCollector::GetOrCreateReport(const std::string& type,
838 const std::string& id) {
839 std::string statsid = StatsId(type, id);
840 StatsReport* report = NULL;
841 std::map<std::string, StatsReport>::iterator it = reports_.find(statsid);
842 if (it == reports_.end()) {
843 report = &reports_[statsid]; // Create new element.
844 report->id = statsid;
847 report = &(it->second);
852 } // namespace webrtc