314 lines
12 KiB
C++
314 lines
12 KiB
C++
// Copyright 2018 The Chromium Authors
|
|
// 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/files/file_enumerator.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback_helpers.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 {
|
|
// 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
|
|
};
|
|
|
|
base::FilePath GetSpareFilePath(const base::FilePath& metrics_dir) {
|
|
return base::GlobalHistogramAllocator::ConstructFilePath(
|
|
metrics_dir, kBrowserMetricsName + std::string("-spare"));
|
|
}
|
|
|
|
// 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 = GetSpareFilePath(metrics_dir);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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): 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 LocalMemory 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);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
BASE_FEATURE(
|
|
kPersistentHistogramsFeature,
|
|
"PersistentHistograms",
|
|
#if BUILDFLAG(IS_FUCHSIA)
|
|
// TODO(crbug.com/1295119): Enable once writable mmap() is supported. Also
|
|
// move the initialization earlier to chrome/app/chrome_main_delegate.cc.
|
|
base::FEATURE_DISABLED_BY_DEFAULT
|
|
#else
|
|
base::FEATURE_ENABLED_BY_DEFAULT
|
|
#endif // BUILDFLAG(IS_FUCHSIA)
|
|
);
|
|
|
|
const char kPersistentHistogramStorageMappedFile[] = "MappedFile";
|
|
const char kPersistentHistogramStorageLocalMemory[] = "LocalMemory";
|
|
|
|
const base::FeatureParam<std::string> kPersistentHistogramsStorage{
|
|
&kPersistentHistogramsFeature, "storage",
|
|
kPersistentHistogramStorageMappedFile};
|
|
|
|
const char kBrowserMetricsName[] = "BrowserMetrics";
|
|
const char kDeferredBrowserMetricsName[] = "DeferredBrowserMetrics";
|
|
|
|
void InstantiatePersistentHistograms(const base::FilePath& metrics_dir,
|
|
bool persistent_histograms_enabled,
|
|
base::StringPiece storage) {
|
|
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 (persistent_histograms_enabled) {
|
|
if (storage == kPersistentHistogramStorageMappedFile) {
|
|
mode = kMappedFile;
|
|
} else if (storage == kPersistentHistogramStorageLocalMemory) {
|
|
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);
|
|
}
|
|
|
|
void PersistentHistogramsCleanup(const base::FilePath& metrics_dir) {
|
|
base::FilePath spare_file = GetSpareFilePath(metrics_dir);
|
|
|
|
// 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));
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
base::ThreadPool::PostDelayedTask(
|
|
FROM_HERE,
|
|
{base::MayBlock(), base::TaskPriority::LOWEST,
|
|
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
|
|
base::BindOnce(&DeleteOldWindowsTempFiles, metrics_dir),
|
|
kDeleteOldWindowsTempFilesDelay);
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
}
|
|
|
|
void InstantiatePersistentHistogramsWithFeaturesAndCleanup(
|
|
const base::FilePath& metrics_dir) {
|
|
InstantiatePersistentHistograms(
|
|
metrics_dir, base::FeatureList::IsEnabled(kPersistentHistogramsFeature),
|
|
kPersistentHistogramsStorage.Get());
|
|
PersistentHistogramsCleanup(metrics_dir);
|
|
}
|
|
|
|
bool DeferBrowserMetrics(const base::FilePath& metrics_dir) {
|
|
base::GlobalHistogramAllocator* allocator =
|
|
base::GlobalHistogramAllocator::Get();
|
|
|
|
if (!allocator || !allocator->HasPersistentLocation()) {
|
|
return false;
|
|
}
|
|
|
|
base::FilePath deferred_metrics_dir =
|
|
metrics_dir.AppendASCII(kDeferredBrowserMetricsName);
|
|
|
|
if (!base::CreateDirectory(deferred_metrics_dir)) {
|
|
return false;
|
|
}
|
|
|
|
return allocator->MovePersistentFile(deferred_metrics_dir);
|
|
}
|