OLD | NEW |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 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 <limits.h> | 5 #include <limits.h> |
6 #include <ctype.h> | 6 #include <ctype.h> |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/histogram.h" | 10 #include "base/histogram.h" |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "net/base/sdch_filter.h" | 12 #include "net/base/sdch_filter.h" |
13 #include "net/base/sdch_manager.h" | 13 #include "net/base/sdch_manager.h" |
14 | 14 |
15 #include "sdch/open-vcdiff/src/google/vcdecoder.h" | 15 #include "sdch/open-vcdiff/src/google/vcdecoder.h" |
16 | 16 |
17 SdchFilter::SdchFilter(const FilterContext& filter_context) | 17 SdchFilter::SdchFilter(const FilterContext& filter_context) |
18 : Filter(filter_context), | 18 : Filter(filter_context), |
19 decoding_status_(DECODING_UNINITIALIZED), | 19 decoding_status_(DECODING_UNINITIALIZED), |
20 vcdiff_streaming_decoder_(NULL), | 20 vcdiff_streaming_decoder_(NULL), |
21 dictionary_hash_(), | 21 dictionary_hash_(), |
22 dictionary_hash_is_plausible_(false), | 22 dictionary_hash_is_plausible_(false), |
23 dictionary_(NULL), | 23 dictionary_(NULL), |
24 dest_buffer_excess_(), | 24 dest_buffer_excess_(), |
25 dest_buffer_excess_index_(0), | 25 dest_buffer_excess_index_(0), |
26 source_bytes_(0), | 26 source_bytes_(0), |
27 output_bytes_(0), | 27 output_bytes_(0), |
28 observed_packet_count_(0), | |
29 bytes_observed_in_packets_(0), | |
30 final_packet_time_(), | |
31 possible_pass_through_(false), | 28 possible_pass_through_(false), |
32 connect_time_(filter_context.GetRequestTime()), | |
33 was_cached_(filter_context.IsCachedContent()) { | 29 was_cached_(filter_context.IsCachedContent()) { |
34 bool success = filter_context.GetMimeType(&mime_type_); | 30 bool success = filter_context.GetMimeType(&mime_type_); |
35 DCHECK(success); | 31 DCHECK(success); |
36 success = filter_context.GetURL(&url_); | 32 success = filter_context.GetURL(&url_); |
37 DCHECK(success); | 33 DCHECK(success); |
38 } | 34 } |
39 | 35 |
40 SdchFilter::~SdchFilter() { | 36 SdchFilter::~SdchFilter() { |
41 // All code here is for gathering stats, and can be removed when SDCH is | 37 // All code here is for gathering stats, and can be removed when SDCH is |
42 // considered stable. | 38 // considered stable. |
(...skipping 30 matching lines...) Expand all Loading... |
73 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedVcdiffOut", output_bytes_); | 69 UMA_HISTOGRAM_COUNTS("Sdch.UnflushedVcdiffOut", output_bytes_); |
74 } | 70 } |
75 | 71 |
76 if (was_cached_) { | 72 if (was_cached_) { |
77 // Not a real error, but it is useful to have this tally. | 73 // Not a real error, but it is useful to have this tally. |
78 // TODO(jar): Remove this stat after SDCH stability is validated. | 74 // TODO(jar): Remove this stat after SDCH stability is validated. |
79 SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED); | 75 SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED); |
80 return; // We don't need timing stats, and we aready got ratios. | 76 return; // We don't need timing stats, and we aready got ratios. |
81 } | 77 } |
82 | 78 |
83 if (base::Time() == connect_time_ || read_times_.empty()) { | |
84 // Not a real error, but it is useful to have this tally. | |
85 // TODO(jar): Remove this stat after SDCH stability is validated. | |
86 SdchManager::SdchErrorRecovery(SdchManager::MISSING_TIME_STATS); | |
87 UMA_HISTOGRAM_COUNTS("Sdch.MissingTimeBytesIn", | |
88 static_cast<int>(filter_context().GetByteReadCount())); | |
89 UMA_HISTOGRAM_COUNTS("Sdch.MissingTimeVcdiffIn", source_bytes_); | |
90 return; | |
91 } | |
92 // Since read_times_ is not empty, we can assert we have some packets times. | |
93 DCHECK(final_packet_time_ != base::Time()); | |
94 | |
95 base::TimeDelta duration = final_packet_time_ - connect_time_; | |
96 // We clip our logging at 10 minutes to prevent anamolous data from being | |
97 // considered (per suggestion from Jake Brutlag). | |
98 if (10 < duration.InMinutes()) { | |
99 SdchManager::SdchErrorRecovery(SdchManager::OVER_10_MINUTES); | |
100 return; | |
101 } | |
102 | |
103 switch (decoding_status_) { | 79 switch (decoding_status_) { |
104 case DECODING_IN_PROGRESS: { | 80 case DECODING_IN_PROGRESS: { |
105 UMA_HISTOGRAM_PERCENTAGE("Sdch.Network_Decode_Ratio_a", static_cast<int>( | 81 UMA_HISTOGRAM_PERCENTAGE("Sdch.Network_Decode_Ratio_a", static_cast<int>( |
106 (filter_context().GetByteReadCount() * 100) / output_bytes_)); | 82 (filter_context().GetByteReadCount() * 100) / output_bytes_)); |
107 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_Latency_F_a", duration, | |
108 base::TimeDelta::FromMilliseconds(20), | |
109 base::TimeDelta::FromMinutes(10), 100); | |
110 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_1st_To_Last_a", | |
111 final_packet_time_ - read_times_[0], | |
112 base::TimeDelta::FromMilliseconds(20), | |
113 base::TimeDelta::FromMinutes(10), 100); | |
114 if (read_times_.size() > 4) { | |
115 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_1st_To_2nd_b", | |
116 read_times_[1] - read_times_[0], | |
117 base::TimeDelta::FromMilliseconds(10), | |
118 base::TimeDelta::FromSeconds(3), 100); | |
119 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_2nd_To_3rd_b", | |
120 read_times_[2] - read_times_[1], | |
121 base::TimeDelta::FromMilliseconds(10), | |
122 base::TimeDelta::FromSeconds(3), 100); | |
123 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_3rd_To_4th_b", | |
124 read_times_[3] - read_times_[2], | |
125 base::TimeDelta::FromMilliseconds(10), | |
126 base::TimeDelta::FromSeconds(3), 100); | |
127 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Decode_4th_To_5th_b", | |
128 read_times_[4] - read_times_[3], | |
129 base::TimeDelta::FromMilliseconds(10), | |
130 base::TimeDelta::FromSeconds(3), 100); | |
131 } | |
132 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Decode_Packets_b", | |
133 observed_packet_count_); | |
134 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_Processed_a", | |
135 static_cast<int>(filter_context().GetByteReadCount())); | |
136 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_VcdiffOut_a", | 83 UMA_HISTOGRAM_COUNTS("Sdch.Network_Decode_Bytes_VcdiffOut_a", |
137 output_bytes_); | 84 output_bytes_); |
| 85 filter_context().RecordPacketStats(FilterContext::SDCH_DECODE); |
| 86 |
| 87 // Allow latency experiments to proceed. |
| 88 SdchManager::Global()->SetAllowLatencyExperiment(url_, true); |
138 return; | 89 return; |
139 } | 90 } |
140 case PASS_THROUGH: { | 91 case PASS_THROUGH: { |
141 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_Latency_F_a", | 92 filter_context().RecordPacketStats(FilterContext::SDCH_PASSTHROUGH); |
142 duration, | |
143 base::TimeDelta::FromMilliseconds(20), | |
144 base::TimeDelta::FromMinutes(10), 100); | |
145 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_1st_To_Last_a", | |
146 final_packet_time_ - read_times_[0], | |
147 base::TimeDelta::FromMilliseconds(20), | |
148 base::TimeDelta::FromMinutes(10), 100); | |
149 if (read_times_.size() > 4) { | |
150 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_1st_To_2nd_b", | |
151 read_times_[1] - read_times_[0], | |
152 base::TimeDelta::FromMilliseconds(10), | |
153 base::TimeDelta::FromSeconds(3), 100); | |
154 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_2nd_To_3rd_b", | |
155 read_times_[2] - read_times_[1], | |
156 base::TimeDelta::FromMilliseconds(10), | |
157 base::TimeDelta::FromSeconds(3), 100); | |
158 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_3rd_To_4th_b", | |
159 read_times_[3] - read_times_[2], | |
160 base::TimeDelta::FromMilliseconds(10), | |
161 base::TimeDelta::FromSeconds(3), 100); | |
162 UMA_HISTOGRAM_CLIPPED_TIMES("Sdch.Network_Pass-through_4th_To_5th_b", | |
163 read_times_[4] - read_times_[3], | |
164 base::TimeDelta::FromMilliseconds(10), | |
165 base::TimeDelta::FromSeconds(3), 100); | |
166 } | |
167 UMA_HISTOGRAM_COUNTS_100("Sdch.Network_Pass-through_Packets_b", | |
168 observed_packet_count_); | |
169 return; | 93 return; |
170 } | 94 } |
171 case DECODING_UNINITIALIZED: { | 95 case DECODING_UNINITIALIZED: { |
172 SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED); | 96 SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED); |
173 return; | 97 return; |
174 } | 98 } |
175 case WAITING_FOR_DICTIONARY_SELECTION: { | 99 case WAITING_FOR_DICTIONARY_SELECTION: { |
176 SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY); | 100 SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY); |
177 return; | 101 return; |
178 } | 102 } |
179 case DECODING_ERROR: { | 103 case DECODING_ERROR: { |
180 SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR); | 104 SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR); |
181 return; | 105 return; |
182 } | 106 } |
183 case META_REFRESH_RECOVERY: { | 107 case META_REFRESH_RECOVERY: { |
184 // Already accounted for when set. | 108 // Already accounted for when set. |
185 return; | 109 return; |
186 } | 110 } |
187 } // end of switch. | 111 } // end of switch. |
188 } | 112 } |
189 | 113 |
190 void SdchFilter::UpdateReadTimes() { | |
191 if (!next_stream_data_ || (stream_data_len_ == 0)) { | |
192 // Don't update when we're called to just flush out our internal buffers. | |
193 return; | |
194 } | |
195 const size_t bytes_read_so_far = | |
196 static_cast<size_t>(filter_context().GetByteReadCount()); | |
197 if (bytes_read_so_far <= bytes_observed_in_packets_) { | |
198 DCHECK(bytes_read_so_far == bytes_observed_in_packets_); | |
199 return; // No new bytes have arrived. | |
200 } | |
201 | |
202 // We only save distinct times for the first 5 packets. | |
203 const size_t kMaxTimesInArray = 5; | |
204 const size_t kTypicalPacketSize = 1430; | |
205 final_packet_time_ = base::Time::Now(); | |
206 while (bytes_read_so_far > bytes_observed_in_packets_) { | |
207 if (++observed_packet_count_ <= kMaxTimesInArray) { | |
208 read_times_.push_back(final_packet_time_); | |
209 } | |
210 bytes_observed_in_packets_ += kTypicalPacketSize; | |
211 } | |
212 // Since packets may not be full, we'll remember the number of bytes we've | |
213 // accounted for in packets thus far. | |
214 bytes_observed_in_packets_ = bytes_read_so_far; | |
215 } | |
216 | |
217 bool SdchFilter::InitDecoding(Filter::FilterType filter_type) { | 114 bool SdchFilter::InitDecoding(Filter::FilterType filter_type) { |
218 if (decoding_status_ != DECODING_UNINITIALIZED) | 115 if (decoding_status_ != DECODING_UNINITIALIZED) |
219 return false; | 116 return false; |
220 | 117 |
221 // Handle case where sdch filter is guessed, but not required. | 118 // Handle case where sdch filter is guessed, but not required. |
222 if (FILTER_TYPE_SDCH_POSSIBLE == filter_type) | 119 if (FILTER_TYPE_SDCH_POSSIBLE == filter_type) |
223 possible_pass_through_ = true; | 120 possible_pass_through_ = true; |
224 | 121 |
225 // Initialize decoder only after we have a dictionary in hand. | 122 // Initialize decoder only after we have a dictionary in hand. |
226 decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION; | 123 decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION; |
(...skipping 11 matching lines...) Expand all Loading... |
238 | 135 |
239 | 136 |
240 Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer, | 137 Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer, |
241 int* dest_len) { | 138 int* dest_len) { |
242 int available_space = *dest_len; | 139 int available_space = *dest_len; |
243 *dest_len = 0; // Nothing output yet. | 140 *dest_len = 0; // Nothing output yet. |
244 | 141 |
245 if (!dest_buffer || available_space <= 0) | 142 if (!dest_buffer || available_space <= 0) |
246 return FILTER_ERROR; | 143 return FILTER_ERROR; |
247 | 144 |
248 UpdateReadTimes(); | |
249 | |
250 if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) { | 145 if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) { |
251 FilterStatus status = InitializeDictionary(); | 146 FilterStatus status = InitializeDictionary(); |
252 if (FILTER_NEED_MORE_DATA == status) | 147 if (FILTER_NEED_MORE_DATA == status) |
253 return FILTER_NEED_MORE_DATA; | 148 return FILTER_NEED_MORE_DATA; |
254 if (FILTER_ERROR == status) { | 149 if (FILTER_ERROR == status) { |
255 DCHECK(DECODING_ERROR == decoding_status_); | 150 DCHECK(DECODING_ERROR == decoding_status_); |
256 DCHECK(0 == dest_buffer_excess_index_); | 151 DCHECK_EQ(0u, dest_buffer_excess_index_); |
257 DCHECK(dest_buffer_excess_.empty()); | 152 DCHECK(dest_buffer_excess_.empty()); |
258 // This is where we try very hard to do error recovery, and make this | 153 // This is where we try very hard to do error recovery, and make this |
259 // protocol robust in teh face of proxies that do many different things. | 154 // protocol robust in teh face of proxies that do many different things. |
260 // If we decide that things are looking very bad (too hard to recover), | 155 // If we decide that things are looking very bad (too hard to recover), |
261 // we may even issue a "meta-refresh" to reload the page without an SDCH | 156 // we may even issue a "meta-refresh" to reload the page without an SDCH |
262 // advertisement (so that we are sure we're not hurting anything). First | 157 // advertisement (so that we are sure we're not hurting anything). First |
263 // we try for some light weight recovery, and teh final else clause below | 158 // we try for some light weight recovery, and teh final else clause below |
264 // supports the last ditch meta-refresh approach. | 159 // supports the last ditch meta-refresh approach. |
265 // | 160 // |
266 // Watch out for an error page inserted by the proxy as part of a 40x | 161 // Watch out for an error page inserted by the proxy as part of a 40x |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
326 } | 221 } |
327 } else { | 222 } else { |
328 DCHECK(DECODING_IN_PROGRESS == decoding_status_); | 223 DCHECK(DECODING_IN_PROGRESS == decoding_status_); |
329 } | 224 } |
330 } | 225 } |
331 | 226 |
332 int amount = OutputBufferExcess(dest_buffer, available_space); | 227 int amount = OutputBufferExcess(dest_buffer, available_space); |
333 *dest_len += amount; | 228 *dest_len += amount; |
334 dest_buffer += amount; | 229 dest_buffer += amount; |
335 available_space -= amount; | 230 available_space -= amount; |
336 DCHECK(available_space >= 0); | 231 DCHECK_GE(available_space, 0); |
337 | 232 |
338 if (available_space <= 0) | 233 if (available_space <= 0) |
339 return FILTER_OK; | 234 return FILTER_OK; |
340 DCHECK(dest_buffer_excess_.empty()); | 235 DCHECK(dest_buffer_excess_.empty()); |
341 DCHECK(0 == dest_buffer_excess_index_); | 236 DCHECK_EQ(0u, dest_buffer_excess_index_); |
342 | 237 |
343 if (decoding_status_ != DECODING_IN_PROGRESS) { | 238 if (decoding_status_ != DECODING_IN_PROGRESS) { |
344 if (META_REFRESH_RECOVERY == decoding_status_) { | 239 if (META_REFRESH_RECOVERY == decoding_status_) { |
345 // Absorb all input data. We've already output page reload HTML. | 240 // Absorb all input data. We've already output page reload HTML. |
346 next_stream_data_ = NULL; | 241 next_stream_data_ = NULL; |
347 stream_data_len_ = 0; | 242 stream_data_len_ = 0; |
348 return FILTER_NEED_MORE_DATA; | 243 return FILTER_NEED_MORE_DATA; |
349 } | 244 } |
350 if (PASS_THROUGH == decoding_status_) { | 245 if (PASS_THROUGH == decoding_status_) { |
351 // We must pass in available_space, but it will be changed to bytes_used. | 246 // We must pass in available_space, but it will be changed to bytes_used. |
(...skipping 29 matching lines...) Expand all Loading... |
381 dest_buffer += amount; | 276 dest_buffer += amount; |
382 available_space -= amount; | 277 available_space -= amount; |
383 if (0 == available_space && !dest_buffer_excess_.empty()) | 278 if (0 == available_space && !dest_buffer_excess_.empty()) |
384 return FILTER_OK; | 279 return FILTER_OK; |
385 return FILTER_NEED_MORE_DATA; | 280 return FILTER_NEED_MORE_DATA; |
386 } | 281 } |
387 | 282 |
388 Filter::FilterStatus SdchFilter::InitializeDictionary() { | 283 Filter::FilterStatus SdchFilter::InitializeDictionary() { |
389 const size_t kServerIdLength = 9; // Dictionary hash plus null from server. | 284 const size_t kServerIdLength = 9; // Dictionary hash plus null from server. |
390 size_t bytes_needed = kServerIdLength - dictionary_hash_.size(); | 285 size_t bytes_needed = kServerIdLength - dictionary_hash_.size(); |
391 DCHECK(bytes_needed > 0); | 286 DCHECK_GT(bytes_needed, 0u); |
392 if (!next_stream_data_) | 287 if (!next_stream_data_) |
393 return FILTER_NEED_MORE_DATA; | 288 return FILTER_NEED_MORE_DATA; |
394 if (static_cast<size_t>(stream_data_len_) < bytes_needed) { | 289 if (static_cast<size_t>(stream_data_len_) < bytes_needed) { |
395 dictionary_hash_.append(next_stream_data_, stream_data_len_); | 290 dictionary_hash_.append(next_stream_data_, stream_data_len_); |
396 next_stream_data_ = NULL; | 291 next_stream_data_ = NULL; |
397 stream_data_len_ = 0; | 292 stream_data_len_ = 0; |
398 return FILTER_NEED_MORE_DATA; | 293 return FILTER_NEED_MORE_DATA; |
399 } | 294 } |
400 dictionary_hash_.append(next_stream_data_, bytes_needed); | 295 dictionary_hash_.append(next_stream_data_, bytes_needed); |
401 DCHECK(kServerIdLength == dictionary_hash_.size()); | 296 DCHECK(kServerIdLength == dictionary_hash_.size()); |
402 stream_data_len_ -= bytes_needed; | 297 stream_data_len_ -= bytes_needed; |
403 DCHECK(0 <= stream_data_len_); | 298 DCHECK_LE(0, stream_data_len_); |
404 if (stream_data_len_ > 0) | 299 if (stream_data_len_ > 0) |
405 next_stream_data_ += bytes_needed; | 300 next_stream_data_ += bytes_needed; |
406 else | 301 else |
407 next_stream_data_ = NULL; | 302 next_stream_data_ = NULL; |
408 | 303 |
409 DCHECK(!dictionary_.get()); | 304 DCHECK(!dictionary_.get()); |
410 dictionary_hash_is_plausible_ = true; // Assume plausible, but check. | 305 dictionary_hash_is_plausible_ = true; // Assume plausible, but check. |
411 | 306 |
412 SdchManager::Dictionary* dictionary = NULL; | 307 SdchManager::Dictionary* dictionary = NULL; |
413 if ('\0' == dictionary_hash_[kServerIdLength - 1]) | 308 if ('\0' == dictionary_hash_[kServerIdLength - 1]) |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
453 memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, | 348 memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, |
454 amount); | 349 amount); |
455 dest_buffer_excess_index_ += amount; | 350 dest_buffer_excess_index_ += amount; |
456 if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { | 351 if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { |
457 DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); | 352 DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); |
458 dest_buffer_excess_.clear(); | 353 dest_buffer_excess_.clear(); |
459 dest_buffer_excess_index_ = 0; | 354 dest_buffer_excess_index_ = 0; |
460 } | 355 } |
461 return amount; | 356 return amount; |
462 } | 357 } |
OLD | NEW |