1 // Copyright 2016 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/content/subprocess_metrics_provider.h"
6
7 #include <utility>
8
9 #include "base/debug/leak_annotations.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_refptr.h"
12 #include "base/metrics/histogram_base.h"
13 #include "base/metrics/persistent_histogram_allocator.h"
14 #include "base/metrics/persistent_memory_allocator.h"
15 #include "base/task/task_traits.h"
16 #include "base/task/thread_pool.h"
17 #include "components/metrics/metrics_features.h"
18 #include "content/public/browser/browser_child_process_host.h"
19 #include "content/public/browser/browser_child_process_host_iterator.h"
20 #include "content/public/browser/child_process_data.h"
21
22 namespace metrics {
23 namespace {
24
25 SubprocessMetricsProvider* g_subprocess_metrics_provider = nullptr;
26
CreateTaskRunner()27 scoped_refptr<base::TaskRunner> CreateTaskRunner() {
28 // This task runner must block shutdown to ensure metrics are not lost.
29 return base::ThreadPool::CreateTaskRunner(
30 {base::TaskPriority::BEST_EFFORT,
31 base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
32 }
33
34 } // namespace
35
36 // static
CreateInstance()37 bool SubprocessMetricsProvider::CreateInstance() {
38 if (g_subprocess_metrics_provider) {
39 return false;
40 }
41 g_subprocess_metrics_provider = new SubprocessMetricsProvider();
42 ANNOTATE_LEAKING_OBJECT_PTR(g_subprocess_metrics_provider);
43 return true;
44 }
45
46 // static
GetInstance()47 SubprocessMetricsProvider* SubprocessMetricsProvider::GetInstance() {
48 return g_subprocess_metrics_provider;
49 }
50
51 // static
MergeHistogramDeltasForTesting(bool async,base::OnceClosure done_callback)52 void SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
53 bool async,
54 base::OnceClosure done_callback) {
55 GetInstance()->MergeHistogramDeltas(async, std::move(done_callback));
56 }
57
RefCountedAllocator(std::unique_ptr<base::PersistentHistogramAllocator> allocator)58 SubprocessMetricsProvider::RefCountedAllocator::RefCountedAllocator(
59 std::unique_ptr<base::PersistentHistogramAllocator> allocator)
60 : allocator_(std::move(allocator)) {
61 CHECK(allocator_);
62 }
63
64 SubprocessMetricsProvider::RefCountedAllocator::~RefCountedAllocator() =
65 default;
66
SubprocessMetricsProvider()67 SubprocessMetricsProvider::SubprocessMetricsProvider()
68 : task_runner_(CreateTaskRunner()) {
69 base::StatisticsRecorder::RegisterHistogramProvider(
70 weak_ptr_factory_.GetWeakPtr());
71 content::BrowserChildProcessObserver::Add(this);
72 g_subprocess_metrics_provider = this;
73
74 // Ensure no child processes currently exist so that we do not miss any.
75 CHECK(content::RenderProcessHost::AllHostsIterator().IsAtEnd());
76 CHECK(content::BrowserChildProcessHostIterator().Done());
77 }
78
~SubprocessMetricsProvider()79 SubprocessMetricsProvider::~SubprocessMetricsProvider() {
80 // This object should never be deleted since it is leaky.
81 NOTREACHED();
82 }
83
RegisterSubprocessAllocator(int id,std::unique_ptr<base::PersistentHistogramAllocator> allocator)84 void SubprocessMetricsProvider::RegisterSubprocessAllocator(
85 int id,
86 std::unique_ptr<base::PersistentHistogramAllocator> allocator) {
87 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
88 CHECK(allocator);
89
90 // Pass a custom RangesManager so that we do not register the BucketRanges
91 // with the global StatisticsRecorder when creating histogram objects using
92 // the allocator's underlying data. This avoids unnecessary contention on the
93 // global StatisticsRecorder lock.
94 // Note: Since |allocator| may be merged from different threads concurrently,
95 // for example on the UI thread and on a background thread, we must use
96 // ThreadSafeRangesManager.
97 allocator->SetRangesManager(new base::ThreadSafeRangesManager());
98 // Insert the allocator into the internal map and verify that there was no
99 // allocator with the same ID already.
100 auto result = allocators_by_id_.emplace(
101 id, base::MakeRefCounted<RefCountedAllocator>(std::move(allocator)));
102 CHECK(result.second);
103 }
104
DeregisterSubprocessAllocator(int id)105 void SubprocessMetricsProvider::DeregisterSubprocessAllocator(int id) {
106 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
107 auto it = allocators_by_id_.find(id);
108 if (it == allocators_by_id_.end()) {
109 return;
110 }
111
112 // Extract the matching allocator from the list of active ones. It is possible
113 // that a background task is currently holding a reference to it. Removing it
114 // from the internal map is fine though, as it is refcounted.
115 scoped_refptr<RefCountedAllocator> allocator = std::move(it->second);
116 allocators_by_id_.erase(it);
117 CHECK(allocator);
118
119 // Merge the last deltas from the allocator before releasing the ref (and
120 // deleting if the last one).
121 auto* allocator_ptr = allocator.get();
122 task_runner_->PostTaskAndReply(
123 FROM_HERE,
124 base::BindOnce(
125 &SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator, id,
126 // Unretained is needed to pass a refcounted class as a raw pointer.
127 // It is safe because it is kept alive by the reply task.
128 base::Unretained(allocator_ptr)),
129 base::BindOnce(
130 &SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator,
131 std::move(allocator)));
132 }
133
MergeHistogramDeltas(bool async,base::OnceClosure done_callback)134 void SubprocessMetricsProvider::MergeHistogramDeltas(
135 bool async,
136 base::OnceClosure done_callback) {
137 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
138 if (async) {
139 // Make a copy of the internal allocators map (with its own refptrs) to pass
140 // to the background task.
141 auto allocators = std::make_unique<AllocatorByIdMap>(allocators_by_id_);
142 auto* allocators_ptr = allocators.get();
143
144 // This is intentionally not posted to |task_runner_| because not running
145 // this task does not imply data loss, so no point in blocking shutdown
146 // (hence CONTINUE_ON_SHUTDOWN). However, there might be some contention on
147 // the StatisticsRecorder between this task and those posted to
148 // |task_runner_|.
149 base::ThreadPool::PostTaskAndReply(
150 FROM_HERE,
151 {base::TaskPriority::BEST_EFFORT,
152 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
153 base::BindOnce(&MergeHistogramDeltasFromAllocators, allocators_ptr),
154 base::BindOnce(
155 &SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators,
156 std::move(allocators), std::move(done_callback)));
157 } else {
158 MergeHistogramDeltasFromAllocators(&allocators_by_id_);
159 std::move(done_callback).Run();
160 }
161 }
162
BrowserChildProcessLaunchedAndConnected(const content::ChildProcessData & data)163 void SubprocessMetricsProvider::BrowserChildProcessLaunchedAndConnected(
164 const content::ChildProcessData& data) {
165 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
166
167 // See if the new process has a memory allocator and take control of it if so.
168 // This call can only be made on the browser's IO thread.
169 content::BrowserChildProcessHost* host =
170 content::BrowserChildProcessHost::FromID(data.id);
171 // |host| should not be null, but such cases have been observed in the wild so
172 // gracefully handle this scenario.
173 if (!host) {
174 return;
175 }
176
177 std::unique_ptr<base::PersistentMemoryAllocator> allocator =
178 host->TakeMetricsAllocator();
179 // The allocator can be null in tests.
180 if (!allocator) {
181 return;
182 }
183
184 RegisterSubprocessAllocator(
185 data.id, std::make_unique<base::PersistentHistogramAllocator>(
186 std::move(allocator)));
187 }
188
BrowserChildProcessHostDisconnected(const content::ChildProcessData & data)189 void SubprocessMetricsProvider::BrowserChildProcessHostDisconnected(
190 const content::ChildProcessData& data) {
191 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
192 DeregisterSubprocessAllocator(data.id);
193 }
194
BrowserChildProcessCrashed(const content::ChildProcessData & data,const content::ChildProcessTerminationInfo & info)195 void SubprocessMetricsProvider::BrowserChildProcessCrashed(
196 const content::ChildProcessData& data,
197 const content::ChildProcessTerminationInfo& info) {
198 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
199 DeregisterSubprocessAllocator(data.id);
200 }
201
BrowserChildProcessKilled(const content::ChildProcessData & data,const content::ChildProcessTerminationInfo & info)202 void SubprocessMetricsProvider::BrowserChildProcessKilled(
203 const content::ChildProcessData& data,
204 const content::ChildProcessTerminationInfo& info) {
205 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
206 DeregisterSubprocessAllocator(data.id);
207 }
208
OnRenderProcessHostCreated(content::RenderProcessHost * host)209 void SubprocessMetricsProvider::OnRenderProcessHostCreated(
210 content::RenderProcessHost* host) {
211 // Sometimes, the same host will cause multiple notifications in tests so
212 // could possibly do the same in a release build.
213 if (!scoped_observations_.IsObservingSource(host)) {
214 scoped_observations_.AddObservation(host);
215 }
216 }
217
RenderProcessReady(content::RenderProcessHost * host)218 void SubprocessMetricsProvider::RenderProcessReady(
219 content::RenderProcessHost* host) {
220 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
221
222 // If the render-process-host passed a persistent-memory-allocator to the
223 // renderer process, extract it and register it here.
224 std::unique_ptr<base::PersistentMemoryAllocator> allocator =
225 host->TakeMetricsAllocator();
226 if (allocator) {
227 RegisterSubprocessAllocator(
228 host->GetID(), std::make_unique<base::PersistentHistogramAllocator>(
229 std::move(allocator)));
230 }
231 }
232
RenderProcessExited(content::RenderProcessHost * host,const content::ChildProcessTerminationInfo & info)233 void SubprocessMetricsProvider::RenderProcessExited(
234 content::RenderProcessHost* host,
235 const content::ChildProcessTerminationInfo& info) {
236 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
237
238 DeregisterSubprocessAllocator(host->GetID());
239 }
240
RenderProcessHostDestroyed(content::RenderProcessHost * host)241 void SubprocessMetricsProvider::RenderProcessHostDestroyed(
242 content::RenderProcessHost* host) {
243 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
244
245 // It's possible for a Renderer to terminate without RenderProcessExited
246 // (above) being called so it's necessary to de-register also upon the
247 // destruction of the host. If both get called, no harm is done.
248
249 DeregisterSubprocessAllocator(host->GetID());
250 scoped_observations_.RemoveObservation(host);
251 }
252
RecreateTaskRunnerForTesting()253 void SubprocessMetricsProvider::RecreateTaskRunnerForTesting() {
254 task_runner_ = CreateTaskRunner();
255 }
256
257 // static
MergeHistogramDeltasFromAllocator(int id,RefCountedAllocator * allocator)258 void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator(
259 int id,
260 RefCountedAllocator* allocator) {
261 DCHECK(allocator);
262
263 int histogram_count = 0;
264 base::PersistentHistogramAllocator* allocator_ptr = allocator->allocator();
265 base::PersistentHistogramAllocator::Iterator hist_iter(allocator_ptr);
266 while (true) {
267 std::unique_ptr<base::HistogramBase> histogram = hist_iter.GetNext();
268 if (!histogram) {
269 break;
270 }
271 // We expect histograms to match as subprocesses shouldn't have version skew
272 // with the browser process.
273 bool merge_success =
274 allocator_ptr->MergeHistogramDeltaToStatisticsRecorder(histogram.get());
275
276 // When merging child process histograms into the parent, we expect the
277 // merge operation to succeed. If it doesn't, it means the histograms have
278 // different types or buckets, which indicates a programming error (i.e.
279 // non-matching logging code across browser and child for a histogram). In
280 // this case DumpWithoutCrashing() with a crash key with the histogram name.
281 if (!merge_success) {
282 SCOPED_CRASH_KEY_STRING256("SubprocessMetricsProvider", "histogram",
283 histogram->histogram_name());
284 base::debug::DumpWithoutCrashing();
285 }
286 ++histogram_count;
287 }
288
289 DVLOG(1) << "Reported " << histogram_count << " histograms from subprocess #"
290 << id;
291 }
292
293 // static
MergeHistogramDeltasFromAllocators(AllocatorByIdMap * allocators)294 void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocators(
295 AllocatorByIdMap* allocators) {
296 for (const auto& iter : *allocators) {
297 MergeHistogramDeltasFromAllocator(iter.first, iter.second.get());
298 }
299 }
300
301 // static
OnMergeHistogramDeltasFromAllocator(scoped_refptr<RefCountedAllocator> allocator)302 void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator(
303 scoped_refptr<RefCountedAllocator> allocator) {
304 // This method does nothing except have ownership on |allocator|. When this
305 // method exits, |allocator| will be released (unless there are background
306 // tasks currently holding references).
307 }
308
309 // static
OnMergeHistogramDeltasFromAllocators(std::unique_ptr<AllocatorByIdMap> allocators,base::OnceClosure done_callback)310 void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators(
311 std::unique_ptr<AllocatorByIdMap> allocators,
312 base::OnceClosure done_callback) {
313 std::move(done_callback).Run();
314 // When this method exits, |allocators| will be released. It's possible some
315 // allocators are from subprocesses that have already been deregistered, so
316 // they will also be released here (assuming no other background tasks
317 // currently hold references).
318 }
319
320 } // namespace metrics
321