Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/renderer/media/webrtc/stun_field_trial.h" | 5 #include "content/renderer/media/webrtc/stun_field_trial.h" |
| 6 | 6 |
| 7 #include <math.h> | 7 #include <math.h> |
| 8 | 8 |
| 9 #include "base/bind.h" | |
| 9 #include "base/logging.h" | 10 #include "base/logging.h" |
| 10 #include "base/macros.h" | 11 #include "base/macros.h" |
| 12 #include "base/message_loop/message_loop.h" | |
| 11 #include "base/metrics/histogram.h" | 13 #include "base/metrics/histogram.h" |
| 12 #include "base/rand_util.h" | 14 #include "base/rand_util.h" |
| 13 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
| 14 #include "base/strings/string_split.h" | 16 #include "base/strings/string_split.h" |
| 15 #include "base/strings/stringprintf.h" | 17 #include "base/strings/stringprintf.h" |
| 16 #include "base/time/time.h" | 18 #include "base/time/time.h" |
| 17 #include "third_party/webrtc/base/asyncpacketsocket.h" | 19 #include "third_party/webrtc/base/asyncpacketsocket.h" |
| 18 #include "third_party/webrtc/base/asyncresolverinterface.h" | 20 #include "third_party/webrtc/base/asyncresolverinterface.h" |
| 19 #include "third_party/webrtc/base/ipaddress.h" | 21 #include "third_party/webrtc/base/ipaddress.h" |
| 20 #include "third_party/webrtc/base/network.h" | 22 #include "third_party/webrtc/base/network.h" |
| 21 #include "third_party/webrtc/base/socketaddress.h" | 23 #include "third_party/webrtc/base/socketaddress.h" |
| 22 #include "third_party/webrtc/base/thread.h" | 24 #include "third_party/webrtc/base/thread.h" |
| 23 #include "third_party/webrtc/p2p/base/packetsocketfactory.h" | 25 #include "third_party/webrtc/p2p/base/packetsocketfactory.h" |
| 24 #include "third_party/webrtc/p2p/stunprober/stunprober.h" | |
| 25 | 26 |
| 26 using stunprober::StunProber; | 27 using stunprober::StunProber; |
| 27 | 28 |
| 28 namespace content { | 29 namespace content { |
| 29 | 30 |
| 30 namespace { | 31 namespace { |
| 31 | 32 |
| 32 // This needs to be the same as NatTypeCounters in histograms.xml. | 33 // This needs to be the same as NatTypeCounters in histograms.xml. |
| 33 enum NatType { | 34 enum NatType { |
| 34 NAT_TYPE_NONE, | 35 NAT_TYPE_NONE, |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 58 } | 59 } |
| 59 | 60 |
| 60 // Below 50ms, we are doing experiments at each 5ms interval. Beyond 50ms, only | 61 // Below 50ms, we are doing experiments at each 5ms interval. Beyond 50ms, only |
| 61 // one experiment of 100ms. | 62 // one experiment of 100ms. |
| 62 int ClampProbingInterval(int interval_ms) { | 63 int ClampProbingInterval(int interval_ms) { |
| 63 return interval_ms > 50 ? 100 : interval_ms; | 64 return interval_ms > 50 ? 100 : interval_ms; |
| 64 } | 65 } |
| 65 | 66 |
| 66 std::string HistogramName(const std::string& prefix, | 67 std::string HistogramName(const std::string& prefix, |
| 67 NatType nat_type, | 68 NatType nat_type, |
| 68 int interval_ms) { | 69 int interval_ms, |
| 69 return base::StringPrintf("WebRTC.Stun.%s.%s.%dms", prefix.c_str(), | 70 int batch_index) { |
| 70 NatTypeNames[nat_type], interval_ms); | 71 return base::StringPrintf("WebRTC.Stun.%s.%s.%dms.%d", prefix.c_str(), |
| 71 } | 72 NatTypeNames[nat_type], interval_ms, batch_index); |
| 72 | |
| 73 void SaveHistogramData(StunProber* prober) { | |
| 74 StunProber::Stats stats; | |
| 75 if (!prober->GetStats(&stats)) | |
| 76 return; | |
| 77 | |
| 78 NatType nat_type = GetNatType(stats.nat_type); | |
| 79 | |
| 80 // Use the real probe interval for reporting, converting from nanosecond to | |
| 81 // millisecond at 5ms boundary. | |
| 82 int interval_ms = | |
| 83 round(static_cast<float>(stats.actual_request_interval_ns) / 5000) * 5; | |
| 84 | |
| 85 interval_ms = ClampProbingInterval(interval_ms); | |
| 86 | |
| 87 UMA_HISTOGRAM_ENUMERATION("WebRTC.NAT.Metrics", nat_type, NAT_TYPE_MAX); | |
| 88 | |
| 89 std::string histogram_name = | |
| 90 HistogramName("SuccessPercent", nat_type, interval_ms); | |
| 91 | |
| 92 // Mimic the same behavior as UMA_HISTOGRAM_PERCENTAGE. We can't use that | |
| 93 // macro as the histogram name is determined dynamically. | |
| 94 base::HistogramBase* histogram = base::Histogram::FactoryGet( | |
| 95 histogram_name, 1, 101, 102, base::Histogram::kUmaTargetedHistogramFlag); | |
| 96 histogram->Add(stats.success_percent); | |
| 97 | |
| 98 DVLOG(1) << "Histogram '" << histogram_name.c_str() | |
| 99 << "' = " << stats.success_percent; | |
| 100 | |
| 101 histogram_name = HistogramName("ResponseLatency", nat_type, interval_ms); | |
| 102 | |
| 103 histogram = base::Histogram::FactoryTimeGet( | |
| 104 histogram_name, base::TimeDelta::FromMilliseconds(1), | |
| 105 base::TimeDelta::FromSeconds(10), 50, | |
| 106 base::Histogram::kUmaTargetedHistogramFlag); | |
| 107 histogram->AddTime(base::TimeDelta::FromMilliseconds(stats.average_rtt_ms)); | |
| 108 | |
| 109 DVLOG(1) << "Histogram '" << histogram_name.c_str() | |
| 110 << "' = " << stats.average_rtt_ms << " ms"; | |
| 111 | |
| 112 DVLOG(1) << "Shared Socket Mode: " << stats.shared_socket_mode; | |
| 113 DVLOG(1) << "Requests sent: " << stats.num_request_sent; | |
| 114 DVLOG(1) << "Responses received: " << stats.num_response_received; | |
| 115 DVLOG(1) << "Target interval (ns): " << stats.target_request_interval_ns; | |
| 116 DVLOG(1) << "Actual interval (ns): " << stats.actual_request_interval_ns; | |
| 117 DVLOG(1) << "NAT Type: " << NatTypeNames[nat_type]; | |
| 118 DVLOG(1) << "Host IP: " << stats.host_ip; | |
| 119 DVLOG(1) << "Server-reflexive ips: "; | |
| 120 for (const auto& ip : stats.srflx_addrs) | |
| 121 DVLOG(1) << "\t" << ip; | |
| 122 } | |
| 123 | |
| 124 void OnStunProbeTrialFinished(StunProber* prober, int result) { | |
| 125 if (result == StunProber::SUCCESS) | |
| 126 SaveHistogramData(prober); | |
| 127 } | 73 } |
| 128 | 74 |
| 129 } // namespace | 75 } // namespace |
| 130 | 76 |
| 131 bool ParseStunProbeParameters(const std::string& params, | 77 StunProberWithWeakPtr::StunProberWithWeakPtr(StunProber* prober) |
| 132 int* requests_per_ip, | 78 : prober_(prober), weak_factory_(this) {} |
| 133 int* interval_ms, | 79 |
| 134 int* shared_socket_mode, | 80 void StunProberWithWeakPtr::SetNextProber(StunProberWithWeakPtr* next_prober) { |
| 135 std::vector<rtc::SocketAddress>* servers) { | 81 DCHECK(!next_prober_); |
| 82 next_prober_ = next_prober; | |
| 83 } | |
| 84 | |
| 85 void StunProberWithWeakPtr::Start(StunProber::Observer* observer) { | |
| 86 if (next_prober_) { | |
| 87 base::MessageLoop::current()->PostDelayedTask( | |
| 88 FROM_HERE, base::Bind(&StunProberWithWeakPtr::Start, | |
| 89 next_prober_->GetWeakPtr(), observer), | |
| 90 base::TimeDelta::FromMilliseconds(prober_->estimated_execution_time())); | |
| 91 } | |
| 92 prober_->Start(observer); | |
| 93 } | |
| 94 | |
| 95 bool StunProberWithWeakPtr::GetStats(StunProber::Stats* stats) { | |
| 96 return prober_->GetStats(stats); | |
| 97 } | |
| 98 | |
| 99 base::WeakPtr<StunProberWithWeakPtr> StunProberWithWeakPtr::GetWeakPtr() { | |
| 100 return weak_factory_.GetWeakPtr(); | |
| 101 } | |
| 102 | |
| 103 StunProberWithWeakPtr::~StunProberWithWeakPtr() { | |
| 104 // DeleteSoon() on |next_prober_| instead of creating a big stack of deleting | |
| 105 // probers. | |
| 106 if (next_prober_) | |
| 107 base::MessageLoop::current()->DeleteSoon(FROM_HERE, next_prober_); | |
| 108 } | |
| 109 | |
| 110 StunProberTrial::StunProberTrial(rtc::NetworkManager* network_manager, | |
| 111 const std::string& params, | |
| 112 rtc::PacketSocketFactory* factory) | |
| 113 : network_manager_(network_manager), | |
| 114 param_line_(params), | |
| 115 factory_(factory) { | |
| 116 // We have to connect to the signal to avoid a race condition if network | |
| 117 // manager hasn't received the network update when we start, the StunProber | |
| 118 // will just fail. | |
| 119 network_manager_->SignalNetworksChanged.connect( | |
| 120 this, &StunProberTrial::OnNetworksChanged); | |
| 121 network_manager_->StartUpdating(); | |
| 122 } | |
| 123 | |
| 124 StunProberTrial::~StunProberTrial() { | |
| 125 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 126 } | |
| 127 | |
| 128 void StunProberTrial::SaveHistogramData() { | |
| 129 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 130 NatType nat_type = NAT_TYPE_MAX; | |
| 131 int interval_ms = 0; | |
| 132 int count = 0; | |
| 133 int total_sent = 0; | |
| 134 int total_recv = 0; | |
| 135 for (StunProberWithWeakPtr* prober = prober_head_.get(); prober != nullptr; | |
| 136 prober = prober->next_prober()) { | |
| 137 ++count; | |
| 138 | |
| 139 // Get the stats. | |
| 140 StunProber::Stats stats; | |
| 141 if (!prober->GetStats(&stats)) | |
| 142 return; | |
| 143 | |
| 144 // Check if the NAT type is consistent. | |
| 145 if (nat_type == NAT_TYPE_MAX) { | |
| 146 nat_type = GetNatType(stats.nat_type); | |
| 147 // If we can't figure out the nattype at the beginning, just return. | |
| 148 if (nat_type == NAT_TYPE_UNKNOWN) | |
| 149 return; | |
| 150 } else if (nat_type != GetNatType(stats.nat_type) && | |
| 151 nat_type != NAT_TYPE_UNKNOWN) { | |
| 152 // For subsequent probers, we might get unknown as nattype if all the | |
| 153 // bindings fail, but it's ok. | |
| 154 return; | |
| 155 } | |
| 156 | |
| 157 // Check that the interval is consistent. | |
| 158 // Use the real probe interval for reporting, converting from nanosecond to | |
| 159 // millisecond at 5ms boundary. | |
| 160 int new_interval_ms = ClampProbingInterval( | |
| 161 round(static_cast<float>(stats.actual_request_interval_ns) / 5000) * 5); | |
| 162 if (interval_ms == 0) { | |
| 163 interval_ms = new_interval_ms; | |
| 164 } else if (interval_ms != new_interval_ms) { | |
| 165 return; | |
| 166 } | |
| 167 | |
| 168 // Sum up the total sent and recv packets. | |
| 169 total_sent += stats.num_request_sent; | |
| 170 total_recv += stats.num_response_received; | |
| 171 | |
| 172 if (count % batch_size_ > 0) | |
| 173 continue; | |
| 174 | |
| 175 if (total_sent == 0) { | |
| 176 total_recv = 0; | |
| 177 continue; | |
| 178 } | |
| 179 | |
| 180 int success_rate = total_recv * 100 / total_sent; | |
| 181 std::string histogram_name = HistogramName( | |
| 182 "SuccessPercent", nat_type, interval_ms, count / batch_size_); | |
| 183 | |
| 184 // Mimic the same behavior as UMA_HISTOGRAM_PERCENTAGE. We can't use | |
| 185 // that macro as the histogram name is determined dynamically. | |
| 186 base::HistogramBase* histogram = | |
| 187 base::Histogram::FactoryGet(histogram_name, 1, 101, 102, | |
| 188 base::Histogram::kUmaTargetedHistogramFlag); | |
| 189 histogram->Add(success_rate); | |
| 190 | |
| 191 DVLOG(1) << "Histogram '" << histogram_name.c_str() | |
| 192 << "' = " << stats.success_percent; | |
| 193 | |
| 194 DVLOG(1) << "Shared Socket Mode: " << stats.shared_socket_mode; | |
| 195 DVLOG(1) << "Requests sent: " << total_sent; | |
| 196 DVLOG(1) << "Responses received: " << total_recv; | |
| 197 DVLOG(1) << "Target interval (ns): " << stats.target_request_interval_ns; | |
| 198 DVLOG(1) << "Actual interval (ns): " << stats.actual_request_interval_ns; | |
| 199 DVLOG(1) << "NAT Type: " << NatTypeNames[nat_type]; | |
| 200 DVLOG(1) << "Host IP: " << stats.host_ip; | |
| 201 | |
| 202 total_sent = 0; | |
| 203 total_recv = 0; | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 bool StunProberTrial::ParseParameters(const std::string& param_line, | |
| 208 StunProberTrial::Param* params) { | |
| 136 std::vector<std::string> stun_params = base::SplitString( | 209 std::vector<std::string> stun_params = base::SplitString( |
| 137 params, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 210 param_line, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| 138 | 211 |
| 139 if (stun_params.size() < 4) { | 212 if (stun_params.size() < 5) { |
| 140 DLOG(ERROR) << "Not enough parameters specified in StartStunProbeTrial"; | 213 DLOG(ERROR) << "Not enough parameters specified in StartStunProbeTrial"; |
| 141 return false; | 214 return false; |
| 142 } | 215 } |
| 143 auto param = stun_params.begin(); | 216 auto param = stun_params.begin(); |
| 144 | 217 |
| 145 if (param->empty()) { | 218 if (param->empty()) { |
| 146 *requests_per_ip = 10; | 219 *params->requests_per_ip = 10; |
| 147 } else if (!base::StringToInt(*param, requests_per_ip)) { | 220 } else if (!base::StringToInt(*param, params->requests_per_ip)) { |
| 148 DLOG(ERROR) << "Failed to parse request_per_ip in StartStunProbeTrial"; | 221 DLOG(ERROR) << "Failed to parse request_per_ip in StartStunProbeTrial"; |
| 149 return false; | 222 return false; |
| 150 } | 223 } |
| 151 param++; | 224 param++; |
| 152 | 225 |
| 153 // Set inter-probe interval randomly from 0, 5, 10, ... 50, 100 ms. | 226 // Set inter-probe interval randomly from 0, 5, 10, ... 50, 100 ms. |
| 154 if ((*param).empty()) { | 227 if ((*param).empty()) { |
| 155 *interval_ms = base::RandInt(0, 11) * 5; | 228 *params->interval_ms = base::RandInt(0, 11) * 5; |
| 156 } else if (!base::StringToInt(*param, interval_ms)) { | 229 } else if (!base::StringToInt(*param, params->interval_ms)) { |
| 157 DLOG(ERROR) << "Failed to parse interval in StartStunProbeTrial"; | 230 DLOG(ERROR) << "Failed to parse interval in StartStunProbeTrial"; |
| 158 return false; | 231 return false; |
| 159 } | 232 } |
| 160 *interval_ms = ClampProbingInterval(*interval_ms); | 233 *params->interval_ms = ClampProbingInterval(*params->interval_ms); |
| 161 param++; | 234 param++; |
| 162 | 235 |
| 163 if ((*param).empty()) { | 236 if ((*param).empty()) { |
| 164 *shared_socket_mode = base::RandInt(0, 1); | 237 *params->shared_socket_mode = base::RandInt(0, 1); |
| 165 } else if (!base::StringToInt(*param, shared_socket_mode)) { | 238 } else if (!base::StringToInt(*param, params->shared_socket_mode)) { |
| 166 DLOG(ERROR) << "Failed to parse shared_socket_mode in StartStunProbeTrial"; | 239 DLOG(ERROR) << "Failed to parse shared_socket_mode in StartStunProbeTrial"; |
| 167 return false; | 240 return false; |
| 168 } | 241 } |
| 169 param++; | 242 param++; |
| 170 | 243 |
| 244 if (param->empty()) { | |
| 245 *params->batch_size = 5; | |
| 246 } else if (!base::StringToInt(*param, params->batch_size)) { | |
| 247 DLOG(ERROR) << "Failed to parse batch_size in StartStunProbeTrial"; | |
| 248 return false; | |
| 249 } | |
| 250 param++; | |
| 251 | |
| 252 if (param->empty()) { | |
| 253 *params->total_batches = 5; | |
| 254 } else if (!base::StringToInt(*param, params->total_batches)) { | |
| 255 DLOG(ERROR) << "Failed to parse total_batches in StartStunProbeTrial"; | |
| 256 return false; | |
| 257 } | |
| 258 param++; | |
| 259 | |
| 171 while (param != stun_params.end() && !param->empty()) { | 260 while (param != stun_params.end() && !param->empty()) { |
| 172 rtc::SocketAddress server; | 261 rtc::SocketAddress server; |
| 173 if (!server.FromString(*param)) { | 262 if (!server.FromString(*param)) { |
| 174 DLOG(ERROR) << "Failed to parse address in StartStunProbeTrial"; | 263 DLOG(ERROR) << "Failed to parse address in StartStunProbeTrial"; |
| 175 return false; | 264 return false; |
| 176 } | 265 } |
| 177 servers->push_back(server); | 266 params->servers->push_back(server); |
| 178 param++; | 267 param++; |
| 179 } | 268 } |
| 180 | 269 |
| 181 return !servers->empty(); | 270 return !params->servers->empty(); |
| 182 } | 271 } |
| 183 | 272 |
| 184 scoped_ptr<stunprober::StunProber> StartStunProbeTrial( | 273 void StunProberTrial::OnNetworksChanged() { |
| 185 const rtc::NetworkManager::NetworkList& networks, | 274 DCHECK(thread_checker_.CalledOnValidThread()); |
| 186 const std::string& params, | 275 DVLOG(1) << "Starting stun trial with params: " << param_line_; |
| 187 rtc::PacketSocketFactory* factory) { | 276 rtc::NetworkManager::NetworkList networks; |
| 188 DVLOG(1) << "Starting stun trial with params: " << params; | 277 network_manager_->GetNetworks(&networks); |
| 189 | 278 |
| 190 // If we don't have local addresses, we won't be able to determine whether | 279 // If we don't have local addresses, we won't be able to determine whether |
| 191 // we're behind NAT or not. | 280 // we're behind NAT or not. |
| 192 if (networks.empty()) { | 281 if (networks.empty()) { |
| 193 DLOG(ERROR) << "No networks specified in StartStunProbeTrial"; | 282 DLOG(ERROR) << "No networks specified in StartStunProbeTrial"; |
| 194 return nullptr; | 283 return; |
| 195 } | 284 } |
| 196 | 285 |
| 286 network_manager_->StopUpdating(); | |
| 287 network_manager_->SignalNetworksChanged.disconnect(this); | |
| 288 | |
| 197 int requests_per_ip; | 289 int requests_per_ip; |
| 290 int shared_socket_mode; | |
| 198 int interval_ms; | 291 int interval_ms; |
| 199 int shared_socket_mode; | 292 int total_batches; |
| 200 std::vector<rtc::SocketAddress> servers; | 293 std::vector<rtc::SocketAddress> servers; |
| 201 | 294 |
| 202 if (!ParseStunProbeParameters(params, &requests_per_ip, &interval_ms, | 295 StunProberTrial::Param params(&requests_per_ip, &interval_ms, |
| 203 &shared_socket_mode, &servers)) { | 296 &shared_socket_mode, &batch_size_, |
| 204 return nullptr; | 297 &total_batches, &servers); |
|
Alexei Svitkine (slow)
2015/10/30 18:39:47
Per my comment below, suggest this instead:
StunP
guoweis_left_chromium
2015/10/30 21:47:13
Done.
| |
| 298 | |
| 299 if (!ParseParameters(param_line_, ¶ms)) { | |
| 300 return; | |
| 205 } | 301 } |
| 206 | 302 |
| 207 scoped_ptr<StunProber> prober( | 303 total_probers_ = total_batches * batch_size_; |
| 208 new StunProber(factory, rtc::Thread::Current(), networks)); | |
| 209 | 304 |
| 210 if (!prober->Start( | 305 StunProberWithWeakPtr* prev_prober = nullptr; |
| 211 servers, (shared_socket_mode != 0), interval_ms, requests_per_ip, | 306 |
| 212 1000, | 307 for (int i = 0; i < total_probers_; i++) { |
| 213 rtc::Callback2<void, StunProber*, int>(&OnStunProbeTrialFinished))) { | 308 stunprober::StunProber* prober = |
| 214 DLOG(ERROR) << "Failed to Start in StartStunProbeTrial"; | 309 new StunProber(factory_, rtc::Thread::Current(), networks); |
| 215 OnStunProbeTrialFinished(prober.get(), StunProber::GENERIC_FAILURE); | 310 scoped_ptr<StunProberWithWeakPtr> prober_wp( |
| 216 return nullptr; | 311 new StunProberWithWeakPtr(prober)); |
| 312 if (!prober->Prepare(servers, (shared_socket_mode != 0), interval_ms, | |
| 313 requests_per_ip, 1000, this)) { | |
| 314 DLOG(ERROR) << "Failed to Prepare in StartStunProbeTrial"; | |
| 315 return; | |
| 316 } | |
| 317 | |
| 318 if (prev_prober) | |
| 319 prev_prober->SetNextProber(prober_wp.get()); | |
| 320 else | |
| 321 prober_head_.reset(prober_wp.get()); | |
| 322 | |
| 323 prev_prober = prober_wp.release(); | |
| 217 } | 324 } |
| 218 | |
| 219 return prober; | |
| 220 } | 325 } |
| 221 | 326 |
| 327 void StunProberTrial::OnFinished(StunProber* prober, | |
| 328 StunProber::Status result) { | |
| 329 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 330 if (result == StunProber::SUCCESS) | |
| 331 ++finished_probers_; | |
| 332 | |
| 333 if (finished_probers_ == total_probers_) | |
| 334 SaveHistogramData(); | |
| 335 } | |
| 336 | |
| 337 void StunProberTrial::OnPrepared(StunProber* prober, | |
| 338 StunProber::Status result) { | |
| 339 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 340 if (result == StunProber::SUCCESS) | |
| 341 ++ready_probers_; | |
| 342 | |
| 343 if (ready_probers_ == total_probers_) | |
| 344 prober_head_->Start(this); | |
| 345 } | |
|
Alexei Svitkine (slow)
2015/10/30 18:39:47
Nit: Add an empty line below.
guoweis_left_chromium
2015/10/30 21:47:13
Done.
| |
| 222 } // namespace content | 346 } // namespace content |
| OLD | NEW |