| // Copyright (c) 2011 The LevelDB Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. See the AUTHORS file for names of contributors. |
| |
| #include "third_party/leveldatabase/env_chromium.h" |
| |
| #if defined(OS_WIN) |
| #include <io.h> |
| #endif |
| |
| #include "base/debug/trace_event.h" |
| #include "base/file_util.h" |
| #include "base/lazy_instance.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "third_party/leveldatabase/env_chromium_stdio.h" |
| #include "third_party/re2/re2/re2.h" |
| |
| #if defined(OS_WIN) |
| #include "base/command_line.h" |
| #include "base/win/win_util.h" |
| #include "third_party/leveldatabase/env_chromium_win.h" |
| #endif |
| |
| using leveldb::FileLock; |
| using leveldb::Slice; |
| using leveldb::Status; |
| |
| namespace leveldb_env { |
| |
| namespace { |
| |
| const base::FilePath::CharType backup_table_extension[] = |
| FILE_PATH_LITERAL(".bak"); |
| const base::FilePath::CharType table_extension[] = FILE_PATH_LITERAL(".ldb"); |
| |
| static const base::FilePath::CharType kLevelDBTestDirectoryPrefix[] |
| = FILE_PATH_LITERAL("leveldb-test-"); |
| |
| class ChromiumFileLock : public FileLock { |
| public: |
| ::base::File file_; |
| std::string name_; |
| }; |
| |
| class Retrier { |
| public: |
| Retrier(MethodID method, RetrierProvider* provider) |
| : start_(base::TimeTicks::Now()), |
| limit_(start_ + base::TimeDelta::FromMilliseconds( |
| provider->MaxRetryTimeMillis())), |
| last_(start_), |
| time_to_sleep_(base::TimeDelta::FromMilliseconds(10)), |
| success_(true), |
| method_(method), |
| last_error_(base::File::FILE_OK), |
| provider_(provider) {} |
| ~Retrier() { |
| if (success_) { |
| provider_->GetRetryTimeHistogram(method_)->AddTime(last_ - start_); |
| if (last_error_ != base::File::FILE_OK) { |
| DCHECK_LT(last_error_, 0); |
| provider_->GetRecoveredFromErrorHistogram(method_)->Add(-last_error_); |
| } |
| } |
| } |
| bool ShouldKeepTrying(base::File::Error last_error) { |
| DCHECK_NE(last_error, base::File::FILE_OK); |
| last_error_ = last_error; |
| if (last_ < limit_) { |
| base::PlatformThread::Sleep(time_to_sleep_); |
| last_ = base::TimeTicks::Now(); |
| return true; |
| } |
| success_ = false; |
| return false; |
| } |
| |
| private: |
| base::TimeTicks start_; |
| base::TimeTicks limit_; |
| base::TimeTicks last_; |
| base::TimeDelta time_to_sleep_; |
| bool success_; |
| MethodID method_; |
| base::File::Error last_error_; |
| RetrierProvider* provider_; |
| }; |
| |
| class IDBEnvStdio : public ChromiumEnvStdio { |
| public: |
| IDBEnvStdio() : ChromiumEnvStdio() { |
| name_ = "LevelDBEnv.IDB"; |
| make_backup_ = true; |
| } |
| }; |
| |
| #if defined(OS_WIN) |
| class IDBEnvWin : public ChromiumEnvWin { |
| public: |
| IDBEnvWin() : ChromiumEnvWin() { |
| name_ = "LevelDBEnv.IDB"; |
| make_backup_ = true; |
| } |
| }; |
| #endif |
| |
| #if defined(OS_WIN) |
| ::base::LazyInstance<IDBEnvWin>::Leaky idb_env = |
| LAZY_INSTANCE_INITIALIZER; |
| #else |
| ::base::LazyInstance<IDBEnvStdio>::Leaky idb_env = |
| LAZY_INSTANCE_INITIALIZER; |
| #endif |
| |
| ::base::LazyInstance<ChromiumEnvStdio>::Leaky default_env = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // unnamed namespace |
| |
| const char* MethodIDToString(MethodID method) { |
| switch (method) { |
| case kSequentialFileRead: |
| return "SequentialFileRead"; |
| case kSequentialFileSkip: |
| return "SequentialFileSkip"; |
| case kRandomAccessFileRead: |
| return "RandomAccessFileRead"; |
| case kWritableFileAppend: |
| return "WritableFileAppend"; |
| case kWritableFileClose: |
| return "WritableFileClose"; |
| case kWritableFileFlush: |
| return "WritableFileFlush"; |
| case kWritableFileSync: |
| return "WritableFileSync"; |
| case kNewSequentialFile: |
| return "NewSequentialFile"; |
| case kNewRandomAccessFile: |
| return "NewRandomAccessFile"; |
| case kNewWritableFile: |
| return "NewWritableFile"; |
| case kDeleteFile: |
| return "DeleteFile"; |
| case kCreateDir: |
| return "CreateDir"; |
| case kDeleteDir: |
| return "DeleteDir"; |
| case kGetFileSize: |
| return "GetFileSize"; |
| case kRenameFile: |
| return "RenameFile"; |
| case kLockFile: |
| return "LockFile"; |
| case kUnlockFile: |
| return "UnlockFile"; |
| case kGetTestDirectory: |
| return "GetTestDirectory"; |
| case kNewLogger: |
| return "NewLogger"; |
| case kSyncParent: |
| return "SyncParent"; |
| case kGetChildren: |
| return "GetChildren"; |
| case kNumEntries: |
| NOTREACHED(); |
| return "kNumEntries"; |
| } |
| NOTREACHED(); |
| return "Unknown"; |
| } |
| |
| Status MakeIOError(Slice filename, |
| const char* message, |
| MethodID method, |
| int saved_errno) { |
| char buf[512]; |
| snprintf(buf, |
| sizeof(buf), |
| "%s (ChromeMethodErrno: %d::%s::%d)", |
| message, |
| method, |
| MethodIDToString(method), |
| saved_errno); |
| return Status::IOError(filename, buf); |
| } |
| |
| Status MakeIOError(Slice filename, |
| const char* message, |
| MethodID method, |
| base::File::Error error) { |
| DCHECK_LT(error, 0); |
| char buf[512]; |
| snprintf(buf, |
| sizeof(buf), |
| "%s (ChromeMethodPFE: %d::%s::%d)", |
| message, |
| method, |
| MethodIDToString(method), |
| -error); |
| return Status::IOError(filename, buf); |
| } |
| |
| Status MakeIOError(Slice filename, const char* message, MethodID method) { |
| char buf[512]; |
| snprintf(buf, |
| sizeof(buf), |
| "%s (ChromeMethodOnly: %d::%s)", |
| message, |
| method, |
| MethodIDToString(method)); |
| return Status::IOError(filename, buf); |
| } |
| |
| ErrorParsingResult ParseMethodAndError(const char* string, |
| MethodID* method_param, |
| int* error) { |
| int method; |
| if (RE2::PartialMatch(string, "ChromeMethodOnly: (\\d+)", &method)) { |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_ONLY; |
| } |
| if (RE2::PartialMatch( |
| string, "ChromeMethodPFE: (\\d+)::.*::(\\d+)", &method, error)) { |
| *error = -*error; |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_AND_PFE; |
| } |
| if (RE2::PartialMatch( |
| string, "ChromeMethodErrno: (\\d+)::.*::(\\d+)", &method, error)) { |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_AND_ERRNO; |
| } |
| return NONE; |
| } |
| |
| // Keep in sync with LevelDBCorruptionTypes in histograms.xml. Also, don't |
| // change the order because indices into this array have been recorded in uma |
| // histograms. |
| const char* patterns[] = { |
| "missing files", |
| "log record too small", |
| "corrupted internal key", |
| "partial record", |
| "missing start of fragmented record", |
| "error in middle of record", |
| "unknown record type", |
| "truncated record at end", |
| "bad record length", |
| "VersionEdit", |
| "FileReader invoked with unexpected value", |
| "corrupted key", |
| "CURRENT file does not end with newline", |
| "no meta-nextfile entry", |
| "no meta-lognumber entry", |
| "no last-sequence-number entry", |
| "malformed WriteBatch", |
| "bad WriteBatch Put", |
| "bad WriteBatch Delete", |
| "unknown WriteBatch tag", |
| "WriteBatch has wrong count", |
| "bad entry in block", |
| "bad block contents", |
| "bad block handle", |
| "truncated block read", |
| "block checksum mismatch", |
| "checksum mismatch", |
| "corrupted compressed block contents", |
| "bad block type", |
| "bad magic number", |
| "file is too short", |
| }; |
| |
| // Returns 1-based index into the above array or 0 if nothing matches. |
| int GetCorruptionCode(const leveldb::Status& status) { |
| DCHECK(!status.IsIOError()); |
| DCHECK(!status.ok()); |
| const int kOtherError = 0; |
| int error = kOtherError; |
| const std::string& str_error = status.ToString(); |
| const size_t kNumPatterns = arraysize(patterns); |
| for (size_t i = 0; i < kNumPatterns; ++i) { |
| if (str_error.find(patterns[i]) != std::string::npos) { |
| error = i + 1; |
| break; |
| } |
| } |
| return error; |
| } |
| |
| int GetNumCorruptionCodes() { |
| // + 1 for the "other" error that is returned when a corruption message |
| // doesn't match any of the patterns. |
| return arraysize(patterns) + 1; |
| } |
| |
| std::string GetCorruptionMessage(const leveldb::Status& status) { |
| int code = GetCorruptionCode(status); |
| if (code == 0) |
| return "Unknown corruption"; |
| return patterns[code - 1]; |
| } |
| |
| bool IndicatesDiskFull(const leveldb::Status& status) { |
| if (status.ok()) |
| return false; |
| leveldb_env::MethodID method; |
| int error = -1; |
| leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError( |
| status.ToString().c_str(), &method, &error); |
| return (result == leveldb_env::METHOD_AND_PFE && |
| static_cast<base::File::Error>(error) == |
| base::File::FILE_ERROR_NO_SPACE) || |
| (result == leveldb_env::METHOD_AND_ERRNO && error == ENOSPC); |
| } |
| |
| bool IsIOError(const leveldb::Status& status) { |
| leveldb_env::MethodID method; |
| int error = -1; |
| leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError( |
| status.ToString().c_str(), &method, &error); |
| return result != leveldb_env::NONE; |
| } |
| |
| bool IsCorruption(const leveldb::Status& status) { |
| // LevelDB returns InvalidArgument when an sst file is truncated but there is |
| // no IsInvalidArgument() accessor defined. |
| return status.IsCorruption() || (!status.ok() && !IsIOError(status)); |
| } |
| |
| std::string FilePathToString(const base::FilePath& file_path) { |
| #if defined(OS_WIN) |
| return base::UTF16ToUTF8(file_path.value()); |
| #else |
| return file_path.value(); |
| #endif |
| } |
| |
| base::FilePath ChromiumEnv::CreateFilePath(const std::string& file_path) { |
| #if defined(OS_WIN) |
| return base::FilePath(base::UTF8ToUTF16(file_path)); |
| #else |
| return base::FilePath(file_path); |
| #endif |
| } |
| |
| bool ChromiumEnv::MakeBackup(const std::string& fname) { |
| base::FilePath original_table_name = CreateFilePath(fname); |
| base::FilePath backup_table_name = |
| original_table_name.ReplaceExtension(backup_table_extension); |
| return base::CopyFile(original_table_name, backup_table_name); |
| } |
| |
| bool ChromiumEnv::HasTableExtension(const base::FilePath& path) { |
| return path.MatchesExtension(table_extension); |
| } |
| |
| ChromiumEnv::ChromiumEnv() |
| : name_("LevelDBEnv"), |
| make_backup_(false), |
| bgsignal_(&mu_), |
| started_bgthread_(false), |
| kMaxRetryTimeMillis(1000) { |
| } |
| |
| ChromiumEnv::~ChromiumEnv() { |
| // In chromium, ChromiumEnv is leaked. It'd be nice to add NOTREACHED here to |
| // ensure that behavior isn't accidentally changed, but there's an instance in |
| // a unit test that is deleted. |
| } |
| |
| bool ChromiumEnv::FileExists(const std::string& fname) { |
| return ::base::PathExists(CreateFilePath(fname)); |
| } |
| |
| const char* ChromiumEnv::FileErrorString(::base::File::Error error) { |
| switch (error) { |
| case ::base::File::FILE_ERROR_FAILED: |
| return "No further details."; |
| case ::base::File::FILE_ERROR_IN_USE: |
| return "File currently in use."; |
| case ::base::File::FILE_ERROR_EXISTS: |
| return "File already exists."; |
| case ::base::File::FILE_ERROR_NOT_FOUND: |
| return "File not found."; |
| case ::base::File::FILE_ERROR_ACCESS_DENIED: |
| return "Access denied."; |
| case ::base::File::FILE_ERROR_TOO_MANY_OPENED: |
| return "Too many files open."; |
| case ::base::File::FILE_ERROR_NO_MEMORY: |
| return "Out of memory."; |
| case ::base::File::FILE_ERROR_NO_SPACE: |
| return "No space left on drive."; |
| case ::base::File::FILE_ERROR_NOT_A_DIRECTORY: |
| return "Not a directory."; |
| case ::base::File::FILE_ERROR_INVALID_OPERATION: |
| return "Invalid operation."; |
| case ::base::File::FILE_ERROR_SECURITY: |
| return "Security error."; |
| case ::base::File::FILE_ERROR_ABORT: |
| return "File operation aborted."; |
| case ::base::File::FILE_ERROR_NOT_A_FILE: |
| return "The supplied path was not a file."; |
| case ::base::File::FILE_ERROR_NOT_EMPTY: |
| return "The file was not empty."; |
| case ::base::File::FILE_ERROR_INVALID_URL: |
| return "Invalid URL."; |
| case ::base::File::FILE_ERROR_IO: |
| return "OS or hardware error."; |
| case ::base::File::FILE_OK: |
| return "OK."; |
| case ::base::File::FILE_ERROR_MAX: |
| NOTREACHED(); |
| } |
| NOTIMPLEMENTED(); |
| return "Unknown error."; |
| } |
| |
| base::FilePath ChromiumEnv::RestoreFromBackup(const base::FilePath& base_name) { |
| base::FilePath table_name = |
| base_name.AddExtension(table_extension); |
| bool result = base::CopyFile(base_name.AddExtension(backup_table_extension), |
| table_name); |
| std::string uma_name(name_); |
| uma_name.append(".TableRestore"); |
| base::BooleanHistogram::FactoryGet( |
| uma_name, base::Histogram::kUmaTargetedHistogramFlag)->AddBoolean(result); |
| return table_name; |
| } |
| |
| void ChromiumEnv::RestoreIfNecessary(const std::string& dir, |
| std::vector<std::string>* result) { |
| std::set<base::FilePath> tables_found; |
| std::set<base::FilePath> backups_found; |
| for (std::vector<std::string>::iterator it = result->begin(); |
| it != result->end(); |
| ++it) { |
| base::FilePath current = CreateFilePath(*it); |
| if (current.MatchesExtension(table_extension)) |
| tables_found.insert(current.RemoveExtension()); |
| if (current.MatchesExtension(backup_table_extension)) |
| backups_found.insert(current.RemoveExtension()); |
| } |
| std::set<base::FilePath> backups_only = |
| base::STLSetDifference<std::set<base::FilePath> >(backups_found, |
| tables_found); |
| |
| if (backups_only.size()) { |
| std::string uma_name(name_); |
| uma_name.append(".MissingFiles"); |
| int num_missing_files = |
| backups_only.size() > INT_MAX ? INT_MAX : backups_only.size(); |
| base::Histogram::FactoryGet(uma_name, |
| 1 /*min*/, |
| 100 /*max*/, |
| 8 /*num_buckets*/, |
| base::Histogram::kUmaTargetedHistogramFlag) |
| ->Add(num_missing_files); |
| } |
| base::FilePath dir_filepath = base::FilePath::FromUTF8Unsafe(dir); |
| for (std::set<base::FilePath>::iterator it = backups_only.begin(); |
| it != backups_only.end(); |
| ++it) { |
| base::FilePath restored_table_name = |
| RestoreFromBackup(dir_filepath.Append(*it)); |
| result->push_back(FilePathToString(restored_table_name.BaseName())); |
| } |
| } |
| |
| Status ChromiumEnv::GetChildren(const std::string& dir_string, |
| std::vector<std::string>* result) { |
| std::vector<base::FilePath> entries; |
| base::File::Error error = |
| GetDirectoryEntries(CreateFilePath(dir_string), &entries); |
| if (error != base::File::FILE_OK) { |
| RecordOSError(kGetChildren, error); |
| return MakeIOError( |
| dir_string, "Could not open/read directory", kGetChildren, error); |
| } |
| result->clear(); |
| for (std::vector<base::FilePath>::iterator it = entries.begin(); |
| it != entries.end(); |
| ++it) { |
| result->push_back(FilePathToString(*it)); |
| } |
| |
| if (make_backup_) |
| RestoreIfNecessary(dir_string, result); |
| return Status::OK(); |
| } |
| |
| Status ChromiumEnv::DeleteFile(const std::string& fname) { |
| Status result; |
| base::FilePath fname_filepath = CreateFilePath(fname); |
| // TODO(jorlow): Should we assert this is a file? |
| if (!::base::DeleteFile(fname_filepath, false)) { |
| result = MakeIOError(fname, "Could not delete file.", kDeleteFile); |
| RecordErrorAt(kDeleteFile); |
| } |
| if (make_backup_ && fname_filepath.MatchesExtension(table_extension)) { |
| base::DeleteFile(fname_filepath.ReplaceExtension(backup_table_extension), |
| false); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::CreateDir(const std::string& name) { |
| Status result; |
| base::File::Error error = base::File::FILE_OK; |
| Retrier retrier(kCreateDir, this); |
| do { |
| if (base::CreateDirectoryAndGetError(CreateFilePath(name), &error)) |
| return result; |
| } while (retrier.ShouldKeepTrying(error)); |
| result = MakeIOError(name, "Could not create directory.", kCreateDir, error); |
| RecordOSError(kCreateDir, error); |
| return result; |
| } |
| |
| Status ChromiumEnv::DeleteDir(const std::string& name) { |
| Status result; |
| // TODO(jorlow): Should we assert this is a directory? |
| if (!::base::DeleteFile(CreateFilePath(name), false)) { |
| result = MakeIOError(name, "Could not delete directory.", kDeleteDir); |
| RecordErrorAt(kDeleteDir); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::GetFileSize(const std::string& fname, uint64_t* size) { |
| Status s; |
| int64_t signed_size; |
| if (!::base::GetFileSize(CreateFilePath(fname), &signed_size)) { |
| *size = 0; |
| s = MakeIOError(fname, "Could not determine file size.", kGetFileSize); |
| RecordErrorAt(kGetFileSize); |
| } else { |
| *size = static_cast<uint64_t>(signed_size); |
| } |
| return s; |
| } |
| |
| Status ChromiumEnv::RenameFile(const std::string& src, const std::string& dst) { |
| Status result; |
| base::FilePath src_file_path = CreateFilePath(src); |
| if (!::base::PathExists(src_file_path)) |
| return result; |
| base::FilePath destination = CreateFilePath(dst); |
| |
| Retrier retrier(kRenameFile, this); |
| base::File::Error error = base::File::FILE_OK; |
| do { |
| if (base::ReplaceFile(src_file_path, destination, &error)) |
| return result; |
| } while (retrier.ShouldKeepTrying(error)); |
| |
| DCHECK(error != base::File::FILE_OK); |
| RecordOSError(kRenameFile, error); |
| char buf[100]; |
| snprintf(buf, |
| sizeof(buf), |
| "Could not rename file: %s", |
| FileErrorString(error)); |
| return MakeIOError(src, buf, kRenameFile, error); |
| } |
| |
| Status ChromiumEnv::LockFile(const std::string& fname, FileLock** lock) { |
| *lock = NULL; |
| Status result; |
| int flags = ::base::File::FLAG_OPEN_ALWAYS | |
| ::base::File::FLAG_READ | |
| ::base::File::FLAG_WRITE; |
| ::base::File::Error error_code; |
| ::base::File file; |
| Retrier retrier(kLockFile, this); |
| do { |
| file.Initialize(CreateFilePath(fname), flags); |
| if (!file.IsValid()) |
| error_code = file.error_details(); |
| } while (!file.IsValid() && retrier.ShouldKeepTrying(error_code)); |
| |
| if (!file.IsValid()) { |
| if (error_code == ::base::File::FILE_ERROR_NOT_FOUND) { |
| ::base::FilePath parent = CreateFilePath(fname).DirName(); |
| ::base::FilePath last_parent; |
| int num_missing_ancestors = 0; |
| do { |
| if (base::DirectoryExists(parent)) |
| break; |
| ++num_missing_ancestors; |
| last_parent = parent; |
| parent = parent.DirName(); |
| } while (parent != last_parent); |
| RecordLockFileAncestors(num_missing_ancestors); |
| } |
| |
| result = MakeIOError(fname, FileErrorString(error_code), kLockFile, |
| error_code); |
| RecordOSError(kLockFile, error_code); |
| return result; |
| } |
| |
| if (!locks_.Insert(fname)) { |
| result = MakeIOError(fname, "Lock file already locked.", kLockFile); |
| return result; |
| } |
| |
| Retrier lock_retrier = Retrier(kLockFile, this); |
| do { |
| error_code = file.Lock(); |
| } while (error_code != ::base::File::FILE_OK && |
| retrier.ShouldKeepTrying(error_code)); |
| |
| if (error_code != ::base::File::FILE_OK) { |
| locks_.Remove(fname); |
| result = MakeIOError(fname, FileErrorString(error_code), kLockFile, |
| error_code); |
| RecordOSError(kLockFile, error_code); |
| return result; |
| } |
| |
| ChromiumFileLock* my_lock = new ChromiumFileLock; |
| my_lock->file_ = file.Pass(); |
| my_lock->name_ = fname; |
| *lock = my_lock; |
| return result; |
| } |
| |
| Status ChromiumEnv::UnlockFile(FileLock* lock) { |
| ChromiumFileLock* my_lock = reinterpret_cast<ChromiumFileLock*>(lock); |
| Status result; |
| |
| ::base::File::Error error_code = my_lock->file_.Unlock(); |
| if (error_code != ::base::File::FILE_OK) { |
| result = |
| MakeIOError(my_lock->name_, "Could not unlock lock file.", kUnlockFile); |
| RecordOSError(kUnlockFile, error_code); |
| } |
| bool removed = locks_.Remove(my_lock->name_); |
| DCHECK(removed); |
| delete my_lock; |
| return result; |
| } |
| |
| Status ChromiumEnv::GetTestDirectory(std::string* path) { |
| mu_.Acquire(); |
| if (test_directory_.empty()) { |
| if (!base::CreateNewTempDirectory(kLevelDBTestDirectoryPrefix, |
| &test_directory_)) { |
| mu_.Release(); |
| RecordErrorAt(kGetTestDirectory); |
| return MakeIOError( |
| "Could not create temp directory.", "", kGetTestDirectory); |
| } |
| } |
| *path = FilePathToString(test_directory_); |
| mu_.Release(); |
| return Status::OK(); |
| } |
| |
| uint64_t ChromiumEnv::NowMicros() { |
| return ::base::TimeTicks::Now().ToInternalValue(); |
| } |
| |
| void ChromiumEnv::SleepForMicroseconds(int micros) { |
| // Round up to the next millisecond. |
| ::base::PlatformThread::Sleep(::base::TimeDelta::FromMicroseconds(micros)); |
| } |
| |
| void ChromiumEnv::RecordErrorAt(MethodID method) const { |
| GetMethodIOErrorHistogram()->Add(method); |
| } |
| |
| void ChromiumEnv::RecordLockFileAncestors(int num_missing_ancestors) const { |
| GetLockFileAncestorHistogram()->Add(num_missing_ancestors); |
| } |
| |
| void ChromiumEnv::RecordOSError(MethodID method, |
| base::File::Error error) const { |
| DCHECK_LT(error, 0); |
| RecordErrorAt(method); |
| GetOSErrorHistogram(method, -base::File::FILE_ERROR_MAX)->Add(-error); |
| } |
| |
| void ChromiumEnv::RecordOSError(MethodID method, int error) const { |
| DCHECK_GT(error, 0); |
| RecordErrorAt(method); |
| GetOSErrorHistogram(method, ERANGE + 1)->Add(error); |
| } |
| |
| void ChromiumEnv::RecordBackupResult(bool result) const { |
| std::string uma_name(name_); |
| uma_name.append(".TableBackup"); |
| base::BooleanHistogram::FactoryGet( |
| uma_name, base::Histogram::kUmaTargetedHistogramFlag)->AddBoolean(result); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetOSErrorHistogram(MethodID method, |
| int limit) const { |
| std::string uma_name(name_); |
| // TODO(dgrogan): This is probably not the best way to concatenate strings. |
| uma_name.append(".IOError.").append(MethodIDToString(method)); |
| return base::LinearHistogram::FactoryGet(uma_name, 1, limit, limit + 1, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetMethodIOErrorHistogram() const { |
| std::string uma_name(name_); |
| uma_name.append(".IOError"); |
| return base::LinearHistogram::FactoryGet(uma_name, 1, kNumEntries, |
| kNumEntries + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetMaxFDHistogram( |
| const std::string& type) const { |
| std::string uma_name(name_); |
| uma_name.append(".MaxFDs.").append(type); |
| // These numbers make each bucket twice as large as the previous bucket. |
| const int kFirstEntry = 1; |
| const int kLastEntry = 65536; |
| const int kNumBuckets = 18; |
| return base::Histogram::FactoryGet( |
| uma_name, kFirstEntry, kLastEntry, kNumBuckets, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetLockFileAncestorHistogram() const { |
| std::string uma_name(name_); |
| uma_name.append(".LockFileAncestorsNotFound"); |
| const int kMin = 1; |
| const int kMax = 10; |
| const int kNumBuckets = 11; |
| return base::LinearHistogram::FactoryGet( |
| uma_name, kMin, kMax, kNumBuckets, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetRetryTimeHistogram(MethodID method) const { |
| std::string uma_name(name_); |
| // TODO(dgrogan): This is probably not the best way to concatenate strings. |
| uma_name.append(".TimeUntilSuccessFor").append(MethodIDToString(method)); |
| |
| const int kBucketSizeMillis = 25; |
| // Add 2, 1 for each of the buckets <1 and >max. |
| const int kNumBuckets = kMaxRetryTimeMillis / kBucketSizeMillis + 2; |
| return base::Histogram::FactoryTimeGet( |
| uma_name, base::TimeDelta::FromMilliseconds(1), |
| base::TimeDelta::FromMilliseconds(kMaxRetryTimeMillis + 1), |
| kNumBuckets, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetRecoveredFromErrorHistogram( |
| MethodID method) const { |
| std::string uma_name(name_); |
| uma_name.append(".RetryRecoveredFromErrorIn") |
| .append(MethodIDToString(method)); |
| return base::LinearHistogram::FactoryGet(uma_name, 1, kNumEntries, |
| kNumEntries + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| class Thread : public ::base::PlatformThread::Delegate { |
| public: |
| Thread(void (*function)(void* arg), void* arg) |
| : function_(function), arg_(arg) { |
| ::base::PlatformThreadHandle handle; |
| bool success = ::base::PlatformThread::Create(0, this, &handle); |
| DCHECK(success); |
| } |
| virtual ~Thread() {} |
| virtual void ThreadMain() { |
| (*function_)(arg_); |
| delete this; |
| } |
| |
| private: |
| void (*function_)(void* arg); |
| void* arg_; |
| }; |
| |
| void ChromiumEnv::Schedule(ScheduleFunc* function, void* arg) { |
| mu_.Acquire(); |
| |
| // Start background thread if necessary |
| if (!started_bgthread_) { |
| started_bgthread_ = true; |
| StartThread(&ChromiumEnv::BGThreadWrapper, this); |
| } |
| |
| // If the queue is currently empty, the background thread may currently be |
| // waiting. |
| if (queue_.empty()) { |
| bgsignal_.Signal(); |
| } |
| |
| // Add to priority queue |
| queue_.push_back(BGItem()); |
| queue_.back().function = function; |
| queue_.back().arg = arg; |
| |
| mu_.Release(); |
| } |
| |
| void ChromiumEnv::BGThread() { |
| base::PlatformThread::SetName(name_.c_str()); |
| |
| while (true) { |
| // Wait until there is an item that is ready to run |
| mu_.Acquire(); |
| while (queue_.empty()) { |
| bgsignal_.Wait(); |
| } |
| |
| void (*function)(void*) = queue_.front().function; |
| void* arg = queue_.front().arg; |
| queue_.pop_front(); |
| |
| mu_.Release(); |
| TRACE_EVENT0("leveldb", "ChromiumEnv::BGThread-Task"); |
| (*function)(arg); |
| } |
| } |
| |
| void ChromiumEnv::StartThread(void (*function)(void* arg), void* arg) { |
| new Thread(function, arg); // Will self-delete. |
| } |
| |
| static std::string GetDirName(const std::string& filename) { |
| base::FilePath file = base::FilePath::FromUTF8Unsafe(filename); |
| return FilePathToString(file.DirName()); |
| } |
| |
| void ChromiumEnv::DidCreateNewFile(const std::string& filename) { |
| base::AutoLock auto_lock(map_lock_); |
| needs_sync_map_[GetDirName(filename)] = true; |
| } |
| |
| bool ChromiumEnv::DoesDirNeedSync(const std::string& filename) { |
| base::AutoLock auto_lock(map_lock_); |
| return needs_sync_map_.find(GetDirName(filename)) != needs_sync_map_.end(); |
| } |
| |
| void ChromiumEnv::DidSyncDir(const std::string& filename) { |
| base::AutoLock auto_lock(map_lock_); |
| needs_sync_map_.erase(GetDirName(filename)); |
| } |
| |
| } // namespace leveldb_env |
| |
| namespace leveldb { |
| |
| Env* IDBEnv() { |
| return leveldb_env::idb_env.Pointer(); |
| } |
| |
| Env* Env::Default() { |
| return leveldb_env::default_env.Pointer(); |
| } |
| |
| } // namespace leveldb |