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