Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Side by Side Diff: content/renderer/media/webrtc/stun_field_trial.cc

Issue 1417663004: Create an experiment to study whether too many bindings cause NAT failure (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address Alexei comments. Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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.%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::set_next_prober(
135 std::vector<rtc::SocketAddress>* servers) { 81 StunProberWithWeakPtr* next_prober) {
82 DCHECK(!next_prober_);
83 next_prober_ = next_prober;
84 }
85
86 void StunProberWithWeakPtr::Start(StunProber::Observer* observer) {
87 if (next_prober_) {
88 base::MessageLoop::current()->PostDelayedTask(
89 FROM_HERE, base::Bind(&StunProberWithWeakPtr::Start,
90 next_prober_->GetWeakPtr(), observer),
Sergey Ulanov 2015/10/30 18:27:49 So if I understand correctly the reason you need w
guoweis_left_chromium 2015/10/30 21:47:13 Done.
91 base::TimeDelta::FromMilliseconds(prober_->estimated_execution_time()));
92 }
93 prober_->Start(observer);
94 }
95
96 StunProberWithWeakPtr::~StunProberWithWeakPtr() {
97 if (next_prober_) {
Alexei Svitkine (slow) 2015/10/30 17:32:18 Nit: No {}'s
98 base::MessageLoop::current()->DeleteSoon(FROM_HERE, next_prober_);
Alexei Svitkine (slow) 2015/10/30 17:32:18 Can you add a comment why DeleteSoon is being used
Sergey Ulanov 2015/10/30 18:27:49 So if you have a chain of 5 probers it would take
Sergey Ulanov 2015/10/30 18:27:49 next_proper_ is effectively owned by StunProberWit
99 }
100 }
101
102 StunProberTrial::StunProberTrial(rtc::NetworkManager* network_manager,
103 const std::string& params,
104 rtc::PacketSocketFactory* factory)
105 : network_manager_(network_manager), params_(params), factory_(factory) {
106 // We have to connect to the signal to avoid a race condition if network
107 // manager hasn't received the network update when we start, the StunProber
108 // will just fail.
109 network_manager_->SignalNetworksChanged.connect(
110 this, &StunProberTrial::OnNetworksChanged);
111 network_manager_->StartUpdating();
112 }
113
114 StunProberTrial::~StunProberTrial() {
115 DCHECK(thread_checker_.CalledOnValidThread());
Sergey Ulanov 2015/10/30 18:27:49 Don't need this. thread_checker_ verifies that it'
guoweis_left_chromium 2015/10/30 21:47:13 Done.
116 }
117
118 void StunProberTrial::SaveHistogramData() {
119 DCHECK(thread_checker_.CalledOnValidThread());
120 NatType nat_type = NAT_TYPE_MAX;
121 int interval_ms = 0;
122 int count = 0;
123 int total_sent = 0;
124 int total_recv = 0;
125 for (StunProberWithWeakPtr* prober = prober_head_.get(); prober != nullptr;
126 prober = prober->GetNextProber()) {
127 ++count;
128
129 // Get the stats.
130 StunProber::Stats stats;
131 if (!prober->GetStats(&stats))
132 return;
133
134 // Check if the NAT type is consistent.
135 if (nat_type == NAT_TYPE_MAX) {
136 nat_type = GetNatType(stats.nat_type);
137 // If we can't figure out the nattype at the beginning, just return.
138 if (nat_type == NAT_TYPE_UNKNOWN)
139 return;
140 }
141 // For subsequent probers, we might get unknown as nattype if all the
142 // bindings fail, but it's ok.
143 else if (nat_type != GetNatType(stats.nat_type) &&
Alexei Svitkine (slow) 2015/10/30 17:32:18 Nit: Put this on the same line as }. Maybe move th
144 nat_type != NAT_TYPE_UNKNOWN)
Alexei Svitkine (slow) 2015/10/30 17:32:19 Nit: {}'s
145 return;
146
147 // Check that the interval is consistent.
148 // Use the real probe interval for reporting, converting from nanosecond to
149 // millisecond at 5ms boundary.
150 int new_interval_ms = ClampProbingInterval(
151 round(static_cast<float>(stats.actual_request_interval_ns) / 5000) * 5);
152 if (interval_ms == 0) {
153 interval_ms = new_interval_ms;
154 } else if (interval_ms != new_interval_ms)
Alexei Svitkine (slow) 2015/10/30 17:32:19 Nit: {}'s to be consistent with if
155 return;
156
157 // Sum up the total sent and recv packets.
158 total_sent += stats.num_request_sent;
159 total_recv += stats.num_response_received;
160
161 if (count % batch_size_ > 0)
162 continue;
163
164 if (total_sent == 0) {
165 total_recv = 0;
166 continue;
167 }
168
169 int success_rate = total_recv * 100 / total_sent;
170 std::string histogram_name = HistogramName(
171 "SuccessPercent", nat_type, interval_ms, count / batch_size_);
172
173 // Mimic the same behavior as UMA_HISTOGRAM_PERCENTAGE. We can't use
174 // that macro as the histogram name is determined dynamically.
175 base::HistogramBase* histogram =
176 base::Histogram::FactoryGet(histogram_name, 1, 101, 102,
177 base::Histogram::kUmaTargetedHistogramFlag);
178 histogram->Add(success_rate);
179
180 DVLOG(1) << "Histogram '" << histogram_name.c_str()
181 << "' = " << stats.success_percent;
182
183 DVLOG(1) << "Shared Socket Mode: " << stats.shared_socket_mode;
184 DVLOG(1) << "Requests sent: " << total_sent;
185 DVLOG(1) << "Responses received: " << total_recv;
186 DVLOG(1) << "Target interval (ns): " << stats.target_request_interval_ns;
187 DVLOG(1) << "Actual interval (ns): " << stats.actual_request_interval_ns;
188 DVLOG(1) << "NAT Type: " << NatTypeNames[nat_type];
189 DVLOG(1) << "Host IP: " << stats.host_ip;
190
191 total_sent = 0;
192 total_recv = 0;
193 }
194 }
195
196 bool StunProberTrial::ParseParameters(
197 const std::string& params,
198 int* requests_per_ip,
199 int* interval_ms,
200 int* shared_socket_mode,
201 int* batch_size,
202 int* total_batches,
203 std::vector<rtc::SocketAddress>* servers) {
Alexei Svitkine (slow) 2015/10/30 17:32:18 Nit: Suggest making a struct to hold the different
136 std::vector<std::string> stun_params = base::SplitString( 204 std::vector<std::string> stun_params = base::SplitString(
137 params, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); 205 params, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
138 206
139 if (stun_params.size() < 4) { 207 if (stun_params.size() < 5) {
140 DLOG(ERROR) << "Not enough parameters specified in StartStunProbeTrial"; 208 DLOG(ERROR) << "Not enough parameters specified in StartStunProbeTrial";
141 return false; 209 return false;
142 } 210 }
143 auto param = stun_params.begin(); 211 auto param = stun_params.begin();
144 212
145 if (param->empty()) { 213 if (param->empty()) {
146 *requests_per_ip = 10; 214 *requests_per_ip = 10;
147 } else if (!base::StringToInt(*param, requests_per_ip)) { 215 } else if (!base::StringToInt(*param, requests_per_ip)) {
148 DLOG(ERROR) << "Failed to parse request_per_ip in StartStunProbeTrial"; 216 DLOG(ERROR) << "Failed to parse request_per_ip in StartStunProbeTrial";
149 return false; 217 return false;
(...skipping 11 matching lines...) Expand all
161 param++; 229 param++;
162 230
163 if ((*param).empty()) { 231 if ((*param).empty()) {
164 *shared_socket_mode = base::RandInt(0, 1); 232 *shared_socket_mode = base::RandInt(0, 1);
165 } else if (!base::StringToInt(*param, shared_socket_mode)) { 233 } else if (!base::StringToInt(*param, shared_socket_mode)) {
166 DLOG(ERROR) << "Failed to parse shared_socket_mode in StartStunProbeTrial"; 234 DLOG(ERROR) << "Failed to parse shared_socket_mode in StartStunProbeTrial";
167 return false; 235 return false;
168 } 236 }
169 param++; 237 param++;
170 238
239 if (param->empty()) {
240 *batch_size = 5;
241 } else if (!base::StringToInt(*param, batch_size)) {
242 DLOG(ERROR) << "Failed to parse batch_size in StartStunProbeTrial";
243 return false;
244 }
245 param++;
246
247 if (param->empty()) {
248 *total_batches = 5;
249 } else if (!base::StringToInt(*param, total_batches)) {
250 DLOG(ERROR) << "Failed to parse total_batches in StartStunProbeTrial";
251 return false;
252 }
253 param++;
254
171 while (param != stun_params.end() && !param->empty()) { 255 while (param != stun_params.end() && !param->empty()) {
172 rtc::SocketAddress server; 256 rtc::SocketAddress server;
173 if (!server.FromString(*param)) { 257 if (!server.FromString(*param)) {
174 DLOG(ERROR) << "Failed to parse address in StartStunProbeTrial"; 258 DLOG(ERROR) << "Failed to parse address in StartStunProbeTrial";
175 return false; 259 return false;
176 } 260 }
177 servers->push_back(server); 261 servers->push_back(server);
178 param++; 262 param++;
179 } 263 }
180 264
181 return !servers->empty(); 265 return !servers->empty();
182 } 266 }
183 267
184 scoped_ptr<stunprober::StunProber> StartStunProbeTrial( 268 void StunProberTrial::OnNetworksChanged() {
185 const rtc::NetworkManager::NetworkList& networks, 269 DCHECK(thread_checker_.CalledOnValidThread());
186 const std::string& params, 270 DVLOG(1) << "Starting stun trial with params: " << params_;
187 rtc::PacketSocketFactory* factory) { 271 rtc::NetworkManager::NetworkList networks;
188 DVLOG(1) << "Starting stun trial with params: " << params; 272 network_manager_->GetNetworks(&networks);
189 273
190 // If we don't have local addresses, we won't be able to determine whether 274 // If we don't have local addresses, we won't be able to determine whether
191 // we're behind NAT or not. 275 // we're behind NAT or not.
192 if (networks.empty()) { 276 if (networks.empty()) {
193 DLOG(ERROR) << "No networks specified in StartStunProbeTrial"; 277 DLOG(ERROR) << "No networks specified in StartStunProbeTrial";
194 return nullptr; 278 return;
195 } 279 }
196 280
281 network_manager_->StopUpdating();
282 network_manager_->SignalNetworksChanged.disconnect(this);
283
197 int requests_per_ip; 284 int requests_per_ip;
285 int shared_socket_mode;
198 int interval_ms; 286 int interval_ms;
199 int shared_socket_mode; 287 int total_batches;
200 std::vector<rtc::SocketAddress> servers; 288 std::vector<rtc::SocketAddress> servers;
201 289
202 if (!ParseStunProbeParameters(params, &requests_per_ip, &interval_ms, 290 if (!ParseParameters(params_, &requests_per_ip, &interval_ms,
203 &shared_socket_mode, &servers)) { 291 &shared_socket_mode, &batch_size_, &total_batches,
204 return nullptr; 292 &servers)) {
293 return;
205 } 294 }
206 295
207 scoped_ptr<StunProber> prober( 296 total_probers_ = total_batches * batch_size_;
208 new StunProber(factory, rtc::Thread::Current(), networks));
209 297
210 if (!prober->Start( 298 StunProberWithWeakPtr* prev_prober = nullptr;
211 servers, (shared_socket_mode != 0), interval_ms, requests_per_ip, 299
212 1000, 300 for (int i = 0; i < total_probers_; i++) {
213 rtc::Callback2<void, StunProber*, int>(&OnStunProbeTrialFinished))) { 301 stunprober::StunProber* prober =
214 DLOG(ERROR) << "Failed to Start in StartStunProbeTrial"; 302 new StunProber(factory_, rtc::Thread::Current(), networks);
215 OnStunProbeTrialFinished(prober.get(), StunProber::GENERIC_FAILURE); 303 scoped_ptr<StunProberWithWeakPtr> prober_wp(
216 return nullptr; 304 new StunProberWithWeakPtr(prober));
305 if (!prober->Prepare(servers, (shared_socket_mode != 0), interval_ms,
306 requests_per_ip, 1000, this)) {
307 DLOG(ERROR) << "Failed to Prepare in StartStunProbeTrial";
308 return;
309 }
310
311 if (prev_prober)
312 prev_prober->set_next_prober(prober_wp.get());
313 else
314 prober_head_.reset(prober_wp.get());
315
316 prev_prober = prober_wp.release();
217 } 317 }
218
219 return prober;
220 } 318 }
221 319
320 void StunProberTrial::OnFinished(StunProber* prober,
321 StunProber::Status result) {
322 DCHECK(thread_checker_.CalledOnValidThread());
323 if (result == StunProber::SUCCESS)
324 ++finished_probers_;
325
326 if (finished_probers_ == total_probers_)
327 SaveHistogramData();
328 }
329
330 void StunProberTrial::OnPrepared(StunProber* prober,
331 StunProber::Status result) {
332 DCHECK(thread_checker_.CalledOnValidThread());
333 if (result == StunProber::SUCCESS)
334 ++ready_probers_;
335
336 if (ready_probers_ == total_probers_) {
337 DCHECK(prober_head_);
Alexei Svitkine (slow) 2015/10/30 17:32:19 Nit: I think we usually omit such DCHECKs since th
338 prober_head_->Start(this);
339 }
340 }
222 } // namespace content 341 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698