1 // Copyright 2018 The Chromium Authors. All rights reserved.
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/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/files/file_util.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/metrics/histogram_macros.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/post_task.h"
18 #include "base/task/thread_pool.h"
19 #include "base/time/time.h"
20 #include "build/build_config.h"
21 #include "components/metrics/persistent_system_profile.h"
22 #include "components/variations/variations_associated_data.h"
23 
24 namespace {
25 
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 defined(OS_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 defined(OS_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::TimeDelta::FromDays(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 =
77     base::TimeDelta::FromMinutes(2);
78 
79 #endif  // defined(OS_WIN)
80 
81 }  // namespace
82 
83 const char kBrowserMetricsName[] = "BrowserMetrics";
84 
85 // Check for feature enabling the use of persistent histogram storage and
86 // enable the global allocator if so.
InstantiatePersistentHistograms(const base::FilePath & metrics_dir,bool default_local_memory)87 void InstantiatePersistentHistograms(const base::FilePath& metrics_dir,
88                                      bool default_local_memory) {
89   // Create a directory for storing completed metrics files. Files in this
90   // directory must have embedded system profiles. If the directory can't be
91   // created, the file will just be deleted below.
92   base::FilePath upload_dir = metrics_dir.AppendASCII(kBrowserMetricsName);
93   base::CreateDirectory(upload_dir);
94 
95   // Metrics files are typically created as a |spare_file| in the profile
96   // directory (e.g. "BrowserMetrics-spare.pma") and are then rotated into
97   // a subdirectory as a stamped file for upload when no longer in use.
98   // (e.g. "BrowserMetrics/BrowserMetrics-1234ABCD-12345.pma")
99   base::FilePath upload_file;
100   base::FilePath active_file;
101   base::FilePath spare_file;
102   base::GlobalHistogramAllocator::ConstructFilePathsForUploadDir(
103       metrics_dir, upload_dir, kBrowserMetricsName, &upload_file, &active_file,
104       &spare_file);
105 
106   // This is used to report results to an UMA histogram.
107   enum InitResult {
108     kLocalMemorySuccess,
109     kLocalMemoryFailed,
110     kMappedFileSuccess,
111     kMappedFileFailed,
112     kMappedFileExists,
113     kNoSpareFile,
114     kNoUploadDir,
115     kMaxValue = kNoUploadDir
116   };
117   InitResult result;
118 
119   // Create persistent/shared memory and allow histograms to be stored in
120   // it. Memory that is not actualy used won't be physically mapped by the
121   // system. BrowserMetrics usage, as reported in UMA, has the 99.99
122   // percentile around 3MiB as of 2018-10-22.
123   // Please update ServicificationBackgroundServiceTest.java if the |kAllocSize|
124   // is changed.
125   const size_t kAllocSize = 4 << 20;     // 4 MiB
126   const uint32_t kAllocId = 0x935DDD43;  // SHA1(BrowserMetrics)
127   std::string storage = variations::GetVariationParamValueByFeature(
128       base::kPersistentHistogramsFeature, "storage");
129 
130   static const char kMappedFile[] = "MappedFile";
131   static const char kLocalMemory[] = "LocalMemory";
132 
133 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
134   // Linux kernel 4.4.0.* shows a huge number of SIGBUS crashes with persistent
135   // histograms enabled using a mapped file.  Change this to use local memory.
136   // https://bugs.chromium.org/p/chromium/issues/detail?id=753741
137   if (storage.empty() || storage == kMappedFile) {
138     int major, minor, bugfix;
139     base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
140     if (major == 4 && minor == 4 && bugfix == 0)
141       storage = kLocalMemory;
142   }
143 #endif
144 
145   // Don't use mapped-file memory by default on low-end devices, especially
146   // Android. The extra disk consumption and/or extra disk access could have
147   // a significant performance impact. https://crbug.com/896394
148   if (storage.empty() && base::SysInfo::IsLowEndDevice())
149     storage = kLocalMemory;
150 
151   // Create a global histogram allocator using the desired storage type.
152   if (storage == kMappedFile || (storage.empty() && !default_local_memory)) {
153     if (!base::PathExists(upload_dir)) {
154       // Handle failure to create the directory.
155       result = kNoUploadDir;
156     } else if (base::PathExists(upload_file)) {
157       // "upload" filename is supposed to be unique so this shouldn't happen.
158       result = kMappedFileExists;
159     } else {
160       // Move any sparse file into the upload position.
161       base::ReplaceFile(spare_file, upload_file, nullptr);
162       // Create global allocator using the "upload" file.
163       if (kSpareFileRequired && !base::PathExists(upload_file)) {
164         result = kNoSpareFile;
165       } else if (base::GlobalHistogramAllocator::CreateWithFile(
166                      upload_file, kAllocSize, kAllocId, kBrowserMetricsName)) {
167         result = kMappedFileSuccess;
168       } else {
169         result = kMappedFileFailed;
170       }
171     }
172     // Schedule the creation of a "spare" file for use on the next run.
173     base::ThreadPool::PostDelayedTask(
174         FROM_HERE,
175         {base::MayBlock(), base::TaskPriority::LOWEST,
176          base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
177         base::BindOnce(base::IgnoreResult(
178                            &base::GlobalHistogramAllocator::CreateSpareFile),
179                        std::move(spare_file), kAllocSize),
180         base::TimeDelta::FromSeconds(kSpareFileCreateDelaySeconds));
181   } else if (storage == kLocalMemory ||
182              (default_local_memory && storage.empty())) {
183     // Use local memory for storage even though it will not persist across
184     // an unclean shutdown. This sets the result but the actual creation is
185     // done below.
186     result = kLocalMemorySuccess;
187   } else {
188     // Persistent metric storage is disabled. Must return here.
189     return;
190   }
191 
192   // Get the allocator that was just created and report result. Exit if the
193   // allocator could not be created.
194   UMA_HISTOGRAM_ENUMERATION("UMA.PersistentHistograms.InitResult", result);
195 
196   base::GlobalHistogramAllocator* allocator =
197       base::GlobalHistogramAllocator::Get();
198   if (!allocator) {
199     // If no allocator was created above, try to create a LocalMemomory one
200     // here. This avoids repeating the call many times above. In the case where
201     // persistence is disabled, an early return is done above.
202     base::GlobalHistogramAllocator::CreateWithLocalMemory(kAllocSize, kAllocId,
203                                                           kBrowserMetricsName);
204     allocator = base::GlobalHistogramAllocator::Get();
205     if (!allocator)
206       return;
207   }
208 
209   // Store a copy of the system profile in this allocator.
210   metrics::GlobalPersistentSystemProfile::GetInstance()
211       ->RegisterPersistentAllocator(allocator->memory_allocator());
212 
213   // Create tracking histograms for the allocator and record storage file.
214   allocator->CreateTrackingHistograms(kBrowserMetricsName);
215 
216 #if defined(OS_WIN)
217   base::ThreadPool::PostDelayedTask(
218       FROM_HERE,
219       {base::MayBlock(), base::TaskPriority::LOWEST,
220        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
221       base::BindOnce(&DeleteOldWindowsTempFiles, std::move(metrics_dir)),
222       kDeleteOldWindowsTempFilesDelay);
223 #endif  // defined(OS_WIN)
224 }
225