1 // Copyright (c) 2013 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.
5 #include "content/browser/indexed_db/leveldb/leveldb_database.h"
9 #include "base/basictypes.h"
10 #include "base/files/file.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/metrics/histogram.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/string_piece.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/sys_info.h"
19 #include "content/browser/indexed_db/indexed_db_class_factory.h"
20 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
21 #include "content/browser/indexed_db/leveldb/leveldb_env.h"
22 #include "content/browser/indexed_db/leveldb/leveldb_iterator_impl.h"
23 #include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
24 #include "third_party/leveldatabase/env_chromium.h"
25 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
26 #include "third_party/leveldatabase/src/include/leveldb/db.h"
27 #include "third_party/leveldatabase/src/include/leveldb/env.h"
28 #include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
29 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
31 using base::StringPiece
;
35 // Forcing flushes to disk at the end of a transaction guarantees that the
36 // data hit disk, but drastically impacts throughput when the filesystem is
37 // busy with background compactions. Not syncing trades off reliability for
38 // performance. Note that background compactions which move data from the
39 // log to SSTs are always done with reliable writes.
41 // Sync writes are necessary on Windows for quota calculations; POSIX
42 // calculates file sizes correctly even when not synced to disk.
44 static const bool kSyncWrites
= true;
46 // TODO(dgrogan): Either remove the #if block or change this back to false.
47 // See http://crbug.com/338385.
48 static const bool kSyncWrites
= true;
51 static leveldb::Slice
MakeSlice(const StringPiece
& s
) {
52 return leveldb::Slice(s
.begin(), s
.size());
55 static StringPiece
MakeStringPiece(const leveldb::Slice
& s
) {
56 return StringPiece(s
.data(), s
.size());
59 LevelDBDatabase::ComparatorAdapter::ComparatorAdapter(
60 const LevelDBComparator
* comparator
)
61 : comparator_(comparator
) {}
63 int LevelDBDatabase::ComparatorAdapter::Compare(const leveldb::Slice
& a
,
64 const leveldb::Slice
& b
) const {
65 return comparator_
->Compare(MakeStringPiece(a
), MakeStringPiece(b
));
68 const char* LevelDBDatabase::ComparatorAdapter::Name() const {
69 return comparator_
->Name();
72 // TODO(jsbell): Support the methods below in the future.
73 void LevelDBDatabase::ComparatorAdapter::FindShortestSeparator(
75 const leveldb::Slice
& limit
) const {}
77 void LevelDBDatabase::ComparatorAdapter::FindShortSuccessor(
78 std::string
* key
) const {}
80 LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase
* db
)
81 : db_(db
->db_
.get()), snapshot_(db_
->GetSnapshot()) {}
83 LevelDBSnapshot::~LevelDBSnapshot() { db_
->ReleaseSnapshot(snapshot_
); }
85 LevelDBDatabase::LevelDBDatabase() {}
87 LevelDBDatabase::~LevelDBDatabase() {
88 // db_'s destructor uses comparator_adapter_; order of deletion is important.
90 comparator_adapter_
.reset();
94 void LevelDBDatabase::CloseDatabase() {
96 base::TimeTicks begin_time
= base::TimeTicks::Now();
98 UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.CloseTime",
99 base::TimeTicks::Now() - begin_time
);
103 static leveldb::Status
OpenDB(
104 leveldb::Comparator
* comparator
,
106 const base::FilePath
& path
,
108 scoped_ptr
<const leveldb::FilterPolicy
>* filter_policy
) {
109 filter_policy
->reset(leveldb::NewBloomFilterPolicy(10));
110 leveldb::Options options
;
111 options
.comparator
= comparator
;
112 options
.create_if_missing
= true;
113 options
.paranoid_checks
= true;
114 options
.filter_policy
= filter_policy
->get();
115 options
.reuse_logs
= leveldb_env::kDefaultLogReuseOptionValue
;
116 options
.compression
= leveldb::kSnappyCompression
;
118 // For info about the troubles we've run into with this parameter, see:
119 // https://code.google.com/p/chromium/issues/detail?id=227313#c11
120 options
.max_open_files
= 80;
123 // ChromiumEnv assumes UTF8, converts back to FilePath before using.
124 leveldb::Status s
= leveldb::DB::Open(options
, path
.AsUTF8Unsafe(), db
);
129 leveldb::Status
LevelDBDatabase::Destroy(const base::FilePath
& file_name
) {
130 leveldb::Options options
;
131 options
.env
= LevelDBEnv::Get();
132 // ChromiumEnv assumes UTF8, converts back to FilePath before using.
133 return leveldb::DestroyDB(file_name
.AsUTF8Unsafe(), options
);
137 class LockImpl
: public LevelDBLock
{
139 explicit LockImpl(leveldb::Env
* env
, leveldb::FileLock
* lock
)
140 : env_(env
), lock_(lock
) {}
141 ~LockImpl() override
{ env_
->UnlockFile(lock_
); }
145 leveldb::FileLock
* lock_
;
147 DISALLOW_COPY_AND_ASSIGN(LockImpl
);
151 scoped_ptr
<LevelDBLock
> LevelDBDatabase::LockForTesting(
152 const base::FilePath
& file_name
) {
153 leveldb::Env
* env
= LevelDBEnv::Get();
154 base::FilePath lock_path
= file_name
.AppendASCII("LOCK");
155 leveldb::FileLock
* lock
= NULL
;
156 leveldb::Status status
= env
->LockFile(lock_path
.AsUTF8Unsafe(), &lock
);
158 return scoped_ptr
<LevelDBLock
>();
160 return scoped_ptr
<LevelDBLock
>(new LockImpl(env
, lock
));
163 static int CheckFreeSpace(const char* const type
,
164 const base::FilePath
& file_name
) {
166 std::string("WebCore.IndexedDB.LevelDB.Open") + type
+ "FreeDiskSpace";
167 int64 free_disk_space_in_k_bytes
=
168 base::SysInfo::AmountOfFreeDiskSpace(file_name
) / 1024;
169 if (free_disk_space_in_k_bytes
< 0) {
170 base::Histogram::FactoryGet(
171 "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
175 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(1 /*sample*/);
178 int clamped_disk_space_k_bytes
= free_disk_space_in_k_bytes
> INT_MAX
180 : free_disk_space_in_k_bytes
;
181 const uint64 histogram_max
= static_cast<uint64
>(1e9
);
182 static_assert(histogram_max
<= INT_MAX
, "histogram_max too big");
183 base::Histogram::FactoryGet(name
,
187 base::HistogramBase::kUmaTargetedHistogramFlag
)
188 ->Add(clamped_disk_space_k_bytes
);
189 return clamped_disk_space_k_bytes
;
192 static void ParseAndHistogramIOErrorDetails(const std::string
& histogram_name
,
193 const leveldb::Status
& s
) {
194 leveldb_env::MethodID method
;
195 base::File::Error error
= base::File::FILE_OK
;
196 leveldb_env::ErrorParsingResult result
=
197 leveldb_env::ParseMethodAndError(s
, &method
, &error
);
198 if (result
== leveldb_env::NONE
)
200 std::string
method_histogram_name(histogram_name
);
201 method_histogram_name
.append(".EnvMethod");
202 base::LinearHistogram::FactoryGet(
203 method_histogram_name
,
205 leveldb_env::kNumEntries
,
206 leveldb_env::kNumEntries
+ 1,
207 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(method
);
209 std::string
error_histogram_name(histogram_name
);
211 if (result
== leveldb_env::METHOD_AND_BFE
) {
213 error_histogram_name
.append(std::string(".BFE.") +
214 leveldb_env::MethodIDToString(method
));
215 base::LinearHistogram::FactoryGet(
216 error_histogram_name
,
218 -base::File::FILE_ERROR_MAX
,
219 -base::File::FILE_ERROR_MAX
+ 1,
220 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(-error
);
224 static void ParseAndHistogramCorruptionDetails(
225 const std::string
& histogram_name
,
226 const leveldb::Status
& status
) {
227 int error
= leveldb_env::GetCorruptionCode(status
);
229 std::string
corruption_histogram_name(histogram_name
);
230 corruption_histogram_name
.append(".Corruption");
231 const int kNumPatterns
= leveldb_env::GetNumCorruptionCodes();
232 base::LinearHistogram::FactoryGet(
233 corruption_histogram_name
,
237 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(error
);
240 static void HistogramLevelDBError(const std::string
& histogram_name
,
241 const leveldb::Status
& s
) {
253 int leveldb_error
= LEVEL_DB_OTHER
;
255 leveldb_error
= LEVEL_DB_NOT_FOUND
;
256 else if (s
.IsCorruption())
257 leveldb_error
= LEVEL_DB_CORRUPTION
;
258 else if (s
.IsIOError())
259 leveldb_error
= LEVEL_DB_IO_ERROR
;
260 base::Histogram::FactoryGet(histogram_name
,
263 LEVEL_DB_MAX_ERROR
+ 1,
264 base::HistogramBase::kUmaTargetedHistogramFlag
)
265 ->Add(leveldb_error
);
267 ParseAndHistogramIOErrorDetails(histogram_name
, s
);
269 ParseAndHistogramCorruptionDetails(histogram_name
, s
);
272 leveldb::Status
LevelDBDatabase::Open(const base::FilePath
& file_name
,
273 const LevelDBComparator
* comparator
,
274 scoped_ptr
<LevelDBDatabase
>* result
,
275 bool* is_disk_full
) {
276 base::TimeTicks begin_time
= base::TimeTicks::Now();
278 scoped_ptr
<ComparatorAdapter
> comparator_adapter(
279 new ComparatorAdapter(comparator
));
282 scoped_ptr
<const leveldb::FilterPolicy
> filter_policy
;
283 const leveldb::Status s
= OpenDB(comparator_adapter
.get(), LevelDBEnv::Get(),
284 file_name
, &db
, &filter_policy
);
287 HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s
);
288 int free_space_k_bytes
= CheckFreeSpace("Failure", file_name
);
289 // Disks with <100k of free space almost never succeed in opening a
292 *is_disk_full
= free_space_k_bytes
>= 0 && free_space_k_bytes
< 100;
294 LOG(ERROR
) << "Failed to open LevelDB database from "
295 << file_name
.AsUTF8Unsafe() << "," << s
.ToString();
299 UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.OpenTime",
300 base::TimeTicks::Now() - begin_time
);
302 CheckFreeSpace("Success", file_name
);
304 (*result
).reset(new LevelDBDatabase
);
305 (*result
)->db_
= make_scoped_ptr(db
);
306 (*result
)->comparator_adapter_
= comparator_adapter
.Pass();
307 (*result
)->comparator_
= comparator
;
308 (*result
)->filter_policy_
= filter_policy
.Pass();
313 scoped_ptr
<LevelDBDatabase
> LevelDBDatabase::OpenInMemory(
314 const LevelDBComparator
* comparator
) {
315 scoped_ptr
<ComparatorAdapter
> comparator_adapter(
316 new ComparatorAdapter(comparator
));
317 scoped_ptr
<leveldb::Env
> in_memory_env(leveldb::NewMemEnv(LevelDBEnv::Get()));
320 scoped_ptr
<const leveldb::FilterPolicy
> filter_policy
;
321 const leveldb::Status s
= OpenDB(comparator_adapter
.get(),
328 LOG(ERROR
) << "Failed to open in-memory LevelDB database: " << s
.ToString();
329 return scoped_ptr
<LevelDBDatabase
>();
332 scoped_ptr
<LevelDBDatabase
> result(new LevelDBDatabase
);
333 result
->env_
= in_memory_env
.Pass();
334 result
->db_
= make_scoped_ptr(db
);
335 result
->comparator_adapter_
= comparator_adapter
.Pass();
336 result
->comparator_
= comparator
;
337 result
->filter_policy_
= filter_policy
.Pass();
339 return result
.Pass();
342 leveldb::Status
LevelDBDatabase::Put(const StringPiece
& key
,
343 std::string
* value
) {
344 base::TimeTicks begin_time
= base::TimeTicks::Now();
346 leveldb::WriteOptions write_options
;
347 write_options
.sync
= kSyncWrites
;
349 const leveldb::Status s
=
350 db_
->Put(write_options
, MakeSlice(key
), MakeSlice(*value
));
352 LOG(ERROR
) << "LevelDB put failed: " << s
.ToString();
354 UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.PutTime",
355 base::TimeTicks::Now() - begin_time
);
359 leveldb::Status
LevelDBDatabase::Remove(const StringPiece
& key
) {
360 leveldb::WriteOptions write_options
;
361 write_options
.sync
= kSyncWrites
;
363 const leveldb::Status s
= db_
->Delete(write_options
, MakeSlice(key
));
365 LOG(ERROR
) << "LevelDB remove failed: " << s
.ToString();
369 leveldb::Status
LevelDBDatabase::Get(const StringPiece
& key
,
372 const LevelDBSnapshot
* snapshot
) {
374 leveldb::ReadOptions read_options
;
375 read_options
.verify_checksums
= true; // TODO(jsbell): Disable this if the
376 // performance impact is too great.
377 read_options
.snapshot
= snapshot
? snapshot
->snapshot_
: 0;
379 const leveldb::Status s
= db_
->Get(read_options
, MakeSlice(key
), value
);
385 return leveldb::Status::OK();
386 HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s
);
387 LOG(ERROR
) << "LevelDB get failed: " << s
.ToString();
391 leveldb::Status
LevelDBDatabase::Write(const LevelDBWriteBatch
& write_batch
) {
392 base::TimeTicks begin_time
= base::TimeTicks::Now();
393 leveldb::WriteOptions write_options
;
394 write_options
.sync
= kSyncWrites
;
396 const leveldb::Status s
=
397 db_
->Write(write_options
, write_batch
.write_batch_
.get());
399 HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s
);
400 LOG(ERROR
) << "LevelDB write failed: " << s
.ToString();
402 UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.WriteTime",
403 base::TimeTicks::Now() - begin_time
);
408 scoped_ptr
<LevelDBIterator
> LevelDBDatabase::CreateIterator(
409 const LevelDBSnapshot
* snapshot
) {
410 leveldb::ReadOptions read_options
;
411 read_options
.verify_checksums
= true; // TODO(jsbell): Disable this if the
412 // performance impact is too great.
413 read_options
.snapshot
= snapshot
? snapshot
->snapshot_
: 0;
415 scoped_ptr
<leveldb::Iterator
> i(db_
->NewIterator(read_options
));
416 return scoped_ptr
<LevelDBIterator
>(
417 IndexedDBClassFactory::Get()->CreateIteratorImpl(i
.Pass()));
420 const LevelDBComparator
* LevelDBDatabase::Comparator() const {
424 void LevelDBDatabase::Compact(const base::StringPiece
& start
,
425 const base::StringPiece
& stop
) {
426 const leveldb::Slice start_slice
= MakeSlice(start
);
427 const leveldb::Slice stop_slice
= MakeSlice(stop
);
428 // NULL batch means just wait for earlier writes to be done
429 db_
->Write(leveldb::WriteOptions(), NULL
);
430 db_
->CompactRange(&start_slice
, &stop_slice
);
433 void LevelDBDatabase::CompactAll() { db_
->CompactRange(NULL
, NULL
); }
435 } // namespace content