1 // Copyright 2016 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/content/subprocess_metrics_provider.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram_base.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/metrics/persistent_histogram_allocator.h"
14 #include "base/metrics/persistent_memory_allocator.h"
15 #include "components/metrics/metrics_service.h"
16 #include "content/public/browser/browser_child_process_host.h"
17 #include "content/public/browser/browser_task_traits.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/child_process_data.h"
20 
21 namespace metrics {
22 namespace {
23 
24 // This is used by tests that don't have an easy way to access the global
25 // instance of this class.
26 SubprocessMetricsProvider* g_subprocess_metrics_provider_for_testing;
27 
28 }  // namespace
29 
SubprocessMetricsProvider()30 SubprocessMetricsProvider::SubprocessMetricsProvider() {
31   base::StatisticsRecorder::RegisterHistogramProvider(
32       weak_ptr_factory_.GetWeakPtr());
33   content::BrowserChildProcessObserver::Add(this);
34   RegisterExistingRenderProcesses();
35   g_subprocess_metrics_provider_for_testing = this;
36 }
37 
~SubprocessMetricsProvider()38 SubprocessMetricsProvider::~SubprocessMetricsProvider() {
39   // Safe even if this object has never been added as an observer.
40   content::BrowserChildProcessObserver::Remove(this);
41   g_subprocess_metrics_provider_for_testing = nullptr;
42 }
43 
44 // static
MergeHistogramDeltasForTesting()45 void SubprocessMetricsProvider::MergeHistogramDeltasForTesting() {
46   DCHECK(g_subprocess_metrics_provider_for_testing);
47   g_subprocess_metrics_provider_for_testing->MergeHistogramDeltas();
48 }
49 
RegisterExistingRenderProcesses()50 void SubprocessMetricsProvider::RegisterExistingRenderProcesses() {
51   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
52 
53   for (auto it(content::RenderProcessHost::AllHostsIterator()); !it.IsAtEnd();
54        it.Advance()) {
55     auto* rph = it.GetCurrentValue();
56     OnRenderProcessHostCreated(rph);
57     if (rph->IsReady())
58       RenderProcessReady(rph);
59   }
60 }
61 
RegisterSubprocessAllocator(int id,std::unique_ptr<base::PersistentHistogramAllocator> allocator)62 void SubprocessMetricsProvider::RegisterSubprocessAllocator(
63     int id,
64     std::unique_ptr<base::PersistentHistogramAllocator> allocator) {
65   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
66   DCHECK(!allocators_by_id_.Lookup(id));
67 
68   // Stop now if this was called without an allocator, typically because
69   // GetSubprocessHistogramAllocatorOnIOThread exited early and returned
70   // null.
71   if (!allocator)
72     return;
73 
74   // Map is "MapOwnPointer" so transfer ownership to it.
75   allocators_by_id_.AddWithID(std::move(allocator), id);
76 }
77 
DeregisterSubprocessAllocator(int id)78 void SubprocessMetricsProvider::DeregisterSubprocessAllocator(int id) {
79   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
80 
81   if (!allocators_by_id_.Lookup(id))
82     return;
83 
84   // Extract the matching allocator from the list of active ones. It will
85   // be automatically released when this method exits.
86   std::unique_ptr<base::PersistentHistogramAllocator> allocator(
87       allocators_by_id_.Replace(id, nullptr));
88   allocators_by_id_.Remove(id);
89   DCHECK(allocator);
90 
91   // Merge the last deltas from the allocator before it is released.
92   MergeHistogramDeltasFromAllocator(id, allocator.get());
93 }
94 
MergeHistogramDeltasFromAllocator(int id,base::PersistentHistogramAllocator * allocator)95 void SubprocessMetricsProvider::MergeHistogramDeltasFromAllocator(
96     int id,
97     base::PersistentHistogramAllocator* allocator) {
98   DCHECK(allocator);
99 
100   int histogram_count = 0;
101   base::PersistentHistogramAllocator::Iterator hist_iter(allocator);
102   while (true) {
103     std::unique_ptr<base::HistogramBase> histogram = hist_iter.GetNext();
104     if (!histogram)
105       break;
106     allocator->MergeHistogramDeltaToStatisticsRecorder(histogram.get());
107     ++histogram_count;
108   }
109 
110   DVLOG(1) << "Reported " << histogram_count << " histograms from subprocess #"
111            << id;
112 }
113 
MergeHistogramDeltas()114 void SubprocessMetricsProvider::MergeHistogramDeltas() {
115   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
116 
117   for (AllocatorByIdMap::iterator iter(&allocators_by_id_); !iter.IsAtEnd();
118        iter.Advance()) {
119     MergeHistogramDeltasFromAllocator(iter.GetCurrentKey(),
120                                       iter.GetCurrentValue());
121   }
122 }
123 
BrowserChildProcessHostConnected(const content::ChildProcessData & data)124 void SubprocessMetricsProvider::BrowserChildProcessHostConnected(
125     const content::ChildProcessData& data) {
126   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
127 
128   // It's necessary to access the BrowserChildProcessHost object that is
129   // managing the child in order to extract the metrics memory from it.
130   // Unfortunately, the required lookup can only be performed on the IO
131   // thread so do the necessary dance.
132   content::GetIOThreadTaskRunner({})->PostTaskAndReplyWithResult(
133       FROM_HERE,
134       base::BindOnce(
135           &SubprocessMetricsProvider::GetSubprocessHistogramAllocatorOnIOThread,
136           data.id),
137       base::BindOnce(&SubprocessMetricsProvider::RegisterSubprocessAllocator,
138                      weak_ptr_factory_.GetWeakPtr(), data.id));
139 }
140 
BrowserChildProcessHostDisconnected(const content::ChildProcessData & data)141 void SubprocessMetricsProvider::BrowserChildProcessHostDisconnected(
142     const content::ChildProcessData& data) {
143   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
144   DeregisterSubprocessAllocator(data.id);
145 }
146 
BrowserChildProcessCrashed(const content::ChildProcessData & data,const content::ChildProcessTerminationInfo & info)147 void SubprocessMetricsProvider::BrowserChildProcessCrashed(
148     const content::ChildProcessData& data,
149     const content::ChildProcessTerminationInfo& info) {
150   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
151   DeregisterSubprocessAllocator(data.id);
152 }
153 
BrowserChildProcessKilled(const content::ChildProcessData & data,const content::ChildProcessTerminationInfo & info)154 void SubprocessMetricsProvider::BrowserChildProcessKilled(
155     const content::ChildProcessData& data,
156     const content::ChildProcessTerminationInfo& info) {
157   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
158   DeregisterSubprocessAllocator(data.id);
159 }
160 
OnRenderProcessHostCreated(content::RenderProcessHost * host)161 void SubprocessMetricsProvider::OnRenderProcessHostCreated(
162     content::RenderProcessHost* host) {
163   // Sometimes, the same host will cause multiple notifications in tests so
164   // could possibly do the same in a release build.
165   if (!scoped_observations_.IsObservingSource(host))
166     scoped_observations_.AddObservation(host);
167 }
168 
RenderProcessReady(content::RenderProcessHost * host)169 void SubprocessMetricsProvider::RenderProcessReady(
170     content::RenderProcessHost* host) {
171   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
172 
173   // If the render-process-host passed a persistent-memory-allocator to the
174   // renderer process, extract it and register it here.
175   std::unique_ptr<base::PersistentMemoryAllocator> allocator =
176       host->TakeMetricsAllocator();
177   if (allocator) {
178     RegisterSubprocessAllocator(
179         host->GetID(), std::make_unique<base::PersistentHistogramAllocator>(
180                            std::move(allocator)));
181   }
182 }
183 
RenderProcessExited(content::RenderProcessHost * host,const content::ChildProcessTerminationInfo & info)184 void SubprocessMetricsProvider::RenderProcessExited(
185     content::RenderProcessHost* host,
186     const content::ChildProcessTerminationInfo& info) {
187   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
188 
189   DeregisterSubprocessAllocator(host->GetID());
190 }
191 
RenderProcessHostDestroyed(content::RenderProcessHost * host)192 void SubprocessMetricsProvider::RenderProcessHostDestroyed(
193     content::RenderProcessHost* host) {
194   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
195 
196   // It's possible for a Renderer to terminate without RenderProcessExited
197   // (above) being called so it's necessary to de-register also upon the
198   // destruction of the host. If both get called, no harm is done.
199 
200   DeregisterSubprocessAllocator(host->GetID());
201   scoped_observations_.RemoveObservation(host);
202 }
203 
204 // static
205 std::unique_ptr<base::PersistentHistogramAllocator>
GetSubprocessHistogramAllocatorOnIOThread(int id)206 SubprocessMetricsProvider::GetSubprocessHistogramAllocatorOnIOThread(int id) {
207   // See if the new process has a memory allocator and take control of it if so.
208   // This call can only be made on the browser's IO thread.
209   content::BrowserChildProcessHost* host =
210       content::BrowserChildProcessHost::FromID(id);
211   if (!host)
212     return nullptr;
213 
214   std::unique_ptr<base::PersistentMemoryAllocator> allocator =
215       host->TakeMetricsAllocator();
216   if (!allocator)
217     return nullptr;
218 
219   return std::make_unique<base::PersistentHistogramAllocator>(
220       std::move(allocator));
221 }
222 
223 }  // namespace metrics
224