// 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 #include "base/bind.h" #include "base/command_line.h" #include "base/containers/flat_map.h" #include "base/feature_list.h" #include "base/files/file.h" #include "base/files/file_enumerator.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_functions.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/persistent_histogram_allocator.h" #include "base/metrics/persistent_memory_allocator.h" #include "base/metrics/ranges_manager.h" #include "base/strings/string_piece.h" #include "base/task/task_runner.h" #include "base/task/task_runner_util.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "components/metrics/metrics_pref_names.h" #include "components/metrics/metrics_service.h" #include "components/metrics/persistent_histograms.h" #include "components/metrics/persistent_system_profile.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" namespace metrics { namespace { // These structures provide values used to define how files are opened and // accessed. It obviates the need for multiple code-paths within several of // the methods. struct SourceOptions { // The flags to be used to open a file on disk. int file_open_flags; // The access mode to be used when mapping a file into memory. base::MemoryMappedFile::Access memory_mapped_access; // Indicates if the file is to be accessed read-only. bool is_read_only; }; // Opening a file typically requires at least these flags. constexpr int STD_OPEN = base::File::FLAG_OPEN | base::File::FLAG_READ; constexpr SourceOptions kSourceOptions[] = { // SOURCE_HISTOGRAMS_ATOMIC_FILE { // Ensure that no other process reads this at the same time. STD_OPEN | base::File::FLAG_WIN_EXCLUSIVE_READ, base::MemoryMappedFile::READ_ONLY, true, }, // SOURCE_HISTOGRAMS_ATOMIC_DIR { // Ensure that no other process reads this at the same time. STD_OPEN | base::File::FLAG_WIN_EXCLUSIVE_READ, base::MemoryMappedFile::READ_ONLY, true, }, // SOURCE_HISTOGRAMS_ACTIVE_FILE { // Allow writing to the file. This is needed so we can keep track of // deltas that have been uploaded (by modifying the file), while the // file may still be open by an external process (e.g. Crashpad). STD_OPEN | base::File::FLAG_WRITE, base::MemoryMappedFile::READ_WRITE, false, }, }; void DeleteFileWhenPossible(const base::FilePath& path) { // Open (with delete) and then immediately close the file by going out of // scope. This is the only cross-platform safe way to delete a file that may // be open elsewhere, a distinct possibility given the asynchronous nature // of the delete task. base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_DELETE_ON_CLOSE); } // A task runner to use for testing. base::TaskRunner* g_task_runner_for_testing = nullptr; // Returns a task runner appropriate for running background tasks that perform // file I/O. scoped_refptr CreateBackgroundTaskRunner() { if (g_task_runner_for_testing) return scoped_refptr(g_task_runner_for_testing); return base::ThreadPool::CreateTaskRunner( {base::MayBlock(), base::TaskPriority::BEST_EFFORT, base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); } } // namespace // This structure stores all the information about the sources being monitored // and their current reporting state. struct FileMetricsProvider::SourceInfo { SourceInfo(const Params& params) : type(params.type), association(params.association), prefs_key(params.prefs_key), filter(params.filter), max_age(params.max_age), max_dir_kib(params.max_dir_kib), max_dir_files(params.max_dir_files) { switch (type) { case SOURCE_HISTOGRAMS_ACTIVE_FILE: DCHECK(prefs_key.empty()); [[fallthrough]]; case SOURCE_HISTOGRAMS_ATOMIC_FILE: path = params.path; break; case SOURCE_HISTOGRAMS_ATOMIC_DIR: directory = params.path; break; } } SourceInfo(const SourceInfo&) = delete; SourceInfo& operator=(const SourceInfo&) = delete; ~SourceInfo() {} struct FoundFile { base::FilePath path; base::FileEnumerator::FileInfo info; }; using FoundFiles = base::flat_map; // How to access this source (file/dir, atomic/active). const SourceType type; // With what run this source is associated. const SourceAssociation association; // Where on disk the directory is located. This will only be populated when // a directory is being monitored. base::FilePath directory; // The files found in the above directory, ordered by last-modified. std::unique_ptr found_files; // Where on disk the file is located. If a directory is being monitored, // this will be updated for whatever file is being read. base::FilePath path; // Name used inside prefs to persistent metadata. std::string prefs_key; // The filter callback for determining what to do with found files. FilterCallback filter; // The maximum allowed age of a file. base::TimeDelta max_age; // The maximum allowed bytes in a directory. size_t max_dir_kib; // The maximum allowed files in a directory. size_t max_dir_files; // The last-seen time of this source to detect change. base::Time last_seen; // Indicates if the data has been read out or not. bool read_complete = false; // Once a file has been recognized as needing to be read, it is mapped // into memory and assigned to an |allocator| object. std::unique_ptr allocator; }; FileMetricsProvider::Params::Params(const base::FilePath& path, SourceType type, SourceAssociation association, base::StringPiece prefs_key) : path(path), type(type), association(association), prefs_key(prefs_key) {} FileMetricsProvider::Params::~Params() {} FileMetricsProvider::FileMetricsProvider(PrefService* local_state) : task_runner_(CreateBackgroundTaskRunner()), pref_service_(local_state), main_task_runner_(base::ThreadTaskRunnerHandle::Get()) { DCHECK(main_task_runner_); base::StatisticsRecorder::RegisterHistogramProvider( weak_factory_.GetWeakPtr()); } FileMetricsProvider::~FileMetricsProvider() {} void FileMetricsProvider::RegisterSource(const Params& params) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Ensure that kSourceOptions has been filled for this type. DCHECK_GT(std::size(kSourceOptions), static_cast(params.type)); std::unique_ptr source(new SourceInfo(params)); // |prefs_key| may be empty if the caller does not wish to persist the // state across instances of the program. if (pref_service_ && !params.prefs_key.empty()) { source->last_seen = pref_service_->GetTime( metrics::prefs::kMetricsLastSeenPrefix + source->prefs_key); } switch (params.association) { case ASSOCIATE_CURRENT_RUN: case ASSOCIATE_INTERNAL_PROFILE: case ASSOCIATE_INTERNAL_PROFILE_SAMPLES_COUNTER: sources_to_check_.push_back(std::move(source)); break; case ASSOCIATE_PREVIOUS_RUN: case ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN: DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_FILE, source->type); sources_for_previous_run_.push_back(std::move(source)); break; } } // static void FileMetricsProvider::RegisterSourcePrefs( PrefRegistrySimple* prefs, const base::StringPiece prefs_key) { prefs->RegisterInt64Pref( metrics::prefs::kMetricsLastSeenPrefix + std::string(prefs_key), 0); } // static void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs) { prefs->RegisterListPref(metrics::prefs::kMetricsFileMetricsMetadata); } // static void FileMetricsProvider::SetTaskRunnerForTesting( const scoped_refptr& task_runner) { DCHECK(!g_task_runner_for_testing || !task_runner); g_task_runner_for_testing = task_runner.get(); } // static void FileMetricsProvider::RecordAccessResult(AccessResult result) { UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX); } // static bool FileMetricsProvider::LocateNextFileInDirectory(SourceInfo* source) { DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_DIR, source->type); DCHECK(!source->directory.empty()); // Cumulative directory stats. These will remain zero if the directory isn't // scanned but that's okay since any work they would cause to be done below // would have been done during the first call where the directory was fully // scanned. size_t total_size_kib = 0; // Using KiB allows 4TiB even on 32-bit builds. size_t file_count = 0; base::Time now_time = base::Time::Now(); if (!source->found_files) { source->found_files = std::make_unique(); base::FileEnumerator file_iter(source->directory, /*recursive=*/false, base::FileEnumerator::FILES); SourceInfo::FoundFile found_file; // Open the directory and find all the files, remembering the last-modified // time of each. for (found_file.path = file_iter.Next(); !found_file.path.empty(); found_file.path = file_iter.Next()) { found_file.info = file_iter.GetInfo(); // Ignore directories. if (found_file.info.IsDirectory()) continue; // Ignore temporary files. base::FilePath::CharType first_character = found_file.path.BaseName().value().front(); if (first_character == FILE_PATH_LITERAL('.') || first_character == FILE_PATH_LITERAL('_')) { continue; } // Ignore non-PMA (Persistent Memory Allocator) files. if (found_file.path.Extension() != base::PersistentMemoryAllocator::kFileExtension) { continue; } // Process real files. total_size_kib += found_file.info.GetSize() >> 10; base::Time modified = found_file.info.GetLastModifiedTime(); if (modified > source->last_seen) { // This file hasn't been read. Remember it (unless from the future). if (modified <= now_time) source->found_files->emplace(modified, std::move(found_file)); ++file_count; } else { // This file has been read. Try to delete it. Ignore any errors because // the file may be un-removeable by this process. It could, for example, // have been created by a privileged process like setup.exe. Even if it // is not removed, it will continue to be ignored bacuse of the older // modification time. base::DeleteFile(found_file.path); } } } // Filter files from the front until one is found for processing. bool have_file = false; while (!source->found_files->empty()) { SourceInfo::FoundFile found = std::move(source->found_files->begin()->second); source->found_files->erase(source->found_files->begin()); bool too_many = source->max_dir_files > 0 && file_count > source->max_dir_files; bool too_big = source->max_dir_kib > 0 && total_size_kib > source->max_dir_kib; bool too_old = source->max_age != base::TimeDelta() && now_time - found.info.GetLastModifiedTime() > source->max_age; if (too_many || too_big || too_old) { base::DeleteFile(found.path); --file_count; total_size_kib -= found.info.GetSize() >> 10; RecordAccessResult(too_many ? ACCESS_RESULT_TOO_MANY_FILES : too_big ? ACCESS_RESULT_TOO_MANY_BYTES : ACCESS_RESULT_TOO_OLD); continue; } AccessResult result = HandleFilterSource(source, found.path); if (result == ACCESS_RESULT_SUCCESS) { source->path = std::move(found.path); have_file = true; break; } // Record the result. Success will be recorded by the caller. if (result != ACCESS_RESULT_THIS_PID) RecordAccessResult(result); } return have_file; } // static void FileMetricsProvider::FinishedWithSource(SourceInfo* source, AccessResult result) { // Different source types require different post-processing. switch (source->type) { case SOURCE_HISTOGRAMS_ATOMIC_FILE: case SOURCE_HISTOGRAMS_ATOMIC_DIR: // Done with this file so delete the allocator and its owned file. source->allocator.reset(); // Remove the file if has been recorded. This prevents them from // accumulating or also being recorded by different instances of // the browser. if (result == ACCESS_RESULT_SUCCESS || result == ACCESS_RESULT_NOT_MODIFIED || result == ACCESS_RESULT_MEMORY_DELETED || result == ACCESS_RESULT_TOO_OLD) { DeleteFileWhenPossible(source->path); } break; case SOURCE_HISTOGRAMS_ACTIVE_FILE: // Keep the allocator open so it doesn't have to be re-mapped each // time. This also allows the contents to be merged on-demand. break; } } void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner( SourceInfoList* sources) { // This method has all state information passed in |sources| and is intended // to run on a worker thread rather than the UI thread. for (std::unique_ptr& source : *sources) { AccessResult result; do { result = CheckAndMapMetricSource(source.get()); // Some results are not reported in order to keep the dashboard clean. if (result != ACCESS_RESULT_DOESNT_EXIST && result != ACCESS_RESULT_NOT_MODIFIED && result != ACCESS_RESULT_THIS_PID) { RecordAccessResult(result); } // If there are no files (or no more files) in this source, stop now. if (result == ACCESS_RESULT_DOESNT_EXIST) break; // Mapping was successful. Merge it. if (result == ACCESS_RESULT_SUCCESS) { // Metrics associated with internal profiles have to be fetched directly // so just keep the mapping for use by the main thread. if (source->association == ASSOCIATE_INTERNAL_PROFILE) break; if (source->association == ASSOCIATE_INTERNAL_PROFILE_SAMPLES_COUNTER) { RecordFileMetadataOnTaskRunner(source.get()); } else { MergeHistogramDeltasFromSource(source.get()); } DCHECK(source->read_complete); } // All done with this source. FinishedWithSource(source.get(), result); // If it's a directory, keep trying until a file is successfully opened. // When there are no more files, ACCESS_RESULT_DOESNT_EXIST will be // returned and the loop will exit above. } while (result != ACCESS_RESULT_SUCCESS && !source->directory.empty()); // If the set of known files is empty, clear the object so the next run // will do a fresh scan of the directory. if (source->found_files && source->found_files->empty()) source->found_files.reset(); } } // This method has all state information passed in |source| and is intended // to run on a worker thread rather than the UI thread. // static FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapMetricSource( SourceInfo* source) { // If source was read, clean up after it. if (source->read_complete) FinishedWithSource(source, ACCESS_RESULT_SUCCESS); source->read_complete = false; DCHECK(!source->allocator); // If the source is a directory, look for files within it. if (!source->directory.empty() && !LocateNextFileInDirectory(source)) return ACCESS_RESULT_DOESNT_EXIST; // Do basic validation on the file metadata. base::File::Info info; if (!base::GetFileInfo(source->path, &info)) return ACCESS_RESULT_DOESNT_EXIST; if (info.is_directory || info.size == 0) return ACCESS_RESULT_INVALID_FILE; if (source->last_seen >= info.last_modified) return ACCESS_RESULT_NOT_MODIFIED; if (source->max_age != base::TimeDelta() && base::Time::Now() - info.last_modified > source->max_age) { return ACCESS_RESULT_TOO_OLD; } // Non-directory files still need to be filtered. if (source->directory.empty()) { AccessResult result = HandleFilterSource(source, source->path); if (result != ACCESS_RESULT_SUCCESS) return result; } // A new file of metrics has been found. base::File file(source->path, kSourceOptions[source->type].file_open_flags); if (!file.IsValid()) return ACCESS_RESULT_NO_OPEN; // Check that file is writable if that is expected. If a write is attempted // on an unwritable memory-mapped file, a SIGBUS will cause a crash. const bool read_only = kSourceOptions[source->type].is_read_only; if (!read_only) { constexpr int kTestSize = 16; char header[kTestSize]; int amount = file.Read(0, header, kTestSize); if (amount != kTestSize) return ACCESS_RESULT_INVALID_CONTENTS; char zeros[kTestSize] = {0}; file.Write(0, zeros, kTestSize); file.Flush(); // A crash here would be unfortunate as the file would be left invalid // and skipped/deleted by later attempts. This is unlikely, however, and // the benefit of avoiding crashes from mapping as read/write a file that // can't be written more than justifies the risk. char check[kTestSize]; amount = file.Read(0, check, kTestSize); if (amount != kTestSize) return ACCESS_RESULT_INVALID_CONTENTS; if (memcmp(check, zeros, kTestSize) != 0) return ACCESS_RESULT_NOT_WRITABLE; file.Write(0, header, kTestSize); file.Flush(); amount = file.Read(0, check, kTestSize); if (amount != kTestSize) return ACCESS_RESULT_INVALID_CONTENTS; if (memcmp(check, header, kTestSize) != 0) return ACCESS_RESULT_NOT_WRITABLE; } std::unique_ptr mapped(new base::MemoryMappedFile()); if (!mapped->Initialize(std::move(file), kSourceOptions[source->type].memory_mapped_access)) { return ACCESS_RESULT_SYSTEM_MAP_FAILURE; } // Ensure any problems below don't occur repeatedly. source->last_seen = info.last_modified; // Test the validity of the file contents. if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*mapped, read_only)) { return ACCESS_RESULT_INVALID_CONTENTS; } // Map the file and validate it. std::unique_ptr memory_allocator = std::make_unique( std::move(mapped), 0, 0, base::StringPiece(), read_only); if (memory_allocator->GetMemoryState() == base::PersistentMemoryAllocator::MEMORY_DELETED) { return ACCESS_RESULT_MEMORY_DELETED; } if (memory_allocator->IsCorrupt()) return ACCESS_RESULT_DATA_CORRUPTION; // Cache the file data while running in a background thread so that there // shouldn't be any I/O when the data is accessed from the main thread. // Files with an internal profile, those from previous runs that include // a full system profile and are fetched via ProvideIndependentMetrics(), // are loaded on a background task and so there's no need to cache the // data in advance. if (source->association != ASSOCIATE_INTERNAL_PROFILE) memory_allocator->Cache(); // Create an allocator for the mapped file. Ownership passes to the allocator. source->allocator = std::make_unique( std::move(memory_allocator)); // Check that an "independent" file has the necessary information present. if (source->association == ASSOCIATE_INTERNAL_PROFILE && !PersistentSystemProfile::GetSystemProfile( *source->allocator->memory_allocator(), nullptr)) { return ACCESS_RESULT_NO_PROFILE; } return ACCESS_RESULT_SUCCESS; } // static void FileMetricsProvider::MergeHistogramDeltasFromSource(SourceInfo* source) { DCHECK(source->allocator); base::PersistentHistogramAllocator::Iterator histogram_iter( source->allocator.get()); const bool read_only = kSourceOptions[source->type].is_read_only; int histogram_count = 0; while (true) { std::unique_ptr histogram = histogram_iter.GetNext(); if (!histogram) break; if (read_only) { source->allocator->MergeHistogramFinalDeltaToStatisticsRecorder( histogram.get()); } else { source->allocator->MergeHistogramDeltaToStatisticsRecorder( histogram.get()); } ++histogram_count; } source->read_complete = true; DVLOG(1) << "Reported " << histogram_count << " histograms from " << source->path.value(); } // static void FileMetricsProvider::RecordHistogramSnapshotsFromSource( base::HistogramSnapshotManager* snapshot_manager, SourceInfo* source) { DCHECK_NE(SOURCE_HISTOGRAMS_ACTIVE_FILE, source->type); base::PersistentHistogramAllocator::Iterator histogram_iter( source->allocator.get()); int histogram_count = 0; while (true) { std::unique_ptr histogram = histogram_iter.GetNext(); if (!histogram) break; snapshot_manager->PrepareFinalDelta(histogram.get()); ++histogram_count; } source->read_complete = true; DVLOG(1) << "Reported " << histogram_count << " histograms from " << source->path.value(); } FileMetricsProvider::AccessResult FileMetricsProvider::HandleFilterSource( SourceInfo* source, const base::FilePath& path) { if (!source->filter) return ACCESS_RESULT_SUCCESS; // Alternatively, pass a Params object to the filter like what was originally // used to configure the source. // Params params(path, source->type, source->association, source->prefs_key); FilterAction action = source->filter.Run(path); switch (action) { case FILTER_PROCESS_FILE: // Process the file. return ACCESS_RESULT_SUCCESS; case FILTER_ACTIVE_THIS_PID: // Even the file for the current process has to be touched or its stamp // will be less than "last processed" and thus skipped on future runs, // even those done by new instances of the browser if a pref key is // provided so that the last-uploaded stamp is recorded. case FILTER_TRY_LATER: { // Touch the file with the current timestamp making it (presumably) the // newest file in the directory. base::Time now = base::Time::Now(); base::TouchFile(path, /*accessed=*/now, /*modified=*/now); if (action == FILTER_ACTIVE_THIS_PID) return ACCESS_RESULT_THIS_PID; return ACCESS_RESULT_FILTER_TRY_LATER; } case FILTER_SKIP_FILE: switch (source->type) { case SOURCE_HISTOGRAMS_ATOMIC_FILE: case SOURCE_HISTOGRAMS_ATOMIC_DIR: // Only "atomic" files are deleted (best-effort). DeleteFileWhenPossible(path); break; case SOURCE_HISTOGRAMS_ACTIVE_FILE: // File will presumably get modified elsewhere and thus tried again. break; } return ACCESS_RESULT_FILTER_SKIP_FILE; } // Code never gets here but some compilers don't realize that and so complain // that "not all control paths return a value". NOTREACHED(); return ACCESS_RESULT_SUCCESS; } /* static */ bool FileMetricsProvider::ProvideIndependentMetricsOnTaskRunner( SourceInfo* source, SystemProfileProto* system_profile_proto, base::HistogramSnapshotManager* snapshot_manager) { if (PersistentSystemProfile::GetSystemProfile( *source->allocator->memory_allocator(), system_profile_proto)) { // Pass a custom RangesManager so that we do not register the BucketRanges // with the global statistics recorder. Otherwise, it could add unnecessary // contention, and a low amount of extra memory that will never be released. source->allocator->SetRangesManager(new base::RangesManager()); system_profile_proto->mutable_stability()->set_from_previous_run(true); RecordHistogramSnapshotsFromSource(snapshot_manager, source); return true; } return false; } void FileMetricsProvider::AppendToSamplesCountPref(size_t samples_count) { ListPrefUpdate update(pref_service_, metrics::prefs::kMetricsFileMetricsMetadata); update->GetList().Append(static_cast(samples_count)); } void FileMetricsProvider::RecordFileMetadataOnTaskRunner(SourceInfo* source) { base::HistogramBase::Count samples_count = 0; base::PersistentHistogramAllocator::Iterator it{source->allocator.get()}; std::unique_ptr histogram; while ((histogram = it.GetNext()) != nullptr) { samples_count += histogram->SnapshotFinalDelta()->TotalCount(); } main_task_runner_->PostTask( FROM_HERE, base::BindOnce(&FileMetricsProvider::AppendToSamplesCountPref, base::Unretained(this), samples_count)); source->read_complete = true; } void FileMetricsProvider::ScheduleSourcesCheck() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (sources_to_check_.empty()) return; // Create an independent list of sources 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. SourceInfoList* check_list = new SourceInfoList(); std::swap(sources_to_check_, *check_list); task_runner_->PostTaskAndReply( FROM_HERE, base::BindOnce( &FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner, base::Unretained(this), base::Unretained(check_list)), base::BindOnce(&FileMetricsProvider::RecordSourcesChecked, weak_factory_.GetWeakPtr(), base::Owned(check_list))); } void FileMetricsProvider::RecordSourcesChecked(SourceInfoList* checked) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Sources that still have an allocator at this point are read/write "active" // files that may need their contents merged on-demand. If there is no // allocator (not a read/write file) but a read was done on the task-runner, // try again immediately to see if more is available (in a directory of // files). Otherwise, remember the source for checking again at a later time. bool did_read = false; for (auto iter = checked->begin(); iter != checked->end();) { auto temp = iter++; SourceInfo* source = temp->get(); if (source->read_complete) { RecordSourceAsRead(source); did_read = true; } if (source->allocator) { if (source->association == ASSOCIATE_INTERNAL_PROFILE) { sources_with_profile_.splice(sources_with_profile_.end(), *checked, temp); } else { sources_mapped_.splice(sources_mapped_.end(), *checked, temp); } } else { sources_to_check_.splice(sources_to_check_.end(), *checked, temp); } } // If a read was done, schedule another one immediately. In the case of a // directory of files, this ensures that all entries get processed. It's // done here instead of as a loop in CheckAndMergeMetricSourcesOnTaskRunner // so that (a) it gives the disk a rest and (b) testing of individual reads // is possible. if (did_read) ScheduleSourcesCheck(); } void FileMetricsProvider::DeleteFileAsync(const base::FilePath& path) { task_runner_->PostTask(FROM_HERE, base::BindOnce(DeleteFileWhenPossible, path)); } void FileMetricsProvider::RecordSourceAsRead(SourceInfo* source) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Persistently record the "last seen" timestamp of the source file to // ensure that the file is never read again unless it is modified again. if (pref_service_ && !source->prefs_key.empty()) { pref_service_->SetTime( metrics::prefs::kMetricsLastSeenPrefix + source->prefs_key, source->last_seen); } } void FileMetricsProvider::OnDidCreateMetricsLog() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // 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 a MayBlock() TaskRunner so as to not cause delays on the main UI // thread (which is currently where metric collection is done). ScheduleSourcesCheck(); // Clear any data for initial metrics since they're always reported // before the first call to this method. It couldn't be released after // being reported in RecordInitialHistogramSnapshots because the data // will continue to be used by the caller after that method returns. Once // here, though, all actions to be done on the data have been completed. for (const std::unique_ptr& source : sources_for_previous_run_) DeleteFileAsync(source->path); sources_for_previous_run_.clear(); } bool FileMetricsProvider::HasIndependentMetrics() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return !sources_with_profile_.empty() || SimulateIndependentMetrics(); } void FileMetricsProvider::ProvideIndependentMetrics( base::OnceCallback done_callback, ChromeUserMetricsExtension* uma_proto, base::HistogramSnapshotManager* snapshot_manager) { SystemProfileProto* system_profile_proto = uma_proto->mutable_system_profile(); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (sources_with_profile_.empty()) { std::move(done_callback).Run(false); return; } std::unique_ptr source = std::move(*sources_with_profile_.begin()); sources_with_profile_.pop_front(); SourceInfo* source_ptr = source.get(); DCHECK(source->allocator); // Do the actual work as a background task. base::PostTaskAndReplyWithResult( task_runner_.get(), FROM_HERE, base::BindOnce( &FileMetricsProvider::ProvideIndependentMetricsOnTaskRunner, source_ptr, system_profile_proto, snapshot_manager), base::BindOnce(&FileMetricsProvider::ProvideIndependentMetricsCleanup, weak_factory_.GetWeakPtr(), std::move(done_callback), std::move(source))); } void FileMetricsProvider::ProvideIndependentMetricsCleanup( base::OnceCallback done_callback, std::unique_ptr source, bool success) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Regardless of whether this source was successfully recorded, it is // never read again. source->read_complete = true; RecordSourceAsRead(source.get()); sources_to_check_.push_back(std::move(source)); ScheduleSourcesCheck(); // Execute the chained callback. std::move(done_callback).Run(success); } bool FileMetricsProvider::HasPreviousSessionData() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Check all sources for previous run to see if they need to be read. for (auto iter = sources_for_previous_run_.begin(); iter != sources_for_previous_run_.end();) { auto temp = iter++; SourceInfo* source = temp->get(); // This would normally be done on a background I/O thread but there // hasn't been a chance to run any at the time this method is called. // Do the check in-line. AccessResult result = CheckAndMapMetricSource(source); UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.InitialAccessResult", result, ACCESS_RESULT_MAX); // If it couldn't be accessed, remove it from the list. There is only ever // one chance to record it so no point keeping it around for later. Also // mark it as having been read since uploading it with a future browser // run would associate it with the then-previous run which would no longer // be the run from which it came. if (result != ACCESS_RESULT_SUCCESS) { DCHECK(!source->allocator); RecordSourceAsRead(source); DeleteFileAsync(source->path); sources_for_previous_run_.erase(temp); continue; } DCHECK(source->allocator); // If the source should be associated with an existing internal profile, // move it to |sources_with_profile_| for later upload. if (source->association == ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN) { if (PersistentSystemProfile::HasSystemProfile( *source->allocator->memory_allocator())) { sources_with_profile_.splice(sources_with_profile_.end(), sources_for_previous_run_, temp); } } } return !sources_for_previous_run_.empty(); } void FileMetricsProvider::RecordInitialHistogramSnapshots( base::HistogramSnapshotManager* snapshot_manager) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); for (const std::unique_ptr& source : sources_for_previous_run_) { // The source needs to have an allocator attached to it in order to read // histograms out of it. DCHECK(!source->read_complete); DCHECK(source->allocator); // Dump all histograms contained within the source to the snapshot-manager. RecordHistogramSnapshotsFromSource(snapshot_manager, source.get()); // Update the last-seen time so it isn't read again unless it changes. RecordSourceAsRead(source.get()); } } void FileMetricsProvider::MergeHistogramDeltas() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); for (std::unique_ptr& source : sources_mapped_) { MergeHistogramDeltasFromSource(source.get()); } } bool FileMetricsProvider::SimulateIndependentMetrics() { if (!pref_service_->HasPrefPath( metrics::prefs::kMetricsFileMetricsMetadata)) { return false; } ListPrefUpdate list_pref(pref_service_, metrics::prefs::kMetricsFileMetricsMetadata); base::Value::List& list_value = list_pref->GetList(); if (list_value.empty()) return false; size_t count = pref_service_->GetInteger( metrics::prefs::kStabilityFileMetricsUnsentSamplesCount); pref_service_->SetInteger( metrics::prefs::kStabilityFileMetricsUnsentSamplesCount, list_value[0].GetInt() + count); pref_service_->SetInteger( metrics::prefs::kStabilityFileMetricsUnsentFilesCount, list_value.size() - 1); list_value.erase(list_value.begin()); return true; } } // namespace metrics