xref: /aosp_15_r20/external/cronet/components/metrics/persistent_histograms.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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