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