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

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

Issue 10919298: 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
« no previous file with comments | « net/http/infinite_cache.h ('k') | net/http/infinite_cache_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 REVALIDATEABLE = 1 << 5,
47 DOOM_METHOD = 1 << 6,
48 CACHED = 1 << 7
49 };
50
51 const int kKeySizeBytes = 20;
52 COMPILE_ASSERT(base::kSHA1Length == static_cast<unsigned>(kKeySizeBytes),
gavinp 2012/09/15 13:55:40 Nit: I'd be happier with implicit_cast<> here, alt
53 invalid_key_length);
54 struct Key {
55 char value[kKeySizeBytes];
56 };
57
58 // The actual data that we store for every resource.
59 struct Details {
60 int32 expiration;
61 int32 last_access;
62 uint16 flags;
63 uint8 use_count;
64 uint8 update_count;
65 uint32 vary_hash;
66 int32 headers_size;
67 int32 response_size;
68 uint32 headers_hash;
69 uint32 response_hash;
70 };
71 const size_t kRecordSize = sizeof(Key) + sizeof(Details);
72
73 // Some constants related to the database file.
74 uint32 kMagicSignature = 0x1f00cace;
75 uint32 kCurrentVersion = 0x10001;
76
77 // Basic limits for the experiment.
78 int kMaxNumEntries = 200 * 1000;
79 int kMaxTrackingSize = 40 * 1024 * 1024;
80
81 // Settings that control how we generate histograms.
82 int kTimerMinutes = 5;
83 int kReportSizeStep = 100 * 1024 * 1024;
84
85 // Buffer to read and write the file.
86 const size_t kBufferSize = 1024 * 1024;
87 const size_t kMaxRecordsToRead = kBufferSize / kRecordSize;
88 COMPILE_ASSERT(kRecordSize * kMaxRecordsToRead < kBufferSize, wrong_buffer);
89
90 // Functor for operator <.
91 struct Key_less {
92 bool operator()(const Key& left, const Key& right) const {
93 // left < right.
94 return (memcmp(left.value, right.value, kKeySizeBytes) < 0);
95 }
96 };
97
98 // Functor for operator ==.
99 struct Key_eq {
100 bool operator()(const Key& left, const Key& right) const {
101 return (memcmp(left.value, right.value, kKeySizeBytes) == 0);
102 }
103 };
104
105 // Simple adaptor for the sha1 interface.
106 void CryptoHash(std::string source, Key* destination) {
107 base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(source.data()),
108 source.size(),
109 reinterpret_cast<unsigned char*>(destination->value));
110 }
111
112 // Simple adaptor for base::ReadPlatformFile.
113 bool ReadPlatformFile(PlatformFile file, size_t offset,
114 void* buffer, size_t buffer_len) {
115 DCHECK_LE(offset, static_cast<size_t>(kuint32max));
116 int bytes = base::ReadPlatformFile(file, static_cast<int64>(offset),
117 reinterpret_cast<char*>(buffer),
118 static_cast<int>(buffer_len));
119 return (bytes == static_cast<int>(buffer_len));
120 }
121
122 // Simple adaptor for base::WritePlatformFile.
123 bool WritePlatformFile(PlatformFile file, size_t offset,
124 const void* buffer, size_t buffer_len) {
125 DCHECK_LE(offset, static_cast<size_t>(kuint32max));
126 int bytes = base::WritePlatformFile(file, static_cast<int64>(offset),
127 reinterpret_cast<const char*>(buffer),
128 static_cast<int>(buffer_len));
129 return (bytes == static_cast<int>(buffer_len));
130 }
131
132 // 1 second resolution, +- 68 years from the baseline.
133 int32 TimeToInt(Time time) {
134 int64 seconds = (time - Time::UnixEpoch()).InSeconds();
135 if (seconds > kint32max)
136 seconds = kint32max;
137 if (seconds < kint32min)
138 seconds = kint32min;
139 return static_cast<int32>(seconds);
140 }
141
142 Time IntToTime(int32 time) {
143 return Time::UnixEpoch() + TimeDelta::FromSeconds(time);
144 }
145
146 int32 GetExpiration(const net::HttpResponseInfo* response) {
147 TimeDelta freshness =
148 response->headers->GetFreshnessLifetime(response->response_time);
149
150 // Avoid overflow when adding to current time.
151 if (freshness.InDays() > 365 * 10)
152 freshness = TimeDelta::FromDays(365 * 10);
153 return TimeToInt(response->response_time + freshness);
154 }
155
156 uint32 GetCacheability(const net::HttpResponseInfo* response) {
157 uint32 cacheability = 0;
158 const net::HttpResponseHeaders* headers = response->headers;
159 if (headers->HasHeaderValue("cache-control", "no-cache") ||
160 headers->HasHeaderValue("pragma", "no-cache") ||
161 headers->HasHeaderValue("vary", "*")) {
162 cacheability |= NO_CACHE;
163 }
164
165 if (headers->HasHeaderValue("cache-control", "no-store"))
166 cacheability |= NO_STORE;
167
168 TimeDelta max_age;
169 if (headers->GetMaxAgeValue(&max_age) && max_age.InSeconds() <= 0)
170 cacheability |= NO_CACHE;
171
172 return cacheability;
173 }
174
175 uint32 GetRevalidationFlags(const net::HttpResponseInfo* response) {
176 uint32 revalidation = 0;
177 std::string etag;
178 response->headers->EnumerateHeader(NULL, "etag", &etag);
179
180 std::string last_modified;
181 response->headers->EnumerateHeader(NULL, "last-modified", &last_modified);
182
183 if (!etag.empty() || !last_modified.empty())
184 revalidation = REVALIDATEABLE;
185
186 if (response->headers->HasStrongValidators())
187 revalidation = RESUMABLE;
188
189 return revalidation;
190 }
191
192
193 uint32 GetVaryHash(const net::HttpResponseInfo* response) {
194 if (!response->vary_data.is_valid())
195 return 0;
196
197 uint32 hash = adler32(0, Z_NULL, 0);
198 Pickle pickle;
199 response->vary_data.Persist(&pickle);
200 return adler32(hash, reinterpret_cast<const Bytef*>(pickle.data()),
201 pickle.size());
202 }
203
204 // Adaptor for PostTaskAndReply.
205 void OnComplete(const net::CompletionCallback& callback, int* result) {
206 callback.Run(*result);
207 }
208
209 } // namespace
210
211 namespace BASE_HASH_NAMESPACE {
212 #if defined(COMPILER_MSVC)
213 inline size_t hash_value(const Key& key) {
214 return base::Hash(key.value, kKeySizeBytes);
215 }
216 #elif defined(COMPILER_GCC)
217 template <>
218 struct hash<Key> {
219 size_t operator()(const Key& key) const {
220 return base::Hash(key.value, kKeySizeBytes);
221 }
222 };
223 #endif
224
225 } // BASE_HASH_NAMESPACE
226
227 namespace net {
228
229 struct InfiniteCacheTransaction::ResourceData {
230 ResourceData() {
231 memset(this, 0, sizeof(*this));
232 }
233
234 Key key;
235 Details details;
236 };
237
238 InfiniteCacheTransaction::InfiniteCacheTransaction(InfiniteCache* cache)
239 : cache_(cache->AsWeakPtr()), must_doom_entry_(false) {
240 }
241
242 InfiniteCacheTransaction::~InfiniteCacheTransaction() {
243 Finish();
244 }
245
246 void InfiniteCacheTransaction::OnRequestStart(const HttpRequestInfo* request) {
247 if (!cache_)
248 return;
249
250 std::string method = request->method;
251 if (method == "POST" || method == "DELETE" || method == "PUT") {
252 must_doom_entry_ = true;
253 } else if (method != "GET") {
254 cache_.reset();
255 return;
256 }
257
258 resource_data_.reset(new ResourceData);
259 CryptoHash(cache_->GenerateKey(request), &resource_data_->key);
260 }
261
262 void InfiniteCacheTransaction::OnResponseReceived(
263 const HttpResponseInfo* response) {
264 if (!cache_)
265 return;
266
267 Details& details = resource_data_->details;
268
269 details.expiration = GetExpiration(response);
270 details.last_access = TimeToInt(response->request_time);
271 details.flags = GetCacheability(response);
272 details.vary_hash = GetVaryHash(response);
273 details.response_hash = adler32(0, Z_NULL, 0); // Init the hash.
274
275 if (!details.flags &&
276 TimeToInt(response->response_time) == details.expiration) {
277 details.flags = EXPIRED;
278 }
279 details.flags |= GetRevalidationFlags(response);
280
281 if (must_doom_entry_)
282 details.flags |= DOOM_METHOD;
283
284 Pickle pickle;
285 response->Persist(&pickle, true, false); // Skip transient headers.
286 details.headers_size = pickle.size();
287 details.headers_hash = adler32(0, Z_NULL, 0);
288 details.headers_hash = adler32(details.headers_hash,
289 reinterpret_cast<const Bytef*>(pickle.data()),
290 pickle.size());
291 }
292
293 void InfiniteCacheTransaction::OnDataRead(const char* data, int data_len) {
294 if (!cache_)
295 return;
296
297 if (!data_len)
298 return Finish();
299
300 resource_data_->details.response_size += data_len;
301
302 resource_data_->details.response_hash =
303 adler32(resource_data_->details.response_hash,
304 reinterpret_cast<const Bytef*>(data), data_len);
305 }
306
307 void InfiniteCacheTransaction::OnTruncatedResponse() {
308 if (!cache_)
309 return;
310
311 resource_data_->details.flags |= TRUNCATED;
312 }
313
314 void InfiniteCacheTransaction::OnServedFromCache() {
315 if (!cache_)
316 return;
317
318 resource_data_->details.flags |= CACHED;
319 }
320
321 void InfiniteCacheTransaction::Finish() {
322 if (!cache_ || !resource_data_.get())
323 return;
324
325 if (!resource_data_->details.headers_size)
326 return;
327
328 cache_->ProcessResource(resource_data_.Pass());
329 cache_.reset();
330 }
331
332 // ----------------------------------------------------------------------------
333
334 // This is the object that performs the bulk of the work.
335 // InfiniteCacheTransaction posts the transaction data to the InfiniteCache, and
336 // the InfiniteCache basically just forward requests to the Worker for actual
337 // processing.
338 // The Worker lives on a worker thread (basically a dedicated worker pool with
339 // only one thread), and flushes data to disk once every five minutes, when it
340 // is notified by the InfiniteCache.
341 // In general, there are no callbacks on completion of tasks, and the Worker can
342 // be as behind as it has to when processing requests.
343 class InfiniteCache::Worker : public base::RefCountedThreadSafe<Worker> {
344 public:
345 Worker() : init_(false), flushed_(false) {}
346
347 // Construction and destruction helpers.
348 void Init(const FilePath& path);
349 void Cleanup();
350
351 // Deletes all tracked data.
352 void DeleteData(int* result);
353
354 // Deletes requests between |initial_time| and |end_time|.
355 void DeleteDataBetween(base::Time initial_time,
356 base::Time end_time,
357 int* result);
358
359 // Performs the actual processing of a new transaction. Takes ownership of
360 // the transaction |data|.
361 void Process(scoped_ptr<InfiniteCacheTransaction::ResourceData> data);
362
363 // Test helpers.
364 void Query(int* result);
365 void Flush(int* result);
366
367 // Timer notification.
368 void OnTimer();
369
370 private:
371 friend class base::RefCountedThreadSafe<Worker>;
372 #if defined(COMPILER_MSVC)
373 typedef BASE_HASH_NAMESPACE::hash_map<
374 Key, Details, BASE_HASH_NAMESPACE::hash_compare<Key, Key_less> > KeyMap;
375 #elif defined(COMPILER_GCC)
376 typedef BASE_HASH_NAMESPACE::hash_map<
377 Key, Details, BASE_HASH_NAMESPACE::hash<Key>, Key_eq> KeyMap;
378 #endif
379
380 // Header for the data file. The file starts with the header, followed by
381 // all the records, and a data hash at the end (just of the records, not the
382 // header). Note that the header has a dedicated hash.
383 struct Header {
384 uint32 magic;
385 uint32 version;
386 int32 num_entries;
387 int32 generation;
388 uint64 creation_time;
389 uint64 update_time;
390 int64 total_size;
391 int64 size_last_report;
392 int32 use_minutes;
393 int32 num_hits;
394 int32 num_bad_hits;
395 int32 num_requests;
396 int32 disabled;
397 uint32 header_hash;
398 };
399
400 ~Worker() {}
401
402 // Methods to load and store data on disk.
403 void LoadData();
404 void StoreData();
405 void InitializeData();
406 bool ReadData(PlatformFile file);
407 bool WriteData(PlatformFile file);
408 bool ReadAndVerifyHeader(PlatformFile file);
409
410 // Book-keeping methods.
411 void Add(const Details& details);
412 void Remove(const Details& details);
413 void UpdateSize(int old_size, int new_size);
414
415 // Bulk of report generation methods.
416 void RecordHit(const Details& old, Details* details);
417 void RecordUpdate(const Details& old, Details* details);
418 void GenerateHistograms();
419
420 // Cache logic methods.
421 bool CanReuse(const Details& old, const Details& current);
422 bool DataChanged(const Details& old, const Details& current);
423 bool HeadersChanged(const Details& old, const Details& current);
424
425 KeyMap map_;
426 bool init_;
427 bool flushed_;
428 scoped_ptr<Header> header_;
429 FilePath path_;
430
431 DISALLOW_COPY_AND_ASSIGN(Worker);
432 };
433
434 void InfiniteCache::Worker::Init(const FilePath& path) {
435 path_ = path;
436 LoadData();
437 }
438
439 void InfiniteCache::Worker::Cleanup() {
440 if (init_)
441 StoreData();
442
443 map_.clear();
444 }
445
446 void InfiniteCache::Worker::DeleteData(int* result) {
447 if (!init_)
448 return;
449
450 map_.clear();
451 InitializeData();
452 file_util::Delete(path_, false);
453 *result = OK;
454 UMA_HISTOGRAM_BOOLEAN("InfiniteCache.DeleteAll", true);
455 }
456
457 void InfiniteCache::Worker::DeleteDataBetween(base::Time initial_time,
458 base::Time end_time,
459 int* result) {
460 if (!init_)
461 return;
462
463 for (KeyMap::iterator i = map_.begin(); i != map_.end();) {
464 Time last_access = IntToTime(i->second.last_access);
465 if (last_access >= initial_time && last_access <= end_time) {
466 KeyMap::iterator next = i;
467 ++next;
468 Remove(i->second);
469 map_.erase(i);
470 i = next;
471 continue;
472 }
473 ++i;
474 }
475
476 file_util::Delete(path_, false);
477 StoreData();
478 *result = OK;
479 UMA_HISTOGRAM_BOOLEAN("InfiniteCache.DeleteAll", true);
480 }
481
482 void InfiniteCache::Worker::Process(
483 scoped_ptr<InfiniteCacheTransaction::ResourceData> data) {
484 if (!init_)
485 return;
486
487 if (data->details.response_size > kMaxTrackingSize)
488 return;
489
490 if (header_->num_entries == kMaxNumEntries)
491 return;
492
493 header_->num_requests++;
494 KeyMap::iterator i = map_.find(data->key);
495 if (i != map_.end()) {
496 if (data->details.flags & DOOM_METHOD) {
497 Remove(i->second);
498 map_.erase(i);
499 return;
500 }
501 data->details.use_count = i->second.use_count;
502 data->details.update_count = i->second.update_count;
503 if (data->details.flags & CACHED) {
504 RecordHit(i->second, &data->details);
505 } else {
506 bool reused = CanReuse(i->second, data->details);
507 bool data_changed = DataChanged(i->second, data->details);
508 bool headers_changed = HeadersChanged(i->second, data->details);
509
510 if (reused && data_changed)
511 header_->num_bad_hits++;
512
513 if (reused)
514 RecordHit(i->second, &data->details);
515
516 if (headers_changed)
517 UMA_HISTOGRAM_BOOLEAN("InfiniteCache.HeadersChange", true);
518
519 if (data_changed)
520 RecordUpdate(i->second, &data->details);
521 }
522
523 if (data->details.flags & NO_STORE) {
524 Remove(i->second);
525 map_.erase(i);
526 return;
527 }
528
529 map_[data->key] = data->details;
530 return;
531 }
532
533 if (data->details.flags & NO_STORE)
534 return;
535
536 if (data->details.flags & DOOM_METHOD)
537 return;
538
539 map_[data->key] = data->details;
540 Add(data->details);
541 }
542
543 void InfiniteCache::Worker::LoadData() {
544 if (path_.empty())
545 return InitializeData();;
546
547 PlatformFile file = base::CreatePlatformFile(
548 path_, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, NULL);
549 if (file == base::kInvalidPlatformFileValue)
550 return InitializeData();
551 if (!ReadData(file))
552 InitializeData();
553 base::ClosePlatformFile(file);
554 if (header_->disabled)
555 map_.clear();
556 }
557
558 void InfiniteCache::Worker::StoreData() {
559 if (!init_ || flushed_ || path_.empty())
560 return;
561
562 header_->update_time = Time::Now().ToInternalValue();
563 header_->generation++;
564 header_->header_hash = base::Hash(
565 reinterpret_cast<char*>(header_.get()), offsetof(Header, header_hash));
566
567 FilePath temp_file = path_.ReplaceExtension(FILE_PATH_LITERAL("tmp"));
568 PlatformFile file = base::CreatePlatformFile(
569 temp_file, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
570 NULL, NULL);
571 if (file == base::kInvalidPlatformFileValue)
572 return;
573 bool success = WriteData(file);
574 base::ClosePlatformFile(file);
575 if (success) {
576 if (!file_util::ReplaceFile(temp_file, path_))
577 file_util::Delete(temp_file, false);
578 } else {
579 LOG(ERROR) << "Failed to write experiment data";
580 }
581 }
582
583 void InfiniteCache::Worker::InitializeData() {
584 header_.reset(new Header);
585 memset(header_.get(), 0, sizeof(Header));
586 header_->magic = kMagicSignature;
587 header_->version = kCurrentVersion;
588 header_->creation_time = Time::Now().ToInternalValue();
589
590 UMA_HISTOGRAM_BOOLEAN("InfiniteCache.Initialize", true);
591 init_ = true;
592 }
593
594 bool InfiniteCache::Worker::ReadData(PlatformFile file) {
595 if (!ReadAndVerifyHeader(file))
596 return false;
597
598 scoped_array<char> buffer(new char[kBufferSize]);
599 size_t offset = sizeof(Header);
600 uint32 hash = adler32(0, Z_NULL, 0);
601
602 for (int remaining_records = header_->num_entries; remaining_records;) {
603 int num_records = std::min(header_->num_entries,
604 static_cast<int>(kMaxRecordsToRead));
605 size_t num_bytes = num_records * kRecordSize;
606 remaining_records -= num_records;
607 if (!remaining_records)
608 num_bytes += sizeof(uint32); // Trailing hash.
609 DCHECK_LE(num_bytes, kBufferSize);
610
611 if (!ReadPlatformFile(file, offset, buffer.get(), num_bytes))
612 return false;
613
614 hash = adler32(hash, reinterpret_cast<const Bytef*>(buffer.get()),
615 num_records * kRecordSize);
616 if (!remaining_records &&
617 hash != *reinterpret_cast<uint32*>(buffer.get() +
618 num_records * kRecordSize)) {
619 return false;
620 }
621
622 for (int i = 0; i < num_records; i++) {
623 char* record = buffer.get() + i * kRecordSize;
624 Key key = *reinterpret_cast<Key*>(record);
625 Details details = *reinterpret_cast<Details*>(record + sizeof(key));
626 map_[key] = details;
627 }
628 offset += num_bytes;
629 }
630 if (header_->num_entries != static_cast<int>(map_.size())) {
631 NOTREACHED();
632 return false;
633 }
634
635 init_ = true;
636 return true;
637 }
638
639 bool InfiniteCache::Worker::WriteData(PlatformFile file) {
640 if (!base::TruncatePlatformFile(file, 0))
641 return false;
642
643 if (!WritePlatformFile(file, 0, header_.get(), sizeof(Header)))
644 return false;
645
646 scoped_array<char> buffer(new char[kBufferSize]);
647 size_t offset = sizeof(Header);
648 uint32 hash = adler32(0, Z_NULL, 0);
649
650 DCHECK_EQ(header_->num_entries, static_cast<int32>(map_.size()));
651 KeyMap::iterator iterator = map_.begin();
652 for (int remaining_records = header_->num_entries; remaining_records;) {
653 int num_records = std::min(header_->num_entries,
654 static_cast<int>(kMaxRecordsToRead));
655 size_t num_bytes = num_records * kRecordSize;
656 remaining_records -= num_records;
657
658 for (int i = 0; i < num_records; i++) {
659 if (iterator == map_.end()) {
660 NOTREACHED();
661 return false;
662 }
663 char* record = buffer.get() + i * kRecordSize;
664 *reinterpret_cast<Key*>(record) = iterator->first;
665 *reinterpret_cast<Details*>(record + sizeof(Key)) = iterator->second;
666 ++iterator;
667 }
668
669 hash = adler32(hash, reinterpret_cast<const Bytef*>(buffer.get()),
670 num_bytes);
671
672 if (!remaining_records) {
673 num_bytes += sizeof(uint32); // Trailing hash.
674 *reinterpret_cast<uint32*>(buffer.get() +
675 num_records * kRecordSize) = hash;
676 }
677
678 DCHECK_LE(num_bytes, kBufferSize);
679 if (!WritePlatformFile(file, offset, buffer.get(), num_bytes))
680 return false;
681
682 offset += num_bytes;
683 }
684 base::FlushPlatformFile(file); // Ignore return value.
685 return true;
686 }
687
688 bool InfiniteCache::Worker::ReadAndVerifyHeader(PlatformFile file) {
689 base::PlatformFileInfo info;
690 if (!base::GetPlatformFileInfo(file, &info))
691 return false;
692
693 if (info.size < static_cast<int>(sizeof(Header)))
694 return false;
695
696 header_.reset(new Header);
697 if (!ReadPlatformFile(file, 0, header_.get(), sizeof(Header)))
698 return false;
699
700 if (header_->magic != kMagicSignature)
701 return false;
702
703 if (header_->version != kCurrentVersion)
704 return false;
705
706 if (header_->num_entries > kMaxNumEntries)
707 return false;
708
709 size_t expected_size = kRecordSize * header_->num_entries +
710 sizeof(Header) + sizeof(uint32); // Trailing hash.
711
712 if (info.size < static_cast<int>(expected_size))
713 return false;
714
715 uint32 hash = base::Hash(reinterpret_cast<char*>(header_.get()),
716 offsetof(Header, header_hash));
717 if (hash != header_->header_hash)
718 return false;
719
720 return true;
721 }
722
723 void InfiniteCache::Worker::Query(int* result) {
724 *result = static_cast<int>(map_.size());
725 }
726
727 void InfiniteCache::Worker::Flush(int* result) {
728 flushed_ = false;
729 StoreData();
730 flushed_ = true;
731 *result = OK;
732 }
733
734 void InfiniteCache::Worker::OnTimer() {
735 header_->use_minutes += kTimerMinutes;
736 GenerateHistograms();
737 StoreData();
738 }
739
740 void InfiniteCache::Worker::Add(const Details& details) {
741 UpdateSize(0, details.headers_size);
742 UpdateSize(0, details.response_size);
743 header_->num_entries = static_cast<int>(map_.size());
744 if (header_->num_entries == kMaxNumEntries) {
745 int use_hours = header_->use_minutes / 60;
746 int age_hours = (Time::Now() -
747 Time::FromInternalValue(header_->creation_time)).InHours();
748 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.MaxUseTime", use_hours);
749 UMA_HISTOGRAM_COUNTS_10000("InfiniteCache.MaxAge", age_hours);
750
751 int entry_size = static_cast<int>(header_->total_size / kMaxNumEntries);
752 UMA_HISTOGRAM_COUNTS("InfiniteCache.FinalAvgEntrySize", entry_size);
753 header_->disabled = 1;
754 map_.clear();
755 }
756 }
757
758 void InfiniteCache::Worker::Remove(const Details& details) {
759 UpdateSize(details.headers_size, 0);
760 UpdateSize(details.response_size, 0);
761 header_->num_entries--;
762 }
763
764 void InfiniteCache::Worker::UpdateSize(int old_size, int new_size) {
765 header_->total_size += new_size - old_size;
766 DCHECK_GE(header_->total_size, 0);
767 }
768
769 void InfiniteCache::Worker::RecordHit(const Details& old, Details* details) {
770 header_->num_hits++;
771 int access_delta = (IntToTime(details->last_access) -
772 IntToTime(old.last_access)).InMinutes();
773 if (old.use_count)
774 UMA_HISTOGRAM_COUNTS("InfiniteCache.ReuseAge", access_delta);
775 else
776 UMA_HISTOGRAM_COUNTS("InfiniteCache.FirstReuseAge", access_delta);
777
778 details->use_count = old.use_count;
779 if (details->use_count < kuint8max)
780 details->use_count++;
781 UMA_HISTOGRAM_CUSTOM_COUNTS("InfiniteCache.UseCount", details->use_count, 0,
782 kuint8max, 25);
783 }
784
785 void InfiniteCache::Worker::RecordUpdate(const Details& old, Details* details) {
786 int access_delta = (IntToTime(details->last_access) -
787 IntToTime(old.last_access)).InMinutes();
788 if (old.update_count)
789 UMA_HISTOGRAM_COUNTS("InfiniteCache.UpdateAge", access_delta);
790 else
791 UMA_HISTOGRAM_COUNTS("InfiniteCache.FirstUpdateAge", access_delta);
792
793 details->update_count = old.update_count;
794 if (details->update_count < kuint8max)
795 details->update_count++;
796
797 UMA_HISTOGRAM_CUSTOM_COUNTS("InfiniteCache.UpdateCount",
798 details->update_count, 0, kuint8max, 25);
799 details->use_count = 0;
800 }
801
802 void InfiniteCache::Worker::GenerateHistograms() {
803 bool new_size_step = (header_->total_size / kReportSizeStep !=
804 header_->size_last_report / kReportSizeStep);
805 header_->size_last_report = header_->total_size;
806 if (!new_size_step && (header_->use_minutes % 60 != 0))
807 return;
808
809 if (header_->disabled)
810 return;
811
812 int hit_ratio = header_->num_hits * 100;
813 if (header_->num_requests)
814 hit_ratio /= header_->num_requests;
815 else
816 hit_ratio = 0;
817
818 // We'll be generating pairs of histograms that can be used to get the hit
819 // ratio for any bucket of the paired histogram.
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);
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",
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_DUMMY_VALUE,
865 // Not an individual value; it's added to another reason.
866 REUSE_REVALIDATEABLE = 7
867 };
868 int reason = REUSE_OK;
869
870 if (old.flags & NO_CACHE)
871 reason = REUSE_NO_CACHE;
872
873 if (old.flags & EXPIRED)
874 reason = REUSE_ALWAYS_EXPIRED;
875
876 if (old.flags & TRUNCATED)
877 reason = REUSE_TRUNCATED;
878
879 Time expiration = IntToTime(old.expiration);
880 if (expiration < Time::Now())
881 reason = REUSE_EXPIRED;
882
883 if (old.vary_hash != current.vary_hash)
884 reason = REUSE_VARY;
885
886 bool have_to_drop = (old.flags & TRUNCATED) && !(old.flags & RESUMABLE);
887 if (reason && (old.flags & REVALIDATEABLE) && !have_to_drop)
888 reason += REUSE_REVALIDATEABLE;
889
890 UMA_HISTOGRAM_ENUMERATION("InfiniteCache.ReuseFailure", reason, 15);
891 return !reason;
892 }
893
894 bool InfiniteCache::Worker::DataChanged(const Details& old,
895 const Details& current) {
896 bool changed = false;
897 if (old.response_size != current.response_size) {
898 changed = true;
899 UpdateSize(old.response_size, current.response_size);
900 }
901
902 if (old.response_hash != current.response_hash)
903 changed = true;
904
905 return changed;
906 }
907
908 bool InfiniteCache::Worker::HeadersChanged(const Details& old,
909 const Details& current) {
910 bool changed = false;
911 if (old.headers_size != current.headers_size) {
912 changed = true;
913 UpdateSize(old.headers_size, current.headers_size);
914 }
915
916 if (old.headers_hash != current.headers_hash)
917 changed = true;
918
919 return changed;
920 }
921
922 // ----------------------------------------------------------------------------
923
924 InfiniteCache::InfiniteCache() {
925 }
926
927 InfiniteCache::~InfiniteCache() {
928 if (!worker_)
929 return;
930
931 task_runner_->PostTask(FROM_HERE,
932 base::Bind(&InfiniteCache::Worker::Cleanup, worker_));
933 worker_ = NULL;
934 }
935
936 void InfiniteCache::Init(const FilePath& path) {
937 worker_pool_ = new base::SequencedWorkerPool(1, "Infinite cache thread");
938 task_runner_ = worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
939 worker_pool_->GetSequenceToken(),
940 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
941
942 worker_ = new Worker();
943 task_runner_->PostTask(FROM_HERE,
944 base::Bind(&InfiniteCache::Worker::Init, worker_,
945 path));
946
947 timer_.Start(FROM_HERE, TimeDelta::FromMinutes(kTimerMinutes), this,
948 &InfiniteCache::OnTimer);
949 }
950
951 InfiniteCacheTransaction* InfiniteCache::CreateInfiniteCacheTransaction() {
952 if (!worker_)
953 return NULL;
954 return new InfiniteCacheTransaction(this);
955 }
956
957 int InfiniteCache::DeleteData(const CompletionCallback& callback) {
958 if (!worker_)
959 return OK;
960 int* result = new int;
961 task_runner_->PostTaskAndReply(
962 FROM_HERE, base::Bind(&InfiniteCache::Worker::DeleteData, worker_,
963 result),
964 base::Bind(&OnComplete, callback, base::Owned(result)));
965 return ERR_IO_PENDING;
966 }
967
968 int InfiniteCache::DeleteDataBetween(base::Time initial_time,
969 base::Time end_time,
970 const CompletionCallback& callback) {
971 if (!worker_)
972 return OK;
973 int* result = new int;
974 task_runner_->PostTaskAndReply(
975 FROM_HERE, base::Bind(&InfiniteCache::Worker::DeleteDataBetween, worker_,
976 initial_time, end_time, result),
977 base::Bind(&OnComplete, callback, base::Owned(result)));
978 return ERR_IO_PENDING;
979 }
980
981 std::string InfiniteCache::GenerateKey(const HttpRequestInfo* request) {
982 // Don't add any upload data identifier.
983 return HttpUtil::SpecForRequest(request->url);
984 }
985
986 void InfiniteCache::ProcessResource(
987 scoped_ptr<InfiniteCacheTransaction::ResourceData> data) {
988 if (!worker_)
989 return;
990 task_runner_->PostTask(FROM_HERE,
991 base::Bind(&InfiniteCache::Worker::Process, worker_,
992 base::Passed(&data)));
993 }
994
995 void InfiniteCache::OnTimer() {
996 task_runner_->PostTask(FROM_HERE,
997 base::Bind(&InfiniteCache::Worker::OnTimer, worker_));
998 }
999
1000 int InfiniteCache::QueryItemsForTest(const CompletionCallback& callback) {
1001 DCHECK(worker_);
1002 int* result = new int;
1003 task_runner_->PostTaskAndReply(
1004 FROM_HERE, base::Bind(&InfiniteCache::Worker::Query, worker_, result),
1005 base::Bind(&OnComplete, callback, base::Owned(result)));
1006 return net::ERR_IO_PENDING;
1007 }
1008
1009 int InfiniteCache::FlushDataForTest(const CompletionCallback& callback) {
1010 DCHECK(worker_);
1011 int* result = new int;
1012 task_runner_->PostTaskAndReply(
1013 FROM_HERE, base::Bind(&InfiniteCache::Worker::Flush, worker_, result),
1014 base::Bind(&OnComplete, callback, base::Owned(result)));
1015 return net::ERR_IO_PENDING;
1016 }
1017
1018 } // namespace net
OLDNEW
« no previous file with comments | « net/http/infinite_cache.h ('k') | net/http/infinite_cache_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698