xref: /aosp_15_r20/external/cronet/components/metrics/histogram_controller.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 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/histogram_controller.h"
6 
7 #include "base/functional/bind.h"
8 #include "base/location.h"
9 #include "base/metrics/histogram_functions.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/process/process_handle.h"
12 #include "base/rand_util.h"
13 #include "components/metrics/histogram_subscriber.h"
14 #include "components/metrics/public/mojom/histogram_fetcher.mojom.h"
15 #include "mojo/public/cpp/bindings/callback_helpers.h"
16 
17 namespace metrics {
18 
19 namespace {
GetPingHistogramName(mojom::UmaPingCallSource call_source)20 const char* GetPingHistogramName(mojom::UmaPingCallSource call_source) {
21   switch (call_source) {
22     case mojom::UmaPingCallSource::PERIODIC:
23       return "UMA.ChildProcess.Ping.Periodic";
24     case mojom::UmaPingCallSource::SHARED_MEMORY_SET_UP:
25       return "UMA.ChildProcess.Ping.SharedMemorySetUp";
26   }
27 }
28 }  // namespace
29 
30 struct HistogramController::ChildHistogramFetcher {
31   mojo::Remote<mojom::ChildHistogramFetcher> remote;
32   ChildProcessMode mode;
33 };
34 
GetInstance()35 HistogramController* HistogramController::GetInstance() {
36   return base::Singleton<HistogramController, base::LeakySingletonTraits<
37                                                   HistogramController>>::get();
38 }
39 
HistogramController()40 HistogramController::HistogramController() : subscriber_(nullptr) {
41   // Unretained is safe because |this| is leaky.
42   timer_.Start(FROM_HERE, base::Minutes(5),
43                base::BindRepeating(&HistogramController::PingChildProcesses,
44                                    base::Unretained(this)));
45 }
46 
47 HistogramController::~HistogramController() = default;
48 
Register(HistogramSubscriber * subscriber)49 void HistogramController::Register(HistogramSubscriber* subscriber) {
50   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
51   DCHECK(!subscriber_);
52   subscriber_ = subscriber;
53 }
54 
Unregister(const HistogramSubscriber * subscriber)55 void HistogramController::Unregister(const HistogramSubscriber* subscriber) {
56   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
57   DCHECK_EQ(subscriber_, subscriber);
58   subscriber_ = nullptr;
59 }
60 
NotifyChildDied(HistogramChildProcess * host)61 void HistogramController::NotifyChildDied(HistogramChildProcess* host) {
62   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
63   RemoveChildHistogramFetcherInterface(
64       MayBeDangling<HistogramChildProcess>(host));
65 }
66 
SetHistogramMemory(HistogramChildProcess * host,base::UnsafeSharedMemoryRegion shared_region,ChildProcessMode mode)67 void HistogramController::SetHistogramMemory(
68     HistogramChildProcess* host,
69     base::UnsafeSharedMemoryRegion shared_region,
70     ChildProcessMode mode) {
71   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
72   mojo::Remote<mojom::ChildHistogramFetcherFactory> factory;
73   host->BindChildHistogramFetcherFactory(factory.BindNewPipeAndPassReceiver());
74 
75   mojo::Remote<mojom::ChildHistogramFetcher> fetcher;
76   factory->CreateFetcher(std::move(shared_region),
77                          fetcher.BindNewPipeAndPassReceiver());
78   PingChildProcess(fetcher.get(),
79                    mojom::UmaPingCallSource::SHARED_MEMORY_SET_UP);
80   InsertChildHistogramFetcherInterface(host, std::move(fetcher), mode);
81 }
82 
InsertChildHistogramFetcherInterface(HistogramChildProcess * host,mojo::Remote<mojom::ChildHistogramFetcher> child_histogram_fetcher,ChildProcessMode mode)83 void HistogramController::InsertChildHistogramFetcherInterface(
84     HistogramChildProcess* host,
85     mojo::Remote<mojom::ChildHistogramFetcher> child_histogram_fetcher,
86     ChildProcessMode mode) {
87   // Broken pipe means remove this from the map. The map size is a proxy for
88   // the number of known processes
89   //
90   // `RemoveChildHistogramFetcherInterface` will only use `host` for address
91   // comparison without being dereferenced , therefore it's not going to create
92   // a UAF.
93   child_histogram_fetcher.set_disconnect_handler(
94       base::BindOnce(&HistogramController::RemoveChildHistogramFetcherInterface,
95                      base::Unretained(this), base::UnsafeDangling(host)));
96   child_histogram_fetchers_.emplace(
97       host, ChildHistogramFetcher{std::move(child_histogram_fetcher), mode});
98 }
99 
PingChildProcesses()100 void HistogramController::PingChildProcesses() {
101   // Only ping ~10% of child processes to avoid possibly "waking up" too many
102   // and causing unnecessary work.
103   for (const auto& fetcher : child_histogram_fetchers_) {
104     if (base::RandGenerator(/*range=*/10) == 0) {
105       PingChildProcess(fetcher.second.remote.get(),
106                        mojom::UmaPingCallSource::PERIODIC);
107     }
108   }
109 }
110 
PingChildProcess(mojom::ChildHistogramFetcherProxy * fetcher,mojom::UmaPingCallSource call_source)111 void HistogramController::PingChildProcess(
112     mojom::ChildHistogramFetcherProxy* fetcher,
113     mojom::UmaPingCallSource call_source) {
114   // 1) Emit a histogram, 2) ping the child process (which should also emit a
115   // histogram), and 3) call Pong(), which again emits a histogram.
116   // If no histograms are lost, in total, the histograms should all be emitted
117   // roughly the same amount of times. The exception is for 1), which may be
118   // emitted more often because this may be called early on in the lifecycle of
119   // the child process, and some child processes are killed very early on,
120   // before any IPC messages are processed.
121   base::UmaHistogramEnumeration(GetPingHistogramName(call_source),
122                                 mojom::UmaChildPingStatus::BROWSER_SENT_IPC);
123   // Unretained is safe because |this| is leaky.
124   fetcher->Ping(call_source,
125                 base::BindOnce(&HistogramController::Pong,
126                                base::Unretained(this), call_source));
127 }
128 
Pong(mojom::UmaPingCallSource call_source)129 void HistogramController::Pong(mojom::UmaPingCallSource call_source) {
130   base::UmaHistogramEnumeration(
131       GetPingHistogramName(call_source),
132       mojom::UmaChildPingStatus::BROWSER_REPLY_CALLBACK);
133 }
134 
RemoveChildHistogramFetcherInterface(MayBeDangling<HistogramChildProcess> host)135 void HistogramController::RemoveChildHistogramFetcherInterface(
136     MayBeDangling<HistogramChildProcess> host) {
137   child_histogram_fetchers_.erase(host);
138 }
139 
GetHistogramData(int sequence_number)140 void HistogramController::GetHistogramData(int sequence_number) {
141   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
142 
143   int pending_processes = 0;
144   for (const auto& fetcher : child_histogram_fetchers_) {
145     if (fetcher.second.mode != ChildProcessMode::kGetHistogramData) {
146       continue;
147     }
148 
149     fetcher.second.remote->GetChildNonPersistentHistogramData(
150         mojo::WrapCallbackWithDefaultInvokeIfNotRun(
151             base::BindOnce(&HistogramController::OnHistogramDataCollected,
152                            base::Unretained(this), sequence_number),
153             std::vector<std::string>()));
154     ++pending_processes;
155   }
156 
157   if (subscriber_) {
158     subscriber_->OnPendingProcesses(sequence_number, pending_processes, true);
159   }
160 }
161 
OnHistogramDataCollected(int sequence_number,const std::vector<std::string> & pickled_histograms)162 void HistogramController::OnHistogramDataCollected(
163     int sequence_number,
164     const std::vector<std::string>& pickled_histograms) {
165   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
166   if (subscriber_) {
167     subscriber_->OnHistogramDataCollected(sequence_number, pickled_histograms);
168   }
169 }
170 
171 }  // namespace metrics
172