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/leveldb/leveldb_comparator.h"
20 #include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
21 #include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
22 #include "third_party/leveldatabase/env_chromium.h"
23 #include "third_party/leveldatabase/env_idb.h"
24 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
25 #include "third_party/leveldatabase/src/include/leveldb/db.h"
26 #include "third_party/leveldatabase/src/include/leveldb/env.h"
27 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
29 using base::StringPiece
;
33 // Forcing flushes to disk at the end of a transaction guarantees that the
34 // data hit disk, but drastically impacts throughput when the filesystem is
35 // busy with background compactions. Not syncing trades off reliability for
36 // performance. Note that background compactions which move data from the
37 // log to SSTs are always done with reliable writes.
39 // Sync writes are necessary on Windows for quota calculations; POSIX
40 // calculates file sizes correctly even when not synced to disk.
42 static const bool kSyncWrites
= true;
44 // TODO(dgrogan): Either remove the #if block or change this back to false.
45 // See http://crbug.com/338385.
46 static const bool kSyncWrites
= true;
49 static leveldb::Slice
MakeSlice(const StringPiece
& s
) {
50 return leveldb::Slice(s
.begin(), s
.size());
53 static StringPiece
MakeStringPiece(const leveldb::Slice
& s
) {
54 return StringPiece(s
.data(), s
.size());
57 LevelDBDatabase::ComparatorAdapter::ComparatorAdapter(
58 const LevelDBComparator
* comparator
)
59 : comparator_(comparator
) {}
61 int LevelDBDatabase::ComparatorAdapter::Compare(const leveldb::Slice
& a
,
62 const leveldb::Slice
& b
) const {
63 return comparator_
->Compare(MakeStringPiece(a
), MakeStringPiece(b
));
66 const char* LevelDBDatabase::ComparatorAdapter::Name() const {
67 return comparator_
->Name();
70 // TODO(jsbell): Support the methods below in the future.
71 void LevelDBDatabase::ComparatorAdapter::FindShortestSeparator(
73 const leveldb::Slice
& limit
) const {}
75 void LevelDBDatabase::ComparatorAdapter::FindShortSuccessor(
76 std::string
* key
) const {}
78 LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase
* db
)
79 : db_(db
->db_
.get()), snapshot_(db_
->GetSnapshot()) {}
81 LevelDBSnapshot::~LevelDBSnapshot() { db_
->ReleaseSnapshot(snapshot_
); }
83 LevelDBDatabase::LevelDBDatabase() {}
85 LevelDBDatabase::~LevelDBDatabase() {
86 // db_'s destructor uses comparator_adapter_; order of deletion is important.
88 comparator_adapter_
.reset();
92 static leveldb::Status
OpenDB(leveldb::Comparator
* comparator
,
94 const base::FilePath
& path
,
96 leveldb::Options options
;
97 options
.comparator
= comparator
;
98 options
.create_if_missing
= true;
99 options
.paranoid_checks
= true;
100 options
.compression
= leveldb::kSnappyCompression
;
102 // For info about the troubles we've run into with this parameter, see:
103 // https://code.google.com/p/chromium/issues/detail?id=227313#c11
104 options
.max_open_files
= 80;
107 // ChromiumEnv assumes UTF8, converts back to FilePath before using.
108 return leveldb::DB::Open(options
, path
.AsUTF8Unsafe(), db
);
111 leveldb::Status
LevelDBDatabase::Destroy(const base::FilePath
& file_name
) {
112 leveldb::Options options
;
113 options
.env
= leveldb::IDBEnv();
114 // ChromiumEnv assumes UTF8, converts back to FilePath before using.
115 return leveldb::DestroyDB(file_name
.AsUTF8Unsafe(), options
);
119 class LockImpl
: public LevelDBLock
{
121 explicit LockImpl(leveldb::Env
* env
, leveldb::FileLock
* lock
)
122 : env_(env
), lock_(lock
) {}
123 virtual ~LockImpl() { env_
->UnlockFile(lock_
); }
126 leveldb::FileLock
* lock_
;
130 scoped_ptr
<LevelDBLock
> LevelDBDatabase::LockForTesting(
131 const base::FilePath
& file_name
) {
132 leveldb::Env
* env
= leveldb::IDBEnv();
133 base::FilePath lock_path
= file_name
.AppendASCII("LOCK");
134 leveldb::FileLock
* lock
= NULL
;
135 leveldb::Status status
= env
->LockFile(lock_path
.AsUTF8Unsafe(), &lock
);
137 return scoped_ptr
<LevelDBLock
>();
139 return scoped_ptr
<LevelDBLock
>(new LockImpl(env
, lock
));
142 static int CheckFreeSpace(const char* const type
,
143 const base::FilePath
& file_name
) {
145 std::string("WebCore.IndexedDB.LevelDB.Open") + type
+ "FreeDiskSpace";
146 int64 free_disk_space_in_k_bytes
=
147 base::SysInfo::AmountOfFreeDiskSpace(file_name
) / 1024;
148 if (free_disk_space_in_k_bytes
< 0) {
149 base::Histogram::FactoryGet(
150 "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
154 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(1 /*sample*/);
157 int clamped_disk_space_k_bytes
= free_disk_space_in_k_bytes
> INT_MAX
159 : free_disk_space_in_k_bytes
;
160 const uint64 histogram_max
= static_cast<uint64
>(1e9
);
161 COMPILE_ASSERT(histogram_max
<= INT_MAX
, histogram_max_too_big
);
162 base::Histogram::FactoryGet(name
,
166 base::HistogramBase::kUmaTargetedHistogramFlag
)
167 ->Add(clamped_disk_space_k_bytes
);
168 return clamped_disk_space_k_bytes
;
171 static void ParseAndHistogramIOErrorDetails(const std::string
& histogram_name
,
172 const leveldb::Status
& s
) {
173 leveldb_env::MethodID method
;
175 leveldb_env::ErrorParsingResult result
=
176 leveldb_env::ParseMethodAndError(s
.ToString().c_str(), &method
, &error
);
177 if (result
== leveldb_env::NONE
)
179 std::string
method_histogram_name(histogram_name
);
180 method_histogram_name
.append(".EnvMethod");
181 base::LinearHistogram::FactoryGet(
182 method_histogram_name
,
184 leveldb_env::kNumEntries
,
185 leveldb_env::kNumEntries
+ 1,
186 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(method
);
188 std::string
error_histogram_name(histogram_name
);
190 if (result
== leveldb_env::METHOD_AND_PFE
) {
192 error_histogram_name
.append(std::string(".PFE.") +
193 leveldb_env::MethodIDToString(method
));
194 base::LinearHistogram::FactoryGet(
195 error_histogram_name
,
197 -base::File::FILE_ERROR_MAX
,
198 -base::File::FILE_ERROR_MAX
+ 1,
199 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(-error
);
200 } else if (result
== leveldb_env::METHOD_AND_ERRNO
) {
201 error_histogram_name
.append(std::string(".Errno.") +
202 leveldb_env::MethodIDToString(method
));
203 base::LinearHistogram::FactoryGet(
204 error_histogram_name
,
208 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(error
);
212 static void ParseAndHistogramCorruptionDetails(
213 const std::string
& histogram_name
,
214 const leveldb::Status
& status
) {
215 int error
= leveldb_env::GetCorruptionCode(status
);
217 std::string
corruption_histogram_name(histogram_name
);
218 corruption_histogram_name
.append(".Corruption");
219 const int kNumPatterns
= leveldb_env::GetNumCorruptionCodes();
220 base::LinearHistogram::FactoryGet(
221 corruption_histogram_name
,
225 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(error
);
228 static void HistogramLevelDBError(const std::string
& histogram_name
,
229 const leveldb::Status
& s
) {
241 int leveldb_error
= LEVEL_DB_OTHER
;
243 leveldb_error
= LEVEL_DB_NOT_FOUND
;
244 else if (s
.IsCorruption())
245 leveldb_error
= LEVEL_DB_CORRUPTION
;
246 else if (s
.IsIOError())
247 leveldb_error
= LEVEL_DB_IO_ERROR
;
248 base::Histogram::FactoryGet(histogram_name
,
251 LEVEL_DB_MAX_ERROR
+ 1,
252 base::HistogramBase::kUmaTargetedHistogramFlag
)
253 ->Add(leveldb_error
);
255 ParseAndHistogramIOErrorDetails(histogram_name
, s
);
257 ParseAndHistogramCorruptionDetails(histogram_name
, s
);
260 leveldb::Status
LevelDBDatabase::Open(const base::FilePath
& file_name
,
261 const LevelDBComparator
* comparator
,
262 scoped_ptr
<LevelDBDatabase
>* result
,
263 bool* is_disk_full
) {
264 scoped_ptr
<ComparatorAdapter
> comparator_adapter(
265 new ComparatorAdapter(comparator
));
268 const leveldb::Status s
=
269 OpenDB(comparator_adapter
.get(), leveldb::IDBEnv(), file_name
, &db
);
272 HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s
);
273 int free_space_k_bytes
= CheckFreeSpace("Failure", file_name
);
274 // Disks with <100k of free space almost never succeed in opening a
277 *is_disk_full
= free_space_k_bytes
>= 0 && free_space_k_bytes
< 100;
279 LOG(ERROR
) << "Failed to open LevelDB database from "
280 << file_name
.AsUTF8Unsafe() << "," << s
.ToString();
284 CheckFreeSpace("Success", file_name
);
286 (*result
).reset(new LevelDBDatabase
);
287 (*result
)->db_
= make_scoped_ptr(db
);
288 (*result
)->comparator_adapter_
= comparator_adapter
.Pass();
289 (*result
)->comparator_
= comparator
;
294 scoped_ptr
<LevelDBDatabase
> LevelDBDatabase::OpenInMemory(
295 const LevelDBComparator
* comparator
) {
296 scoped_ptr
<ComparatorAdapter
> comparator_adapter(
297 new ComparatorAdapter(comparator
));
298 scoped_ptr
<leveldb::Env
> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv()));
301 const leveldb::Status s
= OpenDB(
302 comparator_adapter
.get(), in_memory_env
.get(), base::FilePath(), &db
);
305 LOG(ERROR
) << "Failed to open in-memory LevelDB database: " << s
.ToString();
306 return scoped_ptr
<LevelDBDatabase
>();
309 scoped_ptr
<LevelDBDatabase
> result(new LevelDBDatabase
);
310 result
->env_
= in_memory_env
.Pass();
311 result
->db_
= make_scoped_ptr(db
);
312 result
->comparator_adapter_
= comparator_adapter
.Pass();
313 result
->comparator_
= comparator
;
315 return result
.Pass();
318 leveldb::Status
LevelDBDatabase::Put(const StringPiece
& key
,
319 std::string
* value
) {
320 leveldb::WriteOptions write_options
;
321 write_options
.sync
= kSyncWrites
;
323 const leveldb::Status s
=
324 db_
->Put(write_options
, MakeSlice(key
), MakeSlice(*value
));
326 LOG(ERROR
) << "LevelDB put failed: " << s
.ToString();
330 leveldb::Status
LevelDBDatabase::Remove(const StringPiece
& key
) {
331 leveldb::WriteOptions write_options
;
332 write_options
.sync
= kSyncWrites
;
334 const leveldb::Status s
= db_
->Delete(write_options
, MakeSlice(key
));
336 LOG(ERROR
) << "LevelDB remove failed: " << s
.ToString();
340 leveldb::Status
LevelDBDatabase::Get(const StringPiece
& key
,
343 const LevelDBSnapshot
* snapshot
) {
345 leveldb::ReadOptions read_options
;
346 read_options
.verify_checksums
= true; // TODO(jsbell): Disable this if the
347 // performance impact is too great.
348 read_options
.snapshot
= snapshot
? snapshot
->snapshot_
: 0;
350 const leveldb::Status s
= db_
->Get(read_options
, MakeSlice(key
), value
);
356 return leveldb::Status::OK();
357 HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s
);
358 LOG(ERROR
) << "LevelDB get failed: " << s
.ToString();
362 leveldb::Status
LevelDBDatabase::Write(const LevelDBWriteBatch
& write_batch
) {
363 leveldb::WriteOptions write_options
;
364 write_options
.sync
= kSyncWrites
;
366 const leveldb::Status s
=
367 db_
->Write(write_options
, write_batch
.write_batch_
.get());
369 HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s
);
370 LOG(ERROR
) << "LevelDB write failed: " << s
.ToString();
376 class IteratorImpl
: public LevelDBIterator
{
378 virtual ~IteratorImpl() {}
380 virtual bool IsValid() const OVERRIDE
;
381 virtual void SeekToLast() OVERRIDE
;
382 virtual void Seek(const StringPiece
& target
) OVERRIDE
;
383 virtual void Next() OVERRIDE
;
384 virtual void Prev() OVERRIDE
;
385 virtual StringPiece
Key() const OVERRIDE
;
386 virtual StringPiece
Value() const OVERRIDE
;
389 friend class content::LevelDBDatabase
;
390 explicit IteratorImpl(scoped_ptr
<leveldb::Iterator
> iterator
);
393 scoped_ptr
<leveldb::Iterator
> iterator_
;
397 IteratorImpl::IteratorImpl(scoped_ptr
<leveldb::Iterator
> it
)
398 : iterator_(it
.Pass()) {}
400 void IteratorImpl::CheckStatus() {
401 const leveldb::Status s
= iterator_
->status();
403 LOG(ERROR
) << "LevelDB iterator error: " << s
.ToString();
406 bool IteratorImpl::IsValid() const { return iterator_
->Valid(); }
408 void IteratorImpl::SeekToLast() {
409 iterator_
->SeekToLast();
413 void IteratorImpl::Seek(const StringPiece
& target
) {
414 iterator_
->Seek(MakeSlice(target
));
418 void IteratorImpl::Next() {
424 void IteratorImpl::Prev() {
430 StringPiece
IteratorImpl::Key() const {
432 return MakeStringPiece(iterator_
->key());
435 StringPiece
IteratorImpl::Value() const {
437 return MakeStringPiece(iterator_
->value());
440 scoped_ptr
<LevelDBIterator
> LevelDBDatabase::CreateIterator(
441 const LevelDBSnapshot
* snapshot
) {
442 leveldb::ReadOptions read_options
;
443 read_options
.verify_checksums
= true; // TODO(jsbell): Disable this if the
444 // performance impact is too great.
445 read_options
.snapshot
= snapshot
? snapshot
->snapshot_
: 0;
447 scoped_ptr
<leveldb::Iterator
> i(db_
->NewIterator(read_options
));
448 return scoped_ptr
<LevelDBIterator
>(new IteratorImpl(i
.Pass()));
451 const LevelDBComparator
* LevelDBDatabase::Comparator() const {
455 void LevelDBDatabase::Compact(const base::StringPiece
& start
,
456 const base::StringPiece
& stop
) {
457 const leveldb::Slice start_slice
= MakeSlice(start
);
458 const leveldb::Slice stop_slice
= MakeSlice(stop
);
459 db_
->CompactRange(&start_slice
, &stop_slice
);
462 } // namespace content