// Copyright 2018 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/persistent_histograms.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/persistent_histogram_allocator.h" #include "base/strings/string_util.h" #include "base/system/sys_info.h" #include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "base/time/time.h" #include "build/build_config.h" #include "components/metrics/persistent_system_profile.h" #include "components/variations/variations_associated_data.h" namespace { // Creating a "spare" file for persistent metrics involves a lot of I/O and // isn't important so delay the operation for a while after startup. #if defined(OS_ANDROID) // Android needs the spare file and also launches faster. constexpr bool kSpareFileRequired = true; constexpr int kSpareFileCreateDelaySeconds = 10; #else // Desktop may have to restore a lot of tabs so give it more time before doing // non-essential work. The spare file is still a performance boost but not as // significant of one so it's not required. constexpr bool kSpareFileRequired = false; constexpr int kSpareFileCreateDelaySeconds = 90; #endif #if defined(OS_WIN) // Windows sometimes creates files of the form MyFile.pma~RF71cb1793.TMP // when trying to rename a file to something that exists but is in-use, and // then fails to remove them. See https://crbug.com/934164 void DeleteOldWindowsTempFiles(const base::FilePath& dir) { // Look for any temp files older than one day and remove them. The time check // ensures that nothing in active transition gets deleted; these names only // exists on the order of milliseconds when working properly so "one day" is // generous but still ensures no big build up of these files. This is an // I/O intensive task so do it in the background (enforced by "file" calls). base::Time one_day_ago = base::Time::Now() - base::TimeDelta::FromDays(1); base::FileEnumerator file_iter(dir, /*recursive=*/false, base::FileEnumerator::FILES); for (base::FilePath path = file_iter.Next(); !path.empty(); path = file_iter.Next()) { if (base::ToUpperASCII(path.FinalExtension()) != FILE_PATH_LITERAL(".TMP") || base::ToUpperASCII(path.BaseName().value()) .find(FILE_PATH_LITERAL(".PMA~RF")) < 0) { continue; } const auto& info = file_iter.GetInfo(); if (info.IsDirectory()) continue; if (info.GetLastModifiedTime() > one_day_ago) continue; base::DeleteFile(path, /*recursive=*/false); } } // How much time after startup to run the above function. Two minutes is // enough for the system to stabilize and get the user what they want before // spending time on clean-up efforts. constexpr base::TimeDelta kDeleteOldWindowsTempFilesDelay = base::TimeDelta::FromMinutes(2); #endif // defined(OS_WIN) } // namespace const char kBrowserMetricsName[] = "BrowserMetrics"; // Check for feature enabling the use of persistent histogram storage and // enable the global allocator if so. void InstantiatePersistentHistograms(const base::FilePath& metrics_dir) { // Create a directory for storing completed metrics files. Files in this // directory must have embedded system profiles. If the directory can't be // created, the file will just be deleted below. base::FilePath upload_dir = metrics_dir.AppendASCII(kBrowserMetricsName); base::CreateDirectory(upload_dir); // Metrics files are typically created as a |spare_file| in the profile // directory (e.g. "BrowserMetrics-spare.pma") and are then rotated into // a subdirectory as a stamped file for upload when no longer in use. // (e.g. "BrowserMetrics/BrowserMetrics-1234ABCD-12345.pma") base::FilePath upload_file; base::FilePath active_file; base::FilePath spare_file; base::GlobalHistogramAllocator::ConstructFilePathsForUploadDir( metrics_dir, upload_dir, kBrowserMetricsName, &upload_file, &active_file, &spare_file); // This is used to report results to an UMA histogram. enum InitResult { kLocalMemorySuccess, kLocalMemoryFailed, kMappedFileSuccess, kMappedFileFailed, kMappedFileExists, kNoSpareFile, kNoUploadDir, kMaxValue = kNoUploadDir }; InitResult result; // Create persistent/shared memory and allow histograms to be stored in // it. Memory that is not actualy used won't be physically mapped by the // system. BrowserMetrics usage, as reported in UMA, has the 99.99 // percentile around 3MiB as of 2018-10-22. // Please update ServicificationBackgroundServiceTest.java if the |kAllocSize| // is changed. const size_t kAllocSize = 4 << 20; // 4 MiB const uint32_t kAllocId = 0x935DDD43; // SHA1(BrowserMetrics) std::string storage = variations::GetVariationParamValueByFeature( base::kPersistentHistogramsFeature, "storage"); static const char kMappedFile[] = "MappedFile"; static const char kLocalMemory[] = "LocalMemory"; #if defined(OS_LINUX) && !defined(OS_CHROMEOS) // Linux kernel 4.4.0.* shows a huge number of SIGBUS crashes with persistent // histograms enabled using a mapped file. Change this to use local memory. // https://bugs.chromium.org/p/chromium/issues/detail?id=753741 if (storage.empty() || storage == kMappedFile) { int major, minor, bugfix; base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); if (major == 4 && minor == 4 && bugfix == 0) storage = kLocalMemory; } #endif // Don't use mapped-file memory by default on low-end devices, especially // Android. The extra disk consumption and/or extra disk access could have // a significant performance impact. https://crbug.com/896394 if (storage.empty() && base::SysInfo::IsLowEndDevice()) storage = kLocalMemory; // Create a global histogram allocator using the desired storage type. if (storage.empty() || storage == kMappedFile) { if (!base::PathExists(upload_dir)) { // Handle failure to create the directory. result = kNoUploadDir; } else if (base::PathExists(upload_file)) { // "upload" filename is supposed to be unique so this shouldn't happen. result = kMappedFileExists; } else { // Move any sparse file into the upload position. base::ReplaceFile(spare_file, upload_file, nullptr); // Create global allocator using the "upload" file. if (kSpareFileRequired && !base::PathExists(upload_file)) { result = kNoSpareFile; } else if (base::GlobalHistogramAllocator::CreateWithFile( upload_file, kAllocSize, kAllocId, kBrowserMetricsName)) { result = kMappedFileSuccess; } else { result = kMappedFileFailed; } } // Schedule the creation of a "spare" file for use on the next run. base::ThreadPool::PostDelayedTask( FROM_HERE, {base::MayBlock(), base::TaskPriority::LOWEST, base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, base::BindOnce(base::IgnoreResult( &base::GlobalHistogramAllocator::CreateSpareFile), std::move(spare_file), kAllocSize), base::TimeDelta::FromSeconds(kSpareFileCreateDelaySeconds)); } else if (storage == kLocalMemory) { // Use local memory for storage even though it will not persist across // an unclean shutdown. This sets the result but the actual creation is // done below. result = kLocalMemorySuccess; } else { // Persistent metric storage is disabled. Must return here. return; } // Get the allocator that was just created and report result. Exit if the // allocator could not be created. UMA_HISTOGRAM_ENUMERATION("UMA.PersistentHistograms.InitResult", result); base::GlobalHistogramAllocator* allocator = base::GlobalHistogramAllocator::Get(); if (!allocator) { // If no allocator was created above, try to create a LocalMemomory one // here. This avoids repeating the call many times above. In the case where // persistence is disabled, an early return is done above. base::GlobalHistogramAllocator::CreateWithLocalMemory(kAllocSize, kAllocId, kBrowserMetricsName); allocator = base::GlobalHistogramAllocator::Get(); if (!allocator) return; } // Store a copy of the system profile in this allocator. metrics::GlobalPersistentSystemProfile::GetInstance() ->RegisterPersistentAllocator(allocator->memory_allocator()); // Create tracking histograms for the allocator and record storage file. allocator->CreateTrackingHistograms(kBrowserMetricsName); #if defined(OS_WIN) base::ThreadPool::PostDelayedTask( FROM_HERE, {base::MayBlock(), base::TaskPriority::LOWEST, base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, base::BindOnce(&DeleteOldWindowsTempFiles, std::move(metrics_dir)), kDeleteOldWindowsTempFilesDelay); #endif // defined(OS_WIN) }