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