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 "nsITelemetry.h"
8 #include "nsIVariant.h"
9 #include "nsVariant.h"
10 #include "nsHashKeys.h"
11 #include "nsBaseHashtable.h"
12 #include "nsClassHashtable.h"
13 #include "nsDataHashtable.h"
14 #include "nsIXPConnect.h"
15 #include "nsContentUtils.h"
16 #include "nsThreadUtils.h"
17 #include "nsJSUtils.h"
18 #include "nsPrintfCString.h"
19 #include "mozilla/dom/ContentParent.h"
20 #include "mozilla/dom/PContent.h"
21 #include "mozilla/StaticMutex.h"
22 #include "mozilla/StaticPtr.h"
23 #include "mozilla/Unused.h"
24 
25 #include "TelemetryCommon.h"
26 #include "TelemetryScalar.h"
27 #include "TelemetryScalarData.h"
28 #include "ipc/TelemetryComms.h"
29 #include "ipc/TelemetryIPCAccumulator.h"
30 
31 using mozilla::StaticAutoPtr;
32 using mozilla::StaticMutex;
33 using mozilla::StaticMutexAutoLock;
34 using mozilla::Telemetry::Common::AutoHashtable;
35 using mozilla::Telemetry::Common::CanRecordDataset;
36 using mozilla::Telemetry::Common::GetNameForProcessID;
37 using mozilla::Telemetry::Common::IsExpiredVersion;
38 using mozilla::Telemetry::Common::IsInDataset;
39 using mozilla::Telemetry::Common::IsValidIdentifierString;
40 using mozilla::Telemetry::Common::LogToBrowserConsole;
41 using mozilla::Telemetry::Common::RecordedProcessType;
42 using mozilla::Telemetry::DynamicScalarDefinition;
43 using mozilla::Telemetry::ProcessID;
44 using mozilla::Telemetry::ScalarActionType;
45 using mozilla::Telemetry::ScalarID;
46 using mozilla::Telemetry::ScalarVariant;
47 
48 namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
49 
50 ////////////////////////////////////////////////////////////////////////
51 ////////////////////////////////////////////////////////////////////////
52 //
53 // Naming: there are two kinds of functions in this file:
54 //
55 // * Functions named internal_*: these can only be reached via an
56 //   interface function (TelemetryScalar::*). If they access shared
57 //   state, they require the interface function to have acquired
58 //   |gTelemetryScalarMutex| to ensure thread safety.
59 //
60 // * Functions named TelemetryScalar::*. This is the external interface.
61 //   Entries and exits to these functions are serialised using
62 //   |gTelemetryScalarsMutex|.
63 //
64 // Avoiding races and deadlocks:
65 //
66 // All functions in the external interface (TelemetryScalar::*) are
67 // serialised using the mutex |gTelemetryScalarsMutex|. This means
68 // that the external interface is thread-safe. But it also brings
69 // a danger of deadlock if any function in the external interface can
70 // get back to that interface. That is, we will deadlock on any call
71 // chain like this
72 //
73 // TelemetryScalar::* -> .. any functions .. -> TelemetryScalar::*
74 //
75 // To reduce the danger of that happening, observe the following rules:
76 //
77 // * No function in TelemetryScalar::* may directly call, nor take the
78 //   address of, any other function in TelemetryScalar::*.
79 //
80 // * No internal function internal_* may call, nor take the address
81 //   of, any function in TelemetryScalar::*.
82 
83 ////////////////////////////////////////////////////////////////////////
84 ////////////////////////////////////////////////////////////////////////
85 //
86 // PRIVATE TYPES
87 
88 namespace {
89 
90 const uint32_t kMaximumNumberOfKeys = 100;
91 const uint32_t kMaximumKeyStringLength = 70;
92 const uint32_t kMaximumStringValueLength = 50;
93 // The category and scalar name maximum lengths are used by the dynamic
94 // scalar registration function and must match the constants used by
95 // the 'parse_scalars.py' script for static scalars.
96 const uint32_t kMaximumCategoryNameLength = 40;
97 const uint32_t kMaximumScalarNameLength = 40;
98 const uint32_t kScalarCount =
99     static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
100 
101 enum class ScalarResult : uint8_t {
102   // Nothing went wrong.
103   Ok,
104   // General Scalar Errors
105   NotInitialized,
106   CannotUnpackVariant,
107   CannotRecordInProcess,
108   CannotRecordDataset,
109   KeyedTypeMismatch,
110   UnknownScalar,
111   OperationNotSupported,
112   InvalidType,
113   InvalidValue,
114   // Keyed Scalar Errors
115   KeyIsEmpty,
116   KeyTooLong,
117   TooManyKeys,
118   // String Scalar Errors
119   StringTooLong,
120   // Unsigned Scalar Errors
121   UnsignedNegativeValue,
122   UnsignedTruncatedValue,
123 };
124 
125 // A common identifier for both built-in and dynamic scalars.
126 struct ScalarKey {
127   uint32_t id;
128   bool dynamic;
129 };
130 
131 /**
132  * Scalar information for dynamic definitions.
133  */
134 struct DynamicScalarInfo : BaseScalarInfo {
135   nsCString mDynamicName;
136   bool mDynamicExpiration;
137 
DynamicScalarInfo__anonc2c1bcfa0111::DynamicScalarInfo138   DynamicScalarInfo(uint32_t aKind, bool aRecordOnRelease, bool aExpired,
139                     const nsACString& aName, bool aKeyed, bool aBuiltin)
140       : BaseScalarInfo(aKind,
141                        aRecordOnRelease
142                            ? nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT
143                            : nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
144                        RecordedProcessType::All, aKeyed, aBuiltin),
145         mDynamicName(aName),
146         mDynamicExpiration(aExpired) {}
147 
148   // The following functions will read the stored text
149   // instead of looking it up in the statically generated
150   // tables.
151   const char* name() const override;
152   const char* expiration() const override;
153 };
154 
name() const155 const char* DynamicScalarInfo::name() const { return mDynamicName.get(); }
156 
expiration() const157 const char* DynamicScalarInfo::expiration() const {
158   // Dynamic scalars can either be expired or not (boolean flag).
159   // Return an appropriate version string to leverage the scalar expiration
160   // logic.
161   return mDynamicExpiration ? "1.0" : "never";
162 }
163 
164 typedef nsBaseHashtableET<nsDepCharHashKey, ScalarKey> CharPtrEntryType;
165 typedef AutoHashtable<CharPtrEntryType> ScalarMapType;
166 
167 // Dynamic scalar definitions.
168 StaticAutoPtr<nsTArray<DynamicScalarInfo>> gDynamicScalarInfo;
169 
internal_GetScalarInfo(const StaticMutexAutoLock & lock,const ScalarKey & aId)170 const BaseScalarInfo& internal_GetScalarInfo(const StaticMutexAutoLock& lock,
171                                              const ScalarKey& aId) {
172   if (!aId.dynamic) {
173     return gScalars[aId.id];
174   }
175 
176   return (*gDynamicScalarInfo)[aId.id];
177 }
178 
IsValidEnumId(mozilla::Telemetry::ScalarID aID)179 bool IsValidEnumId(mozilla::Telemetry::ScalarID aID) {
180   return aID < mozilla::Telemetry::ScalarID::ScalarCount;
181 }
182 
internal_IsValidId(const StaticMutexAutoLock & lock,const ScalarKey & aId)183 bool internal_IsValidId(const StaticMutexAutoLock& lock, const ScalarKey& aId) {
184   // Please note that this function needs to be called with the scalar
185   // mutex being acquired: other functions might be messing with
186   // |gDynamicScalarInfo|.
187   return aId.dynamic
188              ? (aId.id < gDynamicScalarInfo->Length())
189              : IsValidEnumId(static_cast<mozilla::Telemetry::ScalarID>(aId.id));
190 }
191 
192 /**
193  * Convert a nsIVariant to a mozilla::Variant, which is used for
194  * accumulating child process scalars.
195  */
GetVariantFromIVariant(nsIVariant * aInput,uint32_t aScalarKind,mozilla::Maybe<ScalarVariant> & aOutput)196 ScalarResult GetVariantFromIVariant(nsIVariant* aInput, uint32_t aScalarKind,
197                                     mozilla::Maybe<ScalarVariant>& aOutput) {
198   switch (aScalarKind) {
199     case nsITelemetry::SCALAR_TYPE_COUNT: {
200       uint32_t val = 0;
201       nsresult rv = aInput->GetAsUint32(&val);
202       if (NS_FAILED(rv)) {
203         return ScalarResult::CannotUnpackVariant;
204       }
205       aOutput = mozilla::Some(mozilla::AsVariant(val));
206       break;
207     }
208     case nsITelemetry::SCALAR_TYPE_STRING: {
209       nsString val;
210       nsresult rv = aInput->GetAsAString(val);
211       if (NS_FAILED(rv)) {
212         return ScalarResult::CannotUnpackVariant;
213       }
214       aOutput = mozilla::Some(mozilla::AsVariant(val));
215       break;
216     }
217     case nsITelemetry::SCALAR_TYPE_BOOLEAN: {
218       bool val = false;
219       nsresult rv = aInput->GetAsBool(&val);
220       if (NS_FAILED(rv)) {
221         return ScalarResult::CannotUnpackVariant;
222       }
223       aOutput = mozilla::Some(mozilla::AsVariant(val));
224       break;
225     }
226     default:
227       MOZ_ASSERT(false, "Unknown scalar kind.");
228       return ScalarResult::UnknownScalar;
229   }
230   return ScalarResult::Ok;
231 }
232 
233 // Implements the methods for ScalarInfo.
name() const234 const char* ScalarInfo::name() const {
235   return &gScalarsStringTable[this->name_offset];
236 }
237 
expiration() const238 const char* ScalarInfo::expiration() const {
239   return &gScalarsStringTable[this->expiration_offset];
240 }
241 
242 /**
243  * The base scalar object, that serves as a common ancestor for storage
244  * purposes.
245  */
246 class ScalarBase {
247  public:
248   virtual ~ScalarBase() = default;
249 
250   // Set, Add and SetMaximum functions as described in the Telemetry IDL.
251   virtual ScalarResult SetValue(nsIVariant* aValue) = 0;
AddValue(nsIVariant * aValue)252   virtual ScalarResult AddValue(nsIVariant* aValue) {
253     return ScalarResult::OperationNotSupported;
254   }
SetMaximum(nsIVariant * aValue)255   virtual ScalarResult SetMaximum(nsIVariant* aValue) {
256     return ScalarResult::OperationNotSupported;
257   }
258 
259   // Convenience methods used by the C++ API.
SetValue(uint32_t aValue)260   virtual void SetValue(uint32_t aValue) {
261     mozilla::Unused << HandleUnsupported();
262   }
SetValue(const nsAString & aValue)263   virtual ScalarResult SetValue(const nsAString& aValue) {
264     return HandleUnsupported();
265   }
SetValue(bool aValue)266   virtual void SetValue(bool aValue) { mozilla::Unused << HandleUnsupported(); }
AddValue(uint32_t aValue)267   virtual void AddValue(uint32_t aValue) {
268     mozilla::Unused << HandleUnsupported();
269   }
SetMaximum(uint32_t aValue)270   virtual void SetMaximum(uint32_t aValue) {
271     mozilla::Unused << HandleUnsupported();
272   }
273 
274   // GetValue is used to get the value of the scalar when persisting it to JS.
275   virtual nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const = 0;
276 
277   // To measure the memory stats.
278   virtual size_t SizeOfIncludingThis(
279       mozilla::MallocSizeOf aMallocSizeOf) const = 0;
280 
281  private:
282   ScalarResult HandleUnsupported() const;
283 };
284 
HandleUnsupported() const285 ScalarResult ScalarBase::HandleUnsupported() const {
286   MOZ_ASSERT(false, "This operation is not support for this scalar type.");
287   return ScalarResult::OperationNotSupported;
288 }
289 
290 /**
291  * The implementation for the unsigned int scalar type.
292  */
293 class ScalarUnsigned : public ScalarBase {
294  public:
295   using ScalarBase::SetValue;
296 
ScalarUnsigned()297   ScalarUnsigned() : mStorage(0){};
298   ~ScalarUnsigned() override = default;
299 
300   ScalarResult SetValue(nsIVariant* aValue) final;
301   void SetValue(uint32_t aValue) final;
302   ScalarResult AddValue(nsIVariant* aValue) final;
303   void AddValue(uint32_t aValue) final;
304   ScalarResult SetMaximum(nsIVariant* aValue) final;
305   void SetMaximum(uint32_t aValue) final;
306   nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
307   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
308 
309  private:
310   uint32_t mStorage;
311 
312   ScalarResult CheckInput(nsIVariant* aValue);
313 
314   // Prevent copying.
315   ScalarUnsigned(const ScalarUnsigned& aOther) = delete;
316   void operator=(const ScalarUnsigned& aOther) = delete;
317 };
318 
SetValue(nsIVariant * aValue)319 ScalarResult ScalarUnsigned::SetValue(nsIVariant* aValue) {
320   ScalarResult sr = CheckInput(aValue);
321   if (sr == ScalarResult::UnsignedNegativeValue) {
322     return sr;
323   }
324 
325   if (NS_FAILED(aValue->GetAsUint32(&mStorage))) {
326     return ScalarResult::InvalidValue;
327   }
328   return sr;
329 }
330 
SetValue(uint32_t aValue)331 void ScalarUnsigned::SetValue(uint32_t aValue) { mStorage = aValue; }
332 
AddValue(nsIVariant * aValue)333 ScalarResult ScalarUnsigned::AddValue(nsIVariant* aValue) {
334   ScalarResult sr = CheckInput(aValue);
335   if (sr == ScalarResult::UnsignedNegativeValue) {
336     return sr;
337   }
338 
339   uint32_t newAddend = 0;
340   nsresult rv = aValue->GetAsUint32(&newAddend);
341   if (NS_FAILED(rv)) {
342     return ScalarResult::InvalidValue;
343   }
344   mStorage += newAddend;
345   return sr;
346 }
347 
AddValue(uint32_t aValue)348 void ScalarUnsigned::AddValue(uint32_t aValue) { mStorage += aValue; }
349 
SetMaximum(nsIVariant * aValue)350 ScalarResult ScalarUnsigned::SetMaximum(nsIVariant* aValue) {
351   ScalarResult sr = CheckInput(aValue);
352   if (sr == ScalarResult::UnsignedNegativeValue) {
353     return sr;
354   }
355 
356   uint32_t newValue = 0;
357   nsresult rv = aValue->GetAsUint32(&newValue);
358   if (NS_FAILED(rv)) {
359     return ScalarResult::InvalidValue;
360   }
361   if (newValue > mStorage) {
362     mStorage = newValue;
363   }
364   return sr;
365 }
366 
SetMaximum(uint32_t aValue)367 void ScalarUnsigned::SetMaximum(uint32_t aValue) {
368   if (aValue > mStorage) {
369     mStorage = aValue;
370   }
371 }
372 
GetValue(nsCOMPtr<nsIVariant> & aResult) const373 nsresult ScalarUnsigned::GetValue(nsCOMPtr<nsIVariant>& aResult) const {
374   nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
375   nsresult rv = outVar->SetAsUint32(mStorage);
376   if (NS_FAILED(rv)) {
377     return rv;
378   }
379   aResult = outVar.forget();
380   return NS_OK;
381 }
382 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const383 size_t ScalarUnsigned::SizeOfIncludingThis(
384     mozilla::MallocSizeOf aMallocSizeOf) const {
385   return aMallocSizeOf(this);
386 }
387 
CheckInput(nsIVariant * aValue)388 ScalarResult ScalarUnsigned::CheckInput(nsIVariant* aValue) {
389   // If this is a floating point value/double, we will probably get truncated.
390   uint16_t type;
391   aValue->GetDataType(&type);
392   if (type == nsIDataType::VTYPE_FLOAT || type == nsIDataType::VTYPE_DOUBLE) {
393     return ScalarResult::UnsignedTruncatedValue;
394   }
395 
396   int32_t signedTest;
397   // If we're able to cast the number to an int, check its sign.
398   // Warn the user if he's trying to set the unsigned scalar to a negative
399   // number.
400   if (NS_SUCCEEDED(aValue->GetAsInt32(&signedTest)) && signedTest < 0) {
401     return ScalarResult::UnsignedNegativeValue;
402   }
403   return ScalarResult::Ok;
404 }
405 
406 /**
407  * The implementation for the string scalar type.
408  */
409 class ScalarString : public ScalarBase {
410  public:
411   using ScalarBase::SetValue;
412 
ScalarString()413   ScalarString() : mStorage(EmptyString()){};
414   ~ScalarString() override = default;
415 
416   ScalarResult SetValue(nsIVariant* aValue) final;
417   ScalarResult SetValue(const nsAString& aValue) final;
418   nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
419   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
420 
421  private:
422   nsString mStorage;
423 
424   // Prevent copying.
425   ScalarString(const ScalarString& aOther) = delete;
426   void operator=(const ScalarString& aOther) = delete;
427 };
428 
SetValue(nsIVariant * aValue)429 ScalarResult ScalarString::SetValue(nsIVariant* aValue) {
430   // Check that we got the correct data type.
431   uint16_t type;
432   aValue->GetDataType(&type);
433   if (type != nsIDataType::VTYPE_CHAR && type != nsIDataType::VTYPE_WCHAR &&
434       type != nsIDataType::VTYPE_DOMSTRING &&
435       type != nsIDataType::VTYPE_CHAR_STR &&
436       type != nsIDataType::VTYPE_WCHAR_STR &&
437       type != nsIDataType::VTYPE_STRING_SIZE_IS &&
438       type != nsIDataType::VTYPE_WSTRING_SIZE_IS &&
439       type != nsIDataType::VTYPE_UTF8STRING &&
440       type != nsIDataType::VTYPE_CSTRING &&
441       type != nsIDataType::VTYPE_ASTRING) {
442     return ScalarResult::InvalidType;
443   }
444 
445   nsAutoString convertedString;
446   nsresult rv = aValue->GetAsAString(convertedString);
447   if (NS_FAILED(rv)) {
448     return ScalarResult::InvalidValue;
449   }
450   return SetValue(convertedString);
451 };
452 
SetValue(const nsAString & aValue)453 ScalarResult ScalarString::SetValue(const nsAString& aValue) {
454   mStorage = Substring(aValue, 0, kMaximumStringValueLength);
455   if (aValue.Length() > kMaximumStringValueLength) {
456     return ScalarResult::StringTooLong;
457   }
458   return ScalarResult::Ok;
459 }
460 
GetValue(nsCOMPtr<nsIVariant> & aResult) const461 nsresult ScalarString::GetValue(nsCOMPtr<nsIVariant>& aResult) const {
462   nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
463   nsresult rv = outVar->SetAsAString(mStorage);
464   if (NS_FAILED(rv)) {
465     return rv;
466   }
467   aResult = outVar.forget();
468   return NS_OK;
469 }
470 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const471 size_t ScalarString::SizeOfIncludingThis(
472     mozilla::MallocSizeOf aMallocSizeOf) const {
473   size_t n = aMallocSizeOf(this);
474   n += mStorage.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
475   return n;
476 }
477 
478 /**
479  * The implementation for the boolean scalar type.
480  */
481 class ScalarBoolean : public ScalarBase {
482  public:
483   using ScalarBase::SetValue;
484 
ScalarBoolean()485   ScalarBoolean() : mStorage(false){};
486   ~ScalarBoolean() override = default;
487 
488   ScalarResult SetValue(nsIVariant* aValue) final;
489   void SetValue(bool aValue) final;
490   nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
491   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
492 
493  private:
494   bool mStorage;
495 
496   // Prevent copying.
497   ScalarBoolean(const ScalarBoolean& aOther) = delete;
498   void operator=(const ScalarBoolean& aOther) = delete;
499 };
500 
SetValue(nsIVariant * aValue)501 ScalarResult ScalarBoolean::SetValue(nsIVariant* aValue) {
502   // Check that we got the correct data type.
503   uint16_t type;
504   aValue->GetDataType(&type);
505   if (type != nsIDataType::VTYPE_BOOL && type != nsIDataType::VTYPE_INT8 &&
506       type != nsIDataType::VTYPE_INT16 && type != nsIDataType::VTYPE_INT32 &&
507       type != nsIDataType::VTYPE_INT64 && type != nsIDataType::VTYPE_UINT8 &&
508       type != nsIDataType::VTYPE_UINT16 && type != nsIDataType::VTYPE_UINT32 &&
509       type != nsIDataType::VTYPE_UINT64) {
510     return ScalarResult::InvalidType;
511   }
512 
513   if (NS_FAILED(aValue->GetAsBool(&mStorage))) {
514     return ScalarResult::InvalidValue;
515   }
516   return ScalarResult::Ok;
517 };
518 
SetValue(bool aValue)519 void ScalarBoolean::SetValue(bool aValue) { mStorage = aValue; }
520 
GetValue(nsCOMPtr<nsIVariant> & aResult) const521 nsresult ScalarBoolean::GetValue(nsCOMPtr<nsIVariant>& aResult) const {
522   nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
523   nsresult rv = outVar->SetAsBool(mStorage);
524   if (NS_FAILED(rv)) {
525     return rv;
526   }
527   aResult = outVar.forget();
528   return NS_OK;
529 }
530 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const531 size_t ScalarBoolean::SizeOfIncludingThis(
532     mozilla::MallocSizeOf aMallocSizeOf) const {
533   return aMallocSizeOf(this);
534 }
535 
536 /**
537  * Allocate a scalar class given the scalar info.
538  *
539  * @param aInfo The informations for the scalar coming from the definition file.
540  * @return nullptr if the scalar type is unknown, otherwise a valid pointer to
541  * the scalar type.
542  */
internal_ScalarAllocate(uint32_t aScalarKind)543 ScalarBase* internal_ScalarAllocate(uint32_t aScalarKind) {
544   ScalarBase* scalar = nullptr;
545   switch (aScalarKind) {
546     case nsITelemetry::SCALAR_TYPE_COUNT:
547       scalar = new ScalarUnsigned();
548       break;
549     case nsITelemetry::SCALAR_TYPE_STRING:
550       scalar = new ScalarString();
551       break;
552     case nsITelemetry::SCALAR_TYPE_BOOLEAN:
553       scalar = new ScalarBoolean();
554       break;
555     default:
556       MOZ_ASSERT(false, "Invalid scalar type");
557   }
558   return scalar;
559 }
560 
561 /**
562  * The implementation for the keyed scalar type.
563  */
564 class KeyedScalar {
565  public:
566   typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair;
567 
KeyedScalar(uint32_t aScalarKind)568   explicit KeyedScalar(uint32_t aScalarKind) : mScalarKind(aScalarKind){};
569   ~KeyedScalar() = default;
570 
571   // Set, Add and SetMaximum functions as described in the Telemetry IDL.
572   // These methods implicitly instantiate a Scalar[*] for each key.
573   ScalarResult SetValue(const nsAString& aKey, nsIVariant* aValue);
574   ScalarResult AddValue(const nsAString& aKey, nsIVariant* aValue);
575   ScalarResult SetMaximum(const nsAString& aKey, nsIVariant* aValue);
576 
577   // Convenience methods used by the C++ API.
578   void SetValue(const nsAString& aKey, uint32_t aValue);
579   void SetValue(const nsAString& aKey, bool aValue);
580   void AddValue(const nsAString& aKey, uint32_t aValue);
581   void SetMaximum(const nsAString& aKey, uint32_t aValue);
582 
583   // GetValue is used to get the key-value pairs stored in the keyed scalar
584   // when persisting it to JS.
585   nsresult GetValue(nsTArray<KeyValuePair>& aValues) const;
586 
587   // To measure the memory stats.
588   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
589 
590  private:
591   typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType;
592 
593   ScalarKeysMapType mScalarKeys;
594   const uint32_t mScalarKind;
595 
596   ScalarResult GetScalarForKey(const nsAString& aKey, ScalarBase** aRet);
597 };
598 
SetValue(const nsAString & aKey,nsIVariant * aValue)599 ScalarResult KeyedScalar::SetValue(const nsAString& aKey, nsIVariant* aValue) {
600   ScalarBase* scalar = nullptr;
601   ScalarResult sr = GetScalarForKey(aKey, &scalar);
602   if (sr != ScalarResult::Ok) {
603     return sr;
604   }
605 
606   return scalar->SetValue(aValue);
607 }
608 
AddValue(const nsAString & aKey,nsIVariant * aValue)609 ScalarResult KeyedScalar::AddValue(const nsAString& aKey, nsIVariant* aValue) {
610   ScalarBase* scalar = nullptr;
611   ScalarResult sr = GetScalarForKey(aKey, &scalar);
612   if (sr != ScalarResult::Ok) {
613     return sr;
614   }
615 
616   return scalar->AddValue(aValue);
617 }
618 
SetMaximum(const nsAString & aKey,nsIVariant * aValue)619 ScalarResult KeyedScalar::SetMaximum(const nsAString& aKey,
620                                      nsIVariant* aValue) {
621   ScalarBase* scalar = nullptr;
622   ScalarResult sr = GetScalarForKey(aKey, &scalar);
623   if (sr != ScalarResult::Ok) {
624     return sr;
625   }
626 
627   return scalar->SetMaximum(aValue);
628 }
629 
SetValue(const nsAString & aKey,uint32_t aValue)630 void KeyedScalar::SetValue(const nsAString& aKey, uint32_t aValue) {
631   ScalarBase* scalar = nullptr;
632   ScalarResult sr = GetScalarForKey(aKey, &scalar);
633   if (sr != ScalarResult::Ok) {
634     MOZ_ASSERT(false,
635                "Key too long or too many keys are recorded in the scalar.");
636     return;
637   }
638 
639   return scalar->SetValue(aValue);
640 }
641 
SetValue(const nsAString & aKey,bool aValue)642 void KeyedScalar::SetValue(const nsAString& aKey, bool aValue) {
643   ScalarBase* scalar = nullptr;
644   ScalarResult sr = GetScalarForKey(aKey, &scalar);
645   if (sr != ScalarResult::Ok) {
646     MOZ_ASSERT(false,
647                "Key too long or too many keys are recorded in the scalar.");
648     return;
649   }
650 
651   return scalar->SetValue(aValue);
652 }
653 
AddValue(const nsAString & aKey,uint32_t aValue)654 void KeyedScalar::AddValue(const nsAString& aKey, uint32_t aValue) {
655   ScalarBase* scalar = nullptr;
656   ScalarResult sr = GetScalarForKey(aKey, &scalar);
657   if (sr != ScalarResult::Ok) {
658     MOZ_ASSERT(false,
659                "Key too long or too many keys are recorded in the scalar.");
660     return;
661   }
662 
663   return scalar->AddValue(aValue);
664 }
665 
SetMaximum(const nsAString & aKey,uint32_t aValue)666 void KeyedScalar::SetMaximum(const nsAString& aKey, uint32_t aValue) {
667   ScalarBase* scalar = nullptr;
668   ScalarResult sr = GetScalarForKey(aKey, &scalar);
669   if (sr != ScalarResult::Ok) {
670     MOZ_ASSERT(false,
671                "Key too long or too many keys are recorded in the scalar.");
672     return;
673   }
674 
675   return scalar->SetMaximum(aValue);
676 }
677 
678 /**
679  * Get a key-value array with the values for the Keyed Scalar.
680  * @param aValue The array that will hold the key-value pairs.
681  * @return {nsresult} NS_OK or an error value as reported by the
682  *         the specific scalar objects implementations (e.g.
683  *         ScalarUnsigned).
684  */
GetValue(nsTArray<KeyValuePair> & aValues) const685 nsresult KeyedScalar::GetValue(nsTArray<KeyValuePair>& aValues) const {
686   for (auto iter = mScalarKeys.ConstIter(); !iter.Done(); iter.Next()) {
687     ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
688 
689     // Get the scalar value.
690     nsCOMPtr<nsIVariant> scalarValue;
691     nsresult rv = scalar->GetValue(scalarValue);
692     if (NS_FAILED(rv)) {
693       return rv;
694     }
695 
696     // Append it to value list.
697     aValues.AppendElement(
698         mozilla::MakePair(nsCString(iter.Key()), scalarValue));
699   }
700 
701   return NS_OK;
702 }
703 
704 /**
705  * Get the scalar for the referenced key.
706  * If there's no such key, instantiate a new Scalar object with the
707  * same type of the Keyed scalar and create the key.
708  */
GetScalarForKey(const nsAString & aKey,ScalarBase ** aRet)709 ScalarResult KeyedScalar::GetScalarForKey(const nsAString& aKey,
710                                           ScalarBase** aRet) {
711   if (aKey.IsEmpty()) {
712     return ScalarResult::KeyIsEmpty;
713   }
714 
715   if (aKey.Length() >= kMaximumKeyStringLength) {
716     return ScalarResult::KeyTooLong;
717   }
718 
719   if (mScalarKeys.Count() >= kMaximumNumberOfKeys) {
720     return ScalarResult::TooManyKeys;
721   }
722 
723   NS_ConvertUTF16toUTF8 utf8Key(aKey);
724 
725   ScalarBase* scalar = nullptr;
726   if (mScalarKeys.Get(utf8Key, &scalar)) {
727     *aRet = scalar;
728     return ScalarResult::Ok;
729   }
730 
731   scalar = internal_ScalarAllocate(mScalarKind);
732   if (!scalar) {
733     return ScalarResult::InvalidType;
734   }
735 
736   mScalarKeys.Put(utf8Key, scalar);
737 
738   *aRet = scalar;
739   return ScalarResult::Ok;
740 }
741 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)742 size_t KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
743   size_t n = aMallocSizeOf(this);
744   for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) {
745     ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
746     n += scalar->SizeOfIncludingThis(aMallocSizeOf);
747   }
748   return n;
749 }
750 
751 typedef nsUint32HashKey ScalarIDHashKey;
752 typedef nsUint32HashKey ProcessIDHashKey;
753 typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
754 typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar>
755     KeyedScalarStorageMapType;
756 typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType>
757     ProcessesScalarsMapType;
758 typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType>
759     ProcessesKeyedScalarsMapType;
760 
761 }  // namespace
762 
763 ////////////////////////////////////////////////////////////////////////
764 ////////////////////////////////////////////////////////////////////////
765 //
766 // PRIVATE STATE, SHARED BY ALL THREADS
767 
768 namespace {
769 
770 // Set to true once this global state has been initialized.
771 bool gInitDone = false;
772 
773 bool gCanRecordBase;
774 bool gCanRecordExtended;
775 
776 // The Name -> ID cache map.
777 ScalarMapType gScalarNameIDMap(kScalarCount);
778 
779 // The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a
780 // nsClassHashtable, it owns the scalar instances and takes care of deallocating
781 // them when they are removed from the map.
782 ProcessesScalarsMapType gScalarStorageMap;
783 // As above, for the keyed scalars.
784 ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
785 // Provide separate storage for "dynamic builtin" plain and keyed scalars,
786 // needed to support "build faster" in local developer builds.
787 ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap;
788 ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap;
789 
790 }  // namespace
791 
792 ////////////////////////////////////////////////////////////////////////
793 ////////////////////////////////////////////////////////////////////////
794 //
795 // PRIVATE: Function that may call JS code.
796 
797 // NOTE: the functions in this section all run without protection from
798 // |gTelemetryScalarsMutex|. If they held the mutex, there would be the
799 // possibility of deadlock because the JS_ calls that they make may call
800 // back into the TelemetryScalar interface, hence trying to re-acquire the
801 // mutex.
802 //
803 // This means that these functions potentially race against threads, but
804 // that seems preferable to risking deadlock.
805 
806 namespace {
807 
808 /**
809  * Converts the error code to a human readable error message and prints it to
810  * the browser console.
811  *
812  * @param aScalarName The name of the scalar that raised the error.
813  * @param aSr The error code.
814  */
internal_LogScalarError(const nsACString & aScalarName,ScalarResult aSr)815 void internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr) {
816   nsAutoString errorMessage;
817   AppendUTF8toUTF16(aScalarName, errorMessage);
818 
819   switch (aSr) {
820     case ScalarResult::NotInitialized:
821       errorMessage.AppendLiteral(u" - Telemetry was not yet initialized.");
822       break;
823     case ScalarResult::CannotUnpackVariant:
824       errorMessage.AppendLiteral(
825           u" - Cannot convert the provided JS value to nsIVariant.");
826       break;
827     case ScalarResult::CannotRecordInProcess:
828       errorMessage.AppendLiteral(
829           u" - Cannot record the scalar in the current process.");
830       break;
831     case ScalarResult::KeyedTypeMismatch:
832       errorMessage.AppendLiteral(
833           u" - Attempting to manage a keyed scalar as a scalar (or "
834           u"vice-versa).");
835       break;
836     case ScalarResult::UnknownScalar:
837       errorMessage.AppendLiteral(u" - Unknown scalar.");
838       break;
839     case ScalarResult::OperationNotSupported:
840       errorMessage.AppendLiteral(
841           u" - The requested operation is not supported on this scalar.");
842       break;
843     case ScalarResult::InvalidType:
844       errorMessage.AppendLiteral(
845           u" - Attempted to set the scalar to an invalid data type.");
846       break;
847     case ScalarResult::InvalidValue:
848       errorMessage.AppendLiteral(
849           u" - Attempted to set the scalar to an incompatible value.");
850       break;
851     case ScalarResult::StringTooLong:
852       errorMessage.AppendLiteral(
853           u" - Truncating scalar value to 50 characters.");
854       break;
855     case ScalarResult::KeyIsEmpty:
856       errorMessage.AppendLiteral(u" - The key must not be empty.");
857       break;
858     case ScalarResult::KeyTooLong:
859       errorMessage.AppendLiteral(
860           u" - The key length must be limited to 70 characters.");
861       break;
862     case ScalarResult::TooManyKeys:
863       errorMessage.AppendLiteral(
864           u" - Keyed scalars cannot have more than 100 keys.");
865       break;
866     case ScalarResult::UnsignedNegativeValue:
867       errorMessage.AppendLiteral(
868           u" - Trying to set an unsigned scalar to a negative number.");
869       break;
870     case ScalarResult::UnsignedTruncatedValue:
871       errorMessage.AppendLiteral(u" - Truncating float/double number.");
872       break;
873     default:
874       // Nothing.
875       return;
876   }
877 
878   LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage);
879 }
880 
881 }  // namespace
882 
883 ////////////////////////////////////////////////////////////////////////
884 ////////////////////////////////////////////////////////////////////////
885 //
886 // PRIVATE: helpers for the external interface
887 
888 namespace {
889 
internal_CanRecordBase(const StaticMutexAutoLock & lock)890 bool internal_CanRecordBase(const StaticMutexAutoLock& lock) {
891   return gCanRecordBase;
892 }
893 
internal_CanRecordExtended(const StaticMutexAutoLock & lock)894 bool internal_CanRecordExtended(const StaticMutexAutoLock& lock) {
895   return gCanRecordExtended;
896 }
897 
898 /**
899  * Check if the given scalar is a keyed scalar.
900  *
901  * @param lock Instance of a lock locking gTelemetryHistogramMutex
902  * @param aId The scalar identifier.
903  * @return true if aId refers to a keyed scalar, false otherwise.
904  */
internal_IsKeyedScalar(const StaticMutexAutoLock & lock,const ScalarKey & aId)905 bool internal_IsKeyedScalar(const StaticMutexAutoLock& lock,
906                             const ScalarKey& aId) {
907   return internal_GetScalarInfo(lock, aId).keyed;
908 }
909 
910 /**
911  * Check if we're allowed to record the given scalar in the current
912  * process.
913  *
914  * @param lock Instance of a lock locking gTelemetryHistogramMutex
915  * @param aId The scalar identifier.
916  * @return true if the scalar is allowed to be recorded in the current process,
917  * false otherwise.
918  */
internal_CanRecordProcess(const StaticMutexAutoLock & lock,const ScalarKey & aId)919 bool internal_CanRecordProcess(const StaticMutexAutoLock& lock,
920                                const ScalarKey& aId) {
921   const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
922   return CanRecordInProcess(info.record_in_processes, XRE_GetProcessType());
923 }
924 
internal_CanRecordForScalarID(const StaticMutexAutoLock & lock,const ScalarKey & aId)925 bool internal_CanRecordForScalarID(const StaticMutexAutoLock& lock,
926                                    const ScalarKey& aId) {
927   // Get the scalar info from the id.
928   const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
929 
930   // Can we record at all?
931   bool canRecordBase = internal_CanRecordBase(lock);
932   if (!canRecordBase) {
933     return false;
934   }
935 
936   bool canRecordDataset = CanRecordDataset(info.dataset, canRecordBase,
937                                            internal_CanRecordExtended(lock));
938   if (!canRecordDataset) {
939     return false;
940   }
941 
942   return true;
943 }
944 
945 /**
946  * Check if we are allowed to record the provided scalar.
947  *
948  * @param lock Instance of a lock locking gTelemetryHistogramMutex
949  * @param aId The scalar identifier.
950  * @param aKeyed Are we attempting to write a keyed scalar?
951  * @return ScalarResult::Ok if we can record, an error code otherwise.
952  */
internal_CanRecordScalar(const StaticMutexAutoLock & lock,const ScalarKey & aId,bool aKeyed)953 ScalarResult internal_CanRecordScalar(const StaticMutexAutoLock& lock,
954                                       const ScalarKey& aId, bool aKeyed) {
955   // Make sure that we have a keyed scalar if we are trying to change one.
956   if (internal_IsKeyedScalar(lock, aId) != aKeyed) {
957     return ScalarResult::KeyedTypeMismatch;
958   }
959 
960   // Are we allowed to record this scalar based on the current Telemetry
961   // settings?
962   if (!internal_CanRecordForScalarID(lock, aId)) {
963     return ScalarResult::CannotRecordDataset;
964   }
965 
966   // Can we record in this process?
967   if (!internal_CanRecordProcess(lock, aId)) {
968     return ScalarResult::CannotRecordInProcess;
969   }
970 
971   return ScalarResult::Ok;
972 }
973 
974 /**
975  * Get the scalar enum id from the scalar name.
976  *
977  * @param lock Instance of a lock locking gTelemetryHistogramMutex
978  * @param aName The scalar name.
979  * @param aId The output variable to contain the enum.
980  * @return
981  *   NS_ERROR_FAILURE if this was called before init is completed.
982  *   NS_ERROR_INVALID_ARG if the name can't be found in the scalar definitions.
983  *   NS_OK if the scalar was found and aId contains a valid enum id.
984  */
internal_GetEnumByScalarName(const StaticMutexAutoLock & lock,const nsACString & aName,ScalarKey * aId)985 nsresult internal_GetEnumByScalarName(const StaticMutexAutoLock& lock,
986                                       const nsACString& aName, ScalarKey* aId) {
987   if (!gInitDone) {
988     return NS_ERROR_FAILURE;
989   }
990 
991   CharPtrEntryType* entry =
992       gScalarNameIDMap.GetEntry(PromiseFlatCString(aName).get());
993   if (!entry) {
994     return NS_ERROR_INVALID_ARG;
995   }
996   *aId = entry->mData;
997   return NS_OK;
998 }
999 
1000 /**
1001  * Get a scalar object by its enum id. This implicitly allocates the scalar
1002  * object in the storage if it wasn't previously allocated.
1003  *
1004  * @param lock Instance of a lock locking gTelemetryHistogramMutex
1005  * @param aId The scalar identifier.
1006  * @param aProcessStorage This drives the selection of the map to use to store
1007  *        the scalar data coming from child processes. This is only meaningful
1008  * when this function is called in parent process. If that's the case, if this
1009  * is not |GeckoProcessType_Default|, the process id is used to allocate
1010  * and store the scalars.
1011  * @param aRes The output variable that stores scalar object.
1012  * @return
1013  *   NS_ERROR_INVALID_ARG if the scalar id is unknown.
1014  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
1015  *   NS_OK if the scalar was found. If that's the case, aResult contains a
1016  *   valid pointer to a scalar type.
1017  */
internal_GetScalarByEnum(const StaticMutexAutoLock & lock,const ScalarKey & aId,ProcessID aProcessStorage,ScalarBase ** aRet)1018 nsresult internal_GetScalarByEnum(const StaticMutexAutoLock& lock,
1019                                   const ScalarKey& aId,
1020                                   ProcessID aProcessStorage,
1021                                   ScalarBase** aRet) {
1022   if (!internal_IsValidId(lock, aId)) {
1023     MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
1024     return NS_ERROR_INVALID_ARG;
1025   }
1026 
1027   const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
1028 
1029   // Dynamic scalars fixup: they are always stored in the "dynamic" process,
1030   // unless they are part of the "builtin" Firefox probes. Please note that
1031   // "dynamic builtin" probes are meant to support "artifact" and "build faster"
1032   // builds.
1033   if (aId.dynamic && !info.builtin) {
1034     aProcessStorage = ProcessID::Dynamic;
1035   }
1036 
1037   ScalarBase* scalar = nullptr;
1038   ScalarStorageMapType* scalarStorage = nullptr;
1039   // Initialize the scalar storage to the parent storage. This will get
1040   // set to the child storage if needed.
1041   uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
1042 
1043   // Put dynamic-builtin scalars (used to support "build faster") in a
1044   // separate storage.
1045   ProcessesScalarsMapType& processStorage =
1046       (aId.dynamic && info.builtin) ? gDynamicBuiltinScalarStorageMap
1047                                     : gScalarStorageMap;
1048 
1049   // Get the process-specific storage or create one if it's not
1050   // available.
1051   if (!processStorage.Get(storageId, &scalarStorage)) {
1052     scalarStorage = new ScalarStorageMapType();
1053     processStorage.Put(storageId, scalarStorage);
1054   }
1055 
1056   // Check if the scalar is already allocated in the parent or in the child
1057   // storage.
1058   if (scalarStorage->Get(aId.id, &scalar)) {
1059     // Dynamic scalars can expire at any time during the session (e.g. an
1060     // add-on was updated). Check if it expired.
1061     if (aId.dynamic) {
1062       const DynamicScalarInfo& dynInfo =
1063           static_cast<const DynamicScalarInfo&>(info);
1064       if (dynInfo.mDynamicExpiration) {
1065         // The Dynamic scalar is expired.
1066         return NS_ERROR_NOT_AVAILABLE;
1067       }
1068     }
1069     // This was not a dynamic scalar or was not expired.
1070     *aRet = scalar;
1071     return NS_OK;
1072   }
1073 
1074   // The scalar storage wasn't already allocated. Check if the scalar is expired
1075   // and then allocate the storage, if needed.
1076   if (IsExpiredVersion(info.expiration())) {
1077     return NS_ERROR_NOT_AVAILABLE;
1078   }
1079 
1080   scalar = internal_ScalarAllocate(info.kind);
1081   if (!scalar) {
1082     return NS_ERROR_INVALID_ARG;
1083   }
1084 
1085   scalarStorage->Put(aId.id, scalar);
1086   *aRet = scalar;
1087   return NS_OK;
1088 }
1089 
1090 /**
1091  * Update the scalar with the provided value. This is used by the JS API.
1092  *
1093  * @param lock Instance of a lock locking gTelemetryHistogramMutex
1094  * @param aName The scalar name.
1095  * @param aType The action type for updating the scalar.
1096  * @param aValue The value to use for updating the scalar.
1097  * @return a ScalarResult error value.
1098  */
internal_UpdateScalar(const StaticMutexAutoLock & lock,const nsACString & aName,ScalarActionType aType,nsIVariant * aValue)1099 ScalarResult internal_UpdateScalar(const StaticMutexAutoLock& lock,
1100                                    const nsACString& aName,
1101                                    ScalarActionType aType, nsIVariant* aValue) {
1102   ScalarKey uniqueId;
1103   nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
1104   if (NS_FAILED(rv)) {
1105     return (rv == NS_ERROR_FAILURE) ? ScalarResult::NotInitialized
1106                                     : ScalarResult::UnknownScalar;
1107   }
1108 
1109   ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, false);
1110   if (sr != ScalarResult::Ok) {
1111     if (sr == ScalarResult::CannotRecordDataset) {
1112       return ScalarResult::Ok;
1113     }
1114     return sr;
1115   }
1116 
1117   // Accumulate in the child process if needed.
1118   if (!XRE_IsParentProcess()) {
1119     const BaseScalarInfo& info = internal_GetScalarInfo(lock, uniqueId);
1120     // Convert the nsIVariant to a Variant.
1121     mozilla::Maybe<ScalarVariant> variantValue;
1122     sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
1123     if (sr != ScalarResult::Ok) {
1124       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
1125       return sr;
1126     }
1127     TelemetryIPCAccumulator::RecordChildScalarAction(
1128         uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
1129     return ScalarResult::Ok;
1130   }
1131 
1132   // Finally get the scalar.
1133   ScalarBase* scalar = nullptr;
1134   rv = internal_GetScalarByEnum(lock, uniqueId, ProcessID::Parent, &scalar);
1135   if (NS_FAILED(rv)) {
1136     // Don't throw on expired scalars.
1137     if (rv == NS_ERROR_NOT_AVAILABLE) {
1138       return ScalarResult::Ok;
1139     }
1140     return ScalarResult::UnknownScalar;
1141   }
1142 
1143   if (aType == ScalarActionType::eAdd) {
1144     return scalar->AddValue(aValue);
1145   }
1146   if (aType == ScalarActionType::eSet) {
1147     return scalar->SetValue(aValue);
1148   }
1149 
1150   return scalar->SetMaximum(aValue);
1151 }
1152 
1153 }  // namespace
1154 
1155 ////////////////////////////////////////////////////////////////////////
1156 ////////////////////////////////////////////////////////////////////////
1157 //
1158 // PRIVATE: thread-unsafe helpers for the keyed scalars
1159 
1160 namespace {
1161 
1162 /**
1163  * Get a keyed scalar object by its enum id. This implicitly allocates the keyed
1164  * scalar object in the storage if it wasn't previously allocated.
1165  *
1166  * @param lock Instance of a lock locking gTelemetryHistogramMutex
1167  * @param aId The scalar identifier.
1168  * @param aProcessStorage This drives the selection of the map to use to store
1169  *        the scalar data coming from child processes. This is only meaningful
1170  * when this function is called in parent process. If that's the case, if this
1171  * is not |GeckoProcessType_Default|, the process id is used to allocate
1172  * and store the scalars.
1173  * @param aRet The output variable that stores scalar object.
1174  * @return
1175  *   NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed
1176  * string scalar. NS_ERROR_NOT_AVAILABLE if the scalar is expired. NS_OK if the
1177  * scalar was found. If that's the case, aResult contains a valid pointer to a
1178  * scalar type.
1179  */
internal_GetKeyedScalarByEnum(const StaticMutexAutoLock & lock,const ScalarKey & aId,ProcessID aProcessStorage,KeyedScalar ** aRet)1180 nsresult internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock,
1181                                        const ScalarKey& aId,
1182                                        ProcessID aProcessStorage,
1183                                        KeyedScalar** aRet) {
1184   if (!internal_IsValidId(lock, aId)) {
1185     MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
1186     return NS_ERROR_INVALID_ARG;
1187   }
1188 
1189   const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
1190 
1191   // Dynamic scalars fixup: they are always stored in the "dynamic" process,
1192   // unless they are part of the "builtin" Firefox probes. Please note that
1193   // "dynamic builtin" probes are meant to support "artifact" and "build faster"
1194   // builds.
1195   if (aId.dynamic && !info.builtin) {
1196     aProcessStorage = ProcessID::Dynamic;
1197   }
1198 
1199   KeyedScalar* scalar = nullptr;
1200   KeyedScalarStorageMapType* scalarStorage = nullptr;
1201   // Initialize the scalar storage to the parent storage. This will get
1202   // set to the child storage if needed.
1203   uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
1204 
1205   // Put dynamic-builtin scalars (used to support "build faster") in a
1206   // separate storage.
1207   ProcessesKeyedScalarsMapType& processStorage =
1208       (aId.dynamic && info.builtin) ? gDynamicBuiltinKeyedScalarStorageMap
1209                                     : gKeyedScalarStorageMap;
1210 
1211   // Get the process-specific storage or create one if it's not
1212   // available.
1213   if (!processStorage.Get(storageId, &scalarStorage)) {
1214     scalarStorage = new KeyedScalarStorageMapType();
1215     processStorage.Put(storageId, scalarStorage);
1216   }
1217 
1218   if (scalarStorage->Get(aId.id, &scalar)) {
1219     *aRet = scalar;
1220     return NS_OK;
1221   }
1222 
1223   if (IsExpiredVersion(info.expiration())) {
1224     return NS_ERROR_NOT_AVAILABLE;
1225   }
1226 
1227   // We don't currently support keyed string scalars. Disable them.
1228   if (info.kind == nsITelemetry::SCALAR_TYPE_STRING) {
1229     MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
1230     return NS_ERROR_INVALID_ARG;
1231   }
1232 
1233   scalar = new KeyedScalar(info.kind);
1234   if (!scalar) {
1235     return NS_ERROR_INVALID_ARG;
1236   }
1237 
1238   scalarStorage->Put(aId.id, scalar);
1239   *aRet = scalar;
1240   return NS_OK;
1241 }
1242 
1243 /**
1244  * Update the keyed scalar with the provided value. This is used by the JS API.
1245  *
1246  * @param lock Instance of a lock locking gTelemetryHistogramMutex
1247  * @param aName The scalar name.
1248  * @param aKey The key name.
1249  * @param aType The action type for updating the scalar.
1250  * @param aValue The value to use for updating the scalar.
1251  * @return a ScalarResult error value.
1252  */
internal_UpdateKeyedScalar(const StaticMutexAutoLock & lock,const nsACString & aName,const nsAString & aKey,ScalarActionType aType,nsIVariant * aValue)1253 ScalarResult internal_UpdateKeyedScalar(const StaticMutexAutoLock& lock,
1254                                         const nsACString& aName,
1255                                         const nsAString& aKey,
1256                                         ScalarActionType aType,
1257                                         nsIVariant* aValue) {
1258   ScalarKey uniqueId;
1259   nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
1260   if (NS_FAILED(rv)) {
1261     return (rv == NS_ERROR_FAILURE) ? ScalarResult::NotInitialized
1262                                     : ScalarResult::UnknownScalar;
1263   }
1264 
1265   ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, true);
1266   if (sr != ScalarResult::Ok) {
1267     if (sr == ScalarResult::CannotRecordDataset) {
1268       return ScalarResult::Ok;
1269     }
1270     return sr;
1271   }
1272 
1273   // Accumulate in the child process if needed.
1274   if (!XRE_IsParentProcess()) {
1275     const BaseScalarInfo& info = internal_GetScalarInfo(lock, uniqueId);
1276     // Convert the nsIVariant to a Variant.
1277     mozilla::Maybe<ScalarVariant> variantValue;
1278     sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
1279     if (sr != ScalarResult::Ok) {
1280       MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
1281       return sr;
1282     }
1283     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1284         uniqueId.id, uniqueId.dynamic, aKey, aType, variantValue.ref());
1285     return ScalarResult::Ok;
1286   }
1287 
1288   // Finally get the scalar.
1289   KeyedScalar* scalar = nullptr;
1290   rv =
1291       internal_GetKeyedScalarByEnum(lock, uniqueId, ProcessID::Parent, &scalar);
1292   if (NS_FAILED(rv)) {
1293     // Don't throw on expired scalars.
1294     if (rv == NS_ERROR_NOT_AVAILABLE) {
1295       return ScalarResult::Ok;
1296     }
1297     return ScalarResult::UnknownScalar;
1298   }
1299 
1300   if (aType == ScalarActionType::eAdd) {
1301     return scalar->AddValue(aKey, aValue);
1302   }
1303   if (aType == ScalarActionType::eSet) {
1304     return scalar->SetValue(aKey, aValue);
1305   }
1306 
1307   return scalar->SetMaximum(aKey, aValue);
1308 }
1309 
1310 /**
1311  * Helper function to convert an array of |DynamicScalarInfo|
1312  * to |DynamicScalarDefinition| used by the IPC calls.
1313  */
internal_DynamicScalarToIPC(const StaticMutexAutoLock & lock,const nsTArray<DynamicScalarInfo> & aDynamicScalarInfos,nsTArray<DynamicScalarDefinition> & aIPCDefs)1314 void internal_DynamicScalarToIPC(
1315     const StaticMutexAutoLock& lock,
1316     const nsTArray<DynamicScalarInfo>& aDynamicScalarInfos,
1317     nsTArray<DynamicScalarDefinition>& aIPCDefs) {
1318   for (auto info : aDynamicScalarInfos) {
1319     DynamicScalarDefinition stubDefinition;
1320     stubDefinition.type = info.kind;
1321     stubDefinition.dataset = info.dataset;
1322     stubDefinition.expired = info.mDynamicExpiration;
1323     stubDefinition.keyed = info.keyed;
1324     stubDefinition.name = info.mDynamicName;
1325     aIPCDefs.AppendElement(stubDefinition);
1326   }
1327 }
1328 
1329 /**
1330  * Broadcasts the dynamic scalar definitions to all the other
1331  * content processes.
1332  */
internal_BroadcastDefinitions(const StaticMutexAutoLock & lock,const nsTArray<DynamicScalarInfo> & scalarInfos)1333 void internal_BroadcastDefinitions(
1334     const StaticMutexAutoLock& lock,
1335     const nsTArray<DynamicScalarInfo>& scalarInfos) {
1336   nsTArray<mozilla::dom::ContentParent*> parents;
1337   mozilla::dom::ContentParent::GetAll(parents);
1338   if (!parents.Length()) {
1339     return;
1340   }
1341 
1342   // Convert the internal scalar representation to a stripped down IPC one.
1343   nsTArray<DynamicScalarDefinition> ipcDefinitions;
1344   internal_DynamicScalarToIPC(lock, scalarInfos, ipcDefinitions);
1345 
1346   // Broadcast the definitions to the other content processes.
1347   for (auto parent : parents) {
1348     mozilla::Unused << parent->SendAddDynamicScalars(ipcDefinitions);
1349   }
1350 }
1351 
internal_RegisterScalars(const StaticMutexAutoLock & lock,const nsTArray<DynamicScalarInfo> & scalarInfos)1352 void internal_RegisterScalars(const StaticMutexAutoLock& lock,
1353                               const nsTArray<DynamicScalarInfo>& scalarInfos) {
1354   // Register the new scalars.
1355   if (!gDynamicScalarInfo) {
1356     gDynamicScalarInfo = new nsTArray<DynamicScalarInfo>();
1357   }
1358 
1359   for (auto scalarInfo : scalarInfos) {
1360     // Allow expiring scalars that were already registered.
1361     CharPtrEntryType* existingKey =
1362         gScalarNameIDMap.GetEntry(scalarInfo.name());
1363     if (existingKey) {
1364       // Change the scalar to expired if needed.
1365       if (scalarInfo.mDynamicExpiration && !scalarInfo.builtin) {
1366         DynamicScalarInfo& scalarData =
1367             (*gDynamicScalarInfo)[existingKey->mData.id];
1368         scalarData.mDynamicExpiration = true;
1369       }
1370       continue;
1371     }
1372 
1373     gDynamicScalarInfo->AppendElement(scalarInfo);
1374     uint32_t scalarId = gDynamicScalarInfo->Length() - 1;
1375     CharPtrEntryType* entry = gScalarNameIDMap.PutEntry(scalarInfo.name());
1376     entry->mData = ScalarKey{scalarId, true};
1377   }
1378 }
1379 
1380 }  // namespace
1381 
1382 ////////////////////////////////////////////////////////////////////////
1383 ////////////////////////////////////////////////////////////////////////
1384 //
1385 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars::
1386 
1387 // This is a StaticMutex rather than a plain Mutex (1) so that
1388 // it gets initialised in a thread-safe manner the first time
1389 // it is used, and (2) because it is never de-initialised, and
1390 // a normal Mutex would show up as a leak in BloatView.  StaticMutex
1391 // also has the "OffTheBooks" property, so it won't show as a leak
1392 // in BloatView.
1393 // Another reason to use a StaticMutex instead of a plain Mutex is
1394 // that, due to the nature of Telemetry, we cannot rely on having a
1395 // mutex initialized in InitializeGlobalState. Unfortunately, we
1396 // cannot make sure that no other function is called before this point.
1397 static StaticMutex gTelemetryScalarsMutex;
1398 
InitializeGlobalState(bool aCanRecordBase,bool aCanRecordExtended)1399 void TelemetryScalar::InitializeGlobalState(bool aCanRecordBase,
1400                                             bool aCanRecordExtended) {
1401   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1402   MOZ_ASSERT(!gInitDone,
1403              "TelemetryScalar::InitializeGlobalState "
1404              "may only be called once");
1405 
1406   gCanRecordBase = aCanRecordBase;
1407   gCanRecordExtended = aCanRecordExtended;
1408 
1409   // Populate the static scalar name->id cache. Note that the scalar names are
1410   // statically allocated and come from the automatically generated
1411   // TelemetryScalarData.h.
1412   uint32_t scalarCount =
1413       static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
1414   for (uint32_t i = 0; i < scalarCount; i++) {
1415     CharPtrEntryType* entry = gScalarNameIDMap.PutEntry(gScalars[i].name());
1416     entry->mData = ScalarKey{i, false};
1417   }
1418 
1419   gInitDone = true;
1420 }
1421 
DeInitializeGlobalState()1422 void TelemetryScalar::DeInitializeGlobalState() {
1423   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1424   gCanRecordBase = false;
1425   gCanRecordExtended = false;
1426   gScalarNameIDMap.Clear();
1427   gScalarStorageMap.Clear();
1428   gKeyedScalarStorageMap.Clear();
1429   gDynamicBuiltinScalarStorageMap.Clear();
1430   gDynamicBuiltinKeyedScalarStorageMap.Clear();
1431   gDynamicScalarInfo = nullptr;
1432   gInitDone = false;
1433 }
1434 
SetCanRecordBase(bool b)1435 void TelemetryScalar::SetCanRecordBase(bool b) {
1436   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1437   gCanRecordBase = b;
1438 }
1439 
SetCanRecordExtended(bool b)1440 void TelemetryScalar::SetCanRecordExtended(bool b) {
1441   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1442   gCanRecordExtended = b;
1443 }
1444 
1445 /**
1446  * Adds the value to the given scalar.
1447  *
1448  * @param aName The scalar name.
1449  * @param aVal The numeric value to add to the scalar.
1450  * @param aCx The JS context.
1451  * @return NS_OK (always) so that the JS API call doesn't throw. In case of
1452  * errors, a warning level message is printed in the browser console.
1453  */
Add(const nsACString & aName,JS::HandleValue aVal,JSContext * aCx)1454 nsresult TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal,
1455                               JSContext* aCx) {
1456   // Unpack the aVal to nsIVariant. This uses the JS context.
1457   nsCOMPtr<nsIVariant> unpackedVal;
1458   nsresult rv = nsContentUtils::XPConnect()->JSToVariant(
1459       aCx, aVal, getter_AddRefs(unpackedVal));
1460   if (NS_FAILED(rv)) {
1461     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
1462     return NS_OK;
1463   }
1464 
1465   ScalarResult sr;
1466   {
1467     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1468     sr = internal_UpdateScalar(locker, aName, ScalarActionType::eAdd,
1469                                unpackedVal);
1470   }
1471 
1472   // Warn the user about the error if we need to.
1473   if (sr != ScalarResult::Ok) {
1474     internal_LogScalarError(aName, sr);
1475   }
1476 
1477   return NS_OK;
1478 }
1479 
1480 /**
1481  * Adds the value to the given scalar.
1482  *
1483  * @param aName The scalar name.
1484  * @param aKey The key name.
1485  * @param aVal The numeric value to add to the scalar.
1486  * @param aCx The JS context.
1487  * @return NS_OK (always) so that the JS API call doesn't throw. In case of
1488  * errors, a warning level message is printed in the browser console.
1489  */
Add(const nsACString & aName,const nsAString & aKey,JS::HandleValue aVal,JSContext * aCx)1490 nsresult TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey,
1491                               JS::HandleValue aVal, JSContext* aCx) {
1492   // Unpack the aVal to nsIVariant. This uses the JS context.
1493   nsCOMPtr<nsIVariant> unpackedVal;
1494   nsresult rv = nsContentUtils::XPConnect()->JSToVariant(
1495       aCx, aVal, getter_AddRefs(unpackedVal));
1496   if (NS_FAILED(rv)) {
1497     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
1498     return NS_OK;
1499   }
1500 
1501   ScalarResult sr;
1502   {
1503     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1504     sr = internal_UpdateKeyedScalar(locker, aName, aKey, ScalarActionType::eAdd,
1505                                     unpackedVal);
1506   }
1507 
1508   // Warn the user about the error if we need to.
1509   if (sr != ScalarResult::Ok) {
1510     internal_LogScalarError(aName, sr);
1511   }
1512 
1513   return NS_OK;
1514 }
1515 
1516 /**
1517  * Adds the value to the given scalar.
1518  *
1519  * @param aId The scalar enum id.
1520  * @param aVal The numeric value to add to the scalar.
1521  */
Add(mozilla::Telemetry::ScalarID aId,uint32_t aValue)1522 void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue) {
1523   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1524     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1525     return;
1526   }
1527 
1528   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1529   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1530 
1531   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
1532     // We can't record this scalar. Bail out.
1533     return;
1534   }
1535 
1536   // Accumulate in the child process if needed.
1537   if (!XRE_IsParentProcess()) {
1538     TelemetryIPCAccumulator::RecordChildScalarAction(
1539         uniqueId.id, uniqueId.dynamic, ScalarActionType::eAdd,
1540         ScalarVariant(aValue));
1541     return;
1542   }
1543 
1544   ScalarBase* scalar = nullptr;
1545   nsresult rv =
1546       internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1547   if (NS_FAILED(rv)) {
1548     return;
1549   }
1550 
1551   scalar->AddValue(aValue);
1552 }
1553 
1554 /**
1555  * Adds the value to the given keyed scalar.
1556  *
1557  * @param aId The scalar enum id.
1558  * @param aKey The key name.
1559  * @param aVal The numeric value to add to the scalar.
1560  */
Add(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,uint32_t aValue)1561 void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId,
1562                           const nsAString& aKey, uint32_t aValue) {
1563   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1564     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1565     return;
1566   }
1567 
1568   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1569   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1570 
1571   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
1572     // We can't record this scalar. Bail out.
1573     return;
1574   }
1575 
1576   // Accumulate in the child process if needed.
1577   if (!XRE_IsParentProcess()) {
1578     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1579         uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eAdd,
1580         ScalarVariant(aValue));
1581     return;
1582   }
1583 
1584   KeyedScalar* scalar = nullptr;
1585   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
1586                                               ProcessID::Parent, &scalar);
1587   if (NS_FAILED(rv)) {
1588     return;
1589   }
1590 
1591   scalar->AddValue(aKey, aValue);
1592 }
1593 
1594 /**
1595  * Sets the scalar to the given value.
1596  *
1597  * @param aName The scalar name.
1598  * @param aVal The value to set the scalar to.
1599  * @param aCx The JS context.
1600  * @return NS_OK (always) so that the JS API call doesn't throw. In case of
1601  * errors, a warning level message is printed in the browser console.
1602  */
Set(const nsACString & aName,JS::HandleValue aVal,JSContext * aCx)1603 nsresult TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal,
1604                               JSContext* aCx) {
1605   // Unpack the aVal to nsIVariant. This uses the JS context.
1606   nsCOMPtr<nsIVariant> unpackedVal;
1607   nsresult rv = nsContentUtils::XPConnect()->JSToVariant(
1608       aCx, aVal, getter_AddRefs(unpackedVal));
1609   if (NS_FAILED(rv)) {
1610     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
1611     return NS_OK;
1612   }
1613 
1614   ScalarResult sr;
1615   {
1616     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1617     sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSet,
1618                                unpackedVal);
1619   }
1620 
1621   // Warn the user about the error if we need to.
1622   if (sr != ScalarResult::Ok) {
1623     internal_LogScalarError(aName, sr);
1624   }
1625 
1626   return NS_OK;
1627 }
1628 
1629 /**
1630  * Sets the keyed scalar to the given value.
1631  *
1632  * @param aName The scalar name.
1633  * @param aKey The key name.
1634  * @param aVal The value to set the scalar to.
1635  * @param aCx The JS context.
1636  * @return NS_OK (always) so that the JS API call doesn't throw. In case of
1637  * errors, a warning level message is printed in the browser console.
1638  */
Set(const nsACString & aName,const nsAString & aKey,JS::HandleValue aVal,JSContext * aCx)1639 nsresult TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey,
1640                               JS::HandleValue aVal, JSContext* aCx) {
1641   // Unpack the aVal to nsIVariant. This uses the JS context.
1642   nsCOMPtr<nsIVariant> unpackedVal;
1643   nsresult rv = nsContentUtils::XPConnect()->JSToVariant(
1644       aCx, aVal, getter_AddRefs(unpackedVal));
1645   if (NS_FAILED(rv)) {
1646     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
1647     return NS_OK;
1648   }
1649 
1650   ScalarResult sr;
1651   {
1652     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1653     sr = internal_UpdateKeyedScalar(locker, aName, aKey, ScalarActionType::eSet,
1654                                     unpackedVal);
1655   }
1656 
1657   // Warn the user about the error if we need to.
1658   if (sr != ScalarResult::Ok) {
1659     internal_LogScalarError(aName, sr);
1660   }
1661 
1662   return NS_OK;
1663 }
1664 
1665 /**
1666  * Sets the scalar to the given numeric value.
1667  *
1668  * @param aId The scalar enum id.
1669  * @param aValue The numeric, unsigned value to set the scalar to.
1670  */
Set(mozilla::Telemetry::ScalarID aId,uint32_t aValue)1671 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue) {
1672   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1673     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1674     return;
1675   }
1676 
1677   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1678   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1679 
1680   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
1681     // We can't record this scalar. Bail out.
1682     return;
1683   }
1684 
1685   // Accumulate in the child process if needed.
1686   if (!XRE_IsParentProcess()) {
1687     TelemetryIPCAccumulator::RecordChildScalarAction(
1688         uniqueId.id, uniqueId.dynamic, ScalarActionType::eSet,
1689         ScalarVariant(aValue));
1690     return;
1691   }
1692 
1693   ScalarBase* scalar = nullptr;
1694   nsresult rv =
1695       internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1696   if (NS_FAILED(rv)) {
1697     return;
1698   }
1699 
1700   scalar->SetValue(aValue);
1701 }
1702 
1703 /**
1704  * Sets the scalar to the given string value.
1705  *
1706  * @param aId The scalar enum id.
1707  * @param aValue The string value to set the scalar to.
1708  */
Set(mozilla::Telemetry::ScalarID aId,const nsAString & aValue)1709 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId,
1710                           const nsAString& aValue) {
1711   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1712     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1713     return;
1714   }
1715 
1716   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1717   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1718 
1719   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
1720     // We can't record this scalar. Bail out.
1721     return;
1722   }
1723 
1724   // Accumulate in the child process if needed.
1725   if (!XRE_IsParentProcess()) {
1726     TelemetryIPCAccumulator::RecordChildScalarAction(
1727         uniqueId.id, uniqueId.dynamic, ScalarActionType::eSet,
1728         ScalarVariant(nsString(aValue)));
1729     return;
1730   }
1731 
1732   ScalarBase* scalar = nullptr;
1733   nsresult rv =
1734       internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1735   if (NS_FAILED(rv)) {
1736     return;
1737   }
1738 
1739   scalar->SetValue(aValue);
1740 }
1741 
1742 /**
1743  * Sets the scalar to the given boolean value.
1744  *
1745  * @param aId The scalar enum id.
1746  * @param aValue The boolean value to set the scalar to.
1747  */
Set(mozilla::Telemetry::ScalarID aId,bool aValue)1748 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue) {
1749   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1750     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1751     return;
1752   }
1753 
1754   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1755   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1756 
1757   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
1758     // We can't record this scalar. Bail out.
1759     return;
1760   }
1761 
1762   // Accumulate in the child process if needed.
1763   if (!XRE_IsParentProcess()) {
1764     TelemetryIPCAccumulator::RecordChildScalarAction(
1765         uniqueId.id, uniqueId.dynamic, ScalarActionType::eSet,
1766         ScalarVariant(aValue));
1767     return;
1768   }
1769 
1770   ScalarBase* scalar = nullptr;
1771   nsresult rv =
1772       internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1773   if (NS_FAILED(rv)) {
1774     return;
1775   }
1776 
1777   scalar->SetValue(aValue);
1778 }
1779 
1780 /**
1781  * Sets the keyed scalar to the given numeric value.
1782  *
1783  * @param aId The scalar enum id.
1784  * @param aKey The scalar key.
1785  * @param aValue The numeric, unsigned value to set the scalar to.
1786  */
Set(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,uint32_t aValue)1787 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId,
1788                           const nsAString& aKey, uint32_t aValue) {
1789   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1790     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1791     return;
1792   }
1793 
1794   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1795   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1796 
1797   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
1798     // We can't record this scalar. Bail out.
1799     return;
1800   }
1801 
1802   // Accumulate in the child process if needed.
1803   if (!XRE_IsParentProcess()) {
1804     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1805         uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eSet,
1806         ScalarVariant(aValue));
1807     return;
1808   }
1809 
1810   KeyedScalar* scalar = nullptr;
1811   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
1812                                               ProcessID::Parent, &scalar);
1813   if (NS_FAILED(rv)) {
1814     return;
1815   }
1816 
1817   scalar->SetValue(aKey, aValue);
1818 }
1819 
1820 /**
1821  * Sets the scalar to the given boolean value.
1822  *
1823  * @param aId The scalar enum id.
1824  * @param aKey The scalar key.
1825  * @param aValue The boolean value to set the scalar to.
1826  */
Set(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,bool aValue)1827 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId,
1828                           const nsAString& aKey, bool aValue) {
1829   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1830     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1831     return;
1832   }
1833 
1834   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1835   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1836 
1837   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
1838     // We can't record this scalar. Bail out.
1839     return;
1840   }
1841 
1842   // Accumulate in the child process if needed.
1843   if (!XRE_IsParentProcess()) {
1844     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1845         uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eSet,
1846         ScalarVariant(aValue));
1847     return;
1848   }
1849 
1850   KeyedScalar* scalar = nullptr;
1851   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
1852                                               ProcessID::Parent, &scalar);
1853   if (NS_FAILED(rv)) {
1854     return;
1855   }
1856 
1857   scalar->SetValue(aKey, aValue);
1858 }
1859 
1860 /**
1861  * Sets the scalar to the maximum of the current and the passed value.
1862  *
1863  * @param aName The scalar name.
1864  * @param aVal The numeric value to set the scalar to.
1865  * @param aCx The JS context.
1866  * @return NS_OK (always) so that the JS API call doesn't throw. In case of
1867  * errors, a warning level message is printed in the browser console.
1868  */
SetMaximum(const nsACString & aName,JS::HandleValue aVal,JSContext * aCx)1869 nsresult TelemetryScalar::SetMaximum(const nsACString& aName,
1870                                      JS::HandleValue aVal, JSContext* aCx) {
1871   // Unpack the aVal to nsIVariant. This uses the JS context.
1872   nsCOMPtr<nsIVariant> unpackedVal;
1873   nsresult rv = nsContentUtils::XPConnect()->JSToVariant(
1874       aCx, aVal, getter_AddRefs(unpackedVal));
1875   if (NS_FAILED(rv)) {
1876     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
1877     return NS_OK;
1878   }
1879 
1880   ScalarResult sr;
1881   {
1882     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1883     sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSetMaximum,
1884                                unpackedVal);
1885   }
1886 
1887   // Warn the user about the error if we need to.
1888   if (sr != ScalarResult::Ok) {
1889     internal_LogScalarError(aName, sr);
1890   }
1891 
1892   return NS_OK;
1893 }
1894 
1895 /**
1896  * Sets the scalar to the maximum of the current and the passed value.
1897  *
1898  * @param aName The scalar name.
1899  * @param aKey The key name.
1900  * @param aVal The numeric value to set the scalar to.
1901  * @param aCx The JS context.
1902  * @return NS_OK (always) so that the JS API call doesn't throw. In case of
1903  * errors, a warning level message is printed in the browser console.
1904  */
SetMaximum(const nsACString & aName,const nsAString & aKey,JS::HandleValue aVal,JSContext * aCx)1905 nsresult TelemetryScalar::SetMaximum(const nsACString& aName,
1906                                      const nsAString& aKey,
1907                                      JS::HandleValue aVal, JSContext* aCx) {
1908   // Unpack the aVal to nsIVariant. This uses the JS context.
1909   nsCOMPtr<nsIVariant> unpackedVal;
1910   nsresult rv = nsContentUtils::XPConnect()->JSToVariant(
1911       aCx, aVal, getter_AddRefs(unpackedVal));
1912   if (NS_FAILED(rv)) {
1913     internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
1914     return NS_OK;
1915   }
1916 
1917   ScalarResult sr;
1918   {
1919     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1920     sr = internal_UpdateKeyedScalar(locker, aName, aKey,
1921                                     ScalarActionType::eSetMaximum, unpackedVal);
1922   }
1923 
1924   // Warn the user about the error if we need to.
1925   if (sr != ScalarResult::Ok) {
1926     internal_LogScalarError(aName, sr);
1927   }
1928 
1929   return NS_OK;
1930 }
1931 
1932 /**
1933  * Sets the scalar to the maximum of the current and the passed value.
1934  *
1935  * @param aId The scalar enum id.
1936  * @param aValue The numeric value to set the scalar to.
1937  */
SetMaximum(mozilla::Telemetry::ScalarID aId,uint32_t aValue)1938 void TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId,
1939                                  uint32_t aValue) {
1940   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1941     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1942     return;
1943   }
1944 
1945   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1946   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1947 
1948   if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
1949     // We can't record this scalar. Bail out.
1950     return;
1951   }
1952 
1953   // Accumulate in the child process if needed.
1954   if (!XRE_IsParentProcess()) {
1955     TelemetryIPCAccumulator::RecordChildScalarAction(
1956         uniqueId.id, uniqueId.dynamic, ScalarActionType::eSetMaximum,
1957         ScalarVariant(aValue));
1958     return;
1959   }
1960 
1961   ScalarBase* scalar = nullptr;
1962   nsresult rv =
1963       internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1964   if (NS_FAILED(rv)) {
1965     return;
1966   }
1967 
1968   scalar->SetMaximum(aValue);
1969 }
1970 
1971 /**
1972  * Sets the keyed scalar to the maximum of the current and the passed value.
1973  *
1974  * @param aId The scalar enum id.
1975  * @param aKey The key name.
1976  * @param aValue The numeric value to set the scalar to.
1977  */
SetMaximum(mozilla::Telemetry::ScalarID aId,const nsAString & aKey,uint32_t aValue)1978 void TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId,
1979                                  const nsAString& aKey, uint32_t aValue) {
1980   if (NS_WARN_IF(!IsValidEnumId(aId))) {
1981     MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1982     return;
1983   }
1984 
1985   ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
1986   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
1987 
1988   if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
1989     // We can't record this scalar. Bail out.
1990     return;
1991   }
1992 
1993   // Accumulate in the child process if needed.
1994   if (!XRE_IsParentProcess()) {
1995     TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1996         uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eSetMaximum,
1997         ScalarVariant(aValue));
1998     return;
1999   }
2000 
2001   KeyedScalar* scalar = nullptr;
2002   nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
2003                                               ProcessID::Parent, &scalar);
2004   if (NS_FAILED(rv)) {
2005     return;
2006   }
2007 
2008   scalar->SetMaximum(aKey, aValue);
2009 }
2010 
2011 /**
2012  * Serializes the scalars from the given dataset to a json-style object and
2013  * resets them. The returned structure looks like:
2014  *    {"process": {"category1.probe":1,"category1.other_probe":false,...}, ...
2015  * }.
2016  *
2017  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or
2018  * DATASET_RELEASE_CHANNEL_OPTIN.
2019  * @param aClear Whether to clear out the scalars after snapshotting.
2020  */
CreateSnapshots(unsigned int aDataset,bool aClearScalars,JSContext * aCx,uint8_t optional_argc,JS::MutableHandle<JS::Value> aResult)2021 nsresult TelemetryScalar::CreateSnapshots(
2022     unsigned int aDataset, bool aClearScalars, JSContext* aCx,
2023     uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult) {
2024   MOZ_ASSERT(
2025       XRE_IsParentProcess(),
2026       "Snapshotting scalars should only happen in the parent processes.");
2027   // If no arguments were passed in, apply the default value.
2028   if (!optional_argc) {
2029     aClearScalars = false;
2030   }
2031 
2032   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
2033   if (!root_obj) {
2034     return NS_ERROR_FAILURE;
2035   }
2036   aResult.setObject(*root_obj);
2037 
2038   // Return `{}` in child processes.
2039   if (!XRE_IsParentProcess()) {
2040     return NS_OK;
2041   }
2042 
2043   // Only lock the mutex while accessing our data, without locking any JS
2044   // related code.
2045   typedef mozilla::Pair<const char*, nsCOMPtr<nsIVariant>> DataPair;
2046   typedef nsTArray<DataPair> ScalarArray;
2047   nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
2048   {
2049     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2050 
2051     // The snapshotting function is the same for both static and dynamic builtin
2052     // scalars. We can use the same function and store the scalars in the same
2053     // output storage.
2054     auto snapshotter = [aDataset, &locker, &scalarsToReflect](
2055                            ProcessesScalarsMapType& aProcessStorage,
2056                            bool aIsBuiltinDynamic) -> nsresult {
2057       // Iterate the scalars in aProcessStorage. The storage may contain empty
2058       // or yet to be initialized scalars from all the supported processes.
2059       for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
2060         ScalarStorageMapType* scalarStorage =
2061             static_cast<ScalarStorageMapType*>(iter.Data());
2062         ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
2063 
2064         // Are we in the "Dynamic" process?
2065         bool isDynamicProcess =
2066             ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
2067 
2068         // Iterate each available child storage.
2069         for (auto childIter = scalarStorage->Iter(); !childIter.Done();
2070              childIter.Next()) {
2071           ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
2072 
2073           // Get the informations for this scalar.
2074           const BaseScalarInfo& info = internal_GetScalarInfo(
2075               locker, ScalarKey{childIter.Key(),
2076                                 aIsBuiltinDynamic ? true : isDynamicProcess});
2077 
2078           // Serialize the scalar if it's in the desired dataset.
2079           if (IsInDataset(info.dataset, aDataset)) {
2080             // Get the scalar value.
2081             nsCOMPtr<nsIVariant> scalarValue;
2082             nsresult rv = scalar->GetValue(scalarValue);
2083             if (NS_FAILED(rv)) {
2084               return rv;
2085             }
2086             // Append it to our list.
2087             processScalars.AppendElement(
2088                 mozilla::MakePair(info.name(), scalarValue));
2089           }
2090         }
2091       }
2092       return NS_OK;
2093     };
2094 
2095     // Take a snapshot of the scalars.
2096     nsresult rv = snapshotter(gScalarStorageMap, false);
2097     if (NS_FAILED(rv)) {
2098       return rv;
2099     }
2100 
2101     // And a snapshot of the dynamic builtin ones.
2102     rv = snapshotter(gDynamicBuiltinScalarStorageMap, true);
2103     if (NS_FAILED(rv)) {
2104       return rv;
2105     }
2106 
2107     if (aClearScalars) {
2108       // The map already takes care of freeing the allocated memory.
2109       gScalarStorageMap.Clear();
2110       gDynamicBuiltinScalarStorageMap.Clear();
2111     }
2112   }
2113 
2114   // Reflect it to JS.
2115   for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
2116     ScalarArray& processScalars = iter.Data();
2117     const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
2118 
2119     // Create the object that will hold the scalars for this process and add it
2120     // to the returned root object.
2121     JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
2122     if (!processObj || !JS_DefineProperty(aCx, root_obj, processName,
2123                                           processObj, JSPROP_ENUMERATE)) {
2124       return NS_ERROR_FAILURE;
2125     }
2126 
2127     for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length();
2128          i++) {
2129       const DataPair& scalar = processScalars[i];
2130 
2131       // Convert it to a JS Val.
2132       JS::Rooted<JS::Value> scalarJsValue(aCx);
2133       nsresult rv = nsContentUtils::XPConnect()->VariantToJS(
2134           aCx, processObj, scalar.second(), &scalarJsValue);
2135       if (NS_FAILED(rv)) {
2136         return rv;
2137       }
2138 
2139       // Add it to the scalar object.
2140       if (!JS_DefineProperty(aCx, processObj, scalar.first(), scalarJsValue,
2141                              JSPROP_ENUMERATE)) {
2142         return NS_ERROR_FAILURE;
2143       }
2144     }
2145   }
2146 
2147   return NS_OK;
2148 }
2149 
2150 /**
2151  * Serializes the scalars from the given dataset to a json-style object and
2152  * resets them. The returned structure looks like: { "process": {
2153  * "category1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
2154  *
2155  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or
2156  * DATASET_RELEASE_CHANNEL_OPTIN.
2157  * @param aClear Whether to clear out the keyed scalars after snapshotting.
2158  */
CreateKeyedSnapshots(unsigned int aDataset,bool aClearScalars,JSContext * aCx,uint8_t optional_argc,JS::MutableHandle<JS::Value> aResult)2159 nsresult TelemetryScalar::CreateKeyedSnapshots(
2160     unsigned int aDataset, bool aClearScalars, JSContext* aCx,
2161     uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult) {
2162   MOZ_ASSERT(
2163       XRE_IsParentProcess(),
2164       "Snapshotting scalars should only happen in the parent processes.");
2165   // If no arguments were passed in, apply the default value.
2166   if (!optional_argc) {
2167     aClearScalars = false;
2168   }
2169 
2170   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
2171   if (!root_obj) {
2172     return NS_ERROR_FAILURE;
2173   }
2174   aResult.setObject(*root_obj);
2175 
2176   // Return `{}` in child processes.
2177   if (!XRE_IsParentProcess()) {
2178     return NS_OK;
2179   }
2180 
2181   // Only lock the mutex while accessing our data, without locking any JS
2182   // related code.
2183   typedef mozilla::Pair<const char*, nsTArray<KeyedScalar::KeyValuePair>>
2184       DataPair;
2185   typedef nsTArray<DataPair> ScalarArray;
2186   nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
2187   {
2188     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2189 
2190     auto snapshotter = [aDataset, &locker, &scalarsToReflect](
2191                            ProcessesKeyedScalarsMapType& aProcessStorage,
2192                            bool aIsBuiltinDynamic) -> nsresult {
2193       // Iterate the scalars in aProcessStorage. The storage may contain empty
2194       // or yet to be initialized scalars from all the supported processes.
2195       for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
2196         KeyedScalarStorageMapType* scalarStorage =
2197             static_cast<KeyedScalarStorageMapType*>(iter.Data());
2198         ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
2199 
2200         // Are we in the "Dynamic" process?
2201         bool isDynamicProcess =
2202             ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
2203 
2204         for (auto childIter = scalarStorage->Iter(); !childIter.Done();
2205              childIter.Next()) {
2206           KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
2207 
2208           // Get the informations for this scalar.
2209           const BaseScalarInfo& info = internal_GetScalarInfo(
2210               locker, ScalarKey{childIter.Key(),
2211                                 aIsBuiltinDynamic ? true : isDynamicProcess});
2212 
2213           // Serialize the scalar if it's in the desired dataset.
2214           if (IsInDataset(info.dataset, aDataset)) {
2215             // Get the keys for this scalar.
2216             nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
2217             nsresult rv = scalar->GetValue(scalarKeyedData);
2218             if (NS_FAILED(rv)) {
2219               return rv;
2220             }
2221             // Append it to our list.
2222             processScalars.AppendElement(
2223                 mozilla::MakePair(info.name(), scalarKeyedData));
2224           }
2225         }
2226       }
2227       return NS_OK;
2228     };
2229 
2230     // Take a snapshot of the scalars.
2231     nsresult rv = snapshotter(gKeyedScalarStorageMap, false);
2232     if (NS_FAILED(rv)) {
2233       return rv;
2234     }
2235 
2236     // And a snapshot of the dynamic builtin ones.
2237     rv = snapshotter(gDynamicBuiltinKeyedScalarStorageMap, true);
2238     if (NS_FAILED(rv)) {
2239       return rv;
2240     }
2241 
2242     if (aClearScalars) {
2243       // The map already takes care of freeing the allocated memory.
2244       gKeyedScalarStorageMap.Clear();
2245       gDynamicBuiltinKeyedScalarStorageMap.Clear();
2246     }
2247   }
2248 
2249   // Reflect it to JS.
2250   for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
2251     ScalarArray& processScalars = iter.Data();
2252     const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
2253 
2254     // Create the object that will hold the scalars for this process and add it
2255     // to the returned root object.
2256     JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
2257     if (!processObj || !JS_DefineProperty(aCx, root_obj, processName,
2258                                           processObj, JSPROP_ENUMERATE)) {
2259       return NS_ERROR_FAILURE;
2260     }
2261 
2262     for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length();
2263          i++) {
2264       const DataPair& keyedScalarData = processScalars[i];
2265 
2266       // Go through each keyed scalar and create a keyed scalar object.
2267       // This object will hold the values for all the keyed scalar keys.
2268       JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
2269 
2270       // Define a property for each scalar key, then add it to the keyed scalar
2271       // object.
2272       const nsTArray<KeyedScalar::KeyValuePair>& keyProps =
2273           keyedScalarData.second();
2274       for (uint32_t i = 0; i < keyProps.Length(); i++) {
2275         const KeyedScalar::KeyValuePair& keyData = keyProps[i];
2276 
2277         // Convert the value for the key to a JSValue.
2278         JS::Rooted<JS::Value> keyJsValue(aCx);
2279         nsresult rv = nsContentUtils::XPConnect()->VariantToJS(
2280             aCx, keyedScalarObj, keyData.second(), &keyJsValue);
2281         if (NS_FAILED(rv)) {
2282           return rv;
2283         }
2284 
2285         // Add the key to the scalar representation.
2286         const NS_ConvertUTF8toUTF16 key(keyData.first());
2287         if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(),
2288                                  keyJsValue, JSPROP_ENUMERATE)) {
2289           return NS_ERROR_FAILURE;
2290         }
2291       }
2292 
2293       // Add the scalar to the root object.
2294       if (!JS_DefineProperty(aCx, processObj, keyedScalarData.first(),
2295                              keyedScalarObj, JSPROP_ENUMERATE)) {
2296         return NS_ERROR_FAILURE;
2297       }
2298     }
2299   }
2300 
2301   return NS_OK;
2302 }
2303 
RegisterScalars(const nsACString & aCategoryName,JS::Handle<JS::Value> aScalarData,bool aBuiltin,JSContext * cx)2304 nsresult TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
2305                                           JS::Handle<JS::Value> aScalarData,
2306                                           bool aBuiltin, JSContext* cx) {
2307   MOZ_ASSERT(XRE_IsParentProcess(),
2308              "Dynamic scalars should only be created in the parent process.");
2309 
2310   if (!IsValidIdentifierString(aCategoryName, kMaximumCategoryNameLength, true,
2311                                false)) {
2312     JS_ReportErrorASCII(cx, "Invalid category name %s.",
2313                         PromiseFlatCString(aCategoryName).get());
2314     return NS_ERROR_INVALID_ARG;
2315   }
2316 
2317   if (!aScalarData.isObject()) {
2318     JS_ReportErrorASCII(cx, "Scalar data parameter should be an object");
2319     return NS_ERROR_INVALID_ARG;
2320   }
2321 
2322   JS::RootedObject obj(cx, &aScalarData.toObject());
2323   JS::Rooted<JS::IdVector> scalarPropertyIds(cx, JS::IdVector(cx));
2324   if (!JS_Enumerate(cx, obj, &scalarPropertyIds)) {
2325     return NS_ERROR_FAILURE;
2326   }
2327 
2328   // Collect the scalar data into local storage first.
2329   // Only after successfully validating all contained scalars will we register
2330   // them into global storage.
2331   nsTArray<DynamicScalarInfo> newScalarInfos;
2332 
2333   for (size_t i = 0, n = scalarPropertyIds.length(); i < n; i++) {
2334     nsAutoJSString scalarName;
2335     if (!scalarName.init(cx, scalarPropertyIds[i])) {
2336       return NS_ERROR_FAILURE;
2337     }
2338 
2339     if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(scalarName),
2340                                  kMaximumScalarNameLength, false, true)) {
2341       JS_ReportErrorASCII(
2342           cx, "Invalid scalar name %s.",
2343           PromiseFlatCString(NS_ConvertUTF16toUTF8(scalarName)).get());
2344       return NS_ERROR_INVALID_ARG;
2345     }
2346 
2347     // Join the category and the probe names.
2348     nsPrintfCString fullName("%s.%s", PromiseFlatCString(aCategoryName).get(),
2349                              NS_ConvertUTF16toUTF8(scalarName).get());
2350 
2351     JS::RootedValue value(cx);
2352     if (!JS_GetPropertyById(cx, obj, scalarPropertyIds[i], &value) ||
2353         !value.isObject()) {
2354       return NS_ERROR_FAILURE;
2355     }
2356     JS::RootedObject scalarDef(cx, &value.toObject());
2357 
2358     // Get the scalar's kind.
2359     if (!JS_GetProperty(cx, scalarDef, "kind", &value) || !value.isInt32()) {
2360       JS_ReportErrorASCII(cx, "Invalid or missing 'kind' for scalar %s.",
2361                           PromiseFlatCString(fullName).get());
2362       return NS_ERROR_FAILURE;
2363     }
2364     uint32_t kind = static_cast<uint32_t>(value.toInt32());
2365 
2366     // Get the optional scalar's recording policy (default to false).
2367     bool hasProperty = false;
2368     bool recordOnRelease = false;
2369     if (JS_HasProperty(cx, scalarDef, "record_on_release", &hasProperty) &&
2370         hasProperty) {
2371       if (!JS_GetProperty(cx, scalarDef, "record_on_release", &value) ||
2372           !value.isBoolean()) {
2373         JS_ReportErrorASCII(cx, "Invalid 'record_on_release' for scalar %s.",
2374                             PromiseFlatCString(fullName).get());
2375         return NS_ERROR_FAILURE;
2376       }
2377       recordOnRelease = static_cast<bool>(value.toBoolean());
2378     }
2379 
2380     // Get the optional scalar's keyed (default to false).
2381     bool keyed = false;
2382     if (JS_HasProperty(cx, scalarDef, "keyed", &hasProperty) && hasProperty) {
2383       if (!JS_GetProperty(cx, scalarDef, "keyed", &value) ||
2384           !value.isBoolean()) {
2385         JS_ReportErrorASCII(cx, "Invalid 'keyed' for scalar %s.",
2386                             PromiseFlatCString(fullName).get());
2387         return NS_ERROR_FAILURE;
2388       }
2389       keyed = static_cast<bool>(value.toBoolean());
2390     }
2391 
2392     // Get the optional scalar's expired state (default to false).
2393     bool expired = false;
2394     if (JS_HasProperty(cx, scalarDef, "expired", &hasProperty) && hasProperty) {
2395       if (!JS_GetProperty(cx, scalarDef, "expired", &value) ||
2396           !value.isBoolean()) {
2397         JS_ReportErrorASCII(cx, "Invalid 'expired' for scalar %s.",
2398                             PromiseFlatCString(fullName).get());
2399         return NS_ERROR_FAILURE;
2400       }
2401       expired = static_cast<bool>(value.toBoolean());
2402     }
2403 
2404     // We defer the actual registration here in case any other event description
2405     // is invalid. In that case we don't need to roll back any partial
2406     // registration.
2407     newScalarInfos.AppendElement(DynamicScalarInfo{
2408         kind, recordOnRelease, expired, fullName, keyed, aBuiltin});
2409   }
2410 
2411   // Register the dynamic definition on the parent process.
2412   {
2413     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2414     ::internal_RegisterScalars(locker, newScalarInfos);
2415 
2416     // Propagate the registration to all the content-processes. Please note that
2417     // this does not require to hold the mutex.
2418     ::internal_BroadcastDefinitions(locker, newScalarInfos);
2419   }
2420   return NS_OK;
2421 }
2422 
2423 /**
2424  * Resets all the stored scalars. This is intended to be only used in tests.
2425  */
ClearScalars()2426 void TelemetryScalar::ClearScalars() {
2427   MOZ_ASSERT(XRE_IsParentProcess(),
2428              "Scalars should only be cleared in the parent process.");
2429   if (!XRE_IsParentProcess()) {
2430     return;
2431   }
2432 
2433   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2434   gScalarStorageMap.Clear();
2435   gKeyedScalarStorageMap.Clear();
2436   gDynamicBuiltinScalarStorageMap.Clear();
2437   gDynamicBuiltinKeyedScalarStorageMap.Clear();
2438 }
2439 
GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)2440 size_t TelemetryScalar::GetMapShallowSizesOfExcludingThis(
2441     mozilla::MallocSizeOf aMallocSizeOf) {
2442   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2443   return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
2444 }
2445 
GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)2446 size_t TelemetryScalar::GetScalarSizesOfIncludingThis(
2447     mozilla::MallocSizeOf aMallocSizeOf) {
2448   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2449   size_t n = 0;
2450 
2451   auto getSizeOf = [aMallocSizeOf](auto& storageMap) {
2452     size_t partial = 0;
2453     for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
2454       auto scalarStorage = iter.UserData();
2455       for (auto childIter = scalarStorage->Iter(); !childIter.Done();
2456            childIter.Next()) {
2457         auto scalar = childIter.UserData();
2458         partial += scalar->SizeOfIncludingThis(aMallocSizeOf);
2459       }
2460     }
2461     return partial;
2462   };
2463 
2464   // Account for all the storage used for the different scalar types.
2465   n += getSizeOf(gScalarStorageMap);
2466   n += getSizeOf(gKeyedScalarStorageMap);
2467   n += getSizeOf(gDynamicBuiltinScalarStorageMap);
2468   n += getSizeOf(gDynamicBuiltinKeyedScalarStorageMap);
2469 
2470   return n;
2471 }
2472 
UpdateChildData(ProcessID aProcessType,const nsTArray<mozilla::Telemetry::ScalarAction> & aScalarActions)2473 void TelemetryScalar::UpdateChildData(
2474     ProcessID aProcessType,
2475     const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions) {
2476   MOZ_ASSERT(XRE_IsParentProcess(),
2477              "The stored child processes scalar data must be updated from the "
2478              "parent process.");
2479   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2480   if (!internal_CanRecordBase(locker)) {
2481     return;
2482   }
2483 
2484   for (auto& upd : aScalarActions) {
2485     ScalarKey uniqueId{upd.mId, upd.mDynamic};
2486     if (NS_WARN_IF(!internal_IsValidId(locker, uniqueId))) {
2487       MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2488       continue;
2489     }
2490 
2491     if (internal_IsKeyedScalar(locker, uniqueId)) {
2492       continue;
2493     }
2494 
2495     // Are we allowed to record this scalar? We don't need to check for
2496     // allowed processes here, that's taken care of when recording
2497     // in child processes.
2498     if (!internal_CanRecordForScalarID(locker, uniqueId)) {
2499       continue;
2500     }
2501 
2502     // Refresh the data in the parent process with the data coming from the
2503     // child processes.
2504     ScalarBase* scalar = nullptr;
2505     nsresult rv =
2506         internal_GetScalarByEnum(locker, uniqueId, aProcessType, &scalar);
2507     if (NS_FAILED(rv)) {
2508       NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
2509       continue;
2510     }
2511 
2512     if (upd.mData.isNothing()) {
2513       MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
2514       continue;
2515     }
2516 
2517     // Get the type of this scalar from the scalar ID. We already checked
2518     // for its validity a few lines above.
2519     const uint32_t scalarType = internal_GetScalarInfo(locker, uniqueId).kind;
2520 
2521     // Extract the data from the mozilla::Variant.
2522     switch (upd.mActionType) {
2523       case ScalarActionType::eSet: {
2524         switch (scalarType) {
2525           case nsITelemetry::SCALAR_TYPE_COUNT:
2526             scalar->SetValue(upd.mData->as<uint32_t>());
2527             break;
2528           case nsITelemetry::SCALAR_TYPE_BOOLEAN:
2529             scalar->SetValue(upd.mData->as<bool>());
2530             break;
2531           case nsITelemetry::SCALAR_TYPE_STRING:
2532             scalar->SetValue(upd.mData->as<nsString>());
2533             break;
2534         }
2535         break;
2536       }
2537       case ScalarActionType::eAdd: {
2538         if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2539           NS_WARNING("Attempting to add on a non count scalar.");
2540           continue;
2541         }
2542         // We only support adding uint32_t.
2543         scalar->AddValue(upd.mData->as<uint32_t>());
2544         break;
2545       }
2546       case ScalarActionType::eSetMaximum: {
2547         if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2548           NS_WARNING("Attempting to add on a non count scalar.");
2549           continue;
2550         }
2551         // We only support SetMaximum on uint32_t.
2552         scalar->SetMaximum(upd.mData->as<uint32_t>());
2553         break;
2554       }
2555       default:
2556         NS_WARNING("Unsupported action coming from scalar child updates.");
2557     }
2558   }
2559 }
2560 
UpdateChildKeyedData(ProcessID aProcessType,const nsTArray<mozilla::Telemetry::KeyedScalarAction> & aScalarActions)2561 void TelemetryScalar::UpdateChildKeyedData(
2562     ProcessID aProcessType,
2563     const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions) {
2564   MOZ_ASSERT(XRE_IsParentProcess(),
2565              "The stored child processes keyed scalar data must be updated "
2566              "from the parent process.");
2567   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2568   if (!internal_CanRecordBase(locker)) {
2569     return;
2570   }
2571 
2572   for (auto& upd : aScalarActions) {
2573     ScalarKey uniqueId{upd.mId, upd.mDynamic};
2574     if (NS_WARN_IF(!internal_IsValidId(locker, uniqueId))) {
2575       MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2576       continue;
2577     }
2578 
2579     if (!internal_IsKeyedScalar(locker, uniqueId)) {
2580       continue;
2581     }
2582 
2583     // Are we allowed to record this scalar? We don't need to check for
2584     // allowed processes here, that's taken care of when recording
2585     // in child processes.
2586     if (!internal_CanRecordForScalarID(locker, uniqueId)) {
2587       continue;
2588     }
2589 
2590     // Refresh the data in the parent process with the data coming from the
2591     // child processes.
2592     KeyedScalar* scalar = nullptr;
2593     nsresult rv =
2594         internal_GetKeyedScalarByEnum(locker, uniqueId, aProcessType, &scalar);
2595     if (NS_FAILED(rv)) {
2596       NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
2597       continue;
2598     }
2599 
2600     if (upd.mData.isNothing()) {
2601       MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
2602       continue;
2603     }
2604 
2605     // Get the type of this scalar from the scalar ID. We already checked
2606     // for its validity a few lines above.
2607     const uint32_t scalarType = internal_GetScalarInfo(locker, uniqueId).kind;
2608 
2609     // Extract the data from the mozilla::Variant.
2610     switch (upd.mActionType) {
2611       case ScalarActionType::eSet: {
2612         switch (scalarType) {
2613           case nsITelemetry::SCALAR_TYPE_COUNT:
2614             scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey),
2615                              upd.mData->as<uint32_t>());
2616             break;
2617           case nsITelemetry::SCALAR_TYPE_BOOLEAN:
2618             scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey),
2619                              upd.mData->as<bool>());
2620             break;
2621           default:
2622             NS_WARNING("Unsupported type coming from scalar child updates.");
2623         }
2624         break;
2625       }
2626       case ScalarActionType::eAdd: {
2627         if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2628           NS_WARNING("Attempting to add on a non count scalar.");
2629           continue;
2630         }
2631         // We only support adding on uint32_t.
2632         scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey),
2633                          upd.mData->as<uint32_t>());
2634         break;
2635       }
2636       case ScalarActionType::eSetMaximum: {
2637         if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2638           NS_WARNING("Attempting to add on a non count scalar.");
2639           continue;
2640         }
2641         // We only support SetMaximum on uint32_t.
2642         scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey),
2643                            upd.mData->as<uint32_t>());
2644         break;
2645       }
2646       default:
2647         NS_WARNING(
2648             "Unsupported action coming from keyed scalar child updates.");
2649     }
2650   }
2651 }
2652 
RecordDiscardedData(ProcessID aProcessType,const mozilla::Telemetry::DiscardedData & aDiscardedData)2653 void TelemetryScalar::RecordDiscardedData(
2654     ProcessID aProcessType,
2655     const mozilla::Telemetry::DiscardedData& aDiscardedData) {
2656   MOZ_ASSERT(XRE_IsParentProcess(),
2657              "Discarded Data must be updated from the parent process.");
2658   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2659   if (!internal_CanRecordBase(locker)) {
2660     return;
2661   }
2662 
2663   ScalarBase* scalar = nullptr;
2664   mozilla::DebugOnly<nsresult> rv;
2665 
2666   rv = internal_GetScalarByEnum(
2667       locker,
2668       ScalarKey{
2669           static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_ACCUMULATIONS),
2670           false},
2671       aProcessType, &scalar);
2672   MOZ_ASSERT(NS_SUCCEEDED(rv));
2673   scalar->AddValue(aDiscardedData.mDiscardedHistogramAccumulations);
2674 
2675   rv = internal_GetScalarByEnum(
2676       locker,
2677       ScalarKey{static_cast<uint32_t>(
2678                     ScalarID::TELEMETRY_DISCARDED_KEYED_ACCUMULATIONS),
2679                 false},
2680       aProcessType, &scalar);
2681   MOZ_ASSERT(NS_SUCCEEDED(rv));
2682   scalar->AddValue(aDiscardedData.mDiscardedKeyedHistogramAccumulations);
2683 
2684   rv = internal_GetScalarByEnum(
2685       locker,
2686       ScalarKey{
2687           static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_SCALAR_ACTIONS),
2688           false},
2689       aProcessType, &scalar);
2690   MOZ_ASSERT(NS_SUCCEEDED(rv));
2691   scalar->AddValue(aDiscardedData.mDiscardedScalarActions);
2692 
2693   rv = internal_GetScalarByEnum(
2694       locker,
2695       ScalarKey{static_cast<uint32_t>(
2696                     ScalarID::TELEMETRY_DISCARDED_KEYED_SCALAR_ACTIONS),
2697                 false},
2698       aProcessType, &scalar);
2699   MOZ_ASSERT(NS_SUCCEEDED(rv));
2700   scalar->AddValue(aDiscardedData.mDiscardedKeyedScalarActions);
2701 
2702   rv = internal_GetScalarByEnum(
2703       locker,
2704       ScalarKey{
2705           static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_CHILD_EVENTS),
2706           false},
2707       aProcessType, &scalar);
2708   MOZ_ASSERT(NS_SUCCEEDED(rv));
2709   scalar->AddValue(aDiscardedData.mDiscardedChildEvents);
2710 }
2711 
2712 /**
2713  * Get the dynamic scalar definitions in an IPC-friendly
2714  * structure.
2715  */
GetDynamicScalarDefinitions(nsTArray<DynamicScalarDefinition> & aDefArray)2716 void TelemetryScalar::GetDynamicScalarDefinitions(
2717     nsTArray<DynamicScalarDefinition>& aDefArray) {
2718   MOZ_ASSERT(XRE_IsParentProcess());
2719   if (!gDynamicScalarInfo) {
2720     // Don't have dynamic scalar definitions. Bail out!
2721     return;
2722   }
2723 
2724   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2725   internal_DynamicScalarToIPC(locker, *gDynamicScalarInfo, aDefArray);
2726 }
2727 
2728 /**
2729  * This adds the dynamic scalar definitions coming from
2730  * the parent process to this child process. If a dynamic
2731  * scalar definition is already defined, check if the new definition
2732  * makes the scalar expired and eventually update the expiration
2733  * state.
2734  */
AddDynamicScalarDefinitions(const nsTArray<DynamicScalarDefinition> & aDefs)2735 void TelemetryScalar::AddDynamicScalarDefinitions(
2736     const nsTArray<DynamicScalarDefinition>& aDefs) {
2737   MOZ_ASSERT(!XRE_IsParentProcess());
2738 
2739   nsTArray<DynamicScalarInfo> dynamicStubs;
2740 
2741   // Populate the definitions array before acquiring the lock.
2742   for (auto def : aDefs) {
2743     bool recordOnRelease =
2744         def.dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT;
2745     dynamicStubs.AppendElement(
2746         DynamicScalarInfo{def.type, recordOnRelease, def.expired, def.name,
2747                           def.keyed, false /* builtin */});
2748   }
2749 
2750   {
2751     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2752     internal_RegisterScalars(locker, dynamicStubs);
2753   }
2754 }
2755