• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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