summaryrefslogtreecommitdiff
path: root/chromium/components/metrics/file_metrics_provider.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/metrics/file_metrics_provider.cc')
-rw-r--r--chromium/components/metrics/file_metrics_provider.cc291
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