1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
// 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)
// Post a best effort task that will delete files. Unlike SKIP_ON_SHUTDOWN,
// which will block on the deletion if the task already started,
// CONTINUE_ON_SHUTDOWN will not block shutdown on the task completing. It's
// not a *necessity* to delete the files the same session they are "detected".
// On shutdown, the deletion will be interrupted.
base::ThreadPool::PostDelayedTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_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);
}
|