diff options
Diffstat (limited to 'chromium/components/metrics/file_metrics_provider.cc')
-rw-r--r-- | chromium/components/metrics/file_metrics_provider.cc | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/chromium/components/metrics/file_metrics_provider.cc b/chromium/components/metrics/file_metrics_provider.cc new file mode 100644 index 00000000000..78fb90a1211 --- /dev/null +++ b/chromium/components/metrics/file_metrics_provider.cc @@ -0,0 +1,291 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/file_metrics_provider.h" + +#include "base/command_line.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/persistent_histogram_allocator.h" +#include "base/metrics/persistent_memory_allocator.h" +#include "base/task_runner.h" +#include "base/time/time.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_service.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + + +namespace metrics { + +// This structure stores all the information about the files being monitored +// and their current reporting state. +struct FileMetricsProvider::FileInfo { + FileInfo() {} + ~FileInfo() {} + + // Where on disk the file is located. + base::FilePath path; + + // How to access this file (atomic/active). + FileType type; + + // Name used inside prefs to persistent metadata. + std::string prefs_key; + + // The last-seen time of this file to detect change. + base::Time last_seen; + + // Once a file has been recognized as needing to be read, it is |mapped| + // into memory. If that file is "atomic" then the data from that file + // will be copied to |data| and the mapped file released. If the file is + // "active", it remains mapped and nothing is copied to local memory. + std::vector<uint8_t> data; + scoped_ptr<base::MemoryMappedFile> mapped; + scoped_ptr<base::PersistentHistogramAllocator> allocator; + + private: + DISALLOW_COPY_AND_ASSIGN(FileInfo); +}; + +FileMetricsProvider::FileMetricsProvider( + const scoped_refptr<base::TaskRunner>& task_runner, + PrefService* local_state) + : task_runner_(task_runner), + pref_service_(local_state), + weak_factory_(this) { +} + +FileMetricsProvider::~FileMetricsProvider() {} + +void FileMetricsProvider::RegisterFile(const base::FilePath& path, + FileType type, + const base::StringPiece prefs_key) { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_ptr<FileInfo> file(new FileInfo()); + file->path = path; + file->type = type; + file->prefs_key = prefs_key.as_string(); + + // |prefs_key| may be empty if the caller does not wish to persist the + // state across instances of the program. + if (pref_service_ && !prefs_key.empty()) { + file->last_seen = base::Time::FromInternalValue( + pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix + + file->prefs_key)); + } + + files_to_check_.push_back(std::move(file)); +} + +// static +void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs, + const base::StringPiece prefs_key) { + prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix + + prefs_key.as_string(), 0); +} + +// static +void FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner( + FileMetricsProvider::FileInfoList* files) { + // This method has all state information passed in |files| and is intended + // to run on a worker thread rather than the UI thread. + for (scoped_ptr<FileInfo>& file : *files) { + AccessResult result = CheckAndMapNewMetrics(file.get()); + // Some results are not reported in order to keep the dashboard clean. + if (result != ACCESS_RESULT_DOESNT_EXIST && + result != ACCESS_RESULT_NOT_MODIFIED) { + UMA_HISTOGRAM_ENUMERATION( + "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX); + } + } +} + +// This method has all state information passed in |file| and is intended +// to run on a worker thread rather than the UI thread. +// static +FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapNewMetrics( + FileMetricsProvider::FileInfo* file) { + DCHECK(!file->mapped); + DCHECK(file->data.empty()); + + base::File::Info info; + if (!base::GetFileInfo(file->path, &info)) + return ACCESS_RESULT_DOESNT_EXIST; + + if (info.is_directory || info.size == 0) + return ACCESS_RESULT_INVALID_FILE; + + if (file->last_seen >= info.last_modified) + return ACCESS_RESULT_NOT_MODIFIED; + + // A new file of metrics has been found. Map it into memory. + // TODO(bcwhite): Make this open read/write when supported for "active". + file->mapped.reset(new base::MemoryMappedFile()); + if (!file->mapped->Initialize(file->path)) { + file->mapped.reset(); + return ACCESS_RESULT_SYSTEM_MAP_FAILURE; + } + + // Ensure any problems below don't occur repeatedly. + file->last_seen = info.last_modified; + + // Test the validity of the file contents. + if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*file->mapped)) + return ACCESS_RESULT_INVALID_CONTENTS; + + // For an "atomic" file, immediately copy the data into local memory and + // release the file so that it is not held open. + if (file->type == FILE_HISTOGRAMS_ATOMIC) { + file->data.assign(file->mapped->data(), + file->mapped->data() + file->mapped->length()); + file->mapped.reset(); + } + + return ACCESS_RESULT_SUCCESS; +} + +void FileMetricsProvider::ScheduleFilesCheck() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (files_to_check_.empty()) + return; + + // Create an independent list of files for checking. This will be Owned() + // by the reply call given to the task-runner, to be deleted when that call + // has returned. It is also passed Unretained() to the task itself, safe + // because that must complete before the reply runs. + FileInfoList* check_list = new FileInfoList(); + std::swap(files_to_check_, *check_list); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner, + base::Unretained(check_list)), + base::Bind(&FileMetricsProvider::RecordFilesChecked, + weak_factory_.GetWeakPtr(), base::Owned(check_list))); +} + +void FileMetricsProvider::RecordHistogramSnapshotsFromFile( + base::HistogramSnapshotManager* snapshot_manager, + FileInfo* file) { + DCHECK(thread_checker_.CalledOnValidThread()); + base::PersistentHistogramAllocator::Iterator histogram_iter( + file->allocator.get()); + + int histogram_count = 0; + while (true) { + scoped_ptr<base::HistogramBase> histogram = histogram_iter.GetNext(); + if (!histogram) + break; + if (file->type == FILE_HISTOGRAMS_ATOMIC) + snapshot_manager->PrepareAbsoluteTakingOwnership(std::move(histogram)); + else + snapshot_manager->PrepareDeltaTakingOwnership(std::move(histogram)); + ++histogram_count; + } + + DVLOG(1) << "Reported " << histogram_count << " histograms from " + << file->path.value(); +} + +void FileMetricsProvider::CreateAllocatorForFile(FileInfo* file) { + DCHECK(!file->allocator); + + // File data was validated earlier. Files are not considered "untrusted" + // as some processes might be (e.g. Renderer) so there's no need to check + // again to try to thwart some malicious actor that may have modified the + // data between then and now. + if (file->mapped) { + DCHECK(file->data.empty()); + // TODO(bcwhite): Make this do read/write when supported for "active". + file->allocator.reset(new base::PersistentHistogramAllocator( + make_scoped_ptr(new base::FilePersistentMemoryAllocator( + std::move(file->mapped), 0, "")))); + } else { + DCHECK(!file->mapped); + file->allocator.reset(new base::PersistentHistogramAllocator( + make_scoped_ptr(new base::PersistentMemoryAllocator( + &file->data[0], file->data.size(), 0, 0, "", true)))); + } +} + +void FileMetricsProvider::RecordFilesChecked(FileInfoList* checked) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Move each processed file to either the "to-read" list (for processing) or + // the "to-check" list (for future checking). + for (auto iter = checked->begin(); iter != checked->end();) { + auto temp = iter++; + const FileInfo* file = temp->get(); + if (file->mapped || !file->data.empty()) + files_to_read_.splice(files_to_read_.end(), *checked, temp); + else + files_to_check_.splice(files_to_check_.end(), *checked, temp); + } +} + +void FileMetricsProvider::RecordFileAsSeen(FileInfo* file) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (pref_service_ && !file->prefs_key.empty()) { + pref_service_->SetInt64(metrics::prefs::kMetricsLastSeenPrefix + + file->prefs_key, + file->last_seen.ToInternalValue()); + } +} + +void FileMetricsProvider::OnDidCreateMetricsLog() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Move finished metric files back to list of monitored files. + for (auto iter = files_to_read_.begin(); iter != files_to_read_.end();) { + auto temp = iter++; + const FileInfo* file = temp->get(); + if (!file->allocator && !file->mapped && file->data.empty()) + files_to_check_.splice(files_to_check_.end(), files_to_read_, temp); + } + + // Schedule a check to see if there are new metrics to load. If so, they + // will be reported during the next collection run after this one. The + // check is run off of the worker-pool so as to not cause delays on the + // main UI thread (which is currently where metric collection is done). + ScheduleFilesCheck(); +} + +void FileMetricsProvider::RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { + DCHECK(thread_checker_.CalledOnValidThread()); + + for (scoped_ptr<FileInfo>& file : files_to_read_) { + // If the file is mapped or loaded then it needs to have an allocator + // attached to it in order to read histograms out of it. + if (file->mapped || !file->data.empty()) + CreateAllocatorForFile(file.get()); + + // A file should not be under "files to read" unless it has an allocator + // or is memory-mapped (at which point it will have received an allocator + // above). However, if this method gets called twice before the scheduled- + // files-check has a chance to clean up, this may trigger. This also + // catches the case where creating an allocator from the file has failed. + if (!file->allocator) + continue; + + // Dump all histograms contained within the file to the snapshot-manager. + RecordHistogramSnapshotsFromFile(snapshot_manager, file.get()); + + // Atomic files are read once and then ignored unless they change. + if (file->type == FILE_HISTOGRAMS_ATOMIC) { + DCHECK(!file->mapped); + file->allocator.reset(); + file->data.clear(); + RecordFileAsSeen(file.get()); + } + } +} + +} // namespace metrics |