1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 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 "Telemetry.h"
8 #include "TelemetryOrigin.h"
9
10 #include "nsTHashMap.h"
11 #include "nsIObserverService.h"
12 #include "nsPrintfCString.h"
13 #include "nsTArray.h"
14 #include "TelemetryCommon.h"
15 #include "TelemetryOriginEnums.h"
16
17 #include "js/Array.h" // JS::NewArrayObject
18 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
19 #include "mozilla/Atomics.h"
20 #include "mozilla/Base64.h"
21 #include "mozilla/dom/PrioEncoder.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/Services.h"
24 #include "mozilla/StaticMutex.h"
25 #include "mozilla/Tuple.h"
26 #include "mozilla/UniquePtr.h"
27
28 #include <cmath>
29 #include <type_traits>
30
31 using mozilla::Get;
32 using mozilla::MakeTuple;
33 using mozilla::MakeUnique;
34 using mozilla::MallocSizeOf;
35 using mozilla::StaticMutex;
36 using mozilla::StaticMutexAutoLock;
37 using mozilla::Tuple;
38 using mozilla::UniquePtr;
39 using mozilla::dom::PrioEncoder;
40 using mozilla::Telemetry::OriginMetricID;
41 using mozilla::Telemetry::Common::ToJSString;
42
43 /***********************************************************************
44 *
45 * Firefox Origin Telemetry
46 * Docs:
47 * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/origin.html
48 *
49 * Origin Telemetry stores pairs of information (metric, origin) which boils
50 * down to "$metric happened on $origin".
51 *
52 * Prio can only encode up-to-2046-length bit vectors. The process of
53 * transforming these pairs of information into bit vectors is called "App
54 * Encoding". The bit vectors are then "Prio Encoded" into binary goop. The
55 * binary goop is then "Base64 Encoded" into strings.
56 *
57 */
58
59 ////////////////////////////////////////////////////////////////////////
60 ////////////////////////////////////////////////////////////////////////
61 //
62 // PRIVATE TYPES
63
64 namespace {
65
66 class OriginMetricIDHashKey : public PLDHashEntryHdr {
67 public:
68 typedef const OriginMetricID& KeyType;
69 typedef const OriginMetricID* KeyTypePointer;
70
OriginMetricIDHashKey(KeyTypePointer aKey)71 explicit OriginMetricIDHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
OriginMetricIDHashKey(OriginMetricIDHashKey && aOther)72 OriginMetricIDHashKey(OriginMetricIDHashKey&& aOther)
73 : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {}
74 ~OriginMetricIDHashKey() = default;
75
GetKey() const76 KeyType GetKey() const { return mValue; }
KeyEquals(KeyTypePointer aKey) const77 bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
78
KeyToPointer(KeyType aKey)79 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
HashKey(KeyTypePointer aKey)80 static PLDHashNumber HashKey(KeyTypePointer aKey) {
81 return static_cast<std::underlying_type<OriginMetricID>::type>(*aKey);
82 }
83 enum { ALLOW_MEMMOVE = true };
84
85 private:
86 const OriginMetricID mValue;
87 };
88
89 } // anonymous namespace
90
91 ////////////////////////////////////////////////////////////////////////
92 ////////////////////////////////////////////////////////////////////////
93 //
94 // PRIVATE STATE, SHARED BY ALL THREADS
95 //
96 // Access for all of this state (except gInitDone) must be guarded by
97 // gTelemetryOriginMutex.
98
99 namespace {
100
101 // This is a StaticMutex rather than a plain Mutex (1) so that
102 // it gets initialised in a thread-safe manner the first time
103 // it is used, and (2) because it is never de-initialised, and
104 // a normal Mutex would show up as a leak in BloatView. StaticMutex
105 // also has the "OffTheBooks" property, so it won't show as a leak
106 // in BloatView.
107 // Another reason to use a StaticMutex instead of a plain Mutex is
108 // that, due to the nature of Telemetry, we cannot rely on having a
109 // mutex initialized in InitializeGlobalState. Unfortunately, we
110 // cannot make sure that no other function is called before this point.
111 static StaticMutex gTelemetryOriginMutex;
112
113 typedef nsTArray<Tuple<const char*, const char*>> OriginHashesList;
114 UniquePtr<OriginHashesList> gOriginHashesList;
115
116 typedef nsTHashMap<nsCStringHashKey, size_t> OriginToIndexMap;
117 UniquePtr<OriginToIndexMap> gOriginToIndexMap;
118
119 typedef nsTHashMap<nsCStringHashKey, size_t> HashToIndexMap;
120 UniquePtr<HashToIndexMap> gHashToIndexMap;
121
122 typedef nsTHashMap<nsCStringHashKey, uint32_t> OriginBag;
123 typedef nsTHashMap<OriginMetricIDHashKey, OriginBag> IdToOriginBag;
124
125 UniquePtr<IdToOriginBag> gMetricToOriginBag;
126
127 mozilla::Atomic<bool, mozilla::Relaxed> gInitDone(false);
128
129 // Useful for app-encoded data
130 typedef nsTArray<std::pair<OriginMetricID, nsTArray<nsTArray<bool>>>>
131 IdBoolsPairArray;
132
133 // Prio has a maximum supported number of bools it can encode at a time.
134 // This means a single metric may result in several encoded payloads if the
135 // number of origins exceeds the number of bools.
136 // Each encoded payload corresponds to an element in the `prioData` array in the
137 // "prio" ping.
138 // This number is the number of encoded payloads needed per metric, and is
139 // equivalent to "how many bitvectors do we need to encode this whole list of
140 // origins?"
141 static uint32_t gPrioDatasPerMetric;
142
143 // The number of "meta-origins": in-band metadata about origin telemetry.
144 // Currently 1: the "unknown origin recorded" meta-origin.
145 static uint32_t kNumMetaOrigins = 1;
146
147 constexpr auto kUnknownOrigin = "__UNKNOWN__"_ns;
148
149 } // namespace
150
151 ////////////////////////////////////////////////////////////////////////
152 ////////////////////////////////////////////////////////////////////////
153 //
154 // PRIVATE: thread-safe helpers
155
156 namespace {
157
GetNameForMetricID(OriginMetricID aId)158 const char* GetNameForMetricID(OriginMetricID aId) {
159 MOZ_ASSERT(aId < OriginMetricID::Count);
160 return mozilla::Telemetry::MetricIDToString[static_cast<uint32_t>(aId)];
161 }
162
163 // Calculates the number of `prioData` elements we'd need if we were asked for
164 // an encoded snapshot right now.
PrioDataCount(const StaticMutexAutoLock & lock)165 uint32_t PrioDataCount(const StaticMutexAutoLock& lock) {
166 uint32_t count = 0;
167 for (const auto& origins : gMetricToOriginBag->Values()) {
168 uint32_t maxOriginCount = 0;
169 for (const auto& data : origins.Values()) {
170 maxOriginCount = std::max(maxOriginCount, data);
171 }
172 count += gPrioDatasPerMetric * maxOriginCount;
173 }
174 return count;
175 }
176
177 // Takes the storage and turns it into bool arrays for Prio to encode, turning
178 // { metric1: [origin1, origin2, ...], ...}
179 // into
180 // [(metric1, [[shard1], [shard2], ...]), ...]
181 // Note: if an origin is present multiple times for a given metric, we must
182 // generate multiple (id, boolvectors) pairs so that they are all reported.
183 // Meaning
184 // { metric1: [origin1, origin2, origin2] }
185 // must turn into (with a pretend gNumBooleans of 1)
186 // [(metric1, [[1], [1]]), (metric1, [[0], [1]])]
AppEncodeTo(const StaticMutexAutoLock & lock,IdBoolsPairArray & aResult)187 nsresult AppEncodeTo(const StaticMutexAutoLock& lock,
188 IdBoolsPairArray& aResult) {
189 for (const auto& bagEntry : *gMetricToOriginBag) {
190 OriginMetricID id = bagEntry.GetKey();
191 const OriginBag& bag = bagEntry.GetData();
192
193 uint32_t generation = 1;
194 uint32_t maxGeneration = 1;
195 do {
196 // Fill in the result bool vectors with `false`s.
197 nsTArray<nsTArray<bool>> metricData(gPrioDatasPerMetric);
198 metricData.SetLength(gPrioDatasPerMetric);
199 for (size_t i = 0; i < metricData.Length() - 1; ++i) {
200 metricData[i].SetLength(PrioEncoder::gNumBooleans);
201 for (auto& metricDatum : metricData[i]) {
202 metricDatum = false;
203 }
204 }
205 auto& lastArray = metricData[metricData.Length() - 1];
206 lastArray.SetLength((gOriginHashesList->Length() + kNumMetaOrigins) %
207 PrioEncoder::gNumBooleans);
208 for (auto& metricDatum : lastArray) {
209 metricDatum = false;
210 }
211
212 for (const auto& originEntry : bag) {
213 uint32_t originCount = originEntry.GetData();
214 if (originCount >= generation) {
215 maxGeneration = std::max(maxGeneration, originCount);
216
217 const nsACString& origin = originEntry.GetKey();
218 size_t index;
219 if (!gOriginToIndexMap->Get(origin, &index)) {
220 return NS_ERROR_FAILURE;
221 }
222 MOZ_ASSERT(index < (gOriginHashesList->Length() + kNumMetaOrigins));
223 size_t shardIndex = index / PrioEncoder::gNumBooleans;
224 MOZ_ASSERT(shardIndex < metricData.Length());
225 MOZ_ASSERT(index % PrioEncoder::gNumBooleans <
226 metricData[shardIndex].Length());
227 metricData[shardIndex][index % PrioEncoder::gNumBooleans] = true;
228 }
229 }
230 aResult.EmplaceBack(id, std::move(metricData));
231 } while (generation++ < maxGeneration);
232 }
233 return NS_OK;
234 }
235
236 } // anonymous namespace
237
238 ////////////////////////////////////////////////////////////////////////
239 ////////////////////////////////////////////////////////////////////////
240 //
241 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryOrigin::
242
InitializeGlobalState()243 void TelemetryOrigin::InitializeGlobalState() {
244 if (!XRE_IsParentProcess()) {
245 return;
246 }
247
248 StaticMutexAutoLock locker(gTelemetryOriginMutex);
249
250 MOZ_ASSERT(!gInitDone,
251 "TelemetryOrigin::InitializeGlobalState "
252 "may only be called once");
253
254 // The contents and order of the arrays that follow matter.
255 // Both ensure a consistent app-encoding.
256 static const char sOriginStrings[] = {
257 #define ORIGIN(origin, hash) origin "\0"
258 #include "TelemetryOriginData.inc"
259 #undef ORIGIN
260 };
261 static const char sHashStrings[] = {
262 #define ORIGIN(origin, hash) hash "\0"
263 #include "TelemetryOriginData.inc"
264 #undef ORIGIN
265 };
266
267 struct OriginHashLengths {
268 uint8_t originLength;
269 uint8_t hashLength;
270 };
271 static const OriginHashLengths sOriginHashLengths[] = {
272 #define ORIGIN(origin, hash) {sizeof(origin), sizeof(hash)},
273 #include "TelemetryOriginData.inc"
274 #undef ORIGIN
275 };
276
277 static const size_t kNumOrigins = MOZ_ARRAY_LENGTH(sOriginHashLengths);
278
279 gOriginHashesList = MakeUnique<OriginHashesList>(kNumOrigins);
280
281 gPrioDatasPerMetric =
282 ceil(static_cast<double>(kNumOrigins + kNumMetaOrigins) /
283 PrioEncoder::gNumBooleans);
284
285 gOriginToIndexMap =
286 MakeUnique<OriginToIndexMap>(kNumOrigins + kNumMetaOrigins);
287 gHashToIndexMap = MakeUnique<HashToIndexMap>(kNumOrigins);
288 size_t originOffset = 0;
289 size_t hashOffset = 0;
290 for (size_t i = 0; i < kNumOrigins; ++i) {
291 const char* origin = &sOriginStrings[originOffset];
292 const char* hash = &sHashStrings[hashOffset];
293 MOZ_ASSERT(!kUnknownOrigin.Equals(origin),
294 "Unknown origin literal is reserved in Origin Telemetry");
295
296 gOriginHashesList->AppendElement(MakeTuple(origin, hash));
297
298 const size_t originLength = sOriginHashLengths[i].originLength;
299 const size_t hashLength = sOriginHashLengths[i].hashLength;
300
301 originOffset += originLength;
302 hashOffset += hashLength;
303
304 // -1 to leave off the null terminators.
305 gOriginToIndexMap->InsertOrUpdate(
306 nsDependentCString(origin, originLength - 1), i);
307 gHashToIndexMap->InsertOrUpdate(nsDependentCString(hash, hashLength - 1),
308 i);
309 }
310
311 // Add the meta-origin for tracking recordings to untracked origins.
312 gOriginToIndexMap->InsertOrUpdate(kUnknownOrigin,
313 gOriginHashesList->Length());
314
315 gMetricToOriginBag = MakeUnique<IdToOriginBag>();
316
317 // This map shouldn't change at runtime, so make debug builds complain
318 // if it tries.
319 gOriginToIndexMap->MarkImmutable();
320 gHashToIndexMap->MarkImmutable();
321
322 gInitDone = true;
323 }
324
DeInitializeGlobalState()325 void TelemetryOrigin::DeInitializeGlobalState() {
326 if (!XRE_IsParentProcess()) {
327 return;
328 }
329
330 StaticMutexAutoLock locker(gTelemetryOriginMutex);
331 MOZ_ASSERT(gInitDone);
332
333 if (!gInitDone) {
334 return;
335 }
336
337 gOriginHashesList = nullptr;
338
339 gOriginToIndexMap = nullptr;
340
341 gHashToIndexMap = nullptr;
342
343 gMetricToOriginBag = nullptr;
344
345 gInitDone = false;
346 }
347
RecordOrigin(OriginMetricID aId,const nsACString & aOrigin)348 nsresult TelemetryOrigin::RecordOrigin(OriginMetricID aId,
349 const nsACString& aOrigin) {
350 if (!XRE_IsParentProcess()) {
351 return NS_ERROR_FAILURE;
352 }
353
354 uint32_t prioDataCount;
355 {
356 StaticMutexAutoLock locker(gTelemetryOriginMutex);
357
358 // Common Telemetry error-handling practices for recording functions:
359 // only illegal calls return errors whereas merely incorrect ones are mutely
360 // ignored.
361 if (!gInitDone) {
362 return NS_OK;
363 }
364
365 size_t index;
366 nsCString origin(aOrigin);
367 if (gHashToIndexMap->Get(aOrigin, &index)) {
368 MOZ_ASSERT(aOrigin.Equals(Get<1>((*gOriginHashesList)[index])));
369 origin = Get<0>((*gOriginHashesList)[index]);
370 }
371
372 if (!gOriginToIndexMap->Contains(origin)) {
373 // Only record one unknown origin per metric per snapshot.
374 // (otherwise we may get swamped and blow our data budget.)
375 if (gMetricToOriginBag->Contains(aId) &&
376 gMetricToOriginBag->LookupOrInsert(aId).Contains(kUnknownOrigin)) {
377 return NS_OK;
378 }
379 origin = kUnknownOrigin;
380 }
381
382 auto& originBag = gMetricToOriginBag->LookupOrInsert(aId);
383 originBag.LookupOrInsert(origin)++;
384
385 prioDataCount = PrioDataCount(locker);
386 }
387
388 static uint32_t sPrioPingLimit =
389 mozilla::Preferences::GetUint("toolkit.telemetry.prioping.dataLimit", 10);
390 if (prioDataCount >= sPrioPingLimit) {
391 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
392 if (os) {
393 // Ensure we don't notify while holding the lock in case of synchronous
394 // dispatch. May deadlock ourselves if we then trigger a snapshot.
395 os->NotifyObservers(nullptr, "origin-telemetry-storage-limit-reached",
396 nullptr);
397 }
398 }
399
400 return NS_OK;
401 }
402
GetOriginSnapshot(bool aClear,JSContext * aCx,JS::MutableHandleValue aResult)403 nsresult TelemetryOrigin::GetOriginSnapshot(bool aClear, JSContext* aCx,
404 JS::MutableHandleValue aResult) {
405 if (NS_WARN_IF(!XRE_IsParentProcess())) {
406 return NS_ERROR_FAILURE;
407 }
408
409 if (!gInitDone) {
410 return NS_OK;
411 }
412
413 // Step 1: Grab the lock, copy into stack-local storage, optionally clear.
414 IdToOriginBag copy;
415 {
416 StaticMutexAutoLock locker(gTelemetryOriginMutex);
417
418 if (aClear) {
419 // I'd really prefer to clear after we're sure the snapshot didn't go
420 // awry, but we can't hold a lock preventing recording while using JS
421 // APIs. And replaying any interleaving recording sounds like too much
422 // squeeze for not enough juice.
423
424 gMetricToOriginBag->SwapElements(copy);
425 } else {
426 for (const auto& entry : *gMetricToOriginBag) {
427 copy.InsertOrUpdate(entry.GetKey(), entry.GetData().Clone());
428 }
429 }
430 }
431
432 // Step 2: Without the lock, generate JS datastructure for snapshotting
433 JS::Rooted<JSObject*> rootObj(aCx, JS_NewPlainObject(aCx));
434 if (NS_WARN_IF(!rootObj)) {
435 return NS_ERROR_FAILURE;
436 }
437 aResult.setObject(*rootObj);
438 for (const auto& entry : copy) {
439 JS::RootedObject originsObj(aCx, JS_NewPlainObject(aCx));
440 if (NS_WARN_IF(!originsObj)) {
441 return NS_ERROR_FAILURE;
442 }
443 if (!JS_DefineProperty(aCx, rootObj, GetNameForMetricID(entry.GetKey()),
444 originsObj, JSPROP_ENUMERATE)) {
445 NS_WARNING("Failed to define property in origin snapshot.");
446 return NS_ERROR_FAILURE;
447 }
448
449 for (const auto& originEntry : entry.GetData()) {
450 if (!JS_DefineProperty(aCx, originsObj,
451 nsPromiseFlatCString(originEntry.GetKey()).get(),
452 originEntry.GetData(), JSPROP_ENUMERATE)) {
453 NS_WARNING("Failed to define origin and count in snapshot.");
454 return NS_ERROR_FAILURE;
455 }
456 }
457 }
458
459 return NS_OK;
460 }
461
GetEncodedOriginSnapshot(bool aClear,JSContext * aCx,JS::MutableHandleValue aSnapshot)462 nsresult TelemetryOrigin::GetEncodedOriginSnapshot(
463 bool aClear, JSContext* aCx, JS::MutableHandleValue aSnapshot) {
464 if (!XRE_IsParentProcess()) {
465 return NS_ERROR_FAILURE;
466 }
467
468 if (!gInitDone) {
469 return NS_OK;
470 }
471
472 // Step 1: Take the lock and app-encode. Optionally clear.
473 nsresult rv;
474 IdBoolsPairArray appEncodedMetricData;
475 {
476 StaticMutexAutoLock lock(gTelemetryOriginMutex);
477
478 rv = AppEncodeTo(lock, appEncodedMetricData);
479 if (NS_WARN_IF(NS_FAILED(rv))) {
480 return rv;
481 }
482
483 if (aClear) {
484 // I'd really prefer to clear after we're sure the snapshot didn't go
485 // awry, but we can't hold a lock preventing recording while using JS
486 // APIs. And replaying any interleaving recording sounds like too much
487 // squeeze for not enough juice.
488
489 gMetricToOriginBag->Clear();
490 }
491 }
492
493 // Step 2: Don't need the lock to prio-encode and base64-encode
494 nsTArray<std::pair<nsCString, std::pair<nsCString, nsCString>>> prioData;
495 for (auto& metricData : appEncodedMetricData) {
496 auto& boolVectors = metricData.second;
497 for (uint32_t i = 0; i < boolVectors.Length(); ++i) {
498 // "encoding" is of the form `metricName-X` where X is the shard index.
499 nsCString encodingName =
500 nsPrintfCString("%s-%u", GetNameForMetricID(metricData.first), i);
501 nsCString aResult;
502 nsCString bResult;
503 rv = PrioEncoder::EncodeNative(encodingName, boolVectors[i], aResult,
504 bResult);
505 if (NS_WARN_IF(NS_FAILED(rv))) {
506 return rv;
507 }
508 nsCString aBase64;
509 rv = mozilla::Base64Encode(aResult, aBase64);
510 if (NS_WARN_IF(NS_FAILED(rv))) {
511 return rv;
512 }
513 nsCString bBase64;
514 rv = mozilla::Base64Encode(bResult, bBase64);
515 if (NS_WARN_IF(NS_FAILED(rv))) {
516 return rv;
517 }
518
519 prioData.EmplaceBack(std::move(encodingName),
520 std::pair(std::move(aBase64), std::move(bBase64)));
521 }
522 }
523
524 // Step 3: Still don't need the lock to translate to JS
525 // The resulting data structure is:
526 // [{
527 // encoding: <encoding name>,
528 // prio: {
529 // a: <base64 string>,
530 // b: <base64 string>,
531 // },
532 // }, ...]
533
534 JS::RootedObject prioDataArray(aCx,
535 JS::NewArrayObject(aCx, prioData.Length()));
536 if (NS_WARN_IF(!prioDataArray)) {
537 return NS_ERROR_FAILURE;
538 }
539 uint32_t i = 0;
540 for (auto& prioDatum : prioData) {
541 JS::RootedObject prioDatumObj(aCx, JS_NewPlainObject(aCx));
542 if (NS_WARN_IF(!prioDatumObj)) {
543 return NS_ERROR_FAILURE;
544 }
545 JSString* encoding = ToJSString(aCx, prioDatum.first);
546 JS::RootedString rootedEncoding(aCx, encoding);
547 if (NS_WARN_IF(!JS_DefineProperty(aCx, prioDatumObj, "encoding",
548 rootedEncoding, JSPROP_ENUMERATE))) {
549 return NS_ERROR_FAILURE;
550 }
551
552 JS::RootedObject prioObj(aCx, JS_NewPlainObject(aCx));
553 if (NS_WARN_IF(!prioObj)) {
554 return NS_ERROR_FAILURE;
555 }
556 if (NS_WARN_IF(!JS_DefineProperty(aCx, prioDatumObj, "prio", prioObj,
557 JSPROP_ENUMERATE))) {
558 return NS_ERROR_FAILURE;
559 }
560
561 JS::RootedString aRootStr(aCx, ToJSString(aCx, prioDatum.second.first));
562 if (NS_WARN_IF(!JS_DefineProperty(aCx, prioObj, "a", aRootStr,
563 JSPROP_ENUMERATE))) {
564 return NS_ERROR_FAILURE;
565 }
566 JS::RootedString bRootStr(aCx, ToJSString(aCx, prioDatum.second.second));
567 if (NS_WARN_IF(!JS_DefineProperty(aCx, prioObj, "b", bRootStr,
568 JSPROP_ENUMERATE))) {
569 return NS_ERROR_FAILURE;
570 }
571
572 if (NS_WARN_IF(!JS_DefineElement(aCx, prioDataArray, i++, prioDatumObj,
573 JSPROP_ENUMERATE))) {
574 return NS_ERROR_FAILURE;
575 }
576 }
577
578 aSnapshot.setObject(*prioDataArray);
579
580 return NS_OK;
581 }
582
583 /**
584 * Resets all the stored events. This is intended to be only used in tests.
585 */
ClearOrigins()586 void TelemetryOrigin::ClearOrigins() {
587 StaticMutexAutoLock lock(gTelemetryOriginMutex);
588
589 if (!gInitDone) {
590 return;
591 }
592
593 gMetricToOriginBag->Clear();
594 }
595
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)596 size_t TelemetryOrigin::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
597 StaticMutexAutoLock locker(gTelemetryOriginMutex);
598 size_t n = 0;
599
600 if (!gInitDone) {
601 return 0;
602 }
603
604 n += gMetricToOriginBag->ShallowSizeOfIncludingThis(aMallocSizeOf);
605 for (const auto& origins : gMetricToOriginBag->Values()) {
606 // The string hashkey and count should both be contained by the hashtable.
607 n += origins.ShallowSizeOfExcludingThis(aMallocSizeOf);
608 }
609
610 // The string hashkey and ID should both be contained within the hashtable.
611 n += gOriginToIndexMap->ShallowSizeOfIncludingThis(aMallocSizeOf);
612
613 return n;
614 }
615
SizeOfPrioDatasPerMetric()616 size_t TelemetryOrigin::SizeOfPrioDatasPerMetric() {
617 return gPrioDatasPerMetric;
618 }
619