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