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