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"
10 #include "base/basictypes.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/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/sys_info.h"
18 #include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
19 #include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
20 #include "content/browser/indexed_db/leveldb/leveldb_slice.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/comparator.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/slice.h"
32 static leveldb::Slice
MakeSlice(const std::vector
<char>& value
) {
33 DCHECK_GT(value
.size(), static_cast<size_t>(0));
34 return leveldb::Slice(&*value
.begin(), value
.size());
37 static leveldb::Slice
MakeSlice(const LevelDBSlice
& s
) {
38 return leveldb::Slice(s
.begin(), s
.end() - s
.begin());
41 static LevelDBSlice
MakeLevelDBSlice(const leveldb::Slice
& s
) {
42 return LevelDBSlice(s
.data(), s
.data() + s
.size());
45 class ComparatorAdapter
: public leveldb::Comparator
{
47 explicit ComparatorAdapter(const LevelDBComparator
* comparator
)
48 : comparator_(comparator
) {}
50 virtual int Compare(const leveldb::Slice
& a
, const leveldb::Slice
& b
) const
52 return comparator_
->Compare(MakeLevelDBSlice(a
), MakeLevelDBSlice(b
));
55 virtual const char* Name() const OVERRIDE
{ return comparator_
->Name(); }
57 // TODO(jsbell): Support the methods below in the future.
58 virtual void FindShortestSeparator(std::string
* start
,
59 const leveldb::Slice
& limit
) const
61 virtual void FindShortSuccessor(std::string
* key
) const OVERRIDE
{}
64 const LevelDBComparator
* comparator_
;
67 LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase
* db
)
68 : db_(db
->db_
.get()), snapshot_(db_
->GetSnapshot()) {}
70 LevelDBSnapshot::~LevelDBSnapshot() { db_
->ReleaseSnapshot(snapshot_
); }
72 LevelDBDatabase::LevelDBDatabase() {}
74 LevelDBDatabase::~LevelDBDatabase() {
75 // db_'s destructor uses comparator_adapter_; order of deletion is important.
77 comparator_adapter_
.reset();
81 static leveldb::Status
OpenDB(leveldb::Comparator
* comparator
,
83 const base::FilePath
& path
,
85 leveldb::Options options
;
86 options
.comparator
= comparator
;
87 options
.create_if_missing
= true;
88 options
.paranoid_checks
= true;
90 // Marking compression as explicitly off so snappy support can be
91 // compiled in for other leveldb clients without implicitly enabling
92 // it for IndexedDB. http://crbug.com/81384
93 options
.compression
= leveldb::kNoCompression
;
95 // For info about the troubles we've run into with this parameter, see:
96 // https://code.google.com/p/chromium/issues/detail?id=227313#c11
97 options
.max_open_files
= 80;
100 // ChromiumEnv assumes UTF8, converts back to FilePath before using.
101 return leveldb::DB::Open(options
, path
.AsUTF8Unsafe(), db
);
104 bool LevelDBDatabase::Destroy(const base::FilePath
& file_name
) {
105 leveldb::Options options
;
106 options
.env
= leveldb::IDBEnv();
107 // ChromiumEnv assumes UTF8, converts back to FilePath before using.
108 const leveldb::Status s
=
109 leveldb::DestroyDB(file_name
.AsUTF8Unsafe(), options
);
113 static int CheckFreeSpace(const char* const type
,
114 const base::FilePath
& file_name
) {
116 std::string("WebCore.IndexedDB.LevelDB.Open") + type
+ "FreeDiskSpace";
117 int64 free_disk_space_in_k_bytes
=
118 base::SysInfo::AmountOfFreeDiskSpace(file_name
) / 1024;
119 if (free_disk_space_in_k_bytes
< 0) {
120 base::Histogram::FactoryGet(
121 "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
125 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(1 /*sample*/);
128 int clamped_disk_space_k_bytes
=
129 free_disk_space_in_k_bytes
> INT_MAX
? INT_MAX
130 : free_disk_space_in_k_bytes
;
131 const uint64 histogram_max
= static_cast<uint64
>(1e9
);
132 COMPILE_ASSERT(histogram_max
<= INT_MAX
, histogram_max_too_big
);
133 base::Histogram::FactoryGet(name
,
137 base::HistogramBase::kUmaTargetedHistogramFlag
)
138 ->Add(clamped_disk_space_k_bytes
);
139 return clamped_disk_space_k_bytes
;
142 static void ParseAndHistogramIOErrorDetails(const std::string
& histogram_name
,
143 const leveldb::Status
& s
) {
144 leveldb_env::MethodID method
;
146 leveldb_env::ErrorParsingResult result
=
147 leveldb_env::ParseMethodAndError(s
.ToString().c_str(), &method
, &error
);
148 if (result
== leveldb_env::NONE
)
150 std::string
method_histogram_name(histogram_name
);
151 method_histogram_name
.append(".EnvMethod");
152 base::LinearHistogram::FactoryGet(
153 method_histogram_name
,
155 leveldb_env::kNumEntries
,
156 leveldb_env::kNumEntries
+ 1,
157 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(method
);
159 std::string
error_histogram_name(histogram_name
);
161 if (result
== leveldb_env::METHOD_AND_PFE
) {
163 error_histogram_name
.append(std::string(".PFE.") +
164 leveldb_env::MethodIDToString(method
));
165 base::LinearHistogram::FactoryGet(
166 error_histogram_name
,
168 -base::PLATFORM_FILE_ERROR_MAX
,
169 -base::PLATFORM_FILE_ERROR_MAX
+ 1,
170 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(-error
);
171 } else if (result
== leveldb_env::METHOD_AND_ERRNO
) {
172 error_histogram_name
.append(std::string(".Errno.") +
173 leveldb_env::MethodIDToString(method
));
174 base::LinearHistogram::FactoryGet(
175 error_histogram_name
,
179 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(error
);
183 static void ParseAndHistogramCorruptionDetails(
184 const std::string
& histogram_name
,
185 const leveldb::Status
& status
) {
186 DCHECK(!status
.IsIOError());
187 DCHECK(!status
.ok());
188 const int kOtherError
= 0;
189 int error
= kOtherError
;
190 const std::string
& str_error
= status
.ToString();
191 // Keep in sync with LevelDBCorruptionTypes in histograms.xml.
192 const char* patterns
[] = {
194 "log record too small",
195 "corrupted internal key",
197 "missing start of fragmented record",
198 "error in middle of record",
199 "unknown record type",
200 "truncated record at end",
203 "FileReader invoked with unexpected value",
205 "CURRENT file does not end with newline",
206 "no meta-nextfile entry",
207 "no meta-lognumber entry",
208 "no last-sequence-number entry",
209 "malformed WriteBatch",
210 "bad WriteBatch Put",
211 "bad WriteBatch Delete",
212 "unknown WriteBatch tag",
213 "WriteBatch has wrong count",
214 "bad entry in block",
215 "bad block contents",
217 "truncated block read",
218 "block checksum mismatch",
220 "corrupted compressed block contents",
225 const size_t kNumPatterns
= arraysize(patterns
);
226 for (size_t i
= 0; i
< kNumPatterns
; ++i
) {
227 if (str_error
.find(patterns
[i
]) != std::string::npos
) {
233 std::string
corruption_histogram_name(histogram_name
);
234 corruption_histogram_name
.append(".Corruption");
235 base::LinearHistogram::FactoryGet(
236 corruption_histogram_name
,
240 base::HistogramBase::kUmaTargetedHistogramFlag
)->Add(error
);
243 static void HistogramLevelDBError(const std::string
& histogram_name
,
244 const leveldb::Status
& s
) {
256 int leveldb_error
= LEVEL_DB_OTHER
;
258 leveldb_error
= LEVEL_DB_NOT_FOUND
;
259 else if (s
.IsCorruption())
260 leveldb_error
= LEVEL_DB_CORRUPTION
;
261 else if (s
.IsIOError())
262 leveldb_error
= LEVEL_DB_IO_ERROR
;
263 base::Histogram::FactoryGet(histogram_name
,
266 LEVEL_DB_MAX_ERROR
+ 1,
267 base::HistogramBase::kUmaTargetedHistogramFlag
)
268 ->Add(leveldb_error
);
270 ParseAndHistogramIOErrorDetails(histogram_name
, s
);
272 ParseAndHistogramCorruptionDetails(histogram_name
, s
);
275 scoped_ptr
<LevelDBDatabase
> LevelDBDatabase::Open(
276 const base::FilePath
& file_name
,
277 const LevelDBComparator
* comparator
,
278 bool* is_disk_full
) {
279 scoped_ptr
<ComparatorAdapter
> comparator_adapter(
280 new ComparatorAdapter(comparator
));
283 const leveldb::Status s
=
284 OpenDB(comparator_adapter
.get(), leveldb::IDBEnv(), file_name
, &db
);
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();
296 return scoped_ptr
<LevelDBDatabase
>();
299 CheckFreeSpace("Success", file_name
);
301 scoped_ptr
<LevelDBDatabase
> result(new LevelDBDatabase
);
302 result
->db_
= make_scoped_ptr(db
);
303 result
->comparator_adapter_
= comparator_adapter
.Pass();
304 result
->comparator_
= comparator
;
306 return result
.Pass();
309 scoped_ptr
<LevelDBDatabase
> LevelDBDatabase::OpenInMemory(
310 const LevelDBComparator
* comparator
) {
311 scoped_ptr
<ComparatorAdapter
> comparator_adapter(
312 new ComparatorAdapter(comparator
));
313 scoped_ptr
<leveldb::Env
> in_memory_env(leveldb::NewMemEnv(leveldb::IDBEnv()));
316 const leveldb::Status s
= OpenDB(
317 comparator_adapter
.get(), in_memory_env
.get(), base::FilePath(), &db
);
320 LOG(ERROR
) << "Failed to open in-memory LevelDB database: " << s
.ToString();
321 return scoped_ptr
<LevelDBDatabase
>();
324 scoped_ptr
<LevelDBDatabase
> result(new LevelDBDatabase
);
325 result
->env_
= in_memory_env
.Pass();
326 result
->db_
= make_scoped_ptr(db
);
327 result
->comparator_adapter_
= comparator_adapter
.Pass();
328 result
->comparator_
= comparator
;
330 return result
.Pass();
333 bool LevelDBDatabase::Put(const LevelDBSlice
& key
,
334 std::vector
<char>* value
) {
335 leveldb::WriteOptions write_options
;
336 write_options
.sync
= true;
338 const leveldb::Status s
=
339 db_
->Put(write_options
, MakeSlice(key
), MakeSlice(*value
));
342 LOG(ERROR
) << "LevelDB put failed: " << s
.ToString();
346 bool LevelDBDatabase::Remove(const LevelDBSlice
& key
) {
347 leveldb::WriteOptions write_options
;
348 write_options
.sync
= true;
350 const leveldb::Status s
= db_
->Delete(write_options
, MakeSlice(key
));
355 LOG(ERROR
) << "LevelDB remove failed: " << s
.ToString();
359 bool LevelDBDatabase::Get(const LevelDBSlice
& key
,
362 const LevelDBSnapshot
* snapshot
) {
364 leveldb::ReadOptions read_options
;
365 read_options
.verify_checksums
= true; // TODO(jsbell): Disable this if the
366 // performance impact is too great.
367 read_options
.snapshot
= snapshot
? snapshot
->snapshot_
: 0;
369 const leveldb::Status s
= db_
->Get(read_options
, MakeSlice(key
), value
);
376 LOG(ERROR
) << "LevelDB get failed: " << s
.ToString();
380 bool LevelDBDatabase::Write(const LevelDBWriteBatch
& write_batch
) {
381 leveldb::WriteOptions write_options
;
382 write_options
.sync
= true;
384 const leveldb::Status s
=
385 db_
->Write(write_options
, write_batch
.write_batch_
.get());
388 HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s
);
389 LOG(ERROR
) << "LevelDB write failed: " << s
.ToString();
394 class IteratorImpl
: public LevelDBIterator
{
396 virtual ~IteratorImpl() {}
398 virtual bool IsValid() const OVERRIDE
;
399 virtual void SeekToLast() OVERRIDE
;
400 virtual void Seek(const LevelDBSlice
& target
) OVERRIDE
;
401 virtual void Next() OVERRIDE
;
402 virtual void Prev() OVERRIDE
;
403 virtual LevelDBSlice
Key() const OVERRIDE
;
404 virtual LevelDBSlice
Value() const OVERRIDE
;
407 friend class content::LevelDBDatabase
;
408 explicit IteratorImpl(scoped_ptr
<leveldb::Iterator
> iterator
);
411 scoped_ptr
<leveldb::Iterator
> iterator_
;
415 IteratorImpl::IteratorImpl(scoped_ptr
<leveldb::Iterator
> it
)
416 : iterator_(it
.Pass()) {}
418 void IteratorImpl::CheckStatus() {
419 const leveldb::Status s
= iterator_
->status();
421 LOG(ERROR
) << "LevelDB iterator error: " << s
.ToString();
424 bool IteratorImpl::IsValid() const { return iterator_
->Valid(); }
426 void IteratorImpl::SeekToLast() {
427 iterator_
->SeekToLast();
431 void IteratorImpl::Seek(const LevelDBSlice
& target
) {
432 iterator_
->Seek(MakeSlice(target
));
436 void IteratorImpl::Next() {
442 void IteratorImpl::Prev() {
448 LevelDBSlice
IteratorImpl::Key() const {
450 return MakeLevelDBSlice(iterator_
->key());
453 LevelDBSlice
IteratorImpl::Value() const {
455 return MakeLevelDBSlice(iterator_
->value());
458 scoped_ptr
<LevelDBIterator
> LevelDBDatabase::CreateIterator(
459 const LevelDBSnapshot
* snapshot
) {
460 leveldb::ReadOptions read_options
;
461 read_options
.verify_checksums
= true; // TODO(jsbell): Disable this if the
462 // performance impact is too great.
463 read_options
.snapshot
= snapshot
? snapshot
->snapshot_
: 0;
465 scoped_ptr
<leveldb::Iterator
> i(db_
->NewIterator(read_options
));
466 return scoped_ptr
<LevelDBIterator
>(new IteratorImpl(i
.Pass()));
469 const LevelDBComparator
* LevelDBDatabase::Comparator() const {
473 } // namespace content