xref: /aosp_15_r20/external/cronet/components/metrics/content/subprocess_metrics_provider.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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