Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "TelemetryIPCAccumulator.h"
8 :
9 : #include "mozilla/dom/ContentChild.h"
10 : #include "mozilla/gfx/GPUParent.h"
11 : #include "mozilla/gfx/GPUProcessManager.h"
12 : #include "mozilla/StaticMutex.h"
13 : #include "mozilla/StaticPtr.h"
14 : #include "mozilla/SystemGroup.h"
15 : #include "mozilla/Unused.h"
16 : #include "nsComponentManagerUtils.h"
17 : #include "nsITimer.h"
18 : #include "nsThreadUtils.h"
19 : #include "TelemetryHistogram.h"
20 : #include "TelemetryScalar.h"
21 :
22 : using mozilla::StaticMutex;
23 : using mozilla::StaticMutexAutoLock;
24 : using mozilla::StaticAutoPtr;
25 : using mozilla::SystemGroup;
26 : using mozilla::TaskCategory;
27 : using mozilla::Telemetry::Accumulation;
28 : using mozilla::Telemetry::DiscardedData;
29 : using mozilla::Telemetry::KeyedAccumulation;
30 : using mozilla::Telemetry::ScalarActionType;
31 : using mozilla::Telemetry::ScalarAction;
32 : using mozilla::Telemetry::KeyedScalarAction;
33 : using mozilla::Telemetry::ScalarVariant;
34 : using mozilla::Telemetry::ChildEventData;
35 :
36 : namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
37 :
38 : // Sending each remote accumulation immediately places undue strain on the
39 : // IPC subsystem. Batch the remote accumulations for a period of time before
40 : // sending them all at once. This value was chosen as a balance between data
41 : // timeliness and performance (see bug 1218576)
42 : const uint32_t kBatchTimeoutMs = 2000;
43 :
44 : // To stop growing unbounded in memory while waiting for kBatchTimeoutMs to
45 : // drain the probe accumulation arrays, we request an immediate flush if the
46 : // arrays manage to reach certain high water mark of elements.
47 : const size_t kHistogramAccumulationsArrayHighWaterMark = 5 * 1024;
48 : const size_t kScalarActionsArrayHighWaterMark = 10000;
49 : // With the current limits, events cost us about 1100 bytes each.
50 : // This limits memory use to about 10MB.
51 : const size_t kEventsArrayHighWaterMark = 10000;
52 : // If we are starved we can overshoot the watermark.
53 : // This is the multiplier over which we will discard data.
54 : const size_t kWaterMarkDiscardFactor = 5;
55 :
56 : // Counts of how many pieces of data we have discarded.
57 : DiscardedData gDiscardedData = {0};
58 :
59 : // This timer is used for batching and sending child process accumulations to the parent.
60 : nsITimer* gIPCTimer = nullptr;
61 : mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false);
62 : mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false);
63 :
64 : // This batches child process accumulations that should be sent to the parent.
65 3 : StaticAutoPtr<nsTArray<Accumulation>> gHistogramAccumulations;
66 3 : StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedHistogramAccumulations;
67 3 : StaticAutoPtr<nsTArray<ScalarAction>> gChildScalarsActions;
68 3 : StaticAutoPtr<nsTArray<KeyedScalarAction>> gChildKeyedScalarsActions;
69 3 : StaticAutoPtr<nsTArray<ChildEventData>> gChildEvents;
70 :
71 : // This is a StaticMutex rather than a plain Mutex so that (1)
72 : // it gets initialised in a thread-safe manner the first time
73 : // it is used, and (2) because it is never de-initialised, and
74 : // a normal Mutex would show up as a leak in BloatView. StaticMutex
75 : // also has the "OffTheBooks" property, so it won't show as a leak
76 : // in BloatView.
77 3 : static StaticMutex gTelemetryIPCAccumulatorMutex;
78 :
79 : namespace {
80 :
81 : void
82 5 : DoArmIPCTimerMainThread(const StaticMutexAutoLock& lock)
83 : {
84 5 : MOZ_ASSERT(NS_IsMainThread());
85 5 : gIPCTimerArming = false;
86 5 : if (gIPCTimerArmed) {
87 0 : return;
88 : }
89 5 : if (!gIPCTimer) {
90 2 : CallCreateInstance(NS_TIMER_CONTRACTID, &gIPCTimer);
91 2 : if (gIPCTimer) {
92 2 : gIPCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::Other));
93 : }
94 : }
95 5 : if (gIPCTimer) {
96 5 : gIPCTimer->InitWithNamedFuncCallback(TelemetryIPCAccumulator::IPCTimerFired,
97 : nullptr, kBatchTimeoutMs,
98 : nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
99 10 : "TelemetryIPCAccumulator::IPCTimerFired");
100 5 : gIPCTimerArmed = true;
101 : }
102 : }
103 :
104 : void
105 1122 : ArmIPCTimer(const StaticMutexAutoLock& lock)
106 : {
107 1122 : if (gIPCTimerArmed || gIPCTimerArming) {
108 1117 : return;
109 : }
110 5 : gIPCTimerArming = true;
111 5 : if (NS_IsMainThread()) {
112 5 : DoArmIPCTimerMainThread(lock);
113 : } else {
114 0 : TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction(
115 : "TelemetryIPCAccumulator::ArmIPCTimer",
116 0 : []() -> void {
117 0 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
118 0 : DoArmIPCTimerMainThread(locker);
119 0 : }));
120 : }
121 : }
122 :
123 : void
124 0 : DispatchIPCTimerFired()
125 : {
126 0 : TelemetryIPCAccumulator::DispatchToMainThread(
127 0 : NS_NewRunnableFunction("TelemetryIPCAccumulator::IPCTimerFired",
128 0 : []() -> void {
129 0 : TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
130 0 : }));
131 0 : }
132 :
133 : } // anonymous namespace
134 :
135 : ////////////////////////////////////////////////////////////////////////
136 : ////////////////////////////////////////////////////////////////////////
137 : //
138 : // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryIPCAccumulator::
139 :
140 : void
141 828 : TelemetryIPCAccumulator::AccumulateChildHistogram(mozilla::Telemetry::HistogramID aId,
142 : uint32_t aSample)
143 : {
144 1656 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
145 828 : if (!gHistogramAccumulations) {
146 2 : gHistogramAccumulations = new nsTArray<Accumulation>();
147 : }
148 828 : if (gHistogramAccumulations->Length() >=
149 : kWaterMarkDiscardFactor * kHistogramAccumulationsArrayHighWaterMark) {
150 0 : gDiscardedData.mDiscardedHistogramAccumulations++;
151 0 : return;
152 : }
153 828 : if (gHistogramAccumulations->Length() == kHistogramAccumulationsArrayHighWaterMark) {
154 0 : DispatchIPCTimerFired();
155 : }
156 828 : gHistogramAccumulations->AppendElement(Accumulation{aId, aSample});
157 828 : ArmIPCTimer(locker);
158 : }
159 :
160 : void
161 294 : TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(mozilla::Telemetry::HistogramID aId,
162 : const nsCString& aKey, uint32_t aSample)
163 : {
164 588 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
165 294 : if (!gKeyedHistogramAccumulations) {
166 2 : gKeyedHistogramAccumulations = new nsTArray<KeyedAccumulation>();
167 : }
168 294 : if (gKeyedHistogramAccumulations->Length() >=
169 : kWaterMarkDiscardFactor * kHistogramAccumulationsArrayHighWaterMark) {
170 0 : gDiscardedData.mDiscardedKeyedHistogramAccumulations++;
171 0 : return;
172 : }
173 294 : if (gKeyedHistogramAccumulations->Length() == kHistogramAccumulationsArrayHighWaterMark) {
174 0 : DispatchIPCTimerFired();
175 : }
176 294 : gKeyedHistogramAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
177 294 : ArmIPCTimer(locker);
178 : }
179 :
180 : void
181 0 : TelemetryIPCAccumulator::RecordChildScalarAction(mozilla::Telemetry::ScalarID aId,
182 : ScalarActionType aAction, const ScalarVariant& aValue)
183 : {
184 0 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
185 : // Make sure to have the storage.
186 0 : if (!gChildScalarsActions) {
187 0 : gChildScalarsActions = new nsTArray<ScalarAction>();
188 : }
189 0 : if (gChildScalarsActions->Length() >=
190 : kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) {
191 0 : gDiscardedData.mDiscardedScalarActions++;
192 0 : return;
193 : }
194 0 : if (gChildScalarsActions->Length() == kScalarActionsArrayHighWaterMark) {
195 0 : DispatchIPCTimerFired();
196 : }
197 : // Store the action.
198 0 : gChildScalarsActions->AppendElement(ScalarAction{aId, aAction, Some(aValue)});
199 0 : ArmIPCTimer(locker);
200 : }
201 :
202 : void
203 0 : TelemetryIPCAccumulator::RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId,
204 : const nsAString& aKey,
205 : ScalarActionType aAction,
206 : const ScalarVariant& aValue)
207 : {
208 0 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
209 : // Make sure to have the storage.
210 0 : if (!gChildKeyedScalarsActions) {
211 0 : gChildKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
212 : }
213 0 : if (gChildKeyedScalarsActions->Length() >=
214 : kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) {
215 0 : gDiscardedData.mDiscardedKeyedScalarActions++;
216 0 : return;
217 : }
218 0 : if (gChildKeyedScalarsActions->Length() == kScalarActionsArrayHighWaterMark) {
219 0 : DispatchIPCTimerFired();
220 : }
221 : // Store the action.
222 0 : gChildKeyedScalarsActions->AppendElement(
223 0 : KeyedScalarAction{aId, aAction, NS_ConvertUTF16toUTF8(aKey), Some(aValue)});
224 0 : ArmIPCTimer(locker);
225 : }
226 :
227 : void
228 0 : TelemetryIPCAccumulator::RecordChildEvent(const mozilla::TimeStamp& timestamp,
229 : const nsACString& category,
230 : const nsACString& method,
231 : const nsACString& object,
232 : const mozilla::Maybe<nsCString>& value,
233 : const nsTArray<mozilla::Telemetry::EventExtraEntry>& extra)
234 : {
235 0 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
236 :
237 0 : if (!gChildEvents) {
238 0 : gChildEvents = new nsTArray<ChildEventData>();
239 : }
240 :
241 0 : if (gChildEvents->Length() >=
242 : kWaterMarkDiscardFactor * kEventsArrayHighWaterMark) {
243 0 : gDiscardedData.mDiscardedChildEvents++;
244 0 : return;
245 : }
246 :
247 0 : if (gChildEvents->Length() == kEventsArrayHighWaterMark) {
248 0 : DispatchIPCTimerFired();
249 : }
250 :
251 : // Store the event.
252 0 : gChildEvents->AppendElement(ChildEventData{timestamp, nsCString(category),
253 : nsCString(method), nsCString(object),
254 : value,
255 0 : nsTArray<mozilla::Telemetry::EventExtraEntry>(extra)});
256 0 : ArmIPCTimer(locker);
257 : }
258 :
259 : // This method takes the lock only to double-buffer the batched telemetry.
260 : // It releases the lock before calling out to IPC code which can (and does)
261 : // Accumulate (which would deadlock)
262 : template<class TActor>
263 : static void
264 3 : SendAccumulatedData(TActor* ipcActor)
265 : {
266 : // Get the accumulated data and free the storage buffers.
267 6 : nsTArray<Accumulation> accumulationsToSend;
268 6 : nsTArray<KeyedAccumulation> keyedAccumulationsToSend;
269 6 : nsTArray<ScalarAction> scalarsToSend;
270 6 : nsTArray<KeyedScalarAction> keyedScalarsToSend;
271 6 : nsTArray<ChildEventData> eventsToSend;
272 : DiscardedData discardedData;
273 :
274 : {
275 6 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
276 3 : if (gHistogramAccumulations) {
277 3 : accumulationsToSend.SwapElements(*gHistogramAccumulations);
278 : }
279 3 : if (gKeyedHistogramAccumulations) {
280 3 : keyedAccumulationsToSend.SwapElements(*gKeyedHistogramAccumulations);
281 : }
282 3 : if (gChildScalarsActions) {
283 0 : scalarsToSend.SwapElements(*gChildScalarsActions);
284 : }
285 3 : if (gChildKeyedScalarsActions) {
286 0 : keyedScalarsToSend.SwapElements(*gChildKeyedScalarsActions);
287 : }
288 3 : if (gChildEvents) {
289 0 : eventsToSend.SwapElements(*gChildEvents);
290 : }
291 3 : discardedData = gDiscardedData;
292 3 : gDiscardedData = {0};
293 : }
294 :
295 : // Send the accumulated data to the parent process.
296 3 : mozilla::Unused << NS_WARN_IF(!ipcActor);
297 3 : if (accumulationsToSend.Length()) {
298 : mozilla::Unused <<
299 3 : NS_WARN_IF(!ipcActor->SendAccumulateChildHistograms(accumulationsToSend));
300 : }
301 3 : if (keyedAccumulationsToSend.Length()) {
302 : mozilla::Unused <<
303 3 : NS_WARN_IF(!ipcActor->SendAccumulateChildKeyedHistograms(keyedAccumulationsToSend));
304 : }
305 3 : if (scalarsToSend.Length()) {
306 : mozilla::Unused <<
307 0 : NS_WARN_IF(!ipcActor->SendUpdateChildScalars(scalarsToSend));
308 : }
309 3 : if (keyedScalarsToSend.Length()) {
310 : mozilla::Unused <<
311 0 : NS_WARN_IF(!ipcActor->SendUpdateChildKeyedScalars(keyedScalarsToSend));
312 : }
313 3 : if (eventsToSend.Length()) {
314 : mozilla::Unused <<
315 0 : NS_WARN_IF(!ipcActor->SendRecordChildEvents(eventsToSend));
316 : }
317 : mozilla::Unused <<
318 3 : NS_WARN_IF(!ipcActor->SendRecordDiscardedData(discardedData));
319 3 : }
320 :
321 :
322 : // To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't
323 : // unset gIPCTimerArmed until the IPC completes
324 : //
325 : // This function must be called on the main thread, otherwise IPC will fail.
326 : void
327 3 : TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure)
328 : {
329 3 : MOZ_ASSERT(NS_IsMainThread());
330 :
331 : // Send accumulated data to the correct parent process.
332 3 : switch (XRE_GetProcessType()) {
333 : case GeckoProcessType_Content:
334 3 : SendAccumulatedData(mozilla::dom::ContentChild::GetSingleton());
335 3 : break;
336 : case GeckoProcessType_GPU:
337 0 : SendAccumulatedData(mozilla::gfx::GPUParent::GetSingleton());
338 0 : break;
339 : default:
340 0 : MOZ_ASSERT_UNREACHABLE("Unsupported process type");
341 : break;
342 : }
343 :
344 3 : gIPCTimerArmed = false;
345 3 : }
346 :
347 : void
348 0 : TelemetryIPCAccumulator::DeInitializeGlobalState()
349 : {
350 0 : MOZ_ASSERT(NS_IsMainThread());
351 :
352 0 : StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
353 0 : if (gIPCTimer) {
354 0 : NS_RELEASE(gIPCTimer);
355 : }
356 :
357 0 : gHistogramAccumulations = nullptr;
358 0 : gKeyedHistogramAccumulations = nullptr;
359 0 : gChildScalarsActions = nullptr;
360 0 : gChildKeyedScalarsActions = nullptr;
361 0 : gChildEvents = nullptr;
362 0 : }
363 :
364 : void
365 0 : TelemetryIPCAccumulator::DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent)
366 : {
367 0 : SystemGroup::EventTargetFor(TaskCategory::Other)->Dispatch(Move(aEvent),
368 0 : nsIEventTarget::DISPATCH_NORMAL);
369 9 : }
|