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.%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 Loading... | |
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 |
OLD | NEW |