| // 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/callback_helpers.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/persistent_histogram_allocator.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/metrics/persistent_system_profile.h" |
| |
| namespace { |
| |
| constexpr char kMappedFileStr[] = "MappedFile"; |
| constexpr char kLocalMemoryStr[] = "LocalMemory"; |
| |
| const base::FeatureParam<std::string> kPersistentHistogramsStorage{ |
| &base::kPersistentHistogramsFeature, "storage", kMappedFileStr}; |
| |
| // 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 BUILDFLAG(IS_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 BUILDFLAG(IS_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::Days(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); |
| } |
| } |
| |
| // 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::Minutes(2); |
| |
| #endif // BUILDFLAG(IS_WIN) |
| |
| // Create persistent/shared memory and allow histograms to be stored in |
| // it. Memory that is not actually 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) |
| |
| // Logged to UMA - keep in sync with enums.xml. |
| enum InitResult { |
| kLocalMemorySuccess, |
| kLocalMemoryFailed, |
| kMappedFileSuccess, |
| kMappedFileFailed, |
| kMappedFileExists, |
| kNoSpareFile, |
| kNoUploadDir, |
| kMaxValue = kNoUploadDir |
| }; |
| |
| // Initializes persistent histograms with a memory-mapped file. |
| InitResult InitWithMappedFile(const base::FilePath& metrics_dir, |
| const base::FilePath& upload_dir) { |
| // The spare file in the user data dir ("BrowserMetrics-spare.pma") would |
| // have been created in the previous session. We will move it to |upload_dir| |
| // and rename it with the current time and process id for use as |active_file| |
| // (e.g. "BrowserMetrics/BrowserMetrics-1234ABCD-12345.pma"). |
| // Any unreported metrics in this file will be uploaded next session. |
| base::FilePath spare_file = base::GlobalHistogramAllocator::ConstructFilePath( |
| metrics_dir, kBrowserMetricsName + std::string("-spare")); |
| base::FilePath active_file = |
| base::GlobalHistogramAllocator::ConstructFilePathForUploadDir( |
| upload_dir, kBrowserMetricsName, base::Time::Now(), |
| base::GetCurrentProcId()); |
| |
| InitResult result; |
| if (!base::PathExists(upload_dir)) { |
| // Handle failure to create the directory. |
| result = kNoUploadDir; |
| } else if (base::PathExists(active_file)) { |
| // "active" filename is supposed to be unique so this shouldn't happen. |
| result = kMappedFileExists; |
| } else { |
| // Disallow multiple writers (Windows only). Needed to ensure multiple |
| // instances of Chrome aren't writing to the same file, which could happen |
| // in some rare circumstances observed in the wild (e.g. on FAT FS where the |
| // file name ends up not being unique due to truncation and two processes |
| // racing on base::PathExists(active_file) above). |
| const bool exclusive_write = true; |
| // Move any spare file into the active position. |
| base::ReplaceFile(spare_file, active_file, nullptr); |
| // Create global allocator using the |active_file|. |
| if (kSpareFileRequired && !base::PathExists(active_file)) { |
| result = kNoSpareFile; |
| } else if (base::GlobalHistogramAllocator::CreateWithFile( |
| active_file, kAllocSize, kAllocId, kBrowserMetricsName, |
| exclusive_write)) { |
| 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::Seconds(kSpareFileCreateDelaySeconds)); |
| |
| return result; |
| } |
| |
| enum PersistentHistogramsMode { |
| kNotEnabled, |
| kMappedFile, |
| kLocalMemory, |
| }; |
| |
| // Implementation of InstantiatePersistentHistograms() that does the work after |
| // the desired |mode| has been determined. |
| void InstantiatePersistentHistogramsImpl(const base::FilePath& metrics_dir, |
| PersistentHistogramsMode mode) { |
| // 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); |
| // TODO(crbug.com/1183166): Only create the dir in kMappedFile mode. |
| base::CreateDirectory(upload_dir); |
| |
| InitResult result; |
| |
| // Create a global histogram allocator using the desired storage type. |
| switch (mode) { |
| case kMappedFile: |
| result = InitWithMappedFile(metrics_dir, upload_dir); |
| break; |
| case 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; |
| break; |
| case kNotEnabled: |
| // Persistent metric storage is disabled. Must return here. |
| // TODO(crbug.com/1183166): Run DeleteOldWindowsTempFiles() here too. |
| // TODO(crbug.com/1183166): Log the histogram below in this case too. |
| return; |
| } |
| |
| // Get the allocator that was just created and report result. Exit if the |
| // allocator could not be created. |
| base::UmaHistogramEnumeration("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) { |
| // TODO(crbug.com/1183166): Run DeleteOldWindowsTempFiles() here too. |
| 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 BUILDFLAG(IS_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 // BUILDFLAG(IS_WIN) |
| } |
| |
| } // namespace |
| |
| const char kBrowserMetricsName[] = "BrowserMetrics"; |
| |
| void InstantiatePersistentHistograms(const base::FilePath& metrics_dir) { |
| PersistentHistogramsMode mode = kNotEnabled; |
| // Note: The extra feature check is needed so that we don't use the default |
| // value of the storage param if the feature is disabled. |
| if (base::FeatureList::IsEnabled(base::kPersistentHistogramsFeature)) { |
| const std::string storage = kPersistentHistogramsStorage.Get(); |
| if (storage == kMappedFileStr) { |
| mode = kMappedFile; |
| } else if (storage == kLocalMemoryStr) { |
| mode = kLocalMemory; |
| } |
| } |
| |
| // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch |
| // of lacros-chrome is complete. |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) |
| // 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 (mode == kMappedFile) { |
| int major, minor, bugfix; |
| base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); |
| if (major == 4 && minor == 4 && bugfix == 0) |
| mode = kLocalMemory; |
| } |
| #endif |
| |
| InstantiatePersistentHistogramsImpl(metrics_dir, mode); |
| } |