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