blob: e77f48b06ee5e855d1a050c18970cd1daf607c38 [file] [log] [blame]
// 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;
// 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;
#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
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,
for (base::FilePath path = file_iter.Next(); !path.empty();
path = file_iter.Next()) {
if (base::ToUpperASCII(path.FinalExtension()) !=
.find(FILE_PATH_LITERAL(".PMA~RF")) < 0) {
const auto& info = file_iter.GetInfo();
if (info.IsDirectory())
if (info.GetLastModifiedTime() > one_day_ago)
// 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 =
#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);
// 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;
metrics_dir, upload_dir, kBrowserMetricsName, &upload_file, &active_file,
// This is used to report results to an UMA histogram.
enum InitResult {
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 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.
if (storage.empty() || storage == kMappedFile) {
int major, minor, bugfix;
base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
if (major == 4 && minor == 4 && bugfix == 0)
storage = kLocalMemory;
// 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.
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::MayBlock(), base::TaskPriority::LOWEST,
std::move(spare_file), kAllocSize),
} 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.
// 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 =
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,
allocator = base::GlobalHistogramAllocator::Get();
if (!allocator)
// Store a copy of the system profile in this allocator.
// Create tracking histograms for the allocator and record storage file.
#if defined(OS_WIN)
{base::MayBlock(), base::TaskPriority::LOWEST,
base::BindOnce(&DeleteOldWindowsTempFiles, std::move(metrics_dir)),
#endif // defined(OS_WIN)