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 RegisterSubprocessAllocator(
184 data.id, std::make_unique<base::PersistentHistogramAllocator>(
185 std::move(allocator)));
186 }
187
BrowserChildProcessHostDisconnected(const content::ChildProcessData & data)188 void SubprocessMetricsProvider::BrowserChildProcessHostDisconnected(
189 const content::ChildProcessData& data) {
190 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
191 DeregisterSubprocessAllocator(data.id);
192 }
193
BrowserChildProcessCrashed(const content::ChildProcessData & data,const content::ChildProcessTerminationInfo & info)194 void SubprocessMetricsProvider::BrowserChildProcessCrashed(
195 const content::ChildProcessData& data,
196 const content::ChildProcessTerminationInfo& info) {
197 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
198 DeregisterSubprocessAllocator(data.id);
199 }
200
BrowserChildProcessKilled(const content::ChildProcessData & data,const content::ChildProcessTerminationInfo & info)201 void SubprocessMetricsProvider::BrowserChildProcessKilled(
202 const content::ChildProcessData& data,
203 const content::ChildProcessTerminationInfo& info) {
204 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
205 DeregisterSubprocessAllocator(data.id);
206 }
207
OnRenderProcessHostCreated(content::RenderProcessHost * host)208 void SubprocessMetricsProvider::OnRenderProcessHostCreated(
209 content::RenderProcessHost* host) {
210 // Sometimes, the same host will cause multiple notifications in tests so
211 // could possibly do the same in a release build.
212 if (!scoped_observations_.IsObservingSource(host))
213 scoped_observations_.AddObservation(host);
214 }
215
RenderProcessReady(content::RenderProcessHost * host)216 void SubprocessMetricsProvider::RenderProcessReady(
217 content::RenderProcessHost* host) {
218 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
219
220 // If the render-process-host passed a persistent-memory-allocator to the
221 // renderer process, extract it and register it here.
222 std::unique_ptr<base::PersistentMemoryAllocator> allocator =
223 host->TakeMetricsAllocator();
224 if (allocator) {
225 RegisterSubprocessAllocator(
226 host->GetID(), std::make_unique<base::PersistentHistogramAllocator>(
227 std::move(allocator)));
228 }
229 }
230
RenderProcessExited(content::RenderProcessHost * host,const content::ChildProcessTerminationInfo & info)231 void SubprocessMetricsProvider::RenderProcessExited(
232 content::RenderProcessHost* host,
233 const content::ChildProcessTerminationInfo& info) {
234 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
235
236 DeregisterSubprocessAllocator(host->GetID());
237 }
238
RenderProcessHostDestroyed(content::RenderProcessHost * host)239 void SubprocessMetricsProvider::RenderProcessHostDestroyed(
240 content::RenderProcessHost* host) {
241 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
242
243 // It's possible for a Renderer to terminate without RenderProcessExited
244 // (above) being called so it's necessary to de-register also upon the
245 // destruction of the host. If both get called, no harm is done.
246
247 DeregisterSubprocessAllocator(host->GetID());
248 scoped_observations_.RemoveObservation(host);
249 }
250
RecreateTaskRunnerForTesting()251 void SubprocessMetricsProvider::RecreateTaskRunnerForTesting() {
252 task_runner_ = CreateTaskRunner();
253 }
254
255 // static
MergeHistogramDeltasFromAllocator(int id,RefCountedAllocator * allocator)256 void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator(
257 int id,
258 RefCountedAllocator* allocator) {
259 DCHECK(allocator);
260
261 int histogram_count = 0;
262 base::PersistentHistogramAllocator* allocator_ptr = allocator->allocator();
263 base::PersistentHistogramAllocator::Iterator hist_iter(allocator_ptr);
264 while (true) {
265 std::unique_ptr<base::HistogramBase> histogram = hist_iter.GetNext();
266 if (!histogram) {
267 break;
268 }
269 allocator_ptr->MergeHistogramDeltaToStatisticsRecorder(histogram.get());
270 ++histogram_count;
271 }
272
273 DVLOG(1) << "Reported " << histogram_count << " histograms from subprocess #"
274 << id;
275 }
276
277 // static
MergeHistogramDeltasFromAllocators(AllocatorByIdMap * allocators)278 void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocators(
279 AllocatorByIdMap* allocators) {
280 for (const auto& iter : *allocators) {
281 MergeHistogramDeltasFromAllocator(iter.first, iter.second.get());
282 }
283 }
284
285 // static
OnMergeHistogramDeltasFromAllocator(scoped_refptr<RefCountedAllocator> allocator)286 void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocator(
287 scoped_refptr<RefCountedAllocator> allocator) {
288 // This method does nothing except have ownership on |allocator|. When this
289 // method exits, |allocator| will be released (unless there are background
290 // tasks currently holding references).
291 }
292
293 // static
OnMergeHistogramDeltasFromAllocators(std::unique_ptr<AllocatorByIdMap> allocators,base::OnceClosure done_callback)294 void SubprocessMetricsProvider::OnMergeHistogramDeltasFromAllocators(
295 std::unique_ptr<AllocatorByIdMap> allocators,
296 base::OnceClosure done_callback) {
297 std::move(done_callback).Run();
298 // When this method exits, |allocators| will be released. It's possible some
299 // allocators are from subprocesses that have already been deregistered, so
300 // they will also be released here (assuming no other background tasks
301 // currently hold references).
302 }
303
304 } // namespace metrics
305