1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/quic/quic_connectivity_monitor.h"
6
7 #include "base/metrics/histogram_functions.h"
8 #include "base/metrics/histogram_macros.h"
9
10 namespace net {
11
12 namespace {
13
IsErrorRelatedToConnectivity(int error_code)14 bool IsErrorRelatedToConnectivity(int error_code) {
15 return (error_code == ERR_ADDRESS_UNREACHABLE ||
16 error_code == ERR_ACCESS_DENIED ||
17 error_code == ERR_INTERNET_DISCONNECTED);
18 }
19
20 } // namespace
21
QuicConnectivityMonitor(handles::NetworkHandle default_network)22 QuicConnectivityMonitor::QuicConnectivityMonitor(
23 handles::NetworkHandle default_network)
24 : default_network_(default_network) {}
25
26 QuicConnectivityMonitor::~QuicConnectivityMonitor() = default;
27
RecordConnectivityStatsToHistograms(const std::string & notification,handles::NetworkHandle affected_network) const28 void QuicConnectivityMonitor::RecordConnectivityStatsToHistograms(
29 const std::string& notification,
30 handles::NetworkHandle affected_network) const {
31 if (notification == "OnNetworkSoonToDisconnect" ||
32 notification == "OnNetworkDisconnected") {
33 // If the disconnected network is not the default network, ignore
34 // stats collections.
35 if (affected_network != default_network_)
36 return;
37 }
38
39 base::ClampedNumeric<int> num_degrading_sessions = GetNumDegradingSessions();
40
41 if (num_sessions_active_during_current_speculative_connectivity_failure_) {
42 UMA_HISTOGRAM_COUNTS_100(
43 "Net.QuicConnectivityMonitor.NumSessionsTrackedSinceSpeculativeError",
44 num_sessions_active_during_current_speculative_connectivity_failure_
45 .value());
46 }
47
48 UMA_HISTOGRAM_COUNTS_100(
49 "Net.QuicConnectivityMonitor.NumActiveQuicSessionsAtNetworkChange",
50 active_sessions_.size());
51
52 int percentage = 0;
53 if (num_sessions_active_during_current_speculative_connectivity_failure_ &&
54 num_sessions_active_during_current_speculative_connectivity_failure_
55 .value() > 0) {
56 percentage = base::saturated_cast<int>(
57 num_all_degraded_sessions_ * 100.0 /
58 num_sessions_active_during_current_speculative_connectivity_failure_
59 .value());
60 }
61
62 UMA_HISTOGRAM_COUNTS_100(
63 "Net.QuicConnectivityMonitor.NumAllSessionsDegradedAtNetworkChange",
64 num_all_degraded_sessions_);
65
66 const std::string raw_histogram_name1 =
67 "Net.QuicConnectivityMonitor.NumAllDegradedSessions." + notification;
68 base::UmaHistogramCustomCounts(raw_histogram_name1,
69 num_all_degraded_sessions_, 1, 100, 50);
70
71 const std::string percentage_histogram_name1 =
72 "Net.QuicConnectivityMonitor.PercentageAllDegradedSessions." +
73 notification;
74
75 base::UmaHistogramPercentageObsoleteDoNotUse(percentage_histogram_name1,
76 percentage);
77
78 // Skip degrading session collection if there are less than two sessions.
79 if (active_sessions_.size() < 2u)
80 return;
81
82 const std::string raw_histogram_name =
83 "Net.QuicConnectivityMonitor.NumActiveDegradingSessions." + notification;
84
85 base::UmaHistogramCustomCounts(raw_histogram_name, num_degrading_sessions, 1,
86 100, 50);
87
88 percentage = base::saturated_cast<double>(num_degrading_sessions * 100.0 /
89 active_sessions_.size());
90
91 const std::string percentage_histogram_name =
92 "Net.QuicConnectivityMonitor.PercentageActiveDegradingSessions." +
93 notification;
94 base::UmaHistogramPercentageObsoleteDoNotUse(percentage_histogram_name,
95 percentage);
96 }
97
GetNumDegradingSessions() const98 size_t QuicConnectivityMonitor::GetNumDegradingSessions() const {
99 return degrading_sessions_.size();
100 }
101
GetCountForWriteErrorCode(int write_error_code) const102 size_t QuicConnectivityMonitor::GetCountForWriteErrorCode(
103 int write_error_code) const {
104 auto it = write_error_map_.find(write_error_code);
105 return it == write_error_map_.end() ? 0u : it->second;
106 }
107
SetInitialDefaultNetwork(handles::NetworkHandle default_network)108 void QuicConnectivityMonitor::SetInitialDefaultNetwork(
109 handles::NetworkHandle default_network) {
110 default_network_ = default_network;
111 }
112
OnSessionPathDegrading(QuicChromiumClientSession * session,handles::NetworkHandle network)113 void QuicConnectivityMonitor::OnSessionPathDegrading(
114 QuicChromiumClientSession* session,
115 handles::NetworkHandle network) {
116 if (network != default_network_)
117 return;
118
119 degrading_sessions_.insert(session);
120 num_all_degraded_sessions_++;
121 // If the degrading session used to be on the previous default network, it is
122 // possible that the session is no longer tracked in |active_sessions_| due
123 // to the recent default network change.
124 active_sessions_.insert(session);
125
126 if (!num_sessions_active_during_current_speculative_connectivity_failure_) {
127 num_sessions_active_during_current_speculative_connectivity_failure_ =
128 active_sessions_.size();
129 } else {
130 // Before seeing session degrading, PACKET_WRITE_ERROR has been observed.
131 UMA_HISTOGRAM_COUNTS_100(
132 "Net.QuicConnectivityMonitor.NumWriteErrorsSeenBeforeDegradation",
133 quic_error_map_[quic::QUIC_PACKET_WRITE_ERROR]);
134 }
135 }
136
OnSessionResumedPostPathDegrading(QuicChromiumClientSession * session,handles::NetworkHandle network)137 void QuicConnectivityMonitor::OnSessionResumedPostPathDegrading(
138 QuicChromiumClientSession* session,
139 handles::NetworkHandle network) {
140 if (network != default_network_)
141 return;
142
143 degrading_sessions_.erase(session);
144
145 // If the resumed session used to be on the previous default network, it is
146 // possible that the session is no longer tracked in |active_sessions_| due
147 // to the recent default network change.
148 active_sessions_.insert(session);
149
150 num_all_degraded_sessions_ = 0u;
151 num_sessions_active_during_current_speculative_connectivity_failure_ =
152 std::nullopt;
153 }
154
OnSessionEncounteringWriteError(QuicChromiumClientSession * session,handles::NetworkHandle network,int error_code)155 void QuicConnectivityMonitor::OnSessionEncounteringWriteError(
156 QuicChromiumClientSession* session,
157 handles::NetworkHandle network,
158 int error_code) {
159 if (network != default_network_)
160 return;
161
162 // If the session used to be on the previous default network, it is
163 // possible that the session is no longer tracked in |active_sessions_| due
164 // to the recent default network change.
165 active_sessions_.insert(session);
166
167 ++write_error_map_[error_code];
168
169 bool is_session_degraded =
170 degrading_sessions_.find(session) != degrading_sessions_.end();
171
172 UMA_HISTOGRAM_BOOLEAN(
173 "Net.QuicConnectivityMonitor.SessionDegradedBeforeWriteError",
174 is_session_degraded);
175
176 if (!num_sessions_active_during_current_speculative_connectivity_failure_ &&
177 IsErrorRelatedToConnectivity(error_code)) {
178 num_sessions_active_during_current_speculative_connectivity_failure_ =
179 active_sessions_.size();
180 }
181 }
182
OnSessionClosedAfterHandshake(QuicChromiumClientSession * session,handles::NetworkHandle network,quic::ConnectionCloseSource source,quic::QuicErrorCode error_code)183 void QuicConnectivityMonitor::OnSessionClosedAfterHandshake(
184 QuicChromiumClientSession* session,
185 handles::NetworkHandle network,
186 quic::ConnectionCloseSource source,
187 quic::QuicErrorCode error_code) {
188 if (network != default_network_)
189 return;
190
191 if (source == quic::ConnectionCloseSource::FROM_PEER) {
192 // Connection closed by the peer post handshake with PUBLIC RESET
193 // is most likely a NAT rebinding issue.
194 if (error_code == quic::QUIC_PUBLIC_RESET)
195 quic_error_map_[error_code]++;
196 return;
197 }
198
199 if (error_code == quic::QUIC_PACKET_WRITE_ERROR ||
200 error_code == quic::QUIC_TOO_MANY_RTOS) {
201 // Connection close by self with PACKET_WRITE_ERROR or TOO_MANY_RTOS
202 // is likely a connectivity issue.
203 quic_error_map_[error_code]++;
204 }
205 }
206
OnSessionRegistered(QuicChromiumClientSession * session,handles::NetworkHandle network)207 void QuicConnectivityMonitor::OnSessionRegistered(
208 QuicChromiumClientSession* session,
209 handles::NetworkHandle network) {
210 if (network != default_network_)
211 return;
212
213 active_sessions_.insert(session);
214 if (num_sessions_active_during_current_speculative_connectivity_failure_) {
215 num_sessions_active_during_current_speculative_connectivity_failure_
216 .value()++;
217 }
218 }
219
OnSessionRemoved(QuicChromiumClientSession * session)220 void QuicConnectivityMonitor::OnSessionRemoved(
221 QuicChromiumClientSession* session) {
222 degrading_sessions_.erase(session);
223 active_sessions_.erase(session);
224 }
225
OnDefaultNetworkUpdated(handles::NetworkHandle default_network)226 void QuicConnectivityMonitor::OnDefaultNetworkUpdated(
227 handles::NetworkHandle default_network) {
228 default_network_ = default_network;
229 active_sessions_.clear();
230 degrading_sessions_.clear();
231 num_sessions_active_during_current_speculative_connectivity_failure_ =
232 std::nullopt;
233 write_error_map_.clear();
234 quic_error_map_.clear();
235 }
236
OnIPAddressChanged()237 void QuicConnectivityMonitor::OnIPAddressChanged() {
238 // If handles::NetworkHandle is supported, connectivity monitor will receive
239 // notifications via OnDefaultNetworkUpdated.
240 if (NetworkChangeNotifier::AreNetworkHandlesSupported())
241 return;
242
243 DCHECK_EQ(default_network_, handles::kInvalidNetworkHandle);
244 degrading_sessions_.clear();
245 write_error_map_.clear();
246 }
247
OnSessionGoingAwayOnIPAddressChange(QuicChromiumClientSession * session)248 void QuicConnectivityMonitor::OnSessionGoingAwayOnIPAddressChange(
249 QuicChromiumClientSession* session) {
250 // This should only be called after ConnectivityMonitor gets notified via
251 // OnIPAddressChanged().
252 DCHECK(degrading_sessions_.empty());
253 // |session| that encounters IP address change will lose track which network
254 // it is bound to. Future connectivity monitoring may be misleading.
255 session->RemoveConnectivityObserver(this);
256 }
257
258 } // namespace net
259