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/session/media/channel.h"
34 #include "webrtc/base/base64.h"
35 #include "webrtc/base/scoped_ptr.h"
36 #include "webrtc/base/timing.h"
40 // The items below are in alphabetical order.
41 const char StatsReport::kStatsValueNameActiveConnection[] =
42 "googActiveConnection";
43 const char StatsReport::kStatsValueNameActualEncBitrate[] =
44 "googActualEncBitrate";
45 const char StatsReport::kStatsValueNameAudioOutputLevel[] = "audioOutputLevel";
46 const char StatsReport::kStatsValueNameAudioInputLevel[] = "audioInputLevel";
47 const char StatsReport::kStatsValueNameAvailableReceiveBandwidth[] =
48 "googAvailableReceiveBandwidth";
49 const char StatsReport::kStatsValueNameAvailableSendBandwidth[] =
50 "googAvailableSendBandwidth";
51 const char StatsReport::kStatsValueNameAvgEncodeMs[] = "googAvgEncodeMs";
52 const char StatsReport::kStatsValueNameBucketDelay[] = "googBucketDelay";
53 const char StatsReport::kStatsValueNameBytesReceived[] = "bytesReceived";
54 const char StatsReport::kStatsValueNameBytesSent[] = "bytesSent";
55 const char StatsReport::kStatsValueNameBandwidthLimitedResolution[] =
56 "googBandwidthLimitedResolution";
57 const char StatsReport::kStatsValueNameCaptureJitterMs[] =
58 "googCaptureJitterMs";
59 const char StatsReport::kStatsValueNameCaptureQueueDelayMsPerS[] =
60 "googCaptureQueueDelayMsPerS";
61 const char StatsReport::kStatsValueNameChannelId[] = "googChannelId";
62 const char StatsReport::kStatsValueNameCodecName[] = "googCodecName";
63 const char StatsReport::kStatsValueNameComponent[] = "googComponent";
64 const char StatsReport::kStatsValueNameContentName[] = "googContentName";
65 const char StatsReport::kStatsValueNameCpuLimitedResolution[] =
66 "googCpuLimitedResolution";
67 const char StatsReport::kStatsValueNameDecodingCTSG[] =
69 const char StatsReport::kStatsValueNameDecodingCTN[] =
71 const char StatsReport::kStatsValueNameDecodingNormal[] =
73 const char StatsReport::kStatsValueNameDecodingPLC[] =
75 const char StatsReport::kStatsValueNameDecodingCNG[] =
77 const char StatsReport::kStatsValueNameDecodingPLCCNG[] =
79 const char StatsReport::kStatsValueNameDer[] = "googDerBase64";
80 // Echo metrics from the audio processing module.
81 const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] =
82 "googEchoCancellationQualityMin";
83 const char StatsReport::kStatsValueNameEchoDelayMedian[] =
84 "googEchoCancellationEchoDelayMedian";
85 const char StatsReport::kStatsValueNameEchoDelayStdDev[] =
86 "googEchoCancellationEchoDelayStdDev";
87 const char StatsReport::kStatsValueNameEchoReturnLoss[] =
88 "googEchoCancellationReturnLoss";
89 const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] =
90 "googEchoCancellationReturnLossEnhancement";
92 const char StatsReport::kStatsValueNameEncodeUsagePercent[] =
93 "googEncodeUsagePercent";
94 const char StatsReport::kStatsValueNameExpandRate[] = "googExpandRate";
95 const char StatsReport::kStatsValueNameFingerprint[] = "googFingerprint";
96 const char StatsReport::kStatsValueNameFingerprintAlgorithm[] =
97 "googFingerprintAlgorithm";
98 const char StatsReport::kStatsValueNameFirsReceived[] = "googFirsReceived";
99 const char StatsReport::kStatsValueNameFirsSent[] = "googFirsSent";
100 const char StatsReport::kStatsValueNameFrameHeightInput[] =
101 "googFrameHeightInput";
102 const char StatsReport::kStatsValueNameFrameHeightReceived[] =
103 "googFrameHeightReceived";
104 const char StatsReport::kStatsValueNameFrameHeightSent[] =
105 "googFrameHeightSent";
106 const char StatsReport::kStatsValueNameFrameRateReceived[] =
107 "googFrameRateReceived";
108 const char StatsReport::kStatsValueNameFrameRateDecoded[] =
109 "googFrameRateDecoded";
110 const char StatsReport::kStatsValueNameFrameRateOutput[] =
111 "googFrameRateOutput";
112 const char StatsReport::kStatsValueNameDecodeMs[] = "googDecodeMs";
113 const char StatsReport::kStatsValueNameMaxDecodeMs[] = "googMaxDecodeMs";
114 const char StatsReport::kStatsValueNameCurrentDelayMs[] = "googCurrentDelayMs";
115 const char StatsReport::kStatsValueNameTargetDelayMs[] = "googTargetDelayMs";
116 const char StatsReport::kStatsValueNameJitterBufferMs[] = "googJitterBufferMs";
117 const char StatsReport::kStatsValueNameMinPlayoutDelayMs[] =
118 "googMinPlayoutDelayMs";
119 const char StatsReport::kStatsValueNameRenderDelayMs[] = "googRenderDelayMs";
121 const char StatsReport::kStatsValueNameCaptureStartNtpTimeMs[] =
122 "googCaptureStartNtpTimeMs";
124 const char StatsReport::kStatsValueNameFrameRateInput[] = "googFrameRateInput";
125 const char StatsReport::kStatsValueNameFrameRateSent[] = "googFrameRateSent";
126 const char StatsReport::kStatsValueNameFrameWidthInput[] =
127 "googFrameWidthInput";
128 const char StatsReport::kStatsValueNameFrameWidthReceived[] =
129 "googFrameWidthReceived";
130 const char StatsReport::kStatsValueNameFrameWidthSent[] = "googFrameWidthSent";
131 const char StatsReport::kStatsValueNameInitiator[] = "googInitiator";
132 const char StatsReport::kStatsValueNameIssuerId[] = "googIssuerId";
133 const char StatsReport::kStatsValueNameJitterReceived[] = "googJitterReceived";
134 const char StatsReport::kStatsValueNameLocalAddress[] = "googLocalAddress";
135 const char StatsReport::kStatsValueNameLocalCandidateType[] =
136 "googLocalCandidateType";
137 const char StatsReport::kStatsValueNameLocalCertificateId[] =
138 "googLocalCertificateId";
139 const char StatsReport::kStatsValueNameAdaptationChanges[] =
140 "googAdaptationChanges";
141 const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived";
142 const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent";
143 const char StatsReport::kStatsValueNamePlisReceived[] = "googPlisReceived";
144 const char StatsReport::kStatsValueNamePlisSent[] = "googPlisSent";
145 const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived";
146 const char StatsReport::kStatsValueNamePacketsSent[] = "packetsSent";
147 const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost";
148 const char StatsReport::kStatsValueNamePreferredJitterBufferMs[] =
149 "googPreferredJitterBufferMs";
150 const char StatsReport::kStatsValueNameReadable[] = "googReadable";
151 const char StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug[] =
152 "googReceivedPacketGroupArrivalTimeDebug";
153 const char StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug[] =
154 "googReceivedPacketGroupPropagationDeltaDebug";
156 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug[] =
157 "googReceivedPacketGroupPropagationDeltaSumDebug";
158 const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress";
159 const char StatsReport::kStatsValueNameRemoteCandidateType[] =
160 "googRemoteCandidateType";
161 const char StatsReport::kStatsValueNameRemoteCertificateId[] =
162 "googRemoteCertificateId";
163 const char StatsReport::kStatsValueNameRetransmitBitrate[] =
164 "googRetransmitBitrate";
165 const char StatsReport::kStatsValueNameRtt[] = "googRtt";
166 const char StatsReport::kStatsValueNameSsrc[] = "ssrc";
167 const char StatsReport::kStatsValueNameTargetEncBitrate[] =
168 "googTargetEncBitrate";
169 const char StatsReport::kStatsValueNameTransmitBitrate[] =
170 "googTransmitBitrate";
171 const char StatsReport::kStatsValueNameTransportId[] = "transportId";
172 const char StatsReport::kStatsValueNameTransportType[] = "googTransportType";
173 const char StatsReport::kStatsValueNameTrackId[] = "googTrackId";
174 const char StatsReport::kStatsValueNameTypingNoiseState[] =
175 "googTypingNoiseState";
176 const char StatsReport::kStatsValueNameViewLimitedResolution[] =
177 "googViewLimitedResolution";
178 const char StatsReport::kStatsValueNameWritable[] = "googWritable";
180 const char StatsReport::kStatsReportTypeSession[] = "googLibjingleSession";
181 const char StatsReport::kStatsReportTypeBwe[] = "VideoBwe";
182 const char StatsReport::kStatsReportTypeRemoteSsrc[] = "remoteSsrc";
183 const char StatsReport::kStatsReportTypeSsrc[] = "ssrc";
184 const char StatsReport::kStatsReportTypeTrack[] = "googTrack";
185 const char StatsReport::kStatsReportTypeIceCandidate[] = "iceCandidate";
186 const char StatsReport::kStatsReportTypeTransport[] = "googTransport";
187 const char StatsReport::kStatsReportTypeComponent[] = "googComponent";
188 const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair";
189 const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate";
191 const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo";
193 // Implementations of functions in statstypes.h
194 void StatsReport::AddValue(StatsReport::StatsValueName name,
195 const std::string& value) {
196 values.push_back(Value(name, value));
199 void StatsReport::AddValue(StatsReport::StatsValueName name, int64 value) {
200 AddValue(name, rtc::ToString<int64>(value));
203 template <typename T>
204 void StatsReport::AddValue(StatsReport::StatsValueName name,
205 const std::vector<T>& value) {
206 std::ostringstream oss;
208 for (size_t i = 0; i < value.size(); ++i) {
209 oss << rtc::ToString<T>(value[i]);
210 if (i != value.size() - 1)
214 AddValue(name, oss.str());
217 void StatsReport::AddBoolean(StatsReport::StatsValueName name, bool value) {
218 AddValue(name, value ? "true" : "false");
221 void StatsReport::ReplaceValue(StatsReport::StatsValueName name,
222 const std::string& value) {
223 for (Values::iterator it = values.begin(); it != values.end(); ++it) {
224 if ((*it).name == name) {
229 // It is not reachable here, add an ASSERT to make sure the overwriting is
236 double GetTimeNow() {
237 return rtc::Timing::WallTimeNow() * rtc::kNumMillisecsPerSec;
240 bool GetTransportIdFromProxy(const cricket::ProxyTransportMap& map,
241 const std::string& proxy,
242 std::string* transport) {
243 // TODO(hta): Remove handling of empty proxy name once tests do not use it.
249 cricket::ProxyTransportMap::const_iterator found = map.find(proxy);
250 if (found == map.end()) {
251 LOG(LS_ERROR) << "No transport ID mapping for " << proxy;
255 std::ostringstream ost;
256 // Component 1 is always used for RTP.
257 ost << "Channel-" << found->second << "-1";
258 *transport = ost.str();
262 std::string StatsId(const std::string& type, const std::string& id) {
263 return type + "_" + id;
266 std::string StatsId(const std::string& type, const std::string& id,
267 StatsCollector::TrackDirection direction) {
268 ASSERT(direction == StatsCollector::kSending ||
269 direction == StatsCollector::kReceiving);
271 // Strings for the direction of the track.
272 const char kSendDirection[] = "send";
273 const char kRecvDirection[] = "recv";
275 const std::string direction_id = (direction == StatsCollector::kSending) ?
276 kSendDirection : kRecvDirection;
277 return type + "_" + id + "_" + direction_id;
280 bool ExtractValueFromReport(
281 const StatsReport& report,
282 StatsReport::StatsValueName name,
283 std::string* value) {
284 StatsReport::Values::const_iterator it = report.values.begin();
285 for (; it != report.values.end(); ++it) {
286 if (it->name == name) {
294 void AddTrackReport(StatsSet* reports, const std::string& track_id) {
295 // Adds an empty track report.
296 StatsReport* report = reports->ReplaceOrAddNew(
297 StatsId(StatsReport::kStatsReportTypeTrack, track_id));
298 report->type = StatsReport::kStatsReportTypeTrack;
299 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
302 template <class TrackVector>
303 void CreateTrackReports(const TrackVector& tracks, StatsSet* reports) {
304 for (size_t j = 0; j < tracks.size(); ++j) {
305 webrtc::MediaStreamTrackInterface* track = tracks[j];
306 AddTrackReport(reports, track->id());
310 void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) {
311 report->AddValue(StatsReport::kStatsValueNameAudioOutputLevel,
313 report->AddValue(StatsReport::kStatsValueNameBytesReceived,
315 report->AddValue(StatsReport::kStatsValueNameJitterReceived,
317 report->AddValue(StatsReport::kStatsValueNameJitterBufferMs,
318 info.jitter_buffer_ms);
319 report->AddValue(StatsReport::kStatsValueNamePreferredJitterBufferMs,
320 info.jitter_buffer_preferred_ms);
321 report->AddValue(StatsReport::kStatsValueNameCurrentDelayMs,
322 info.delay_estimate_ms);
323 report->AddValue(StatsReport::kStatsValueNameExpandRate,
324 rtc::ToString<float>(info.expand_rate));
325 report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
327 report->AddValue(StatsReport::kStatsValueNamePacketsLost,
329 report->AddValue(StatsReport::kStatsValueNameDecodingCTSG,
330 info.decoding_calls_to_silence_generator);
331 report->AddValue(StatsReport::kStatsValueNameDecodingCTN,
332 info.decoding_calls_to_neteq);
333 report->AddValue(StatsReport::kStatsValueNameDecodingNormal,
334 info.decoding_normal);
335 report->AddValue(StatsReport::kStatsValueNameDecodingPLC,
337 report->AddValue(StatsReport::kStatsValueNameDecodingCNG,
339 report->AddValue(StatsReport::kStatsValueNameDecodingPLCCNG,
340 info.decoding_plc_cng);
341 report->AddValue(StatsReport::kStatsValueNameCaptureStartNtpTimeMs,
342 info.capture_start_ntp_time_ms);
343 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
346 void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) {
347 report->AddValue(StatsReport::kStatsValueNameAudioInputLevel,
349 report->AddValue(StatsReport::kStatsValueNameBytesSent,
351 report->AddValue(StatsReport::kStatsValueNamePacketsSent,
353 report->AddValue(StatsReport::kStatsValueNamePacketsLost,
355 report->AddValue(StatsReport::kStatsValueNameJitterReceived,
357 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms);
358 report->AddValue(StatsReport::kStatsValueNameEchoCancellationQualityMin,
359 rtc::ToString<float>(info.aec_quality_min));
360 report->AddValue(StatsReport::kStatsValueNameEchoDelayMedian,
361 info.echo_delay_median_ms);
362 report->AddValue(StatsReport::kStatsValueNameEchoDelayStdDev,
363 info.echo_delay_std_ms);
364 report->AddValue(StatsReport::kStatsValueNameEchoReturnLoss,
365 info.echo_return_loss);
366 report->AddValue(StatsReport::kStatsValueNameEchoReturnLossEnhancement,
367 info.echo_return_loss_enhancement);
368 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
369 report->AddBoolean(StatsReport::kStatsValueNameTypingNoiseState,
370 info.typing_noise_detected);
373 void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) {
374 report->AddValue(StatsReport::kStatsValueNameBytesReceived,
376 report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
378 report->AddValue(StatsReport::kStatsValueNamePacketsLost,
381 report->AddValue(StatsReport::kStatsValueNameFirsSent,
383 report->AddValue(StatsReport::kStatsValueNamePlisSent,
385 report->AddValue(StatsReport::kStatsValueNameNacksSent,
387 report->AddValue(StatsReport::kStatsValueNameFrameWidthReceived,
389 report->AddValue(StatsReport::kStatsValueNameFrameHeightReceived,
391 report->AddValue(StatsReport::kStatsValueNameFrameRateReceived,
392 info.framerate_rcvd);
393 report->AddValue(StatsReport::kStatsValueNameFrameRateDecoded,
394 info.framerate_decoded);
395 report->AddValue(StatsReport::kStatsValueNameFrameRateOutput,
396 info.framerate_output);
398 report->AddValue(StatsReport::kStatsValueNameDecodeMs,
400 report->AddValue(StatsReport::kStatsValueNameMaxDecodeMs,
402 report->AddValue(StatsReport::kStatsValueNameCurrentDelayMs,
403 info.current_delay_ms);
404 report->AddValue(StatsReport::kStatsValueNameTargetDelayMs,
405 info.target_delay_ms);
406 report->AddValue(StatsReport::kStatsValueNameJitterBufferMs,
407 info.jitter_buffer_ms);
408 report->AddValue(StatsReport::kStatsValueNameMinPlayoutDelayMs,
409 info.min_playout_delay_ms);
410 report->AddValue(StatsReport::kStatsValueNameRenderDelayMs,
411 info.render_delay_ms);
413 report->AddValue(StatsReport::kStatsValueNameCaptureStartNtpTimeMs,
414 info.capture_start_ntp_time_ms);
417 void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) {
418 report->AddValue(StatsReport::kStatsValueNameBytesSent,
420 report->AddValue(StatsReport::kStatsValueNamePacketsSent,
422 report->AddValue(StatsReport::kStatsValueNamePacketsLost,
425 report->AddValue(StatsReport::kStatsValueNameFirsReceived,
427 report->AddValue(StatsReport::kStatsValueNamePlisReceived,
429 report->AddValue(StatsReport::kStatsValueNameNacksReceived,
431 report->AddValue(StatsReport::kStatsValueNameFrameWidthInput,
432 info.input_frame_width);
433 report->AddValue(StatsReport::kStatsValueNameFrameHeightInput,
434 info.input_frame_height);
435 report->AddValue(StatsReport::kStatsValueNameFrameWidthSent,
436 info.send_frame_width);
437 report->AddValue(StatsReport::kStatsValueNameFrameHeightSent,
438 info.send_frame_height);
439 report->AddValue(StatsReport::kStatsValueNameFrameRateInput,
440 info.framerate_input);
441 report->AddValue(StatsReport::kStatsValueNameFrameRateSent,
442 info.framerate_sent);
443 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms);
444 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
445 report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution,
446 (info.adapt_reason & 0x1) > 0);
447 report->AddBoolean(StatsReport::kStatsValueNameBandwidthLimitedResolution,
448 (info.adapt_reason & 0x2) > 0);
449 report->AddBoolean(StatsReport::kStatsValueNameViewLimitedResolution,
450 (info.adapt_reason & 0x4) > 0);
451 report->AddValue(StatsReport::kStatsValueNameAdaptationChanges,
453 report->AddValue(StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms);
454 report->AddValue(StatsReport::kStatsValueNameCaptureJitterMs,
455 info.capture_jitter_ms);
456 report->AddValue(StatsReport::kStatsValueNameCaptureQueueDelayMsPerS,
457 info.capture_queue_delay_ms_per_s);
458 report->AddValue(StatsReport::kStatsValueNameEncodeUsagePercent,
459 info.encode_usage_percent);
462 void ExtractStats(const cricket::BandwidthEstimationInfo& info,
463 double stats_gathering_started,
464 PeerConnectionInterface::StatsOutputLevel level,
465 StatsReport* report) {
466 ASSERT(report->id == StatsReport::kStatsReportVideoBweId);
467 report->type = StatsReport::kStatsReportTypeBwe;
469 // Clear out stats from previous GatherStats calls if any.
470 if (report->timestamp != stats_gathering_started) {
471 report->values.clear();
472 report->timestamp = stats_gathering_started;
475 report->AddValue(StatsReport::kStatsValueNameAvailableSendBandwidth,
476 info.available_send_bandwidth);
477 report->AddValue(StatsReport::kStatsValueNameAvailableReceiveBandwidth,
478 info.available_recv_bandwidth);
479 report->AddValue(StatsReport::kStatsValueNameTargetEncBitrate,
480 info.target_enc_bitrate);
481 report->AddValue(StatsReport::kStatsValueNameActualEncBitrate,
482 info.actual_enc_bitrate);
483 report->AddValue(StatsReport::kStatsValueNameRetransmitBitrate,
484 info.retransmit_bitrate);
485 report->AddValue(StatsReport::kStatsValueNameTransmitBitrate,
486 info.transmit_bitrate);
487 report->AddValue(StatsReport::kStatsValueNameBucketDelay,
489 if (level >= PeerConnectionInterface::kStatsOutputLevelDebug) {
491 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug,
492 info.total_received_propagation_delta_ms);
493 if (info.recent_received_propagation_delta_ms.size() > 0) {
495 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug,
496 info.recent_received_propagation_delta_ms);
498 StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug,
499 info.recent_received_packet_group_arrival_time_ms);
504 void ExtractRemoteStats(const cricket::MediaSenderInfo& info,
505 StatsReport* report) {
506 report->timestamp = info.remote_stats[0].timestamp;
507 // TODO(hta): Extract some stats here.
510 void ExtractRemoteStats(const cricket::MediaReceiverInfo& info,
511 StatsReport* report) {
512 report->timestamp = info.remote_stats[0].timestamp;
513 // TODO(hta): Extract some stats here.
516 // Template to extract stats from a data vector.
517 // In order to use the template, the functions that are called from it,
518 // ExtractStats and ExtractRemoteStats, must be defined and overloaded
521 void ExtractStatsFromList(const std::vector<T>& data,
522 const std::string& transport_id,
523 StatsCollector* collector,
524 StatsCollector::TrackDirection direction) {
525 typename std::vector<T>::const_iterator it = data.begin();
526 for (; it != data.end(); ++it) {
528 uint32 ssrc = it->ssrc();
529 // Each track can have stats for both local and remote objects.
530 // TODO(hta): Handle the case of multiple SSRCs per object.
531 StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id,
534 ExtractStats(*it, report);
536 if (it->remote_stats.size() > 0) {
537 report = collector->PrepareRemoteReport(ssrc, transport_id,
542 ExtractRemoteStats(*it, report);
549 StatsCollector::StatsCollector(WebRtcSession* session)
550 : session_(session), stats_gathering_started_(0) {
554 StatsCollector::~StatsCollector() {
557 // Adds a MediaStream with tracks that can be used as a |selector| in a call
559 void StatsCollector::AddStream(MediaStreamInterface* stream) {
560 ASSERT(stream != NULL);
562 CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(),
564 CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(),
568 void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track,
570 ASSERT(audio_track != NULL);
571 for (LocalAudioTrackVector::iterator it = local_audio_tracks_.begin();
572 it != local_audio_tracks_.end(); ++it) {
573 ASSERT(it->first != audio_track || it->second != ssrc);
576 local_audio_tracks_.push_back(std::make_pair(audio_track, ssrc));
578 // Create the kStatsReportTypeTrack report for the new track if there is no
580 StatsReport* found = reports_.Find(
581 StatsId(StatsReport::kStatsReportTypeTrack, audio_track->id()));
583 AddTrackReport(&reports_, audio_track->id());
586 void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
588 ASSERT(audio_track != NULL);
589 for (LocalAudioTrackVector::iterator it = local_audio_tracks_.begin();
590 it != local_audio_tracks_.end(); ++it) {
591 if (it->first == audio_track && it->second == ssrc) {
592 local_audio_tracks_.erase(it);
600 void StatsCollector::GetStats(MediaStreamTrackInterface* track,
601 StatsReports* reports) {
602 ASSERT(reports != NULL);
603 ASSERT(reports->empty());
606 StatsSet::const_iterator it;
607 for (it = reports_.begin(); it != reports_.end(); ++it)
608 reports->push_back(&(*it));
612 StatsReport* report =
613 reports_.Find(StatsId(StatsReport::kStatsReportTypeSession,
616 reports->push_back(report);
618 report = reports_.Find(
619 StatsId(StatsReport::kStatsReportTypeTrack, track->id()));
624 reports->push_back(report);
626 std::string track_id;
627 for (StatsSet::const_iterator it = reports_.begin(); it != reports_.end();
629 if (it->type != StatsReport::kStatsReportTypeSsrc)
632 if (ExtractValueFromReport(*it,
633 StatsReport::kStatsValueNameTrackId,
635 if (track_id == track->id()) {
636 reports->push_back(&(*it));
643 StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
644 double time_now = GetTimeNow();
645 // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of
646 // ms apart will be ignored.
647 const double kMinGatherStatsPeriod = 50;
648 if (stats_gathering_started_ != 0 &&
649 stats_gathering_started_ + kMinGatherStatsPeriod > time_now) {
652 stats_gathering_started_ = time_now;
655 ExtractSessionInfo();
657 ExtractVideoInfo(level);
661 StatsReport* StatsCollector::PrepareLocalReport(
663 const std::string& transport_id,
664 TrackDirection direction) {
665 const std::string ssrc_id = rtc::ToString<uint32>(ssrc);
666 StatsReport* report = reports_.Find(
667 StatsId(StatsReport::kStatsReportTypeSsrc, ssrc_id, direction));
669 // Use the ID of the track that is currently mapped to the SSRC, if any.
670 std::string track_id;
671 if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) {
673 // The ssrc is not used by any track or existing report, return NULL
674 // in such case to indicate no report is prepared for the ssrc.
678 // The ssrc is not used by any existing track. Keeps the old track id
679 // since we want to report the stats for inactive ssrc.
680 ExtractValueFromReport(*report,
681 StatsReport::kStatsValueNameTrackId,
685 report = GetOrCreateReport(
686 StatsReport::kStatsReportTypeSsrc, ssrc_id, direction);
688 // Clear out stats from previous GatherStats calls if any.
689 // This is required since the report will be returned for the new values.
690 // Having the old values in the report will lead to multiple values with
692 // TODO(xians): Consider changing StatsReport to use map instead of vector.
693 report->values.clear();
694 report->timestamp = stats_gathering_started_;
696 report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
697 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
698 // Add the mapping of SSRC to transport.
699 report->AddValue(StatsReport::kStatsValueNameTransportId,
704 StatsReport* StatsCollector::PrepareRemoteReport(
706 const std::string& transport_id,
707 TrackDirection direction) {
708 const std::string ssrc_id = rtc::ToString<uint32>(ssrc);
709 StatsReport* report = reports_.Find(
710 StatsId(StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction));
712 // Use the ID of the track that is currently mapped to the SSRC, if any.
713 std::string track_id;
714 if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) {
716 // The ssrc is not used by any track or existing report, return NULL
717 // in such case to indicate no report is prepared for the ssrc.
721 // The ssrc is not used by any existing track. Keeps the old track id
722 // since we want to report the stats for inactive ssrc.
723 ExtractValueFromReport(*report,
724 StatsReport::kStatsValueNameTrackId,
728 report = GetOrCreateReport(
729 StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction);
731 // Clear out stats from previous GatherStats calls if any.
732 // The timestamp will be added later. Zero it for debugging.
733 report->values.clear();
734 report->timestamp = 0;
736 report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
737 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
738 // Add the mapping of SSRC to transport.
739 report->AddValue(StatsReport::kStatsValueNameTransportId,
744 std::string StatsCollector::AddOneCertificateReport(
745 const rtc::SSLCertificate* cert, const std::string& issuer_id) {
746 // TODO(bemasc): Move this computation to a helper class that caches these
747 // values to reduce CPU use in GetStats. This will require adding a fast
748 // SSLCertificate::Equals() method to detect certificate changes.
750 std::string digest_algorithm;
751 if (!cert->GetSignatureDigestAlgorithm(&digest_algorithm))
752 return std::string();
754 rtc::scoped_ptr<rtc::SSLFingerprint> ssl_fingerprint(
755 rtc::SSLFingerprint::Create(digest_algorithm, cert));
757 // SSLFingerprint::Create can fail if the algorithm returned by
758 // SSLCertificate::GetSignatureDigestAlgorithm is not supported by the
759 // implementation of SSLCertificate::ComputeDigest. This currently happens
760 // with MD5- and SHA-224-signed certificates when linked to libNSS.
761 if (!ssl_fingerprint)
762 return std::string();
764 std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint();
766 rtc::Buffer der_buffer;
767 cert->ToDER(&der_buffer);
768 std::string der_base64;
769 rtc::Base64::EncodeFromArray(
770 der_buffer.data(), der_buffer.length(), &der_base64);
772 StatsReport* report = reports_.ReplaceOrAddNew(
773 StatsId(StatsReport::kStatsReportTypeCertificate, fingerprint));
774 report->type = StatsReport::kStatsReportTypeCertificate;
775 report->timestamp = stats_gathering_started_;
776 report->AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint);
777 report->AddValue(StatsReport::kStatsValueNameFingerprintAlgorithm,
779 report->AddValue(StatsReport::kStatsValueNameDer, der_base64);
780 if (!issuer_id.empty())
781 report->AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id);
785 std::string StatsCollector::AddCertificateReports(
786 const rtc::SSLCertificate* cert) {
787 // Produces a chain of StatsReports representing this certificate and the rest
788 // of its chain, and adds those reports to |reports_|. The return value is
789 // the id of the leaf report. The provided cert must be non-null, so at least
790 // one report will always be provided and the returned string will never be
792 ASSERT(cert != NULL);
794 std::string issuer_id;
795 rtc::scoped_ptr<rtc::SSLCertChain> chain;
796 if (cert->GetChain(chain.accept())) {
797 // This loop runs in reverse, i.e. from root to leaf, so that each
798 // certificate's issuer's report ID is known before the child certificate's
799 // report is generated. The root certificate does not have an issuer ID
801 for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) {
802 const rtc::SSLCertificate& cert_i = chain->Get(i);
803 issuer_id = AddOneCertificateReport(&cert_i, issuer_id);
806 // Add the leaf certificate.
807 return AddOneCertificateReport(cert, issuer_id);
810 void StatsCollector::ExtractSessionInfo() {
811 // Extract information from the base session.
812 StatsReport* report = reports_.ReplaceOrAddNew(
813 StatsId(StatsReport::kStatsReportTypeSession, session_->id()));
814 report->type = StatsReport::kStatsReportTypeSession;
815 report->timestamp = stats_gathering_started_;
816 report->values.clear();
817 report->AddBoolean(StatsReport::kStatsValueNameInitiator,
818 session_->initiator());
820 cricket::SessionStats stats;
821 if (session_->GetStats(&stats)) {
822 // Store the proxy map away for use in SSRC reporting.
823 proxy_to_transport_ = stats.proxy_to_transport;
825 for (cricket::TransportStatsMap::iterator transport_iter
826 = stats.transport_stats.begin();
827 transport_iter != stats.transport_stats.end(); ++transport_iter) {
828 // Attempt to get a copy of the certificates from the transport and
829 // expose them in stats reports. All channels in a transport share the
830 // same local and remote certificates.
832 // Note that Transport::GetIdentity and Transport::GetRemoteCertificate
833 // invoke method calls on the worker thread and block this thread, but
834 // messages are still processed on this thread, which may blow way the
835 // existing transports. So we cannot reuse |transport| after these calls.
836 std::string local_cert_report_id, remote_cert_report_id;
838 cricket::Transport* transport =
839 session_->GetTransport(transport_iter->second.content_name);
840 rtc::scoped_ptr<rtc::SSLIdentity> identity;
841 if (transport && transport->GetIdentity(identity.accept())) {
842 local_cert_report_id =
843 AddCertificateReports(&(identity->certificate()));
846 transport = session_->GetTransport(transport_iter->second.content_name);
847 rtc::scoped_ptr<rtc::SSLCertificate> cert;
848 if (transport && transport->GetRemoteCertificate(cert.accept())) {
849 remote_cert_report_id = AddCertificateReports(cert.get());
852 for (cricket::TransportChannelStatsList::iterator channel_iter
853 = transport_iter->second.channel_stats.begin();
854 channel_iter != transport_iter->second.channel_stats.end();
856 std::ostringstream ostc;
857 ostc << "Channel-" << transport_iter->second.content_name
858 << "-" << channel_iter->component;
859 StatsReport* channel_report = reports_.ReplaceOrAddNew(ostc.str());
860 channel_report->type = StatsReport::kStatsReportTypeComponent;
861 channel_report->timestamp = stats_gathering_started_;
862 channel_report->AddValue(StatsReport::kStatsValueNameComponent,
863 channel_iter->component);
864 if (!local_cert_report_id.empty())
865 channel_report->AddValue(
866 StatsReport::kStatsValueNameLocalCertificateId,
867 local_cert_report_id);
868 if (!remote_cert_report_id.empty())
869 channel_report->AddValue(
870 StatsReport::kStatsValueNameRemoteCertificateId,
871 remote_cert_report_id);
873 i < channel_iter->connection_infos.size();
875 std::ostringstream ost;
876 ost << "Conn-" << transport_iter->first << "-"
877 << channel_iter->component << "-" << i;
878 StatsReport* report = reports_.ReplaceOrAddNew(ost.str());
879 report->type = StatsReport::kStatsReportTypeCandidatePair;
880 report->timestamp = stats_gathering_started_;
881 // Link from connection to its containing channel.
882 report->AddValue(StatsReport::kStatsValueNameChannelId,
885 const cricket::ConnectionInfo& info =
886 channel_iter->connection_infos[i];
887 report->AddValue(StatsReport::kStatsValueNameBytesSent,
888 info.sent_total_bytes);
889 report->AddValue(StatsReport::kStatsValueNameBytesReceived,
890 info.recv_total_bytes);
891 report->AddBoolean(StatsReport::kStatsValueNameWritable,
893 report->AddBoolean(StatsReport::kStatsValueNameReadable,
895 report->AddBoolean(StatsReport::kStatsValueNameActiveConnection,
896 info.best_connection);
897 report->AddValue(StatsReport::kStatsValueNameLocalAddress,
898 info.local_candidate.address().ToString());
899 report->AddValue(StatsReport::kStatsValueNameRemoteAddress,
900 info.remote_candidate.address().ToString());
901 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt);
902 report->AddValue(StatsReport::kStatsValueNameTransportType,
903 info.local_candidate.protocol());
904 report->AddValue(StatsReport::kStatsValueNameLocalCandidateType,
905 info.local_candidate.type());
906 report->AddValue(StatsReport::kStatsValueNameRemoteCandidateType,
907 info.remote_candidate.type());
914 void StatsCollector::ExtractVoiceInfo() {
915 if (!session_->voice_channel()) {
918 cricket::VoiceMediaInfo voice_info;
919 if (!session_->voice_channel()->GetStats(&voice_info)) {
920 LOG(LS_ERROR) << "Failed to get voice channel stats.";
923 std::string transport_id;
924 if (!GetTransportIdFromProxy(proxy_to_transport_,
925 session_->voice_channel()->content_name(),
927 LOG(LS_ERROR) << "Failed to get transport name for proxy "
928 << session_->voice_channel()->content_name();
931 ExtractStatsFromList(voice_info.receivers, transport_id, this, kReceiving);
932 ExtractStatsFromList(voice_info.senders, transport_id, this, kSending);
934 UpdateStatsFromExistingLocalAudioTracks();
937 void StatsCollector::ExtractVideoInfo(
938 PeerConnectionInterface::StatsOutputLevel level) {
939 if (!session_->video_channel()) {
942 cricket::StatsOptions options;
943 options.include_received_propagation_stats =
944 (level >= PeerConnectionInterface::kStatsOutputLevelDebug) ?
946 cricket::VideoMediaInfo video_info;
947 if (!session_->video_channel()->GetStats(options, &video_info)) {
948 LOG(LS_ERROR) << "Failed to get video channel stats.";
951 std::string transport_id;
952 if (!GetTransportIdFromProxy(proxy_to_transport_,
953 session_->video_channel()->content_name(),
955 LOG(LS_ERROR) << "Failed to get transport name for proxy "
956 << session_->video_channel()->content_name();
959 ExtractStatsFromList(video_info.receivers, transport_id, this, kReceiving);
960 ExtractStatsFromList(video_info.senders, transport_id, this, kSending);
961 if (video_info.bw_estimations.size() != 1) {
962 LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size();
964 StatsReport* report =
965 reports_.FindOrAddNew(StatsReport::kStatsReportVideoBweId);
967 video_info.bw_estimations[0], stats_gathering_started_, level, report);
971 StatsReport* StatsCollector::GetReport(const std::string& type,
972 const std::string& id,
973 TrackDirection direction) {
974 ASSERT(type == StatsReport::kStatsReportTypeSsrc ||
975 type == StatsReport::kStatsReportTypeRemoteSsrc);
976 return reports_.Find(StatsId(type, id, direction));
979 StatsReport* StatsCollector::GetOrCreateReport(const std::string& type,
980 const std::string& id,
981 TrackDirection direction) {
982 ASSERT(type == StatsReport::kStatsReportTypeSsrc ||
983 type == StatsReport::kStatsReportTypeRemoteSsrc);
984 StatsReport* report = GetReport(type, id, direction);
985 if (report == NULL) {
986 std::string statsid = StatsId(type, id, direction);
987 report = reports_.FindOrAddNew(statsid);
988 ASSERT(report->id == statsid);
995 void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() {
996 // Loop through the existing local audio tracks.
997 for (LocalAudioTrackVector::const_iterator it = local_audio_tracks_.begin();
998 it != local_audio_tracks_.end(); ++it) {
999 AudioTrackInterface* track = it->first;
1000 uint32 ssrc = it->second;
1001 std::string ssrc_id = rtc::ToString<uint32>(ssrc);
1002 StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc,
1005 if (report == NULL) {
1006 // This can happen if a local audio track is added to a stream on the
1007 // fly and the report has not been set up yet. Do nothing in this case.
1008 LOG(LS_ERROR) << "Stats report does not exist for ssrc " << ssrc;
1012 // The same ssrc can be used by both local and remote audio tracks.
1013 std::string track_id;
1014 if (!ExtractValueFromReport(*report,
1015 StatsReport::kStatsValueNameTrackId,
1017 track_id != track->id()) {
1021 UpdateReportFromAudioTrack(track, report);
1025 void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track,
1026 StatsReport* report) {
1027 ASSERT(track != NULL);
1031 int signal_level = 0;
1032 if (track->GetSignalLevel(&signal_level)) {
1033 report->ReplaceValue(StatsReport::kStatsValueNameAudioInputLevel,
1034 rtc::ToString<int>(signal_level));
1037 rtc::scoped_refptr<AudioProcessorInterface> audio_processor(
1038 track->GetAudioProcessor());
1039 if (audio_processor.get() == NULL)
1042 AudioProcessorInterface::AudioProcessorStats stats;
1043 audio_processor->GetStats(&stats);
1044 report->ReplaceValue(StatsReport::kStatsValueNameTypingNoiseState,
1045 stats.typing_noise_detected ? "true" : "false");
1046 report->ReplaceValue(StatsReport::kStatsValueNameEchoReturnLoss,
1047 rtc::ToString<int>(stats.echo_return_loss));
1048 report->ReplaceValue(
1049 StatsReport::kStatsValueNameEchoReturnLossEnhancement,
1050 rtc::ToString<int>(stats.echo_return_loss_enhancement));
1051 report->ReplaceValue(StatsReport::kStatsValueNameEchoDelayMedian,
1052 rtc::ToString<int>(stats.echo_delay_median_ms));
1053 report->ReplaceValue(StatsReport::kStatsValueNameEchoCancellationQualityMin,
1054 rtc::ToString<float>(stats.aec_quality_min));
1055 report->ReplaceValue(StatsReport::kStatsValueNameEchoDelayStdDev,
1056 rtc::ToString<int>(stats.echo_delay_std_ms));
1059 bool StatsCollector::GetTrackIdBySsrc(uint32 ssrc, std::string* track_id,
1060 TrackDirection direction) {
1061 if (direction == kSending) {
1062 if (!session_->GetLocalTrackIdBySsrc(ssrc, track_id)) {
1063 LOG(LS_WARNING) << "The SSRC " << ssrc
1064 << " is not associated with a sending track";
1068 ASSERT(direction == kReceiving);
1069 if (!session_->GetRemoteTrackIdBySsrc(ssrc, track_id)) {
1070 LOG(LS_WARNING) << "The SSRC " << ssrc
1071 << " is not associated with a receiving track";
1079 void StatsCollector::ClearUpdateStatsCache() {
1080 stats_gathering_started_ = 0;
1083 } // namespace webrtc