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

Side by Side Diff: net/http/infinite_cache.cc

Issue 10909136: Http Cache: Add code for simulating an infinite HTTP cache. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 3 months 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 | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
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/http/infinite_cache.h"
6
7 #include <algorithm>
8
9 #include "base/compiler_specific.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/file_path.h"
13 #include "base/file_util.h"
14 #include "base/hash.h"
15 #include "base/hash_tables.h"
16 #include "base/location.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/metrics/histogram.h"
19 #include "base/pickle.h"
20 #include "base/platform_file.h"
21 #include "base/rand_util.h"
22 #include "base/sha1.h"
23 #include "base/time.h"
24 #include "base/threading/sequenced_worker_pool.h"
25 #include "net/base/net_errors.h"
26 #include "net/http/http_cache_transaction.h"
27 #include "net/http/http_request_info.h"
28 #include "net/http/http_response_headers.h"
29 #include "net/http/http_response_info.h"
30 #include "net/http/http_util.h"
31 #include "third_party/zlib/zlib.h"
32
33 using base::PlatformFile;
34 using base::Time;
35 using base::TimeDelta;
36
37 namespace {
38
39 // Flags to use with a particular resource.
40 enum Flags {
41 NO_CACHE = 1 << 0,
42 NO_STORE = 1 << 1,
43 EXPIRED = 1 << 2,
44 TRUNCATED = 1 << 3,
45 RESUMABLE = 1 << 4,
46 REVALIDABLE = 1 << 5,
gavinp 2012/09/13 04:45:43 REVALIDATEABLE?
47 DOOM_METHOD = 1 << 6,
48 CACHED = 1 << 7,
49 };
gavinp 2012/09/13 04:45:43 Nit: enums without prefixes in a default/global na
rvargas (doing something else) 2012/09/13 22:44:07 This is an anonymous namespace, and nothing should
gavinp 2012/09/14 03:27:28 If anyone has an enum in namespace net with an ent
50
51 const int kKeySizeBytes = 20;
52 COMPILE_ASSERT(base::kSHA1Length == kKeySizeBytes, invalid_key_length);
53 struct Key {
54 char value[kKeySizeBytes];
55 };
gavinp 2012/09/13 04:45:43 Nit: Would a typedef suit us better than a struct?
rvargas (doing something else) 2012/09/13 23:50:28 I just tried to do that and run into issues readin
gavinp 2012/09/14 03:27:28 sgtm.
56
57 // The actual data that we store for every resource.
58 struct Details {
59 int32 expiration;
60 int32 last_access;
61 uint16 flags;
62 uint8 use_count;
63 uint8 update_count;
64 uint32 vary_hash;
65 int32 headers_size;
66 int32 response_size;
67 uint32 headers_hash;
68 uint32 response_hash;
69 };
70 const size_t kRecordSize = sizeof(Key) + sizeof(Details);
71
72 // Some constants related to the database file.
73 uint32 kMagicSignature = 0x1f00cace;
74 uint32 kCurrentVersion = 0x10001;
75
76 // Basic limits for the experiment.
77 int kMaxNumEntries = 200 * 1000;
78 int kMaxTrackingSize = 40 * 1024 * 1024;
79
80 // Settings that control how we generate histograms.
81 int kTimerMinutes = 5;
82 int kReportSizeStep = 100 * 1024 * 1024;
83
84 // Buffer to read and write the file.
85 const size_t kBufferSize = 1024 * 1024;
86 const size_t kMaxRecordsToRead = kBufferSize / kRecordSize;
87 COMPILE_ASSERT(kRecordSize * kMaxRecordsToRead < kBufferSize, wrong_buffer);
88
89 // Functor for operator <.
90 struct Key_less {
91 bool operator()(const Key& left, const Key& right) const {
92 // left < right.
93 return (memcmp(left.value, right.value, kKeySizeBytes) < 0);
94 }
95 };
96
97 // Functor for operator ==.
98 struct Key_eq {
99 bool operator()(const Key& left, const Key& right) const {
100 return (memcmp(left.value, right.value, kKeySizeBytes) == 0);
101 }
102 };
103
104 // Simple adaptor for the sha1 interface.
105 void CryptoHash(std::string source, Key* destination) {
106 base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(source.data()),
107 source.size(),
108 reinterpret_cast<unsigned char*>(destination->value));
109 }
110
111 // Simple adaptor for base::ReadPlatformFile.
112 bool ReadPlatformFile(PlatformFile file, size_t offset,
113 void* buffer, size_t buffer_len) {
114 DCHECK_LE(offset, static_cast<size_t>(kuint32max));
115 int bytes = base::ReadPlatformFile(file, static_cast<int64>(offset),
116 reinterpret_cast<char*>(buffer),
117 static_cast<int>(buffer_len));
118 return (bytes == static_cast<int>(buffer_len));
119 }
120
121 // Simple adaptor for base::WritePlatformFile.
122 bool WritePlatformFile(PlatformFile file, size_t offset,
123 const void* buffer, size_t buffer_len) {
124 DCHECK_LE(offset, static_cast<size_t>(kuint32max));
125 int bytes = base::WritePlatformFile(file, static_cast<int64>(offset),
126 reinterpret_cast<const char*>(buffer),
127 static_cast<int>(buffer_len));
128 return (bytes == static_cast<int>(buffer_len));
129 }
130
131 // Returns the base time to use for time comparisons.
132 Time GetBaseline() {
gavinp 2012/09/13 04:45:43 Why not drop this function, and instead use Time::
rvargas (doing something else) 2012/09/13 22:44:07 see below
133 Time::Exploded baseline = {};
134 baseline.year = 2010;
135 baseline.month = 1;
136 baseline.day_of_month = 1;
137 return Time::FromUTCExploded(baseline);
138 }
139
140 // 1 second resolution, +- 17 years from the baseline.
gavinp 2012/09/13 04:45:43 I'm confused: won't this code be fine +- 68 years?
rvargas (doing something else) 2012/09/13 22:44:07 Looks like I missed a four (or apply 2x in the wro
141 int32 TimeToInt(Time time) {
142 int64 seconds = (time - GetBaseline()).InSeconds();
143 if (seconds > kint32max)
gavinp 2012/09/13 04:45:43 Do we have a coding standard on kint32max vs std::
rvargas (doing something else) 2012/09/13 22:44:07 My personal position is that as long as kint32max
144 seconds = kint32max;
145 if (seconds < kint32min)
146 seconds = kint32min;
147 return static_cast<int32>(seconds);
148 }
149
150 Time IntToTime(int32 time) {
151 return GetBaseline() + TimeDelta::FromSeconds(time);
152 }
153
154 int32 GetExpiration(const net::HttpResponseInfo* response) {
155 TimeDelta freshness =
156 response->headers->GetFreshnessLifetime(response->response_time);
157
158 // Avoid overflow when adding to current time.
gavinp 2012/09/13 04:45:43 Won't TimeToInt function deal gracefully with over
rvargas (doing something else) 2012/09/13 22:44:07 freshness can be kint64max, which overflows to a n
gavinp 2012/09/14 03:27:28 This is getting into crazy Nit territory, since 10
rvargas (doing something else) 2012/09/14 18:37:18 That seems like a more convoluted way to deal with
159 if (freshness.InDays() > 365 * 10)
160 freshness = TimeDelta::FromDays(365 * 10);
161 return TimeToInt(response->response_time + freshness);
162 }
163
164 uint32 GetCacheability(const net::HttpResponseInfo* response) {
165 uint32 cacheability = 0;
gavinp 2012/09/13 04:45:43 So a cacheability of 0 means that an item can be c
rvargas (doing something else) 2012/09/13 22:44:07 It means that there are no cache related headers.
gavinp 2012/09/14 03:27:28 Right. And each of those headers limit cacheabilit
166 const net::HttpResponseHeaders* headers = response->headers;
167 if (headers->HasHeaderValue("cache-control", "no-cache") ||
168 headers->HasHeaderValue("pragma", "no-cache") ||
169 headers->HasHeaderValue("vary", "*")) {
170 cacheability |= NO_CACHE;
171 }
172
173 if (headers->HasHeaderValue("cache-control", "no-store"))
174 cacheability |= NO_STORE;
175
176 TimeDelta max_age;
177 if (headers->GetMaxAgeValue(&max_age) && max_age.InSeconds() <= 0)
gavinp 2012/09/13 04:45:43 This if is valid, since there's a sequence point i
178 cacheability |= NO_CACHE;
179
180 return cacheability;
181 }
182
183 uint32 GetRevalidationFlags(const net::HttpResponseInfo* response) {
184 uint32 revalidation = 0;
185 std::string etag;
186 response->headers->EnumerateHeader(NULL, "etag", &etag);
187
188 std::string last_modified;
189 response->headers->EnumerateHeader(NULL, "last-modified", &last_modified);
190
191 if (!etag.empty() || !last_modified.empty())
192 revalidation = REVALIDABLE;
193
194 if (response->headers->HasStrongValidators())
195 revalidation = RESUMABLE;
196
197 return revalidation;
198 }
199
200
201 uint32 GetVaryHash(const net::HttpResponseInfo* response) {
202 if (!response->vary_data.is_valid())
203 return 0;
204
205 uint32 hash = adler32(0, Z_NULL, 0);
206 Pickle pickle;
207 response->vary_data.Persist(&pickle);
208 return adler32(hash, reinterpret_cast<const Bytef*>(pickle.data()),
gavinp 2012/09/13 04:45:43 I think MurmurHash is faster than adler32, and it'
rvargas (doing something else) 2012/09/13 23:08:19 That's not currently a dependency of net (or any m
209 pickle.size());
210 }
211
212 // Adaptor for PostTaskAndReply.
213 void OnComplete(const net::CompletionCallback& callback, int* result) {
214 callback.Run(*result);
215 }
216
217 } // namespace
218
219 namespace BASE_HASH_NAMESPACE {
220 #if defined(COMPILER_MSVC)
221 inline size_t hash_value(const Key& key) {
222 return base::Hash(key.value, kKeySizeBytes);
223 }
224 #elif defined(COMPILER_GCC)
225 template <>
226 struct hash<Key> {
227 size_t operator()(const Key& key) const {
228 return base::Hash(key.value, kKeySizeBytes);
229 }
230 };
231 #endif
232
233 } // BASE_HASH_NAMESPACE
234
235 namespace net {
236
237 struct InfiniteCacheTransaction::ResourceData {
gavinp 2012/09/13 04:45:43 Nit: Isn't this rather than information about a re
rvargas (doing something else) 2012/09/13 22:44:07 No in that details (and actually also key) refer t
gavinp 2012/09/14 03:27:28 It's constructed once per transaction, and sent to
rvargas (doing something else) 2012/09/14 18:37:18 Sure... the way to get information about resources
238 ResourceData() {
239 memset(this, 0, sizeof(*this));
240 }
241
242 Key key;
243 Details details;
244 };
245
246 InfiniteCacheTransaction::InfiniteCacheTransaction(InfiniteCache* cache)
247 : cache_(cache->AsWeakPtr()), done_(false), doom_method_(false) {
248 }
249
250 InfiniteCacheTransaction::~InfiniteCacheTransaction() {
251 Finish();
252 }
253
254 void InfiniteCacheTransaction::OnRequestStart(const HttpRequestInfo* request) {
255 if (!cache_ || done_)
256 return;
257
258 std::string method = request->method;
259 if (method == "POST" || method == "DELETE" || method == "PUT") {
260 doom_method_ = true;
261 } else if (method != "GET") {
262 done_ = true;
263 return;
264 }
265
266 resource_data_.reset(new ResourceData);
gavinp 2012/09/13 04:45:43 Since ResourceData is non-POD, this is fine, but I
rvargas (doing something else) 2012/09/13 22:44:07 I think the standard leans towards avoiding decora
267 CryptoHash(cache_->GenerateKey(request), &resource_data_->key);
268 }
269
270 void InfiniteCacheTransaction::OnResponseReceived(
271 const HttpResponseInfo* response) {
272 if (!cache_ || done_)
273 return;
274
275 Details& details = resource_data_->details;
276
277 details.expiration = GetExpiration(response);
278 details.last_access = TimeToInt(response->request_time);
279 details.flags = GetCacheability(response);
280 details.vary_hash = GetVaryHash(response);
281 details.response_hash = adler32(0, Z_NULL, 0); // Init the hash.
282
283 if (!details.flags &&
284 TimeToInt(response->response_time) == details.expiration) {
gavinp 2012/09/13 04:45:43 Not <= ?
rvargas (doing something else) 2012/09/13 22:44:07 expiration cannot be lower than response_time. On
285 details.flags = EXPIRED;
286 }
287 details.flags |= GetRevalidationFlags(response);
288
289 if (doom_method_)
290 details.flags |= DOOM_METHOD;
291
292 Pickle pickle;
293 response->Persist(&pickle, true, false); // Skip transient headers.
294 details.headers_size = pickle.size();
295 details.headers_hash = adler32(0, Z_NULL, 0);
296 details.headers_hash = adler32(details.headers_hash,
297 reinterpret_cast<const Bytef*>(pickle.data()),
298 pickle.size());
299 }
300
301 void InfiniteCacheTransaction::OnDataRead(const char* data, int data_len) {
302 if (!cache_ || done_)
303 return;
304
305 if (!data_len)
306 return Finish();
307
308 resource_data_->details.response_size += data_len;
309
310 resource_data_->details.response_hash =
311 adler32(resource_data_->details.response_hash,
312 reinterpret_cast<const Bytef*>(data), data_len);
313 }
314
315 void InfiniteCacheTransaction::OnTruncatedResponse() {
316 if (!cache_ || done_)
317 return;
318
319 resource_data_->details.flags |= TRUNCATED;
320 }
321
322 void InfiniteCacheTransaction::OnServedFromCache() {
323 if (!cache_ || done_)
324 return;
325
326 resource_data_->details.flags |= CACHED;
327 }
328
329 void InfiniteCacheTransaction::Finish() {
330 if (!cache_ || done_ || !resource_data_.get())
331 return;
332
333 if (!resource_data_->details.headers_size)
334 return;
335
336 done_ = true;
337 cache_->ProcessResource(resource_data_.Pass());
gavinp 2012/09/13 04:45:43 Could we cache_.reset() here, and get rid of done_
rvargas (doing something else) 2012/09/13 22:44:07 ok
338 }
339
340 // ----------------------------------------------------------------------------
341
342 // This is the object that performs the bulk of the work.
343 // InfiniteCacheTransaction posts the transaction data to the InfiniteCache, and
344 // the InfiniteCache basically just forward requests to the Worker for actual
345 // processing.
346 // The Worker lives on a worker thread (basically a dedicated worker pool with
347 // only one thread), and flushes data to disk once every five minutes, when it
348 // is notified by the InfiniteCache.
349 // In general, there are no callbacks on completion of tasks, and the Worker can
350 // be as behind as it has to when processing requests.
351 class InfiniteCache::Worker : public base::RefCountedThreadSafe<Worker> {
gavinp 2012/09/13 04:45:43 Why isn't this in the anon namespace?
rvargas (doing something else) 2012/09/13 22:44:07 Because the cache has a pointer to it.
352 public:
353 Worker() : init_(false), flushed_(false) {}
354
355 // Construction and destruction helpers.
356 void Init(const FilePath& path);
357 void Cleanup();
358
359 // Deletes all tracked data.
360 void DeleteData(int* result);
361
362 // Deletes requests between |initial_time| and |end_time|.
363 void DeleteDataBetween(base::Time initial_time,
364 base::Time end_time,
365 int* result);
366
367 // Performs the actual processing of a new transaction. Takes ownership of
368 // the transaction |data|.
369 void Process(scoped_ptr<InfiniteCacheTransaction::ResourceData> data);
370
371 // Test helpers.
372 void Query(int* result);
373 void Flush(int* result);
374
375 // Timer notification.
376 void OnTimer();
377
378 private:
379 friend base::RefCountedThreadSafe<Worker>;
380 #if defined(COMPILER_MSVC)
381 typedef BASE_HASH_NAMESPACE::hash_map<
gavinp 2012/09/13 04:45:43 Does our need to occasionally iterate over a range
rvargas (doing something else) 2012/09/13 22:44:07 I think we should optimize for the common case. It
gavinp 2012/09/14 03:27:28 sgtm.
382 Key, Details, BASE_HASH_NAMESPACE::hash_compare<Key, Key_less> > KeyMap;
383 #elif defined(COMPILER_GCC)
384 typedef BASE_HASH_NAMESPACE::hash_map<
385 Key, Details, BASE_HASH_NAMESPACE::hash<Key>, Key_eq> KeyMap;
386 #endif
387
388 // Header for the data file. The file starts with the header, followed by
389 // all the records, and a data hash at the end (just of the records, not the
390 // header). Note that the header has a dedicated hash.
391 struct Header {
392 uint32 magic;
393 uint32 version;
394 int32 num_entries;
395 int32 generation;
396 uint64 creation_time;
397 uint64 update_time;
398 int64 total_size;
399 int64 size_last_report;
400 int32 use_minutes;
401 int32 num_hits;
402 int32 num_bad_hits;
403 int32 num_requests;
404 int32 disabled;
405 uint32 header_hash;
406 };
407
408 ~Worker() {}
409
410 // Methods to load and store data on disk.
411 void LoadData();
412 void StoreData();
413 void InitializeData();
414 bool ReadData(PlatformFile file);
415 bool WriteData(PlatformFile file);
416 bool ReadAndVerifyHeader(PlatformFile file);
417
418 // Book-keeping methods.
419 void Add(const Details& details);
420 void Remove(const Details& details);
421 void UpdateSize(int old_size, int new_size);
422
423 // Bulk of report generation methods.
424 void RecordHit(const Details& old, Details* details);
425 void RecordUpdate(const Details& old, Details* details);
426 void GenerateHistograms();
427
428 // Cache logic methods.
429 bool CanReuse(const Details& old, const Details& current);
430 bool DataChanged(const Details& old, const Details& current);
431 bool HeadersChanged(const Details& old, const Details& current);
432
433 KeyMap map_;
434 bool init_;
435 bool flushed_;
436 scoped_ptr<Header> header_;
437 FilePath path_;
438
439 DISALLOW_COPY_AND_ASSIGN(Worker);
440 };
441
442 void InfiniteCache::Worker::Init(const FilePath& path) {
443 path_ = path;
444 LoadData();
445 }
446
447 void InfiniteCache::Worker::Cleanup() {
448 if (init_)
449 StoreData();
450
451 map_.clear();
452 }
453
454 void InfiniteCache::Worker::DeleteData(int* result) {
455 if (!init_)
456 return;
457
458 map_.clear();
459 InitializeData();
460 file_util::Delete(path_, false);
461 *result = OK;
462 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.DeleteAll", 1, 2);
463 }
464
465 void InfiniteCache::Worker::DeleteDataBetween(base::Time initial_time,
466 base::Time end_time,
467 int* result) {
468 if (!init_)
469 return;
470
471 for (KeyMap::iterator i = map_.begin(); i != map_.end();) {
gavinp 2012/09/13 04:45:43 Deletion mid iteration is kind of scary. It looks
rvargas (doing something else) 2012/09/13 22:44:07 We may be deleting basically the whole list... a l
gavinp 2012/09/14 03:27:28 40 megabytes/sizeof(Details) * sizeof(void*) ~= 5
rvargas (doing something else) 2012/09/14 18:37:18 So you mean a set of iterators... That's scary. I
472 Time last_access = IntToTime(i->second.last_access);
473 if (last_access >= initial_time && last_access <= end_time) {
474 KeyMap::iterator next = i;
475 ++next;
476 Remove(i->second);
477 map_.erase(i);
478 i = next;
479 continue;
480 }
481 ++i;
482 }
483
484 file_util::Delete(path_, false);
485 StoreData();
486 *result = OK;
487 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.DeleteAll", 1, 2);
gavinp 2012/09/13 04:45:43 UMA_HISTOGRAM_BOOLEAN?
488 }
489
490 void InfiniteCache::Worker::Process(
491 scoped_ptr<InfiniteCacheTransaction::ResourceData> data) {
492 if (!init_)
493 return;
494
495 if (data->details.response_size > kMaxTrackingSize)
496 return;
497
498 if (header_->num_entries == kMaxNumEntries)
499 return;
500
501 header_->num_requests++;
502 KeyMap::iterator i = map_.find(data->key);
503 if (i != map_.end()) {
504 if (data->details.flags & DOOM_METHOD) {
505 Remove(i->second);
506 map_.erase(i);
507 return;
508 }
509 data->details.use_count = i->second.use_count;
510 data->details.update_count = i->second.update_count;
511 if (data->details.flags & CACHED) {
512 RecordHit(i->second, &data->details);
513 } else {
514 bool reused = CanReuse(i->second, data->details);
515 bool data_changed = DataChanged(i->second, data->details);
516 bool headers_changed = HeadersChanged(i->second, data->details);
517
518 if (reused && data_changed)
519 header_->num_bad_hits++;
520
521 if (reused)
522 RecordHit(i->second, &data->details);
523
524 if (headers_changed)
525 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.HeadersChange", 1, 2);
526
527 if (data_changed)
528 RecordUpdate(i->second, &data->details);
529 }
530
tburkard 2012/09/11 21:02:00 What if the object was in the map_, but now has NO
rvargas (doing something else) 2012/09/13 22:44:07 Right. It looks like I have not made up my mind ab
rvargas (doing something else) 2012/09/13 23:50:28 We'd still need forwarding just to delete the old
531 map_[data->key] = data->details;
532 return;
533 }
534
535 if (data->details.flags & NO_STORE)
536 return;
537
538 if (data->details.flags & DOOM_METHOD)
539 return;
540
541 map_[data->key] = data->details;
542 Add(data->details);
543 }
544
545 void InfiniteCache::Worker::LoadData() {
546 if (path_.empty())
547 return InitializeData();;
548
549 PlatformFile file = base::CreatePlatformFile(
550 path_, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, NULL);
551 if (file == base::kInvalidPlatformFileValue)
552 return InitializeData();
553 if (!ReadData(file))
554 InitializeData();
555 base::ClosePlatformFile(file);
556 if (header_->disabled)
557 map_.clear();
558 }
559
560 void InfiniteCache::Worker::StoreData() {
561 if (!init_ || flushed_ || path_.empty())
562 return;
563
564 header_->update_time = Time::Now().ToInternalValue();
565 header_->generation++;
566 header_->header_hash = base::Hash(
567 reinterpret_cast<char*>(header_.get()), offsetof(Header, header_hash));
568
569 FilePath temp_file = path_.ReplaceExtension(FILE_PATH_LITERAL("tmp"));
570 PlatformFile file = base::CreatePlatformFile(
571 temp_file, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
572 NULL, NULL);
573 if (file == base::kInvalidPlatformFileValue)
574 return;
575 bool success = WriteData(file);
576 base::ClosePlatformFile(file);
577 if (success) {
578 if (!file_util::ReplaceFile(temp_file, path_))
579 file_util::Delete(temp_file, false);
580 } else {
581 LOG(ERROR) << "Failed to write experiment data";
582 }
583 }
584
585 void InfiniteCache::Worker::InitializeData() {
586 header_.reset(new Header);
587 memset(header_.get(), 0, sizeof(Header));
588 header_->magic = kMagicSignature;
589 header_->version = kCurrentVersion;
590 header_->creation_time = Time::Now().ToInternalValue();
591
592 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.Initialize", 1, 2);
593 init_ = true;
594 }
595
596 bool InfiniteCache::Worker::ReadData(PlatformFile file) {
597 if (!ReadAndVerifyHeader(file))
598 return false;
599
600 scoped_array<char> buffer(new char[kBufferSize]);
601 size_t offset = sizeof(Header);
602 uint32 hash = adler32(0, Z_NULL, 0);
603
604 for (int remaining_records = header_->num_entries; remaining_records;) {
605 int num_records = std::min(header_->num_entries,
606 static_cast<int>(kMaxRecordsToRead));
607 size_t num_bytes = num_records * kRecordSize;
608 remaining_records -= num_records;
609 if (!remaining_records)
610 num_bytes += sizeof(uint32); // Trailing hash.
611 DCHECK_LE(num_bytes, kBufferSize);
612
613 if (!ReadPlatformFile(file, offset, buffer.get(), num_bytes))
614 return false;
615
616 hash = adler32(hash, reinterpret_cast<const Bytef*>(buffer.get()),
617 num_records * kRecordSize);
618 if (!remaining_records &&
619 hash != *reinterpret_cast<uint32*>(buffer.get() +
620 num_records * kRecordSize)) {
621 return false;
622 }
623
624 for (int i = 0; i < num_records; i++) {
625 char* record = buffer.get() + i * kRecordSize;
626 Key key = *reinterpret_cast<Key*>(record);
627 Details details = *reinterpret_cast<Details*>(record + sizeof(key));
628 map_[key] = details;
629 }
630 offset += num_bytes;
631 }
632 if (header_->num_entries != static_cast<int>(map_.size())) {
633 NOTREACHED();
634 return false;
635 }
636
tburkard 2012/09/11 21:02:00 How about a checksum on the header + file contents
rvargas (doing something else) 2012/09/13 22:44:07 There is a checksum on the record set. It is just
637 init_ = true;
638 return true;
639 }
640
641 bool InfiniteCache::Worker::WriteData(PlatformFile file) {
642 if (!base::TruncatePlatformFile(file, 0))
643 return false;
644
645 if (!WritePlatformFile(file, 0, header_.get(), sizeof(Header)))
646 return false;
647
648 scoped_array<char> buffer(new char[kBufferSize]);
649 size_t offset = sizeof(Header);
650 uint32 hash = adler32(0, Z_NULL, 0);
651
652 DCHECK_EQ(header_->num_entries, static_cast<int32>(map_.size()));
653 KeyMap::iterator iterator = map_.begin();
654 for (int remaining_records = header_->num_entries; remaining_records;) {
655 int num_records = std::min(header_->num_entries,
656 static_cast<int>(kMaxRecordsToRead));
657 size_t num_bytes = num_records * kRecordSize;
658 remaining_records -= num_records;
659
660 for (int i = 0; i < num_records; i++) {
661 if (iterator == map_.end()) {
662 NOTREACHED();
663 return false;
664 }
665 char* record = buffer.get() + i * kRecordSize;
666 *reinterpret_cast<Key*>(record) = iterator->first;
667 *reinterpret_cast<Details*>(record + sizeof(Key)) = iterator->second;
668 ++iterator;
669 }
670
671 hash = adler32(hash, reinterpret_cast<const Bytef*>(buffer.get()),
672 num_bytes);
673
674 if (!remaining_records) {
675 num_bytes += sizeof(uint32); // Trailing hash.
676 *reinterpret_cast<uint32*>(buffer.get() +
677 num_records * kRecordSize) = hash;
678 }
679
680 DCHECK_LE(num_bytes, kBufferSize);
681 if (!WritePlatformFile(file, offset, buffer.get(), num_bytes))
682 return false;
683
684 offset += num_bytes;
685 }
686 base::FlushPlatformFile(file); // Ignore return value.
687 return true;
688 }
689
690 bool InfiniteCache::Worker::ReadAndVerifyHeader(PlatformFile file) {
691 base::PlatformFileInfo info;
692 if (!base::GetPlatformFileInfo(file, &info))
693 return false;
694
695 if (info.size < static_cast<int>(sizeof(Header)))
696 return false;
697
698 header_.reset(new Header);
699 if (!ReadPlatformFile(file, 0, header_.get(), sizeof(Header)))
700 return false;
701
702 if (header_->magic != kMagicSignature)
703 return false;
704
705 if (header_->version != kCurrentVersion)
706 return false;
707
708 if (header_->num_entries > kMaxNumEntries)
709 return false;
710
711 size_t expected_size = kRecordSize * header_->num_entries +
712 sizeof(Header) + sizeof(uint32); // Trailing hash.
713
714 if (info.size < static_cast<int>(expected_size))
715 return false;
716
717 uint32 hash = base::Hash(reinterpret_cast<char*>(header_.get()),
718 offsetof(Header, header_hash));
719 if (hash != header_->header_hash)
720 return false;
721
722 return true;
723 }
724
725 void InfiniteCache::Worker::Query(int* result) {
726 *result = static_cast<int>(map_.size());
727 }
728
729 void InfiniteCache::Worker::Flush(int* result) {
730 StoreData();
731 flushed_ = true;
732 *result = OK;
733 }
734
735 void InfiniteCache::Worker::OnTimer() {
736 header_->use_minutes += kTimerMinutes;
737 GenerateHistograms();
738 StoreData();
739 }
740
741 void InfiniteCache::Worker::Add(const Details& details) {
742 UpdateSize(0, details.headers_size);
743 UpdateSize(0, details.response_size);
744 header_->num_entries = static_cast<int>(map_.size());
745 if (header_->num_entries == kMaxNumEntries) {
746 int use_hours = header_->use_minutes / 60;
747 int age_hours = (Time::Now() -
748 Time::FromInternalValue(header_->creation_time)).InHours();
749 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.MaxUseTime", use_hours);
750 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.MaxAge", age_hours);
751
752 int entry_size = static_cast<int>(header_->total_size / kMaxNumEntries);
753 UMA_HISTOGRAM_COUNTS("InfiniteCache.FinalAvgEntrySize", entry_size);
754 header_->disabled = 1;
755 map_.clear();
756 }
757 }
758
759 void InfiniteCache::Worker::Remove(const Details& details) {
760 UpdateSize(details.headers_size, 0);
761 UpdateSize(details.response_size, 0);
762 header_->num_entries--;
763 }
764
765 void InfiniteCache::Worker::UpdateSize(int old_size, int new_size) {
766 header_->total_size += new_size - old_size;
767 DCHECK_GE(header_->total_size, 0);
768 }
769
770 void InfiniteCache::Worker::RecordHit(const Details& old, Details* details) {
771 header_->num_hits++;
772 int access_delta = (IntToTime(details->last_access) -
773 IntToTime(old.last_access)).InMinutes();
774 if (old.use_count)
775 UMA_HISTOGRAM_COUNTS("InfiniteCache.ReuseAge", access_delta);
776 else
777 UMA_HISTOGRAM_COUNTS("InfiniteCache.FirstReuseAge", access_delta);
778
779 details->use_count = old.use_count;
780 if (details->use_count < kuint8max)
781 details->use_count++;
782 UMA_HISTOGRAM_CUSTOM_COUNTS("InfiniteCache.UseCount", details->use_count, 0,
783 kuint8max, 25);
784 }
785
786 void InfiniteCache::Worker::RecordUpdate(const Details& old, Details* details) {
787 header_->num_hits++;
tburkard 2012/09/11 21:02:00 If reused was false, this wouldn't have been a hit
rvargas (doing something else) 2012/09/13 22:44:07 yeah, this is not right.
788 int access_delta = (IntToTime(details->last_access) -
789 IntToTime(old.last_access)).InMinutes();
790 if (old.update_count)
791 UMA_HISTOGRAM_COUNTS("InfiniteCache.UpdateAge", access_delta);
792 else
793 UMA_HISTOGRAM_COUNTS("InfiniteCache.FirstUpdateAge", access_delta);
794
795 details->update_count = old.update_count;
796 if (details->update_count < kuint8max)
797 details->update_count++;
798
799 UMA_HISTOGRAM_CUSTOM_COUNTS("InfiniteCache.UpdateCount",
800 details->update_count, 0, kuint8max, 25);
801 details->use_count = 0;
802 }
803
804 void InfiniteCache::Worker::GenerateHistograms() {
805 bool new_size_step = (header_->total_size / kReportSizeStep !=
806 header_->size_last_report / kReportSizeStep);
807 header_->size_last_report = header_->total_size;
808 if (!new_size_step && (header_->use_minutes % 60 != 0))
809 return;
810
811 if (header_->disabled)
812 return;
813
814 int hit_ratio = header_->num_hits * 100;
815 if (header_->num_requests)
816 hit_ratio /= header_->num_requests;
817 else
818 hit_ratio = 0;
819
820 bool report_second_stat = base::RandInt(0, 99) < hit_ratio;
821
822 if (header_->use_minutes % 60 == 0) {
823 int use_hours = header_->use_minutes / 60;
824 int age_hours = (Time::Now() -
825 Time::FromInternalValue(header_->creation_time)).InHours();
826 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.UseTime", use_hours);
827 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.Age", age_hours);
828 if (report_second_stat) {
829 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.HitRatioByUseTime", use_hours);
tburkard 2012/09/11 21:02:00 These two histograms seem to be identical to the t
rvargas (doing something else) 2012/09/13 22:44:07 No, this is Gavin's trick of dividing two histogra
830 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.HitRatioByAge", age_hours);
831 }
832 }
833
834 if (new_size_step) {
835 int size_bucket = static_cast<int>(header_->total_size / kReportSizeStep);
836 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.Size", std::min(size_bucket, 50),
837 51);
838 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.SizeCoarse", size_bucket / 5, 51);
839 UMA_HISTOGRAM_COUNTS("InfiniteCache.Entries", header_->num_entries);
840 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.BadHits", header_->num_bad_hits);
841 if (report_second_stat) {
842 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.HitRatioBySize",
tburkard 2012/09/11 21:02:00 Seems like you are just duplicating the previous h
843 std::min(size_bucket, 50), 51);
844 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.HitRatioBySizeCoarse",
845 size_bucket / 5, 51);
846 UMA_HISTOGRAM_COUNTS("InfiniteCache.HitRatioByEntries",
847 header_->num_entries);
848 }
849 header_->num_hits = 0;
850 header_->num_bad_hits = 0;
851 header_->num_requests = 0;
852 }
853 }
854
855 bool InfiniteCache::Worker::CanReuse(const Details& old,
856 const Details& current) {
857 enum ReuseStatus {
858 REUSE_OK = 0,
859 REUSE_NO_CACHE,
860 REUSE_ALWAYS_EXPIRED,
861 REUSE_EXPIRED,
862 REUSE_TRUNCATED,
863 REUSE_VARY,
864 REUSE_REVALIDABLE // Not an individual value; it's added to another reason.
865 };
866 int reason = REUSE_OK;
867
868 if (old.flags & NO_CACHE)
869 reason = REUSE_NO_CACHE;
870
871 if (old.flags & EXPIRED)
872 reason = REUSE_ALWAYS_EXPIRED;
873
874 if (old.flags & TRUNCATED)
875 reason = REUSE_TRUNCATED;
876
877 Time expiration = IntToTime(old.expiration);
878 if (expiration < Time::Now())
879 reason = REUSE_EXPIRED;
880
881 if (old.vary_hash != current.vary_hash)
882 reason = REUSE_VARY;
883
884 bool have_to_drop = (old.flags & TRUNCATED) && !(old.flags & RESUMABLE);
885 if (reason && (old.flags & REVALIDABLE) && !have_to_drop)
886 reason = REUSE_REVALIDABLE;
887
888 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.ReuseFailure", reason, 12);
889 return !reason;
890 }
891
892 bool InfiniteCache::Worker::DataChanged(const Details& old,
893 const Details& current) {
894 bool changed = false;
895 if (old.response_size != current.response_size) {
896 changed = true;
897 UpdateSize(old.response_size, current.response_size);
898 }
899
900 if (old.response_hash != current.response_hash)
901 changed = true;
902
903 return changed;
904 }
905
906 bool InfiniteCache::Worker::HeadersChanged(const Details& old,
907 const Details& current) {
908 bool changed = false;
909 if (old.headers_size != current.headers_size) {
910 changed = true;
911 UpdateSize(old.headers_size, current.headers_size);
912 }
913
914 if (old.headers_hash != current.headers_hash)
915 changed = true;
916
917 return changed;
918 }
919
920 // ----------------------------------------------------------------------------
921
922 InfiniteCache::InfiniteCache() {
923 }
924
925 InfiniteCache::~InfiniteCache() {
926 if (!worker_)
927 return;
928
929 task_runner_->PostTask(FROM_HERE,
930 base::Bind(&InfiniteCache::Worker::Cleanup, worker_));
931 worker_ = NULL;
932 }
933
934 void InfiniteCache::Init(const FilePath& path) {
935 worker_pool_ = new base::SequencedWorkerPool(1, "Infinite cache thread");
936 task_runner_ = worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
937 worker_pool_->GetSequenceToken(),
938 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
939
940 worker_ = new Worker();
941 task_runner_->PostTask(FROM_HERE,
942 base::Bind(&InfiniteCache::Worker::Init, worker_,
943 path));
944
945 timer_.Start(FROM_HERE, TimeDelta::FromMinutes(kTimerMinutes), this,
946 &InfiniteCache::OnTimer);
947 }
948
949 InfiniteCacheTransaction* InfiniteCache::CreateInfiniteCacheTransaction() {
950 if (!worker_)
951 return NULL;
952 return new InfiniteCacheTransaction(this);
953 }
954
955 int InfiniteCache::DeleteData(const CompletionCallback& callback) {
956 if (!worker_)
957 return OK;
958 int* result = new int;
959 task_runner_->PostTaskAndReply(
960 FROM_HERE, base::Bind(&InfiniteCache::Worker::DeleteData, worker_,
961 result),
962 base::Bind(&OnComplete, callback, base::Owned(result)));
963 return ERR_IO_PENDING;
964 }
965
966 int InfiniteCache::DeleteDataBetween(base::Time initial_time,
967 base::Time end_time,
968 const CompletionCallback& callback) {
969 if (!worker_)
970 return OK;
971 int* result = new int;
972 task_runner_->PostTaskAndReply(
973 FROM_HERE, base::Bind(&InfiniteCache::Worker::DeleteDataBetween, worker_,
974 initial_time, end_time, result),
975 base::Bind(&OnComplete, callback, base::Owned(result)));
976 return ERR_IO_PENDING;
977 }
978
979 std::string InfiniteCache::GenerateKey(const HttpRequestInfo* request) {
980 // Don't add any upload data identifier.
981 return HttpUtil::SpecForRequest(request->url);
982 }
983
984 void InfiniteCache::ProcessResource(
985 scoped_ptr<InfiniteCacheTransaction::ResourceData> data) {
986 if (!worker_)
987 return;
988 task_runner_->PostTask(FROM_HERE,
989 base::Bind(&InfiniteCache::Worker::Process, worker_,
990 base::Passed(&data)));
991 }
992
993 void InfiniteCache::OnTimer() {
994 task_runner_->PostTask(FROM_HERE,
995 base::Bind(&InfiniteCache::Worker::OnTimer, worker_));
996 }
997
998 int InfiniteCache::QueryItemsForTest(const CompletionCallback& callback) {
999 DCHECK(worker_);
1000 int* result = new int;
1001 task_runner_->PostTaskAndReply(
1002 FROM_HERE, base::Bind(&InfiniteCache::Worker::Query, worker_, result),
1003 base::Bind(&OnComplete, callback, base::Owned(result)));
1004 return net::ERR_IO_PENDING;
1005 }
1006
1007 int InfiniteCache::FlushDataForTest(const CompletionCallback& callback) {
1008 DCHECK(worker_);
1009 int* result = new int;
1010 task_runner_->PostTaskAndReply(
1011 FROM_HERE, base::Bind(&InfiniteCache::Worker::Flush, worker_, result),
1012 base::Bind(&OnComplete, callback, base::Owned(result)));
1013 return net::ERR_IO_PENDING;
1014 }
1015
1016 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698