| // 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/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::PostDelayedTask( |
| FROM_HERE, |
| {base::ThreadPool(), 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::PostDelayedTask( |
| FROM_HERE, |
| {base::ThreadPool(), base::MayBlock(), base::TaskPriority::LOWEST, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, |
| base::BindOnce(&DeleteOldWindowsTempFiles, std::move(metrics_dir)), |
| kDeleteOldWindowsTempFilesDelay); |
| #endif // defined(OS_WIN) |
| } |