1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/metrics/persistent_histograms.h"
6
7 #include "base/files/file_enumerator.h"
8 #include "base/files/file_util.h"
9 #include "base/functional/bind.h"
10 #include "base/functional/callback_helpers.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/metrics/field_trial_params.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/metrics/persistent_histogram_allocator.h"
15 #include "base/strings/string_util.h"
16 #include "base/system/sys_info.h"
17 #include "base/task/thread_pool.h"
18 #include "base/time/time.h"
19 #include "build/build_config.h"
20 #include "build/chromeos_buildflags.h"
21 #include "components/metrics/persistent_system_profile.h"
22
23 namespace {
24 // Creating a "spare" file for persistent metrics involves a lot of I/O and
25 // isn't important so delay the operation for a while after startup.
26 #if BUILDFLAG(IS_ANDROID)
27 // Android needs the spare file and also launches faster.
28 constexpr bool kSpareFileRequired = true;
29 constexpr int kSpareFileCreateDelaySeconds = 10;
30 #else
31 // Desktop may have to restore a lot of tabs so give it more time before doing
32 // non-essential work. The spare file is still a performance boost but not as
33 // significant of one so it's not required.
34 constexpr bool kSpareFileRequired = false;
35 constexpr int kSpareFileCreateDelaySeconds = 90;
36 #endif
37
38 #if BUILDFLAG(IS_WIN)
39
40 // Windows sometimes creates files of the form MyFile.pma~RF71cb1793.TMP
41 // when trying to rename a file to something that exists but is in-use, and
42 // then fails to remove them. See https://crbug.com/934164
DeleteOldWindowsTempFiles(const base::FilePath & dir)43 void DeleteOldWindowsTempFiles(const base::FilePath& dir) {
44 // Look for any temp files older than one day and remove them. The time check
45 // ensures that nothing in active transition gets deleted; these names only
46 // exists on the order of milliseconds when working properly so "one day" is
47 // generous but still ensures no big build up of these files. This is an
48 // I/O intensive task so do it in the background (enforced by "file" calls).
49 base::Time one_day_ago = base::Time::Now() - base::Days(1);
50 base::FileEnumerator file_iter(dir, /*recursive=*/false,
51 base::FileEnumerator::FILES);
52 for (base::FilePath path = file_iter.Next(); !path.empty();
53 path = file_iter.Next()) {
54 if (base::ToUpperASCII(path.FinalExtension()) !=
55 FILE_PATH_LITERAL(".TMP") ||
56 base::ToUpperASCII(path.BaseName().value())
57 .find(FILE_PATH_LITERAL(".PMA~RF")) < 0) {
58 continue;
59 }
60
61 const auto& info = file_iter.GetInfo();
62 if (info.IsDirectory())
63 continue;
64 if (info.GetLastModifiedTime() > one_day_ago)
65 continue;
66
67 base::DeleteFile(path);
68 }
69 }
70
71 // How much time after startup to run the above function. Two minutes is
72 // enough for the system to stabilize and get the user what they want before
73 // spending time on clean-up efforts.
74 constexpr base::TimeDelta kDeleteOldWindowsTempFilesDelay = base::Minutes(2);
75
76 #endif // BUILDFLAG(IS_WIN)
77
78 // Create persistent/shared memory and allow histograms to be stored in
79 // it. Memory that is not actually used won't be physically mapped by the
80 // system. BrowserMetrics usage, as reported in UMA, has the 99.99
81 // percentile around 3MiB as of 2018-10-22.
82 // Please update ServicificationBackgroundServiceTest.java if the |kAllocSize|
83 // is changed.
84 const size_t kAllocSize = 4 << 20; // 4 MiB
85 const uint32_t kAllocId = 0x935DDD43; // SHA1(BrowserMetrics)
86
87 // Logged to UMA - keep in sync with enums.xml.
88 enum InitResult {
89 kLocalMemorySuccess,
90 kLocalMemoryFailed,
91 kMappedFileSuccess,
92 kMappedFileFailed,
93 kMappedFileExists,
94 kNoSpareFile,
95 kNoUploadDir,
96 kMaxValue = kNoUploadDir
97 };
98
GetSpareFilePath(const base::FilePath & metrics_dir)99 base::FilePath GetSpareFilePath(const base::FilePath& metrics_dir) {
100 return base::GlobalHistogramAllocator::ConstructFilePath(
101 metrics_dir, kBrowserMetricsName + std::string("-spare"));
102 }
103
104 // Initializes persistent histograms with a memory-mapped file.
InitWithMappedFile(const base::FilePath & metrics_dir,const base::FilePath & upload_dir)105 InitResult InitWithMappedFile(const base::FilePath& metrics_dir,
106 const base::FilePath& upload_dir) {
107 // The spare file in the user data dir ("BrowserMetrics-spare.pma") would
108 // have been created in the previous session. We will move it to |upload_dir|
109 // and rename it with the current time and process id for use as |active_file|
110 // (e.g. "BrowserMetrics/BrowserMetrics-1234ABCD-12345.pma").
111 // Any unreported metrics in this file will be uploaded next session.
112 base::FilePath spare_file = GetSpareFilePath(metrics_dir);
113 base::FilePath active_file =
114 base::GlobalHistogramAllocator::ConstructFilePathForUploadDir(
115 upload_dir, kBrowserMetricsName, base::Time::Now(),
116 base::GetCurrentProcId());
117
118 InitResult result;
119 if (!base::PathExists(upload_dir)) {
120 // Handle failure to create the directory.
121 result = kNoUploadDir;
122 } else if (base::PathExists(active_file)) {
123 // "active" filename is supposed to be unique so this shouldn't happen.
124 result = kMappedFileExists;
125 } else {
126 // Disallow multiple writers (Windows only). Needed to ensure multiple
127 // instances of Chrome aren't writing to the same file, which could happen
128 // in some rare circumstances observed in the wild (e.g. on FAT FS where the
129 // file name ends up not being unique due to truncation and two processes
130 // racing on base::PathExists(active_file) above).
131 const bool exclusive_write = true;
132 // Move any spare file into the active position.
133 base::ReplaceFile(spare_file, active_file, nullptr);
134 // Create global allocator using the |active_file|.
135 if (kSpareFileRequired && !base::PathExists(active_file)) {
136 result = kNoSpareFile;
137 } else if (base::GlobalHistogramAllocator::CreateWithFile(
138 active_file, kAllocSize, kAllocId, kBrowserMetricsName,
139 exclusive_write)) {
140 result = kMappedFileSuccess;
141 } else {
142 result = kMappedFileFailed;
143 }
144 }
145
146 return result;
147 }
148
149 enum PersistentHistogramsMode {
150 kNotEnabled,
151 kMappedFile,
152 kLocalMemory,
153 };
154
155 // Implementation of InstantiatePersistentHistograms() that does the work after
156 // the desired |mode| has been determined.
InstantiatePersistentHistogramsImpl(const base::FilePath & metrics_dir,PersistentHistogramsMode mode)157 void InstantiatePersistentHistogramsImpl(const base::FilePath& metrics_dir,
158 PersistentHistogramsMode mode) {
159 // Create a directory for storing completed metrics files. Files in this
160 // directory must have embedded system profiles. If the directory can't be
161 // created, the file will just be deleted below.
162 base::FilePath upload_dir = metrics_dir.AppendASCII(kBrowserMetricsName);
163 // TODO(crbug.com/1183166): Only create the dir in kMappedFile mode.
164 base::CreateDirectory(upload_dir);
165
166 InitResult result;
167
168 // Create a global histogram allocator using the desired storage type.
169 switch (mode) {
170 case kMappedFile:
171 result = InitWithMappedFile(metrics_dir, upload_dir);
172 break;
173 case kLocalMemory:
174 // Use local memory for storage even though it will not persist across
175 // an unclean shutdown. This sets the result but the actual creation is
176 // done below.
177 result = kLocalMemorySuccess;
178 break;
179 case kNotEnabled:
180 // Persistent metric storage is disabled. Must return here.
181 // TODO(crbug.com/1183166): Log the histogram below in this case too.
182 return;
183 }
184
185 // Get the allocator that was just created and report result. Exit if the
186 // allocator could not be created.
187 base::UmaHistogramEnumeration("UMA.PersistentHistograms.InitResult", result);
188
189 base::GlobalHistogramAllocator* allocator =
190 base::GlobalHistogramAllocator::Get();
191 if (!allocator) {
192 // If no allocator was created above, try to create a LocalMemory one here.
193 // This avoids repeating the call many times above. In the case where
194 // persistence is disabled, an early return is done above.
195 base::GlobalHistogramAllocator::CreateWithLocalMemory(kAllocSize, kAllocId,
196 kBrowserMetricsName);
197 allocator = base::GlobalHistogramAllocator::Get();
198 if (!allocator) {
199 return;
200 }
201 }
202
203 // Store a copy of the system profile in this allocator.
204 metrics::GlobalPersistentSystemProfile::GetInstance()
205 ->RegisterPersistentAllocator(allocator->memory_allocator());
206
207 // Create tracking histograms for the allocator and record storage file.
208 allocator->CreateTrackingHistograms(kBrowserMetricsName);
209 }
210
211 } // namespace
212
213 BASE_FEATURE(
214 kPersistentHistogramsFeature,
215 "PersistentHistograms",
216 #if BUILDFLAG(IS_FUCHSIA)
217 // TODO(crbug.com/42050425): Enable once writable mmap() is supported. Also
218 // move the initialization earlier to chrome/app/chrome_main_delegate.cc.
219 base::FEATURE_DISABLED_BY_DEFAULT
220 #else
221 base::FEATURE_ENABLED_BY_DEFAULT
222 #endif // BUILDFLAG(IS_FUCHSIA)
223 );
224
225 const char kPersistentHistogramStorageMappedFile[] = "MappedFile";
226 const char kPersistentHistogramStorageLocalMemory[] = "LocalMemory";
227
228 const base::FeatureParam<std::string> kPersistentHistogramsStorage{
229 &kPersistentHistogramsFeature, "storage",
230 kPersistentHistogramStorageMappedFile};
231
232 const char kBrowserMetricsName[] = "BrowserMetrics";
233 const char kDeferredBrowserMetricsName[] = "DeferredBrowserMetrics";
234
InstantiatePersistentHistograms(const base::FilePath & metrics_dir,bool persistent_histograms_enabled,base::StringPiece storage)235 void InstantiatePersistentHistograms(const base::FilePath& metrics_dir,
236 bool persistent_histograms_enabled,
237 base::StringPiece storage) {
238 PersistentHistogramsMode mode = kNotEnabled;
239 // Note: The extra feature check is needed so that we don't use the default
240 // value of the storage param if the feature is disabled.
241 if (persistent_histograms_enabled) {
242 if (storage == kPersistentHistogramStorageMappedFile) {
243 mode = kMappedFile;
244 } else if (storage == kPersistentHistogramStorageLocalMemory) {
245 mode = kLocalMemory;
246 }
247 }
248
249 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
250 // of lacros-chrome is complete.
251 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
252 // Linux kernel 4.4.0.* shows a huge number of SIGBUS crashes with persistent
253 // histograms enabled using a mapped file. Change this to use local memory.
254 // https://bugs.chromium.org/p/chromium/issues/detail?id=753741
255 if (mode == kMappedFile) {
256 int major, minor, bugfix;
257 base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
258 if (major == 4 && minor == 4 && bugfix == 0)
259 mode = kLocalMemory;
260 }
261 #endif
262
263 InstantiatePersistentHistogramsImpl(metrics_dir, mode);
264 }
265
PersistentHistogramsCleanup(const base::FilePath & metrics_dir)266 void PersistentHistogramsCleanup(const base::FilePath& metrics_dir) {
267 base::FilePath spare_file = GetSpareFilePath(metrics_dir);
268
269 // Schedule the creation of a "spare" file for use on the next run.
270 base::ThreadPool::PostDelayedTask(
271 FROM_HERE,
272 {base::MayBlock(), base::TaskPriority::LOWEST,
273 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
274 base::BindOnce(
275 base::IgnoreResult(&base::GlobalHistogramAllocator::CreateSpareFile),
276 std::move(spare_file), kAllocSize),
277 base::Seconds(kSpareFileCreateDelaySeconds));
278
279 #if BUILDFLAG(IS_WIN)
280 // Post a best effort task that will delete files. Unlike SKIP_ON_SHUTDOWN,
281 // which will block on the deletion if the task already started,
282 // CONTINUE_ON_SHUTDOWN will not block shutdown on the task completing. It's
283 // not a *necessity* to delete the files the same session they are "detected".
284 // On shutdown, the deletion will be interrupted.
285 base::ThreadPool::PostDelayedTask(
286 FROM_HERE,
287 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
288 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
289 base::BindOnce(&DeleteOldWindowsTempFiles, metrics_dir),
290 kDeleteOldWindowsTempFilesDelay);
291 #endif // BUILDFLAG(IS_WIN)
292 }
293
InstantiatePersistentHistogramsWithFeaturesAndCleanup(const base::FilePath & metrics_dir)294 void InstantiatePersistentHistogramsWithFeaturesAndCleanup(
295 const base::FilePath& metrics_dir) {
296 InstantiatePersistentHistograms(
297 metrics_dir, base::FeatureList::IsEnabled(kPersistentHistogramsFeature),
298 kPersistentHistogramsStorage.Get());
299 PersistentHistogramsCleanup(metrics_dir);
300 }
301
DeferBrowserMetrics(const base::FilePath & metrics_dir)302 bool DeferBrowserMetrics(const base::FilePath& metrics_dir) {
303 base::GlobalHistogramAllocator* allocator =
304 base::GlobalHistogramAllocator::Get();
305
306 if (!allocator || !allocator->HasPersistentLocation()) {
307 return false;
308 }
309
310 base::FilePath deferred_metrics_dir =
311 metrics_dir.AppendASCII(kDeferredBrowserMetricsName);
312
313 if (!base::CreateDirectory(deferred_metrics_dir)) {
314 return false;
315 }
316
317 return allocator->MovePersistentFile(deferred_metrics_dir);
318 }
319