Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "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/JSONWriter.h"
22 : #include "mozilla/Preferences.h"
23 : #include "mozilla/StaticMutex.h"
24 : #include "mozilla/StaticPtr.h"
25 : #include "mozilla/Unused.h"
26 :
27 : #include "TelemetryCommon.h"
28 : #include "TelemetryScalar.h"
29 : #include "TelemetryScalarData.h"
30 : #include "ipc/TelemetryComms.h"
31 : #include "ipc/TelemetryIPCAccumulator.h"
32 :
33 : using mozilla::Preferences;
34 : using mozilla::StaticAutoPtr;
35 : using mozilla::StaticMutex;
36 : using mozilla::StaticMutexAutoLock;
37 : using mozilla::Some;
38 : using mozilla::Nothing;
39 : using mozilla::Telemetry::Common::AutoHashtable;
40 : using mozilla::Telemetry::Common::IsExpiredVersion;
41 : using mozilla::Telemetry::Common::CanRecordDataset;
42 : using mozilla::Telemetry::Common::CanRecordProduct;
43 : using mozilla::Telemetry::Common::IsInDataset;
44 : using mozilla::Telemetry::Common::LogToBrowserConsole;
45 : using mozilla::Telemetry::Common::GetNameForProcessID;
46 : using mozilla::Telemetry::Common::GetIDForProcessName;
47 : using mozilla::Telemetry::Common::RecordedProcessType;
48 : using mozilla::Telemetry::Common::IsValidIdentifierString;
49 : using mozilla::Telemetry::Common::GetCurrentProduct;
50 : using mozilla::Telemetry::Common::SupportedProduct;
51 : using mozilla::Telemetry::ScalarActionType;
52 : using mozilla::Telemetry::ScalarAction;
53 : using mozilla::Telemetry::KeyedScalarAction;
54 : using mozilla::Telemetry::ScalarID;
55 : using mozilla::Telemetry::DynamicScalarDefinition;
56 : using mozilla::Telemetry::ScalarVariant;
57 : using mozilla::Telemetry::ProcessID;
58 :
59 : namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
60 :
61 : ////////////////////////////////////////////////////////////////////////
62 : ////////////////////////////////////////////////////////////////////////
63 : //
64 : // Naming: there are two kinds of functions in this file:
65 : //
66 : // * Functions named internal_*: these can only be reached via an
67 : // interface function (TelemetryScalar::*). If they access shared
68 : // state, they require the interface function to have acquired
69 : // |gTelemetryScalarMutex| to ensure thread safety.
70 : //
71 : // * Functions named TelemetryScalar::*. This is the external interface.
72 : // Entries and exits to these functions are serialised using
73 : // |gTelemetryScalarsMutex|.
74 : //
75 : // Avoiding races and deadlocks:
76 : //
77 : // All functions in the external interface (TelemetryScalar::*) are
78 : // serialised using the mutex |gTelemetryScalarsMutex|. This means
79 : // that the external interface is thread-safe. But it also brings
80 : // a danger of deadlock if any function in the external interface can
81 : // get back to that interface. That is, we will deadlock on any call
82 : // chain like this
83 : //
84 : // TelemetryScalar::* -> .. any functions .. -> TelemetryScalar::*
85 : //
86 : // To reduce the danger of that happening, observe the following rules:
87 : //
88 : // * No function in TelemetryScalar::* may directly call, nor take the
89 : // address of, any other function in TelemetryScalar::*.
90 : //
91 : // * No internal function internal_* may call, nor take the address
92 : // of, any function in TelemetryScalar::*.
93 :
94 : ////////////////////////////////////////////////////////////////////////
95 : ////////////////////////////////////////////////////////////////////////
96 : //
97 : // PRIVATE TYPES
98 :
99 : namespace {
100 :
101 : const uint32_t kMaximumNumberOfKeys = 100;
102 : const uint32_t kMaximumKeyStringLength = 72;
103 : const uint32_t kMaximumStringValueLength = 50;
104 : // The category and scalar name maximum lengths are used by the dynamic
105 : // scalar registration function and must match the constants used by
106 : // the 'parse_scalars.py' script for static scalars.
107 : const uint32_t kMaximumCategoryNameLength = 40;
108 : const uint32_t kMaximumScalarNameLength = 40;
109 : const uint32_t kScalarCount =
110 : static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
111 :
112 : // To stop growing unbounded in memory while waiting for scalar deserialization
113 : // to finish, we immediately apply pending operations if the array reaches
114 : // a certain high water mark of elements.
115 : const size_t kScalarActionsArrayHighWaterMark = 10000;
116 :
117 : enum class ScalarResult : uint8_t {
118 : // Nothing went wrong.
119 : Ok,
120 : // General Scalar Errors
121 : NotInitialized,
122 : CannotUnpackVariant,
123 : CannotRecordInProcess,
124 : CannotRecordDataset,
125 : KeyedTypeMismatch,
126 : UnknownScalar,
127 : OperationNotSupported,
128 : InvalidType,
129 : InvalidValue,
130 : // Keyed Scalar Errors
131 : KeyIsEmpty,
132 : KeyTooLong,
133 : TooManyKeys,
134 : // String Scalar Errors
135 : StringTooLong,
136 : // Unsigned Scalar Errors
137 : UnsignedNegativeValue,
138 : UnsignedTruncatedValue,
139 : };
140 :
141 : // A common identifier for both built-in and dynamic scalars.
142 : struct ScalarKey {
143 : uint32_t id;
144 : bool dynamic;
145 : };
146 :
147 : /**
148 : * Scalar information for dynamic definitions.
149 : */
150 0 : struct DynamicScalarInfo : BaseScalarInfo {
151 : nsCString mDynamicName;
152 : bool mDynamicExpiration;
153 :
154 0 : DynamicScalarInfo(uint32_t aKind, bool aRecordOnRelease,
155 : bool aExpired, const nsACString& aName,
156 : bool aKeyed, bool aBuiltin)
157 0 : : BaseScalarInfo(aKind,
158 : aRecordOnRelease ?
159 : nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT :
160 : nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
161 : RecordedProcessType::All,
162 : aKeyed,
163 : GetCurrentProduct(),
164 : aBuiltin)
165 : , mDynamicName(aName)
166 0 : , mDynamicExpiration(aExpired)
167 0 : {}
168 :
169 : // The following functions will read the stored text
170 : // instead of looking it up in the statically generated
171 : // tables.
172 : const char *name() const override;
173 : const char *expiration() const override;
174 : };
175 :
176 : const char *
177 0 : DynamicScalarInfo::name() const
178 : {
179 0 : return mDynamicName.get();
180 : }
181 :
182 : const char *
183 0 : DynamicScalarInfo::expiration() const
184 : {
185 : // Dynamic scalars can either be expired or not (boolean flag).
186 : // Return an appropriate version string to leverage the scalar expiration
187 : // logic.
188 0 : return mDynamicExpiration ? "1.0" : "never";
189 : }
190 :
191 : typedef nsBaseHashtableET<nsDepCharHashKey, ScalarKey> CharPtrEntryType;
192 : typedef AutoHashtable<CharPtrEntryType> ScalarMapType;
193 :
194 : // Dynamic scalar definitions.
195 0 : StaticAutoPtr<nsTArray<DynamicScalarInfo>> gDynamicScalarInfo;
196 :
197 : const BaseScalarInfo&
198 0 : internal_GetScalarInfo(const StaticMutexAutoLock& lock, const ScalarKey& aId)
199 : {
200 0 : if (!aId.dynamic) {
201 0 : return gScalars[aId.id];
202 : }
203 :
204 0 : return (*gDynamicScalarInfo)[aId.id];
205 : }
206 :
207 : bool
208 : IsValidEnumId(mozilla::Telemetry::ScalarID aID)
209 : {
210 0 : return aID < mozilla::Telemetry::ScalarID::ScalarCount;
211 : }
212 :
213 : bool
214 0 : internal_IsValidId(const StaticMutexAutoLock& lock, const ScalarKey& aId)
215 : {
216 : // Please note that this function needs to be called with the scalar
217 : // mutex being acquired: other functions might be messing with
218 : // |gDynamicScalarInfo|.
219 0 : return aId.dynamic ? (aId.id < gDynamicScalarInfo->Length()) :
220 0 : IsValidEnumId(static_cast<mozilla::Telemetry::ScalarID>(aId.id));
221 : }
222 :
223 : /**
224 : * Convert a nsIVariant to a mozilla::Variant, which is used for
225 : * accumulating child process scalars.
226 : */
227 : ScalarResult
228 0 : GetVariantFromIVariant(nsIVariant* aInput, uint32_t aScalarKind,
229 : mozilla::Maybe<ScalarVariant>& aOutput)
230 : {
231 0 : switch (aScalarKind) {
232 : case nsITelemetry::SCALAR_TYPE_COUNT:
233 : {
234 0 : uint32_t val = 0;
235 0 : nsresult rv = aInput->GetAsUint32(&val);
236 0 : if (NS_FAILED(rv)) {
237 0 : return ScalarResult::CannotUnpackVariant;
238 : }
239 0 : aOutput = mozilla::Some(mozilla::AsVariant(val));
240 0 : break;
241 : }
242 : case nsITelemetry::SCALAR_TYPE_STRING:
243 : {
244 0 : nsString val;
245 0 : nsresult rv = aInput->GetAsAString(val);
246 0 : if (NS_FAILED(rv)) {
247 0 : return ScalarResult::CannotUnpackVariant;
248 : }
249 0 : aOutput = mozilla::Some(mozilla::AsVariant(val));
250 0 : break;
251 : }
252 : case nsITelemetry::SCALAR_TYPE_BOOLEAN:
253 : {
254 0 : bool val = false;
255 0 : nsresult rv = aInput->GetAsBool(&val);
256 0 : if (NS_FAILED(rv)) {
257 0 : return ScalarResult::CannotUnpackVariant;
258 : }
259 0 : aOutput = mozilla::Some(mozilla::AsVariant(val));
260 0 : break;
261 : }
262 : default:
263 0 : MOZ_ASSERT(false, "Unknown scalar kind.");
264 : return ScalarResult::UnknownScalar;
265 : }
266 : return ScalarResult::Ok;
267 : }
268 :
269 : /**
270 : * Write a nsIVariant with a JSONWriter, used for GeckoView persistence.
271 : */
272 : nsresult
273 0 : WriteVariantToJSONWriter(uint32_t aScalarType, nsIVariant* aInputValue,
274 : const char* aPropertyName, mozilla::JSONWriter& aWriter)
275 : {
276 0 : MOZ_ASSERT(aInputValue);
277 :
278 0 : switch (aScalarType) {
279 : case nsITelemetry::SCALAR_TYPE_COUNT:
280 : {
281 0 : uint32_t val = 0;
282 0 : nsresult rv = aInputValue->GetAsUint32(&val);
283 0 : NS_ENSURE_SUCCESS(rv, rv);
284 0 : aWriter.IntProperty(aPropertyName, val);
285 0 : break;
286 : }
287 : case nsITelemetry::SCALAR_TYPE_STRING:
288 : {
289 0 : nsCString val;
290 0 : nsresult rv = aInputValue->GetAsACString(val);
291 0 : NS_ENSURE_SUCCESS(rv, rv);
292 0 : aWriter.StringProperty(aPropertyName, val.get());
293 0 : break;
294 : }
295 : case nsITelemetry::SCALAR_TYPE_BOOLEAN:
296 : {
297 0 : bool val = false;
298 0 : nsresult rv = aInputValue->GetAsBool(&val);
299 0 : NS_ENSURE_SUCCESS(rv, rv);
300 0 : aWriter.BoolProperty(aPropertyName, val);
301 0 : break;
302 : }
303 : default:
304 0 : MOZ_ASSERT(false, "Unknown scalar kind.");
305 : return NS_ERROR_FAILURE;
306 : }
307 :
308 : return NS_OK;
309 : }
310 :
311 : // Implements the methods for ScalarInfo.
312 : const char *
313 0 : ScalarInfo::name() const
314 : {
315 0 : return &gScalarsStringTable[this->name_offset];
316 : }
317 :
318 : const char *
319 0 : ScalarInfo::expiration() const
320 : {
321 0 : return &gScalarsStringTable[this->expiration_offset];
322 : }
323 :
324 : /**
325 : * The base scalar object, that serves as a common ancestor for storage
326 : * purposes.
327 : */
328 : class ScalarBase
329 : {
330 : public:
331 : virtual ~ScalarBase() = default;
332 :
333 : // Set, Add and SetMaximum functions as described in the Telemetry IDL.
334 : virtual ScalarResult SetValue(nsIVariant* aValue) = 0;
335 0 : virtual ScalarResult AddValue(nsIVariant* aValue) { return ScalarResult::OperationNotSupported; }
336 0 : virtual ScalarResult SetMaximum(nsIVariant* aValue) { return ScalarResult::OperationNotSupported; }
337 :
338 : // Convenience methods used by the C++ API.
339 0 : virtual void SetValue(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); }
340 0 : virtual ScalarResult SetValue(const nsAString& aValue) { return HandleUnsupported(); }
341 0 : virtual void SetValue(bool aValue) { mozilla::Unused << HandleUnsupported(); }
342 0 : virtual void AddValue(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); }
343 0 : virtual void SetMaximum(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); }
344 :
345 : // GetValue is used to get the value of the scalar when persisting it to JS.
346 : virtual nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const = 0;
347 :
348 : // To measure the memory stats.
349 : virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
350 :
351 : private:
352 : ScalarResult HandleUnsupported() const;
353 : };
354 :
355 : ScalarResult
356 0 : ScalarBase::HandleUnsupported() const
357 : {
358 0 : MOZ_ASSERT(false, "This operation is not support for this scalar type.");
359 : return ScalarResult::OperationNotSupported;
360 : }
361 :
362 : /**
363 : * The implementation for the unsigned int scalar type.
364 : */
365 : class ScalarUnsigned : public ScalarBase
366 : {
367 : public:
368 : using ScalarBase::SetValue;
369 :
370 0 : ScalarUnsigned() : mStorage(0) {};
371 0 : ~ScalarUnsigned() override = default;
372 :
373 : ScalarResult SetValue(nsIVariant* aValue) final;
374 : void SetValue(uint32_t aValue) final;
375 : ScalarResult AddValue(nsIVariant* aValue) final;
376 : void AddValue(uint32_t aValue) final;
377 : ScalarResult SetMaximum(nsIVariant* aValue) final;
378 : void SetMaximum(uint32_t aValue) final;
379 : nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
380 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
381 :
382 : private:
383 : uint32_t mStorage;
384 :
385 : ScalarResult CheckInput(nsIVariant* aValue);
386 :
387 : // Prevent copying.
388 : ScalarUnsigned(const ScalarUnsigned& aOther) = delete;
389 : void operator=(const ScalarUnsigned& aOther) = delete;
390 : };
391 :
392 : ScalarResult
393 0 : ScalarUnsigned::SetValue(nsIVariant* aValue)
394 : {
395 0 : ScalarResult sr = CheckInput(aValue);
396 0 : if (sr == ScalarResult::UnsignedNegativeValue) {
397 : return sr;
398 : }
399 :
400 0 : if (NS_FAILED(aValue->GetAsUint32(&mStorage))) {
401 : return ScalarResult::InvalidValue;
402 : }
403 0 : return sr;
404 : }
405 :
406 : void
407 0 : ScalarUnsigned::SetValue(uint32_t aValue)
408 : {
409 0 : mStorage = aValue;
410 0 : }
411 :
412 : ScalarResult
413 0 : ScalarUnsigned::AddValue(nsIVariant* aValue)
414 : {
415 0 : ScalarResult sr = CheckInput(aValue);
416 0 : if (sr == ScalarResult::UnsignedNegativeValue) {
417 : return sr;
418 : }
419 :
420 0 : uint32_t newAddend = 0;
421 0 : nsresult rv = aValue->GetAsUint32(&newAddend);
422 0 : if (NS_FAILED(rv)) {
423 : return ScalarResult::InvalidValue;
424 : }
425 0 : mStorage += newAddend;
426 0 : return sr;
427 : }
428 :
429 : void
430 0 : ScalarUnsigned::AddValue(uint32_t aValue)
431 : {
432 0 : mStorage += aValue;
433 0 : }
434 :
435 : ScalarResult
436 0 : ScalarUnsigned::SetMaximum(nsIVariant* aValue)
437 : {
438 0 : ScalarResult sr = CheckInput(aValue);
439 0 : if (sr == ScalarResult::UnsignedNegativeValue) {
440 : return sr;
441 : }
442 :
443 0 : uint32_t newValue = 0;
444 0 : nsresult rv = aValue->GetAsUint32(&newValue);
445 0 : if (NS_FAILED(rv)) {
446 : return ScalarResult::InvalidValue;
447 : }
448 0 : if (newValue > mStorage) {
449 0 : mStorage = newValue;
450 : }
451 : return sr;
452 : }
453 :
454 : void
455 0 : ScalarUnsigned::SetMaximum(uint32_t aValue)
456 : {
457 0 : if (aValue > mStorage) {
458 0 : mStorage = aValue;
459 : }
460 0 : }
461 :
462 : nsresult
463 0 : ScalarUnsigned::GetValue(nsCOMPtr<nsIVariant>& aResult) const
464 : {
465 0 : nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
466 0 : nsresult rv = outVar->SetAsUint32(mStorage);
467 0 : if (NS_FAILED(rv)) {
468 : return rv;
469 : }
470 0 : aResult = outVar.forget();
471 0 : return NS_OK;
472 : }
473 :
474 : size_t
475 0 : ScalarUnsigned::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
476 : {
477 0 : return aMallocSizeOf(this);
478 : }
479 :
480 : ScalarResult
481 0 : ScalarUnsigned::CheckInput(nsIVariant* aValue)
482 : {
483 : // If this is a floating point value/double, we will probably get truncated.
484 : uint16_t type;
485 0 : aValue->GetDataType(&type);
486 0 : if (type == nsIDataType::VTYPE_FLOAT ||
487 : type == nsIDataType::VTYPE_DOUBLE) {
488 : return ScalarResult::UnsignedTruncatedValue;
489 : }
490 :
491 : int32_t signedTest;
492 : // If we're able to cast the number to an int, check its sign.
493 : // Warn the user if he's trying to set the unsigned scalar to a negative
494 : // number.
495 0 : if (NS_SUCCEEDED(aValue->GetAsInt32(&signedTest)) &&
496 0 : signedTest < 0) {
497 : return ScalarResult::UnsignedNegativeValue;
498 : }
499 : return ScalarResult::Ok;
500 : }
501 :
502 : /**
503 : * The implementation for the string scalar type.
504 : */
505 : class ScalarString : public ScalarBase
506 : {
507 : public:
508 : using ScalarBase::SetValue;
509 :
510 0 : ScalarString() : mStorage(EmptyString()) {};
511 0 : ~ScalarString() override = default;
512 :
513 : ScalarResult SetValue(nsIVariant* aValue) final;
514 : ScalarResult SetValue(const nsAString& aValue) final;
515 : nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
516 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
517 :
518 : private:
519 : nsString mStorage;
520 :
521 : // Prevent copying.
522 : ScalarString(const ScalarString& aOther) = delete;
523 : void operator=(const ScalarString& aOther) = delete;
524 : };
525 :
526 : ScalarResult
527 0 : ScalarString::SetValue(nsIVariant* aValue)
528 : {
529 : // Check that we got the correct data type.
530 : uint16_t type;
531 0 : aValue->GetDataType(&type);
532 0 : if (type != nsIDataType::VTYPE_CHAR &&
533 0 : type != nsIDataType::VTYPE_WCHAR &&
534 0 : type != nsIDataType::VTYPE_DOMSTRING &&
535 0 : type != nsIDataType::VTYPE_CHAR_STR &&
536 0 : type != nsIDataType::VTYPE_WCHAR_STR &&
537 0 : type != nsIDataType::VTYPE_STRING_SIZE_IS &&
538 0 : type != nsIDataType::VTYPE_WSTRING_SIZE_IS &&
539 0 : type != nsIDataType::VTYPE_UTF8STRING &&
540 0 : type != nsIDataType::VTYPE_CSTRING &&
541 : type != nsIDataType::VTYPE_ASTRING) {
542 : return ScalarResult::InvalidType;
543 : }
544 :
545 0 : nsAutoString convertedString;
546 0 : nsresult rv = aValue->GetAsAString(convertedString);
547 0 : if (NS_FAILED(rv)) {
548 : return ScalarResult::InvalidValue;
549 : }
550 0 : return SetValue(convertedString);
551 : };
552 :
553 : ScalarResult
554 0 : ScalarString::SetValue(const nsAString& aValue)
555 : {
556 0 : mStorage = Substring(aValue, 0, kMaximumStringValueLength);
557 0 : if (aValue.Length() > kMaximumStringValueLength) {
558 : return ScalarResult::StringTooLong;
559 : }
560 0 : return ScalarResult::Ok;
561 : }
562 :
563 : nsresult
564 0 : ScalarString::GetValue(nsCOMPtr<nsIVariant>& aResult) const
565 : {
566 0 : nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
567 0 : nsresult rv = outVar->SetAsAString(mStorage);
568 0 : if (NS_FAILED(rv)) {
569 : return rv;
570 : }
571 0 : aResult = outVar.forget();
572 0 : return NS_OK;
573 : }
574 :
575 : size_t
576 0 : ScalarString::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
577 : {
578 0 : size_t n = aMallocSizeOf(this);
579 0 : n+= mStorage.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
580 0 : return n;
581 : }
582 :
583 : /**
584 : * The implementation for the boolean scalar type.
585 : */
586 : class ScalarBoolean : public ScalarBase
587 : {
588 : public:
589 : using ScalarBase::SetValue;
590 :
591 0 : ScalarBoolean() : mStorage(false) {};
592 0 : ~ScalarBoolean() override = default;
593 :
594 : ScalarResult SetValue(nsIVariant* aValue) final;
595 : void SetValue(bool aValue) final;
596 : nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final;
597 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
598 :
599 : private:
600 : bool mStorage;
601 :
602 : // Prevent copying.
603 : ScalarBoolean(const ScalarBoolean& aOther) = delete;
604 : void operator=(const ScalarBoolean& aOther) = delete;
605 : };
606 :
607 : ScalarResult
608 0 : ScalarBoolean::SetValue(nsIVariant* aValue)
609 : {
610 : // Check that we got the correct data type.
611 : uint16_t type;
612 0 : aValue->GetDataType(&type);
613 0 : if (type != nsIDataType::VTYPE_BOOL &&
614 0 : type != nsIDataType::VTYPE_INT8 &&
615 0 : type != nsIDataType::VTYPE_INT16 &&
616 0 : type != nsIDataType::VTYPE_INT32 &&
617 0 : type != nsIDataType::VTYPE_INT64 &&
618 0 : type != nsIDataType::VTYPE_UINT8 &&
619 0 : type != nsIDataType::VTYPE_UINT16 &&
620 0 : type != nsIDataType::VTYPE_UINT32 &&
621 : type != nsIDataType::VTYPE_UINT64) {
622 : return ScalarResult::InvalidType;
623 : }
624 :
625 0 : if (NS_FAILED(aValue->GetAsBool(&mStorage))) {
626 : return ScalarResult::InvalidValue;
627 : }
628 0 : return ScalarResult::Ok;
629 : };
630 :
631 : void
632 0 : ScalarBoolean::SetValue(bool aValue)
633 : {
634 0 : mStorage = aValue;
635 0 : }
636 :
637 : nsresult
638 0 : ScalarBoolean::GetValue(nsCOMPtr<nsIVariant>& aResult) const
639 : {
640 0 : nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
641 0 : nsresult rv = outVar->SetAsBool(mStorage);
642 0 : if (NS_FAILED(rv)) {
643 : return rv;
644 : }
645 0 : aResult = outVar.forget();
646 0 : return NS_OK;
647 : }
648 :
649 : size_t
650 0 : ScalarBoolean::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
651 : {
652 0 : return aMallocSizeOf(this);
653 : }
654 :
655 : /**
656 : * Allocate a scalar class given the scalar info.
657 : *
658 : * @param aInfo The informations for the scalar coming from the definition file.
659 : * @return nullptr if the scalar type is unknown, otherwise a valid pointer to the
660 : * scalar type.
661 : */
662 : ScalarBase*
663 0 : internal_ScalarAllocate(uint32_t aScalarKind)
664 : {
665 0 : ScalarBase* scalar = nullptr;
666 0 : switch (aScalarKind) {
667 : case nsITelemetry::SCALAR_TYPE_COUNT:
668 0 : scalar = new ScalarUnsigned();
669 0 : break;
670 : case nsITelemetry::SCALAR_TYPE_STRING:
671 0 : scalar = new ScalarString();
672 0 : break;
673 : case nsITelemetry::SCALAR_TYPE_BOOLEAN:
674 0 : scalar = new ScalarBoolean();
675 0 : break;
676 : default:
677 0 : MOZ_ASSERT(false, "Invalid scalar type");
678 : }
679 0 : return scalar;
680 : }
681 :
682 : /**
683 : * The implementation for the keyed scalar type.
684 : */
685 : class KeyedScalar
686 : {
687 : public:
688 : typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair;
689 :
690 : explicit KeyedScalar(uint32_t aScalarKind)
691 0 : : mScalarKind(aScalarKind)
692 0 : , mMaximumNumberOfKeys(kMaximumNumberOfKeys)
693 : { };
694 0 : ~KeyedScalar() = default;
695 :
696 : // Set, Add and SetMaximum functions as described in the Telemetry IDL.
697 : // These methods implicitly instantiate a Scalar[*] for each key.
698 : ScalarResult SetValue(const nsAString& aKey, nsIVariant* aValue);
699 : ScalarResult AddValue(const nsAString& aKey, nsIVariant* aValue);
700 : ScalarResult SetMaximum(const nsAString& aKey, nsIVariant* aValue);
701 :
702 : // Convenience methods used by the C++ API.
703 : void SetValue(const nsAString& aKey, uint32_t aValue);
704 : void SetValue(const nsAString& aKey, bool aValue);
705 : void AddValue(const nsAString& aKey, uint32_t aValue);
706 : void SetMaximum(const nsAString& aKey, uint32_t aValue);
707 :
708 : // GetValue is used to get the key-value pairs stored in the keyed scalar
709 : // when persisting it to JS.
710 : nsresult GetValue(nsTArray<KeyValuePair>& aValues) const;
711 :
712 : // To measure the memory stats.
713 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
714 :
715 : // To permit more keys than normal.
716 : void SetMaximumNumberOfKeys(uint32_t aMaximumNumberOfKeys)
717 : {
718 0 : mMaximumNumberOfKeys = aMaximumNumberOfKeys;
719 : };
720 :
721 : private:
722 : typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType;
723 :
724 : ScalarKeysMapType mScalarKeys;
725 : const uint32_t mScalarKind;
726 : uint32_t mMaximumNumberOfKeys;
727 :
728 : ScalarResult GetScalarForKey(const nsAString& aKey, ScalarBase** aRet);
729 : };
730 :
731 : ScalarResult
732 0 : KeyedScalar::SetValue(const nsAString& aKey, nsIVariant* aValue)
733 : {
734 0 : ScalarBase* scalar = nullptr;
735 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
736 0 : if (sr != ScalarResult::Ok) {
737 : return sr;
738 : }
739 :
740 0 : return scalar->SetValue(aValue);
741 : }
742 :
743 : ScalarResult
744 0 : KeyedScalar::AddValue(const nsAString& aKey, nsIVariant* aValue)
745 : {
746 0 : ScalarBase* scalar = nullptr;
747 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
748 0 : if (sr != ScalarResult::Ok) {
749 : return sr;
750 : }
751 :
752 0 : return scalar->AddValue(aValue);
753 : }
754 :
755 : ScalarResult
756 0 : KeyedScalar::SetMaximum(const nsAString& aKey, nsIVariant* aValue)
757 : {
758 0 : ScalarBase* scalar = nullptr;
759 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
760 0 : if (sr != ScalarResult::Ok) {
761 : return sr;
762 : }
763 :
764 0 : return scalar->SetMaximum(aValue);
765 : }
766 :
767 : void
768 0 : KeyedScalar::SetValue(const nsAString& aKey, uint32_t aValue)
769 : {
770 0 : ScalarBase* scalar = nullptr;
771 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
772 0 : if (sr != ScalarResult::Ok) {
773 0 : MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
774 : return;
775 : }
776 :
777 0 : return scalar->SetValue(aValue);
778 : }
779 :
780 : void
781 0 : KeyedScalar::SetValue(const nsAString& aKey, bool aValue)
782 : {
783 0 : ScalarBase* scalar = nullptr;
784 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
785 0 : if (sr != ScalarResult::Ok) {
786 0 : MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
787 : return;
788 : }
789 :
790 0 : return scalar->SetValue(aValue);
791 : }
792 :
793 : void
794 0 : KeyedScalar::AddValue(const nsAString& aKey, uint32_t aValue)
795 : {
796 0 : ScalarBase* scalar = nullptr;
797 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
798 0 : if (sr != ScalarResult::Ok) {
799 0 : MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
800 : return;
801 : }
802 :
803 0 : return scalar->AddValue(aValue);
804 : }
805 :
806 : void
807 0 : KeyedScalar::SetMaximum(const nsAString& aKey, uint32_t aValue)
808 : {
809 0 : ScalarBase* scalar = nullptr;
810 0 : ScalarResult sr = GetScalarForKey(aKey, &scalar);
811 0 : if (sr != ScalarResult::Ok) {
812 0 : MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar.");
813 : return;
814 : }
815 :
816 0 : return scalar->SetMaximum(aValue);
817 : }
818 :
819 : /**
820 : * Get a key-value array with the values for the Keyed Scalar.
821 : * @param aValue The array that will hold the key-value pairs.
822 : * @return {nsresult} NS_OK or an error value as reported by the
823 : * the specific scalar objects implementations (e.g.
824 : * ScalarUnsigned).
825 : */
826 : nsresult
827 0 : KeyedScalar::GetValue(nsTArray<KeyValuePair>& aValues) const
828 : {
829 0 : for (auto iter = mScalarKeys.ConstIter(); !iter.Done(); iter.Next()) {
830 0 : ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
831 :
832 : // Get the scalar value.
833 0 : nsCOMPtr<nsIVariant> scalarValue;
834 0 : nsresult rv = scalar->GetValue(scalarValue);
835 0 : if (NS_FAILED(rv)) {
836 0 : return rv;
837 : }
838 :
839 : // Append it to value list.
840 0 : aValues.AppendElement(mozilla::MakePair(nsCString(iter.Key()), scalarValue));
841 : }
842 :
843 0 : return NS_OK;
844 : }
845 :
846 : /**
847 : * Get the scalar for the referenced key.
848 : * If there's no such key, instantiate a new Scalar object with the
849 : * same type of the Keyed scalar and create the key.
850 : */
851 : ScalarResult
852 0 : KeyedScalar::GetScalarForKey(const nsAString& aKey, ScalarBase** aRet)
853 : {
854 0 : if (aKey.IsEmpty()) {
855 : return ScalarResult::KeyIsEmpty;
856 : }
857 :
858 0 : if (aKey.Length() > kMaximumKeyStringLength) {
859 : return ScalarResult::KeyTooLong;
860 : }
861 :
862 0 : NS_ConvertUTF16toUTF8 utf8Key(aKey);
863 :
864 0 : ScalarBase* scalar = nullptr;
865 0 : if (mScalarKeys.Get(utf8Key, &scalar)) {
866 0 : *aRet = scalar;
867 0 : return ScalarResult::Ok;
868 : }
869 :
870 0 : if (mScalarKeys.Count() >= mMaximumNumberOfKeys) {
871 : return ScalarResult::TooManyKeys;
872 : }
873 :
874 0 : scalar = internal_ScalarAllocate(mScalarKind);
875 0 : if (!scalar) {
876 : return ScalarResult::InvalidType;
877 : }
878 :
879 0 : mScalarKeys.Put(utf8Key, scalar);
880 :
881 0 : *aRet = scalar;
882 0 : return ScalarResult::Ok;
883 : }
884 :
885 : size_t
886 0 : KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
887 : {
888 0 : size_t n = aMallocSizeOf(this);
889 0 : for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) {
890 0 : ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
891 0 : n += scalar->SizeOfIncludingThis(aMallocSizeOf);
892 : }
893 0 : return n;
894 : }
895 :
896 : typedef nsUint32HashKey ScalarIDHashKey;
897 : typedef nsUint32HashKey ProcessIDHashKey;
898 : typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
899 : typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
900 : typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> ProcessesScalarsMapType;
901 : typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> ProcessesKeyedScalarsMapType;
902 :
903 : typedef mozilla::Tuple<const char*, nsCOMPtr<nsIVariant>, uint32_t> ScalarDataTuple;
904 : typedef nsTArray<ScalarDataTuple> ScalarTupleArray;
905 : typedef nsDataHashtable<ProcessIDHashKey, ScalarTupleArray> ScalarSnapshotTable;
906 :
907 : typedef mozilla::Tuple<const char*, nsTArray<KeyedScalar::KeyValuePair>, uint32_t> KeyedScalarDataTuple;
908 : typedef nsTArray<KeyedScalarDataTuple> KeyedScalarTupleArray;
909 : typedef nsDataHashtable<ProcessIDHashKey, KeyedScalarTupleArray> KeyedScalarSnapshotTable;
910 :
911 : } // namespace
912 :
913 : ////////////////////////////////////////////////////////////////////////
914 : ////////////////////////////////////////////////////////////////////////
915 : //
916 : // PRIVATE STATE, SHARED BY ALL THREADS
917 :
918 : namespace {
919 :
920 : // Set to true once this global state has been initialized.
921 : bool gInitDone = false;
922 :
923 : bool gCanRecordBase;
924 : bool gCanRecordExtended;
925 :
926 : // The Name -> ID cache map.
927 0 : ScalarMapType gScalarNameIDMap(kScalarCount);
928 :
929 : // The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a nsClassHashtable,
930 : // it owns the scalar instances and takes care of deallocating them when they are
931 : // removed from the map.
932 0 : ProcessesScalarsMapType gScalarStorageMap;
933 : // As above, for the keyed scalars.
934 0 : ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
935 : // Provide separate storage for "dynamic builtin" plain and keyed scalars,
936 : // needed to support "build faster" in local developer builds.
937 0 : ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap;
938 0 : ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap;
939 :
940 : // Whether or not the deserialization of persisted scalars is still in progress.
941 : // This is never the case on Desktop or Fennec.
942 : // Only GeckoView restores persisted scalars.
943 : bool gIsDeserializing = false;
944 : // This batches scalar accumulations that should be applied once loading finished.
945 0 : StaticAutoPtr<nsTArray<ScalarAction>> gScalarsActions;
946 0 : StaticAutoPtr<nsTArray<KeyedScalarAction>> gKeyedScalarsActions;
947 :
948 : bool
949 : internal_IsScalarDeserializing(const StaticMutexAutoLock& lock)
950 : {
951 0 : return gIsDeserializing;
952 : }
953 :
954 : } // namespace
955 :
956 : ////////////////////////////////////////////////////////////////////////
957 : ////////////////////////////////////////////////////////////////////////
958 : //
959 : // PRIVATE: Function that may call JS code.
960 :
961 : // NOTE: the functions in this section all run without protection from
962 : // |gTelemetryScalarsMutex|. If they held the mutex, there would be the
963 : // possibility of deadlock because the JS_ calls that they make may call
964 : // back into the TelemetryScalar interface, hence trying to re-acquire the mutex.
965 : //
966 : // This means that these functions potentially race against threads, but
967 : // that seems preferable to risking deadlock.
968 :
969 : namespace {
970 :
971 : /**
972 : * Converts the error code to a human readable error message and prints it to the
973 : * browser console.
974 : *
975 : * @param aScalarName The name of the scalar that raised the error.
976 : * @param aSr The error code.
977 : */
978 : void
979 0 : internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
980 : {
981 0 : nsAutoString errorMessage;
982 0 : AppendUTF8toUTF16(aScalarName, errorMessage);
983 :
984 0 : switch (aSr) {
985 : case ScalarResult::NotInitialized:
986 0 : errorMessage.AppendLiteral(u" - Telemetry was not yet initialized.");
987 0 : break;
988 : case ScalarResult::CannotUnpackVariant:
989 0 : errorMessage.AppendLiteral(u" - Cannot convert the provided JS value to nsIVariant.");
990 0 : break;
991 : case ScalarResult::CannotRecordInProcess:
992 0 : errorMessage.AppendLiteral(u" - Cannot record the scalar in the current process.");
993 0 : break;
994 : case ScalarResult::KeyedTypeMismatch:
995 0 : errorMessage.AppendLiteral(u" - Attempting to manage a keyed scalar as a scalar (or vice-versa).");
996 0 : break;
997 : case ScalarResult::UnknownScalar:
998 0 : errorMessage.AppendLiteral(u" - Unknown scalar.");
999 0 : break;
1000 : case ScalarResult::OperationNotSupported:
1001 0 : errorMessage.AppendLiteral(u" - The requested operation is not supported on this scalar.");
1002 0 : break;
1003 : case ScalarResult::InvalidType:
1004 0 : errorMessage.AppendLiteral(u" - Attempted to set the scalar to an invalid data type.");
1005 0 : break;
1006 : case ScalarResult::InvalidValue:
1007 0 : errorMessage.AppendLiteral(u" - Attempted to set the scalar to an incompatible value.");
1008 0 : break;
1009 : case ScalarResult::StringTooLong:
1010 0 : AppendUTF8toUTF16(nsPrintfCString(" - Truncating scalar value to %d characters.", kMaximumStringValueLength), errorMessage);
1011 0 : break;
1012 : case ScalarResult::KeyIsEmpty:
1013 0 : errorMessage.AppendLiteral(u" - The key must not be empty.");
1014 0 : break;
1015 : case ScalarResult::KeyTooLong:
1016 0 : AppendUTF8toUTF16(nsPrintfCString(" - The key length must be limited to %d characters.", kMaximumKeyStringLength), errorMessage);
1017 0 : break;
1018 : case ScalarResult::TooManyKeys:
1019 0 : AppendUTF8toUTF16(nsPrintfCString(" - Keyed scalars cannot have more than %d keys.", kMaximumNumberOfKeys), errorMessage);
1020 0 : break;
1021 : case ScalarResult::UnsignedNegativeValue:
1022 0 : errorMessage.AppendLiteral(u" - Trying to set an unsigned scalar to a negative number.");
1023 0 : break;
1024 : case ScalarResult::UnsignedTruncatedValue:
1025 0 : errorMessage.AppendLiteral(u" - Truncating float/double number.");
1026 0 : break;
1027 : default:
1028 : // Nothing.
1029 0 : return;
1030 : }
1031 :
1032 0 : LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage);
1033 : }
1034 :
1035 : } // namespace
1036 :
1037 : ////////////////////////////////////////////////////////////////////////
1038 : ////////////////////////////////////////////////////////////////////////
1039 : //
1040 : // PRIVATE: helpers for the external interface
1041 :
1042 : namespace {
1043 :
1044 : bool
1045 : internal_CanRecordBase(const StaticMutexAutoLock& lock)
1046 : {
1047 0 : return gCanRecordBase;
1048 : }
1049 :
1050 : bool
1051 : internal_CanRecordExtended(const StaticMutexAutoLock& lock)
1052 : {
1053 0 : return gCanRecordExtended;
1054 : }
1055 :
1056 : /**
1057 : * Check if the given scalar is a keyed scalar.
1058 : *
1059 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1060 : * @param aId The scalar identifier.
1061 : * @return true if aId refers to a keyed scalar, false otherwise.
1062 : */
1063 : bool
1064 0 : internal_IsKeyedScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId)
1065 : {
1066 0 : return internal_GetScalarInfo(lock, aId).keyed;
1067 : }
1068 :
1069 : /**
1070 : * Check if we're allowed to record the given scalar in the current
1071 : * process.
1072 : *
1073 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1074 : * @param aId The scalar identifier.
1075 : * @return true if the scalar is allowed to be recorded in the current process, false
1076 : * otherwise.
1077 : */
1078 : bool
1079 0 : internal_CanRecordProcess(const StaticMutexAutoLock& lock,
1080 : const ScalarKey& aId)
1081 : {
1082 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
1083 0 : return CanRecordInProcess(info.record_in_processes, XRE_GetProcessType());
1084 : }
1085 :
1086 : bool
1087 0 : internal_CanRecordProduct(const StaticMutexAutoLock& lock,
1088 : const ScalarKey& aId)
1089 : {
1090 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
1091 0 : return CanRecordProduct(info.products);
1092 : }
1093 :
1094 : bool
1095 0 : internal_CanRecordForScalarID(const StaticMutexAutoLock& lock,
1096 : const ScalarKey& aId)
1097 : {
1098 : // Get the scalar info from the id.
1099 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
1100 :
1101 : // Can we record at all?
1102 0 : bool canRecordBase = internal_CanRecordBase(lock);
1103 0 : if (!canRecordBase) {
1104 : return false;
1105 : }
1106 :
1107 0 : bool canRecordDataset = CanRecordDataset(info.dataset,
1108 : canRecordBase,
1109 0 : internal_CanRecordExtended(lock));
1110 0 : if (!canRecordDataset) {
1111 : return false;
1112 : }
1113 :
1114 : return true;
1115 : }
1116 :
1117 : /**
1118 : * Check if we are allowed to record the provided scalar.
1119 : *
1120 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1121 : * @param aId The scalar identifier.
1122 : * @param aKeyed Are we attempting to write a keyed scalar?
1123 : * @return ScalarResult::Ok if we can record, an error code otherwise.
1124 : */
1125 : ScalarResult
1126 0 : internal_CanRecordScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId,
1127 : bool aKeyed)
1128 : {
1129 : // Make sure that we have a keyed scalar if we are trying to change one.
1130 0 : if (internal_IsKeyedScalar(lock, aId) != aKeyed) {
1131 : return ScalarResult::KeyedTypeMismatch;
1132 : }
1133 :
1134 : // Are we allowed to record this scalar based on the current Telemetry
1135 : // settings?
1136 0 : if (!internal_CanRecordForScalarID(lock, aId)) {
1137 : return ScalarResult::CannotRecordDataset;
1138 : }
1139 :
1140 : // Can we record in this process?
1141 0 : if (!internal_CanRecordProcess(lock, aId)) {
1142 : return ScalarResult::CannotRecordInProcess;
1143 : }
1144 :
1145 : // Can we record on this product?
1146 0 : if (!internal_CanRecordProduct(lock, aId)) {
1147 : return ScalarResult::CannotRecordDataset;
1148 : }
1149 :
1150 0 : return ScalarResult::Ok;
1151 : }
1152 :
1153 : /**
1154 : * Get the scalar enum id from the scalar name.
1155 : *
1156 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1157 : * @param aName The scalar name.
1158 : * @param aId The output variable to contain the enum.
1159 : * @return
1160 : * NS_ERROR_FAILURE if this was called before init is completed.
1161 : * NS_ERROR_INVALID_ARG if the name can't be found in the scalar definitions.
1162 : * NS_OK if the scalar was found and aId contains a valid enum id.
1163 : */
1164 : nsresult
1165 0 : internal_GetEnumByScalarName(const StaticMutexAutoLock& lock,
1166 : const nsACString& aName,
1167 : ScalarKey* aId)
1168 : {
1169 0 : if (!gInitDone) {
1170 : return NS_ERROR_FAILURE;
1171 : }
1172 :
1173 0 : CharPtrEntryType *entry = gScalarNameIDMap.GetEntry(PromiseFlatCString(aName).get());
1174 0 : if (!entry) {
1175 : return NS_ERROR_INVALID_ARG;
1176 : }
1177 0 : *aId = entry->mData;
1178 : return NS_OK;
1179 : }
1180 :
1181 : /**
1182 : * Get a scalar object by its enum id. This implicitly allocates the scalar
1183 : * object in the storage if it wasn't previously allocated.
1184 : *
1185 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1186 : * @param aId The scalar identifier.
1187 : * @param aProcessStorage This drives the selection of the map to use to store
1188 : * the scalar data coming from child processes. This is only meaningful when
1189 : * this function is called in parent process. If that's the case, if
1190 : * this is not |GeckoProcessType_Default|, the process id is used to
1191 : * allocate and store the scalars.
1192 : * @param aRes The output variable that stores scalar object.
1193 : * @return
1194 : * NS_ERROR_INVALID_ARG if the scalar id is unknown.
1195 : * NS_ERROR_NOT_AVAILABLE if the scalar is expired.
1196 : * NS_OK if the scalar was found. If that's the case, aResult contains a
1197 : * valid pointer to a scalar type.
1198 : */
1199 : nsresult
1200 0 : internal_GetScalarByEnum(const StaticMutexAutoLock& lock,
1201 : const ScalarKey& aId,
1202 : ProcessID aProcessStorage,
1203 : ScalarBase** aRet)
1204 : {
1205 0 : if (!internal_IsValidId(lock, aId)) {
1206 0 : MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
1207 : return NS_ERROR_INVALID_ARG;
1208 : }
1209 :
1210 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
1211 :
1212 : // Dynamic scalars fixup: they are always stored in the "dynamic" process,
1213 : // unless they are part of the "builtin" Firefox probes. Please note that
1214 : // "dynamic builtin" probes are meant to support "artifact" and "build faster"
1215 : // builds.
1216 0 : if (aId.dynamic && !info.builtin) {
1217 0 : aProcessStorage = ProcessID::Dynamic;
1218 : }
1219 :
1220 0 : ScalarBase* scalar = nullptr;
1221 0 : ScalarStorageMapType* scalarStorage = nullptr;
1222 : // Initialize the scalar storage to the parent storage. This will get
1223 : // set to the child storage if needed.
1224 0 : uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
1225 :
1226 : // Put dynamic-builtin scalars (used to support "build faster") in a
1227 : // separate storage.
1228 : ProcessesScalarsMapType& processStorage =
1229 0 : (aId.dynamic && info.builtin) ? gDynamicBuiltinScalarStorageMap : gScalarStorageMap;
1230 :
1231 : // Get the process-specific storage or create one if it's not
1232 : // available.
1233 0 : if (!processStorage.Get(storageId, &scalarStorage)) {
1234 0 : scalarStorage = new ScalarStorageMapType();
1235 0 : processStorage.Put(storageId, scalarStorage);
1236 : }
1237 :
1238 : // Check if the scalar is already allocated in the parent or in the child storage.
1239 0 : if (scalarStorage->Get(aId.id, &scalar)) {
1240 : // Dynamic scalars can expire at any time during the session (e.g. an
1241 : // add-on was updated). Check if it expired.
1242 0 : if (aId.dynamic) {
1243 0 : const DynamicScalarInfo& dynInfo = static_cast<const DynamicScalarInfo&>(info);
1244 0 : if (dynInfo.mDynamicExpiration) {
1245 : // The Dynamic scalar is expired.
1246 : return NS_ERROR_NOT_AVAILABLE;
1247 : }
1248 : }
1249 : // This was not a dynamic scalar or was not expired.
1250 0 : *aRet = scalar;
1251 : return NS_OK;
1252 : }
1253 :
1254 : // The scalar storage wasn't already allocated. Check if the scalar is expired and
1255 : // then allocate the storage, if needed.
1256 0 : if (IsExpiredVersion(info.expiration())) {
1257 : return NS_ERROR_NOT_AVAILABLE;
1258 : }
1259 :
1260 0 : scalar = internal_ScalarAllocate(info.kind);
1261 0 : if (!scalar) {
1262 : return NS_ERROR_INVALID_ARG;
1263 : }
1264 :
1265 0 : scalarStorage->Put(aId.id, scalar);
1266 0 : *aRet = scalar;
1267 : return NS_OK;
1268 : }
1269 :
1270 : void internal_ApplyPendingOperations(const StaticMutexAutoLock& lock);
1271 :
1272 : /**
1273 : * Record that the high-water mark for the pending operations list was reached once.
1274 : *
1275 : * Important:
1276 : * This appends one additional operation.
1277 : * This needs to happen while still in deserialization mode.
1278 : */
1279 0 : void internal_RecordHighwatermarkReached(const StaticMutexAutoLock& lock)
1280 : {
1281 0 : MOZ_ASSERT(gIsDeserializing);
1282 0 : MOZ_ASSERT(gScalarsActions);
1283 :
1284 : // We can't call `internal_RecordScalarAction` here, because we are already
1285 : // getting called from there after the high-water mark check.
1286 : // But we know that `gScalarsActions` is a valid array and can append directly.
1287 0 : ScalarID id = ScalarID::TELEMETRY_PENDING_OPERATIONS_HIGHWATERMARK_REACHED;
1288 : ScalarAction action{
1289 : static_cast<uint32_t>(id), false, ScalarActionType::eAdd,
1290 0 : Some(ScalarVariant(1u)), ProcessID::Parent
1291 0 : };
1292 0 : gScalarsActions->AppendElement(action);
1293 0 : }
1294 :
1295 : /**
1296 : * Record the given action on a scalar into the pending actions list.
1297 : *
1298 : * If the pending actions list overflows the high water mark length
1299 : * all operations are immediately applied, including the passed action.
1300 : *
1301 : * @param aScalarAction The action to record.
1302 : */
1303 : void
1304 0 : internal_RecordScalarAction(const StaticMutexAutoLock& lock,
1305 : const ScalarAction& aScalarAction)
1306 : {
1307 : // Make sure to have the storage.
1308 0 : if (!gScalarsActions) {
1309 0 : gScalarsActions = new nsTArray<ScalarAction>();
1310 : }
1311 :
1312 : // Store the action.
1313 0 : gScalarsActions->AppendElement(aScalarAction);
1314 :
1315 : // If this action overflows the pending actions array, we immediately apply pending operations
1316 : // and assume loading is over.
1317 : // If loading still happens afterwards, some scalar values might be
1318 : // overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
1319 0 : if (gScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
1320 0 : internal_RecordHighwatermarkReached(lock);
1321 0 : internal_ApplyPendingOperations(lock);
1322 : return;
1323 : }
1324 : }
1325 :
1326 : /**
1327 : * Record the given action on a scalar on the main process into the pending actions list.
1328 : *
1329 : * If the pending actions list overflows the high water mark length
1330 : * all operations are immediately applied, including the passed action.
1331 : *
1332 : * @param aId The scalar's ID this action applies to
1333 : * @param aDynamic Determines if the scalar is dynamic
1334 : * @param aAction The action to record
1335 : * @param aValue The additional data for the recorded action
1336 : */
1337 : void
1338 0 : internal_RecordScalarAction(const StaticMutexAutoLock& lock,
1339 : uint32_t aId, bool aDynamic,
1340 : ScalarActionType aAction, const ScalarVariant& aValue)
1341 : {
1342 0 : internal_RecordScalarAction(lock, ScalarAction{
1343 : aId, aDynamic, aAction,
1344 : Some(aValue), ProcessID::Parent
1345 0 : });
1346 0 : }
1347 :
1348 : /**
1349 : * Record the given action on a keyed scalar into the pending actions list.
1350 : *
1351 : * If the pending actions list overflows the high water mark length
1352 : * all operations are immediately applied, including the passed action.
1353 : *
1354 : * @param aScalarAction The action to record.
1355 : */
1356 : void
1357 0 : internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
1358 : const KeyedScalarAction& aScalarAction)
1359 : {
1360 : // Make sure to have the storage.
1361 0 : if (!gKeyedScalarsActions) {
1362 0 : gKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
1363 : }
1364 :
1365 : // Store the action.
1366 0 : gKeyedScalarsActions->AppendElement(aScalarAction);
1367 :
1368 : // If this action overflows the pending actions array, we immediately apply pending operations
1369 : // and assume loading is over.
1370 : // If loading still happens afterwards, some scalar values might be
1371 : // overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
1372 0 : if (gKeyedScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
1373 0 : internal_RecordHighwatermarkReached(lock);
1374 0 : internal_ApplyPendingOperations(lock);
1375 : return;
1376 : }
1377 : }
1378 :
1379 : /**
1380 : * Record the given action on a keyed scalar on the main process into the pending actions list.
1381 : *
1382 : * If the pending actions list overflows the high water mark length
1383 : * all operations are immediately applied, including the passed action.
1384 : *
1385 : * @param aId The scalar's ID this action applies to
1386 : * @param aDynamic Determines if the scalar is dynamic
1387 : * @param aKey The scalar's key
1388 : * @param aAction The action to record
1389 : * @param aValue The additional data for the recorded action
1390 : */
1391 : void
1392 0 : internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
1393 : uint32_t aId, bool aDynamic,
1394 : const nsAString& aKey,
1395 : ScalarActionType aAction,
1396 : const ScalarVariant& aValue)
1397 : {
1398 0 : internal_RecordKeyedScalarAction(lock, KeyedScalarAction{
1399 0 : aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey),
1400 : Some(aValue), ProcessID::Parent
1401 0 : });
1402 0 : }
1403 :
1404 : /**
1405 : * Update the scalar with the provided value. This is used by the JS API.
1406 : *
1407 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1408 : * @param aName The scalar name.
1409 : * @param aType The action type for updating the scalar.
1410 : * @param aValue The value to use for updating the scalar.
1411 : * @param aProcessOverride The process for which the scalar must be updated.
1412 : * This must only be used for GeckoView persistence. It must be
1413 : * set to the ProcessID::Parent for all the other cases.
1414 : * @param aForce Whether to force updating even if load is in progress.
1415 : * @return a ScalarResult error value.
1416 : */
1417 : ScalarResult
1418 0 : internal_UpdateScalar(const StaticMutexAutoLock& lock, const nsACString& aName,
1419 : ScalarActionType aType, nsIVariant* aValue,
1420 : ProcessID aProcessOverride = ProcessID::Parent,
1421 : bool aForce = false)
1422 : {
1423 : ScalarKey uniqueId;
1424 0 : nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
1425 0 : if (NS_FAILED(rv)) {
1426 0 : return (rv == NS_ERROR_FAILURE) ?
1427 : ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
1428 : }
1429 :
1430 5 : ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, false);
1431 1 : if (sr != ScalarResult::Ok) {
1432 1 : if (sr == ScalarResult::CannotRecordDataset) {
1433 : return ScalarResult::Ok;
1434 : }
1435 0 : return sr;
1436 : }
1437 :
1438 : // Accumulate in the child process if needed.
1439 4 : if (!XRE_IsParentProcess()) {
1440 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
1441 : // Convert the nsIVariant to a Variant.
1442 0 : mozilla::Maybe<ScalarVariant> variantValue;
1443 0 : sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
1444 0 : if (sr != ScalarResult::Ok) {
1445 0 : MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
1446 : return sr;
1447 : }
1448 0 : TelemetryIPCAccumulator::RecordChildScalarAction(
1449 0 : uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
1450 0 : return ScalarResult::Ok;
1451 : }
1452 :
1453 0 : if (!aForce && internal_IsScalarDeserializing(lock)) {
1454 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
1455 : // Convert the nsIVariant to a Variant.
1456 0 : mozilla::Maybe<ScalarVariant> variantValue;
1457 0 : sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
1458 0 : if (sr != ScalarResult::Ok) {
1459 0 : MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
1460 : return sr;
1461 : }
1462 0 : internal_RecordScalarAction(lock, uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
1463 0 : return ScalarResult::Ok;
1464 : }
1465 :
1466 : // Finally get the scalar.
1467 4 : ScalarBase* scalar = nullptr;
1468 4 : rv = internal_GetScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
1469 4 : if (NS_FAILED(rv)) {
1470 : // Don't throw on expired scalars.
1471 0 : if (rv == NS_ERROR_NOT_AVAILABLE) {
1472 : return ScalarResult::Ok;
1473 : }
1474 0 : return ScalarResult::UnknownScalar;
1475 : }
1476 :
1477 4 : if (aType == ScalarActionType::eAdd) {
1478 1 : return scalar->AddValue(aValue);
1479 : }
1480 3 : if (aType == ScalarActionType::eSet) {
1481 1 : return scalar->SetValue(aValue);
1482 : }
1483 :
1484 2 : return scalar->SetMaximum(aValue);
1485 : }
1486 :
1487 : } // namespace
1488 :
1489 :
1490 :
1491 : ////////////////////////////////////////////////////////////////////////
1492 : ////////////////////////////////////////////////////////////////////////
1493 : //
1494 : // PRIVATE: thread-unsafe helpers for the keyed scalars
1495 :
1496 : namespace {
1497 :
1498 : /**
1499 : * Get a keyed scalar object by its enum id. This implicitly allocates the keyed
1500 : * scalar object in the storage if it wasn't previously allocated.
1501 : *
1502 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1503 : * @param aId The scalar identifier.
1504 : * @param aProcessStorage This drives the selection of the map to use to store
1505 : * the scalar data coming from child processes. This is only meaningful when
1506 : * this function is called in parent process. If that's the case, if
1507 : * this is not |GeckoProcessType_Default|, the process id is used to
1508 : * allocate and store the scalars.
1509 : * @param aRet The output variable that stores scalar object.
1510 : * @return
1511 : * NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string
1512 : * scalar.
1513 : * NS_ERROR_NOT_AVAILABLE if the scalar is expired.
1514 : * NS_OK if the scalar was found. If that's the case, aResult contains a
1515 : * valid pointer to a scalar type.
1516 : */
1517 : nsresult
1518 2 : internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock,
1519 : const ScalarKey& aId,
1520 : ProcessID aProcessStorage,
1521 : KeyedScalar** aRet)
1522 : {
1523 2 : if (!internal_IsValidId(lock, aId)) {
1524 0 : MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
1525 : return NS_ERROR_INVALID_ARG;
1526 : }
1527 :
1528 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
1529 :
1530 : // Dynamic scalars fixup: they are always stored in the "dynamic" process,
1531 : // unless they are part of the "builtin" Firefox probes. Please note that
1532 : // "dynamic builtin" probes are meant to support "artifact" and "build faster"
1533 : // builds.
1534 2 : if (aId.dynamic && !info.builtin) {
1535 0 : aProcessStorage = ProcessID::Dynamic;
1536 : }
1537 :
1538 2 : KeyedScalar* scalar = nullptr;
1539 2 : KeyedScalarStorageMapType* scalarStorage = nullptr;
1540 : // Initialize the scalar storage to the parent storage. This will get
1541 : // set to the child storage if needed.
1542 1 : uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
1543 :
1544 : // Put dynamic-builtin scalars (used to support "build faster") in a
1545 : // separate storage.
1546 : ProcessesKeyedScalarsMapType& processStorage =
1547 0 : (aId.dynamic && info.builtin) ? gDynamicBuiltinKeyedScalarStorageMap : gKeyedScalarStorageMap;
1548 :
1549 : // Get the process-specific storage or create one if it's not
1550 : // available.
1551 0 : if (!processStorage.Get(storageId, &scalarStorage)) {
1552 0 : scalarStorage = new KeyedScalarStorageMapType();
1553 1 : processStorage.Put(storageId, scalarStorage);
1554 : }
1555 :
1556 2 : if (scalarStorage->Get(aId.id, &scalar)) {
1557 0 : *aRet = scalar;
1558 : return NS_OK;
1559 : }
1560 :
1561 2 : if (IsExpiredVersion(info.expiration())) {
1562 : return NS_ERROR_NOT_AVAILABLE;
1563 : }
1564 :
1565 : // We don't currently support keyed string scalars. Disable them.
1566 2 : if (info.kind == nsITelemetry::SCALAR_TYPE_STRING) {
1567 0 : MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
1568 : return NS_ERROR_INVALID_ARG;
1569 : }
1570 :
1571 4 : scalar = new KeyedScalar(info.kind);
1572 2 : if (!scalar) {
1573 : return NS_ERROR_INVALID_ARG;
1574 : }
1575 :
1576 2 : scalarStorage->Put(aId.id, scalar);
1577 0 : *aRet = scalar;
1578 : return NS_OK;
1579 : }
1580 :
1581 : /**
1582 : * Update the keyed scalar with the provided value. This is used by the JS API.
1583 : *
1584 : * @param lock Instance of a lock locking gTelemetryHistogramMutex
1585 : * @param aName The scalar name.
1586 : * @param aKey The key name.
1587 : * @param aType The action type for updating the scalar.
1588 : * @param aValue The value to use for updating the scalar.
1589 : * @param aProcessOverride The process for which the scalar must be updated.
1590 : * This must only be used for GeckoView persistence. It must be
1591 : * set to the ProcessID::Parent for all the other cases.
1592 : * @return a ScalarResult error value.
1593 : */
1594 : ScalarResult
1595 0 : internal_UpdateKeyedScalar(const StaticMutexAutoLock& lock,
1596 : const nsACString& aName, const nsAString& aKey,
1597 : ScalarActionType aType, nsIVariant* aValue,
1598 : ProcessID aProcessOverride = ProcessID::Parent,
1599 : bool aForce = false)
1600 : {
1601 : ScalarKey uniqueId;
1602 0 : nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
1603 0 : if (NS_FAILED(rv)) {
1604 0 : return (rv == NS_ERROR_FAILURE) ?
1605 : ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
1606 : }
1607 :
1608 0 : ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, true);
1609 0 : if (sr != ScalarResult::Ok) {
1610 0 : if (sr == ScalarResult::CannotRecordDataset) {
1611 : return ScalarResult::Ok;
1612 : }
1613 0 : return sr;
1614 : }
1615 :
1616 : // Accumulate in the child process if needed.
1617 0 : if (!XRE_IsParentProcess()) {
1618 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
1619 : // Convert the nsIVariant to a Variant.
1620 0 : mozilla::Maybe<ScalarVariant> variantValue;
1621 0 : sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
1622 0 : if (sr != ScalarResult::Ok) {
1623 0 : MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
1624 : return sr;
1625 : }
1626 0 : TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1627 0 : uniqueId.id, uniqueId.dynamic, aKey, aType, variantValue.ref());
1628 0 : return ScalarResult::Ok;
1629 : }
1630 :
1631 0 : if (!aForce && internal_IsScalarDeserializing(lock)) {
1632 0 : const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
1633 : // Convert the nsIVariant to a Variant.
1634 0 : mozilla::Maybe<ScalarVariant> variantValue;
1635 0 : sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
1636 0 : if (sr != ScalarResult::Ok) {
1637 0 : MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
1638 : return sr;
1639 : }
1640 0 : internal_RecordKeyedScalarAction(lock,
1641 0 : uniqueId.id, uniqueId.dynamic,
1642 0 : aKey, aType, variantValue.ref());
1643 0 : return ScalarResult::Ok;
1644 : }
1645 :
1646 : // Finally get the scalar.
1647 0 : KeyedScalar* scalar = nullptr;
1648 0 : rv = internal_GetKeyedScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
1649 0 : if (NS_FAILED(rv)) {
1650 : // Don't throw on expired scalars.
1651 0 : if (rv == NS_ERROR_NOT_AVAILABLE) {
1652 : return ScalarResult::Ok;
1653 : }
1654 0 : return ScalarResult::UnknownScalar;
1655 : }
1656 :
1657 0 : if (aType == ScalarActionType::eAdd) {
1658 0 : return scalar->AddValue(aKey, aValue);
1659 : }
1660 0 : if (aType == ScalarActionType::eSet) {
1661 0 : return scalar->SetValue(aKey, aValue);
1662 : }
1663 :
1664 0 : return scalar->SetMaximum(aKey, aValue);
1665 : }
1666 :
1667 : /**
1668 : * Helper function to convert an array of |DynamicScalarInfo|
1669 : * to |DynamicScalarDefinition| used by the IPC calls.
1670 : */
1671 : void
1672 0 : internal_DynamicScalarToIPC(const StaticMutexAutoLock& lock,
1673 : const nsTArray<DynamicScalarInfo>& aDynamicScalarInfos,
1674 : nsTArray<DynamicScalarDefinition>& aIPCDefs)
1675 : {
1676 5 : for (auto info : aDynamicScalarInfos) {
1677 0 : DynamicScalarDefinition stubDefinition;
1678 0 : stubDefinition.type = info.kind;
1679 1 : stubDefinition.dataset = info.dataset;
1680 1 : stubDefinition.expired = info.mDynamicExpiration;
1681 0 : stubDefinition.keyed = info.keyed;
1682 0 : stubDefinition.name = info.mDynamicName;
1683 1 : aIPCDefs.AppendElement(stubDefinition);
1684 : }
1685 1 : }
1686 :
1687 : /**
1688 : * Broadcasts the dynamic scalar definitions to all the other
1689 : * content processes.
1690 : */
1691 : void
1692 0 : internal_BroadcastDefinitions(const StaticMutexAutoLock& lock,
1693 : const nsTArray<DynamicScalarInfo>& scalarInfos)
1694 : {
1695 0 : nsTArray<mozilla::dom::ContentParent*> parents;
1696 0 : mozilla::dom::ContentParent::GetAll(parents);
1697 0 : if (!parents.Length()) {
1698 0 : return;
1699 : }
1700 :
1701 : // Convert the internal scalar representation to a stripped down IPC one.
1702 0 : nsTArray<DynamicScalarDefinition> ipcDefinitions;
1703 0 : internal_DynamicScalarToIPC(lock, scalarInfos, ipcDefinitions);
1704 :
1705 : // Broadcast the definitions to the other content processes.
1706 0 : for (auto parent : parents) {
1707 0 : mozilla::Unused << parent->SendAddDynamicScalars(ipcDefinitions);
1708 : }
1709 : }
1710 :
1711 : void
1712 0 : internal_RegisterScalars(const StaticMutexAutoLock& lock,
1713 : const nsTArray<DynamicScalarInfo>& scalarInfos)
1714 : {
1715 : // Register the new scalars.
1716 1 : if (!gDynamicScalarInfo) {
1717 2 : gDynamicScalarInfo = new nsTArray<DynamicScalarInfo>();
1718 : }
1719 :
1720 5 : for (auto scalarInfo : scalarInfos) {
1721 : // Allow expiring scalars that were already registered.
1722 2 : CharPtrEntryType *existingKey = gScalarNameIDMap.GetEntry(scalarInfo.name());
1723 1 : if (existingKey) {
1724 : // Change the scalar to expired if needed.
1725 0 : if (scalarInfo.mDynamicExpiration && !scalarInfo.builtin) {
1726 0 : DynamicScalarInfo& scalarData = (*gDynamicScalarInfo)[existingKey->mData.id];
1727 0 : scalarData.mDynamicExpiration = true;
1728 : }
1729 0 : continue;
1730 : }
1731 :
1732 0 : gDynamicScalarInfo->AppendElement(scalarInfo);
1733 0 : uint32_t scalarId = gDynamicScalarInfo->Length() - 1;
1734 0 : CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(scalarInfo.name());
1735 1 : entry->mData = ScalarKey{scalarId, true};
1736 : }
1737 0 : }
1738 :
1739 : /**
1740 : * Creates a snapshot of the desired scalar storage.
1741 : * @param {aLock} The proof of lock to access scalar data.
1742 : * @param {aScalarsToReflect} The table that will contain the snapshot.
1743 : * @param {aDataset} The dataset we're asking the snapshot for.
1744 : * @param {aProcessStorage} The scalar storage to take a snapshot of.
1745 : * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin scalars.
1746 : * @return NS_OK or the error code describing the failure reason.
1747 : */
1748 : nsresult
1749 0 : internal_ScalarSnapshotter(const StaticMutexAutoLock& aLock,
1750 : ScalarSnapshotTable& aScalarsToReflect,
1751 : unsigned int aDataset,
1752 : ProcessesScalarsMapType& aProcessStorage,
1753 : bool aIsBuiltinDynamic)
1754 : {
1755 : // Iterate the scalars in aProcessStorage. The storage may contain empty or yet to be
1756 : // initialized scalars from all the supported processes.
1757 0 : for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
1758 0 : ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
1759 0 : ScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
1760 :
1761 : // Are we in the "Dynamic" process?
1762 0 : bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
1763 :
1764 : // Iterate each available child storage.
1765 0 : for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
1766 0 : ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
1767 :
1768 : // Get the informations for this scalar.
1769 : const BaseScalarInfo& info =
1770 0 : internal_GetScalarInfo(aLock, ScalarKey{childIter.Key(),
1771 0 : aIsBuiltinDynamic ? true : isDynamicProcess});
1772 :
1773 : // Serialize the scalar if it's in the desired dataset.
1774 0 : if (IsInDataset(info.dataset, aDataset)) {
1775 : // Get the scalar value.
1776 0 : nsCOMPtr<nsIVariant> scalarValue;
1777 0 : nsresult rv = scalar->GetValue(scalarValue);
1778 0 : if (NS_FAILED(rv)) {
1779 0 : return rv;
1780 : }
1781 : // Append it to our list.
1782 0 : processScalars.AppendElement(mozilla::MakeTuple(info.name(), scalarValue, info.kind));
1783 : }
1784 : }
1785 : }
1786 0 : return NS_OK;
1787 : }
1788 :
1789 : /**
1790 : * Creates a snapshot of the desired keyed scalar storage.
1791 : * @param {aLock} The proof of lock to access scalar data.
1792 : * @param {aScalarsToReflect} The table that will contain the snapshot.
1793 : * @param {aDataset} The dataset we're asking the snapshot for.
1794 : * @param {aProcessStorage} The scalar storage to take a snapshot of.
1795 : * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin scalars.
1796 : * @return NS_OK or the error code describing the failure reason.
1797 : */
1798 : nsresult
1799 0 : internal_KeyedScalarSnapshotter(const StaticMutexAutoLock& aLock,
1800 : KeyedScalarSnapshotTable& aScalarsToReflect,
1801 : unsigned int aDataset,
1802 : ProcessesKeyedScalarsMapType& aProcessStorage,
1803 : bool aIsBuiltinDynamic)
1804 : {
1805 : // Iterate the scalars in aProcessStorage. The storage may contain empty or yet
1806 : // to be initialized scalars from all the supported processes.
1807 0 : for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
1808 : KeyedScalarStorageMapType* scalarStorage =
1809 0 : static_cast<KeyedScalarStorageMapType*>(iter.Data());
1810 0 : KeyedScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
1811 :
1812 : // Are we in the "Dynamic" process?
1813 0 : bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
1814 :
1815 0 : for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
1816 0 : KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
1817 :
1818 : // Get the informations for this scalar.
1819 : const BaseScalarInfo& info =
1820 0 : internal_GetScalarInfo(aLock, ScalarKey{childIter.Key(),
1821 0 : aIsBuiltinDynamic ? true : isDynamicProcess});
1822 :
1823 : // Serialize the scalar if it's in the desired dataset.
1824 0 : if (IsInDataset(info.dataset, aDataset)) {
1825 : // Get the keys for this scalar.
1826 0 : nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
1827 0 : nsresult rv = scalar->GetValue(scalarKeyedData);
1828 0 : if (NS_FAILED(rv)) {
1829 0 : return rv;
1830 : }
1831 : // Append it to our list.
1832 0 : processScalars.AppendElement(
1833 0 : mozilla::MakeTuple(info.name(), scalarKeyedData, info.kind));
1834 : }
1835 : }
1836 : }
1837 0 : return NS_OK;
1838 : }
1839 :
1840 : /**
1841 : * Helper function to get a snapshot of the scalars.
1842 : *
1843 : * @param {aLock} The proof of lock to access scalar data.
1844 : * @param {aScalarsToReflect} The table that will contain the snapshot.
1845 : * @param {aDataset} The dataset we're asking the snapshot for.
1846 : * @param {aClearScalars} Whether or not to clear the scalar storage.
1847 : * @return NS_OK or the error code describing the failure reason.
1848 : */
1849 : nsresult
1850 0 : internal_GetScalarSnapshot(const StaticMutexAutoLock& aLock,
1851 : ScalarSnapshotTable& aScalarsToReflect,
1852 : unsigned int aDataset, bool aClearScalars)
1853 : {
1854 : // Take a snapshot of the scalars.
1855 : nsresult rv = internal_ScalarSnapshotter(aLock,
1856 : aScalarsToReflect,
1857 : aDataset,
1858 : gScalarStorageMap,
1859 0 : false /*aIsBuiltinDynamic*/);
1860 0 : if (NS_FAILED(rv)) {
1861 : return rv;
1862 : }
1863 :
1864 : // And a snapshot of the dynamic builtin ones.
1865 : rv = internal_ScalarSnapshotter(aLock,
1866 : aScalarsToReflect,
1867 : aDataset,
1868 : gDynamicBuiltinScalarStorageMap,
1869 0 : true /*aIsBuiltinDynamic*/);
1870 0 : if (NS_FAILED(rv)) {
1871 : return rv;
1872 : }
1873 :
1874 0 : if (aClearScalars) {
1875 : // The map already takes care of freeing the allocated memory.
1876 0 : gScalarStorageMap.Clear();
1877 : gDynamicBuiltinScalarStorageMap.Clear();
1878 : }
1879 :
1880 : return NS_OK;
1881 : }
1882 :
1883 : /**
1884 : * Helper function to get a snapshot of the keyed scalars.
1885 : *
1886 : * @param {aLock} The proof of lock to access scalar data.
1887 : * @param {aScalarsToReflect} The table that will contain the snapshot.
1888 : * @param {aDataset} The dataset we're asking the snapshot for.
1889 : * @param {aClearScalars} Whether or not to clear the scalar storage.
1890 : * @return NS_OK or the error code describing the failure reason.
1891 : */
1892 : nsresult
1893 0 : internal_GetKeyedScalarSnapshot(const StaticMutexAutoLock& aLock,
1894 : KeyedScalarSnapshotTable& aScalarsToReflect,
1895 : unsigned int aDataset, bool aClearScalars)
1896 : {
1897 : // Take a snapshot of the scalars.
1898 : nsresult rv = internal_KeyedScalarSnapshotter(aLock,
1899 : aScalarsToReflect,
1900 : aDataset,
1901 : gKeyedScalarStorageMap,
1902 0 : false /*aIsBuiltinDynamic*/);
1903 0 : if (NS_FAILED(rv)) {
1904 : return rv;
1905 : }
1906 :
1907 : // And a snapshot of the dynamic builtin ones.
1908 : rv = internal_KeyedScalarSnapshotter(aLock,
1909 : aScalarsToReflect,
1910 : aDataset,
1911 : gDynamicBuiltinKeyedScalarStorageMap,
1912 0 : true /*aIsBuiltinDynamic*/);
1913 0 : if (NS_FAILED(rv)) {
1914 : return rv;
1915 : }
1916 :
1917 0 : if (aClearScalars) {
1918 : // The map already takes care of freeing the allocated memory.
1919 0 : gKeyedScalarStorageMap.Clear();
1920 : gDynamicBuiltinKeyedScalarStorageMap.Clear();
1921 : }
1922 :
1923 : return NS_OK;
1924 : }
1925 :
1926 : } // namespace
1927 :
1928 : // helpers for recording/applying scalar operations
1929 : namespace {
1930 :
1931 : void
1932 0 : internal_ApplyScalarActions(const StaticMutexAutoLock& lock,
1933 : const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions,
1934 : const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
1935 : {
1936 0 : if (!internal_CanRecordBase(lock)) {
1937 : return;
1938 : }
1939 :
1940 0 : for (auto& upd : aScalarActions) {
1941 0 : ScalarKey uniqueId{upd.mId, upd.mDynamic};
1942 0 : if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
1943 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1944 0 : continue;
1945 : }
1946 :
1947 0 : if (internal_IsKeyedScalar(lock, uniqueId)) {
1948 : continue;
1949 : }
1950 :
1951 : // Are we allowed to record this scalar? We don't need to check for
1952 : // allowed processes here, that's taken care of when recording
1953 : // in child processes.
1954 0 : if (!internal_CanRecordForScalarID(lock, uniqueId)) {
1955 : continue;
1956 : }
1957 :
1958 : // Either we got passed a process type or it was explicitely set on the recorded action.
1959 : // It should never happen that it is set to an invalid value (such as ProcessID::Count)
1960 0 : ProcessID processType = aProcessType.valueOr(upd.mProcessType);
1961 0 : MOZ_ASSERT(processType != ProcessID::Count);
1962 :
1963 : // Refresh the data in the parent process with the data coming from the child
1964 : // processes.
1965 0 : ScalarBase* scalar = nullptr;
1966 : nsresult rv = internal_GetScalarByEnum(lock, uniqueId, processType,
1967 0 : &scalar);
1968 0 : if (NS_FAILED(rv)) {
1969 0 : NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
1970 : continue;
1971 : }
1972 :
1973 0 : if (upd.mData.isNothing()) {
1974 0 : MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
1975 : continue;
1976 : }
1977 :
1978 : // Get the type of this scalar from the scalar ID. We already checked
1979 : // for its validity a few lines above.
1980 0 : const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
1981 :
1982 : // Extract the data from the mozilla::Variant.
1983 0 : switch (upd.mActionType)
1984 : {
1985 : case ScalarActionType::eSet:
1986 : {
1987 0 : switch (scalarType)
1988 : {
1989 : case nsITelemetry::SCALAR_TYPE_COUNT:
1990 0 : scalar->SetValue(upd.mData->as<uint32_t>());
1991 : break;
1992 : case nsITelemetry::SCALAR_TYPE_BOOLEAN:
1993 0 : scalar->SetValue(upd.mData->as<bool>());
1994 : break;
1995 : case nsITelemetry::SCALAR_TYPE_STRING:
1996 0 : scalar->SetValue(upd.mData->as<nsString>());
1997 : break;
1998 : }
1999 : break;
2000 : }
2001 : case ScalarActionType::eAdd:
2002 : {
2003 0 : if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2004 0 : NS_WARNING("Attempting to add on a non count scalar.");
2005 : continue;
2006 : }
2007 : // We only support adding uint32_t.
2008 0 : scalar->AddValue(upd.mData->as<uint32_t>());
2009 : break;
2010 : }
2011 : case ScalarActionType::eSetMaximum:
2012 : {
2013 0 : if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2014 0 : NS_WARNING("Attempting to add on a non count scalar.");
2015 : continue;
2016 : }
2017 : // We only support SetMaximum on uint32_t.
2018 0 : scalar->SetMaximum(upd.mData->as<uint32_t>());
2019 : break;
2020 : }
2021 : default:
2022 0 : NS_WARNING("Unsupported action coming from scalar child updates.");
2023 : }
2024 : }
2025 : }
2026 :
2027 : void
2028 0 : internal_ApplyKeyedScalarActions(const StaticMutexAutoLock& lock,
2029 : const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions,
2030 : const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
2031 : {
2032 0 : if (!internal_CanRecordBase(lock)) {
2033 : return;
2034 : }
2035 :
2036 0 : for (auto& upd : aScalarActions) {
2037 0 : ScalarKey uniqueId{upd.mId, upd.mDynamic};
2038 0 : if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
2039 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2040 0 : continue;
2041 : }
2042 :
2043 0 : if (!internal_IsKeyedScalar(lock, uniqueId)) {
2044 : continue;
2045 : }
2046 :
2047 : // Are we allowed to record this scalar? We don't need to check for
2048 : // allowed processes here, that's taken care of when recording
2049 : // in child processes.
2050 0 : if (!internal_CanRecordForScalarID(lock, uniqueId)) {
2051 : continue;
2052 : }
2053 :
2054 : // Either we got passed a process type or it was explicitely set on the recorded action.
2055 : // It should never happen that it is set to an invalid value (such as ProcessID::Count)
2056 0 : ProcessID processType = aProcessType.valueOr(upd.mProcessType);
2057 0 : MOZ_ASSERT(processType != ProcessID::Count);
2058 :
2059 : // Refresh the data in the parent process with the data coming from the child
2060 : // processes.
2061 0 : KeyedScalar* scalar = nullptr;
2062 : nsresult rv = internal_GetKeyedScalarByEnum(lock, uniqueId, processType,
2063 0 : &scalar);
2064 0 : if (NS_FAILED(rv)) {
2065 0 : NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
2066 : continue;
2067 : }
2068 :
2069 0 : if (upd.mData.isNothing()) {
2070 0 : MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
2071 : continue;
2072 : }
2073 :
2074 : // Get the type of this scalar from the scalar ID. We already checked
2075 : // for its validity a few lines above.
2076 0 : const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
2077 :
2078 : // Extract the data from the mozilla::Variant.
2079 0 : switch (upd.mActionType)
2080 : {
2081 : case ScalarActionType::eSet:
2082 : {
2083 0 : switch (scalarType)
2084 : {
2085 : case nsITelemetry::SCALAR_TYPE_COUNT:
2086 0 : scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
2087 : break;
2088 : case nsITelemetry::SCALAR_TYPE_BOOLEAN:
2089 0 : scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<bool>());
2090 : break;
2091 : default:
2092 0 : NS_WARNING("Unsupported type coming from scalar child updates.");
2093 : }
2094 : break;
2095 : }
2096 : case ScalarActionType::eAdd:
2097 : {
2098 0 : if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2099 0 : NS_WARNING("Attempting to add on a non count scalar.");
2100 : continue;
2101 : }
2102 : // We only support adding on uint32_t.
2103 0 : scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
2104 : break;
2105 : }
2106 : case ScalarActionType::eSetMaximum:
2107 : {
2108 0 : if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
2109 0 : NS_WARNING("Attempting to add on a non count scalar.");
2110 : continue;
2111 : }
2112 : // We only support SetMaximum on uint32_t.
2113 0 : scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
2114 : break;
2115 : }
2116 : default:
2117 0 : NS_WARNING("Unsupported action coming from keyed scalar child updates.");
2118 : }
2119 : }
2120 : }
2121 :
2122 : void
2123 0 : internal_ApplyPendingOperations(const StaticMutexAutoLock& lock)
2124 : {
2125 0 : if (gScalarsActions && gScalarsActions->Length() > 0) {
2126 0 : internal_ApplyScalarActions(lock, *gScalarsActions);
2127 0 : gScalarsActions->Clear();
2128 : }
2129 :
2130 0 : if (gKeyedScalarsActions && gKeyedScalarsActions->Length() > 0) {
2131 0 : internal_ApplyKeyedScalarActions(lock, *gKeyedScalarsActions);
2132 0 : gKeyedScalarsActions->Clear();
2133 : }
2134 :
2135 : // After all pending operations are applied deserialization is done
2136 0 : gIsDeserializing = false;
2137 0 : }
2138 :
2139 : } // namespace
2140 :
2141 : ////////////////////////////////////////////////////////////////////////
2142 : ////////////////////////////////////////////////////////////////////////
2143 : //
2144 : // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars::
2145 :
2146 : // This is a StaticMutex rather than a plain Mutex (1) so that
2147 : // it gets initialised in a thread-safe manner the first time
2148 : // it is used, and (2) because it is never de-initialised, and
2149 : // a normal Mutex would show up as a leak in BloatView. StaticMutex
2150 : // also has the "OffTheBooks" property, so it won't show as a leak
2151 : // in BloatView.
2152 : // Another reason to use a StaticMutex instead of a plain Mutex is
2153 : // that, due to the nature of Telemetry, we cannot rely on having a
2154 : // mutex initialized in InitializeGlobalState. Unfortunately, we
2155 : // cannot make sure that no other function is called before this point.
2156 1 : static StaticMutex gTelemetryScalarsMutex;
2157 :
2158 : void
2159 1 : TelemetryScalar::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtended)
2160 : {
2161 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2162 0 : MOZ_ASSERT(!gInitDone, "TelemetryScalar::InitializeGlobalState "
2163 : "may only be called once");
2164 :
2165 0 : gCanRecordBase = aCanRecordBase;
2166 1 : gCanRecordExtended = aCanRecordExtended;
2167 :
2168 : // Populate the static scalar name->id cache. Note that the scalar names are
2169 : // statically allocated and come from the automatically generated TelemetryScalarData.h.
2170 0 : uint32_t scalarCount = static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
2171 0 : for (uint32_t i = 0; i < scalarCount; i++) {
2172 0 : CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(gScalars[i].name());
2173 0 : entry->mData = ScalarKey{i, false};
2174 : }
2175 :
2176 : // To summarize dynamic events we need a dynamic scalar.
2177 : const nsTArray<DynamicScalarInfo> initialDynamicScalars({
2178 : DynamicScalarInfo{
2179 : nsITelemetry::SCALAR_TYPE_COUNT,
2180 : true /* recordOnRelease */,
2181 : false /* expired */,
2182 2 : nsAutoCString("telemetry.dynamic_event_counts"),
2183 : true /* keyed */,
2184 : false /* built-in */,
2185 : },
2186 0 : });
2187 0 : internal_RegisterScalars(locker, initialDynamicScalars);
2188 :
2189 1 : gInitDone = true;
2190 0 : }
2191 :
2192 : void
2193 0 : TelemetryScalar::DeInitializeGlobalState()
2194 : {
2195 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2196 0 : gCanRecordBase = false;
2197 0 : gCanRecordExtended = false;
2198 0 : gScalarNameIDMap.Clear();
2199 0 : gScalarStorageMap.Clear();
2200 0 : gKeyedScalarStorageMap.Clear();
2201 0 : gDynamicBuiltinScalarStorageMap.Clear();
2202 0 : gDynamicBuiltinKeyedScalarStorageMap.Clear();
2203 0 : gDynamicScalarInfo = nullptr;
2204 0 : gInitDone = false;
2205 0 : }
2206 :
2207 : void
2208 0 : TelemetryScalar::DeserializationStarted()
2209 : {
2210 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2211 0 : gIsDeserializing = true;
2212 0 : }
2213 :
2214 : void
2215 0 : TelemetryScalar::ApplyPendingOperations()
2216 : {
2217 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2218 0 : internal_ApplyPendingOperations(locker);
2219 0 : }
2220 :
2221 : void
2222 0 : TelemetryScalar::SetCanRecordBase(bool b)
2223 : {
2224 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2225 0 : gCanRecordBase = b;
2226 0 : }
2227 :
2228 : void
2229 1 : TelemetryScalar::SetCanRecordExtended(bool b) {
2230 2 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2231 1 : gCanRecordExtended = b;
2232 0 : }
2233 :
2234 : /**
2235 : * Adds the value to the given scalar.
2236 : *
2237 : * @param aName The scalar name.
2238 : * @param aVal The numeric value to add to the scalar.
2239 : * @param aCx The JS context.
2240 : * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
2241 : * a warning level message is printed in the browser console.
2242 : */
2243 : nsresult
2244 1 : TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
2245 : {
2246 : // Unpack the aVal to nsIVariant. This uses the JS context.
2247 2 : nsCOMPtr<nsIVariant> unpackedVal;
2248 : nsresult rv =
2249 2 : nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
2250 1 : if (NS_FAILED(rv)) {
2251 0 : internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
2252 0 : return NS_OK;
2253 : }
2254 :
2255 : ScalarResult sr;
2256 : {
2257 2 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2258 1 : sr = internal_UpdateScalar(locker, aName, ScalarActionType::eAdd,
2259 1 : unpackedVal);
2260 : }
2261 :
2262 : // Warn the user about the error if we need to.
2263 0 : if (sr != ScalarResult::Ok) {
2264 0 : internal_LogScalarError(aName, sr);
2265 : }
2266 :
2267 : return NS_OK;
2268 : }
2269 :
2270 : /**
2271 : * Adds the value to the given scalar.
2272 : *
2273 : * @param aName The scalar name.
2274 : * @param aKey The key name.
2275 : * @param aVal The numeric value to add to the scalar.
2276 : * @param aCx The JS context.
2277 : * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
2278 : * a warning level message is printed in the browser console.
2279 : */
2280 : nsresult
2281 0 : TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
2282 : JSContext* aCx)
2283 : {
2284 : // Unpack the aVal to nsIVariant. This uses the JS context.
2285 0 : nsCOMPtr<nsIVariant> unpackedVal;
2286 : nsresult rv =
2287 0 : nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
2288 0 : if (NS_FAILED(rv)) {
2289 0 : internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
2290 0 : return NS_OK;
2291 : }
2292 :
2293 : ScalarResult sr;
2294 : {
2295 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2296 0 : sr = internal_UpdateKeyedScalar(locker, aName, aKey,
2297 : ScalarActionType::eAdd,
2298 0 : unpackedVal);
2299 : }
2300 :
2301 : // Warn the user about the error if we need to.
2302 0 : if (sr != ScalarResult::Ok) {
2303 0 : internal_LogScalarError(aName, sr);
2304 : }
2305 :
2306 : return NS_OK;
2307 : }
2308 :
2309 : /**
2310 : * Adds the value to the given scalar.
2311 : *
2312 : * @param aId The scalar enum id.
2313 : * @param aVal The numeric value to add to the scalar.
2314 : */
2315 : void
2316 0 : TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
2317 : {
2318 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2319 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2320 : return;
2321 : }
2322 :
2323 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2324 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2325 :
2326 0 : if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
2327 : // We can't record this scalar. Bail out.
2328 0 : return;
2329 : }
2330 :
2331 : // Accumulate in the child process if needed.
2332 0 : if (!XRE_IsParentProcess()) {
2333 0 : TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
2334 : ScalarActionType::eAdd,
2335 0 : ScalarVariant(aValue));
2336 0 : return;
2337 : }
2338 :
2339 0 : if (internal_IsScalarDeserializing(locker)) {
2340 0 : internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2341 : ScalarActionType::eAdd,
2342 0 : ScalarVariant(aValue));
2343 0 : return;
2344 : }
2345 :
2346 0 : ScalarBase* scalar = nullptr;
2347 : nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
2348 0 : &scalar);
2349 0 : if (NS_FAILED(rv)) {
2350 : return;
2351 : }
2352 :
2353 0 : scalar->AddValue(aValue);
2354 : }
2355 :
2356 : /**
2357 : * Adds the value to the given keyed scalar.
2358 : *
2359 : * @param aId The scalar enum id.
2360 : * @param aKey The key name.
2361 : * @param aVal The numeric value to add to the scalar.
2362 : */
2363 : void
2364 1 : TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2365 : uint32_t aValue)
2366 : {
2367 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2368 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2369 : return;
2370 : }
2371 :
2372 1 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2373 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2374 :
2375 1 : if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
2376 : // We can't record this scalar. Bail out.
2377 0 : return;
2378 : }
2379 :
2380 : // Accumulate in the child process if needed.
2381 1 : if (!XRE_IsParentProcess()) {
2382 0 : TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
2383 0 : aKey, ScalarActionType::eAdd, ScalarVariant(aValue));
2384 0 : return;
2385 : }
2386 :
2387 2 : if (internal_IsScalarDeserializing(locker)) {
2388 0 : internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2389 : aKey,
2390 : ScalarActionType::eAdd,
2391 0 : ScalarVariant(aValue));
2392 0 : return;
2393 : }
2394 :
2395 1 : KeyedScalar* scalar = nullptr;
2396 : nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
2397 : ProcessID::Parent,
2398 1 : &scalar);
2399 1 : if (NS_FAILED(rv)) {
2400 : return;
2401 : }
2402 :
2403 1 : scalar->AddValue(aKey, aValue);
2404 : }
2405 :
2406 : /**
2407 : * Sets the scalar to the given value.
2408 : *
2409 : * @param aName The scalar name.
2410 : * @param aVal The value to set the scalar to.
2411 : * @param aCx The JS context.
2412 : * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
2413 : * a warning level message is printed in the browser console.
2414 : */
2415 : nsresult
2416 2 : TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
2417 : {
2418 : // Unpack the aVal to nsIVariant. This uses the JS context.
2419 4 : nsCOMPtr<nsIVariant> unpackedVal;
2420 : nsresult rv =
2421 4 : nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
2422 2 : if (NS_FAILED(rv)) {
2423 0 : internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
2424 0 : return NS_OK;
2425 : }
2426 :
2427 : ScalarResult sr;
2428 : {
2429 4 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2430 2 : sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSet,
2431 2 : unpackedVal);
2432 : }
2433 :
2434 : // Warn the user about the error if we need to.
2435 0 : if (sr != ScalarResult::Ok) {
2436 0 : internal_LogScalarError(aName, sr);
2437 : }
2438 :
2439 : return NS_OK;
2440 : }
2441 :
2442 : /**
2443 : * Sets the keyed scalar to the given value.
2444 : *
2445 : * @param aName The scalar name.
2446 : * @param aKey The key name.
2447 : * @param aVal The value to set the scalar to.
2448 : * @param aCx The JS context.
2449 : * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
2450 : * a warning level message is printed in the browser console.
2451 : */
2452 : nsresult
2453 0 : TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
2454 : JSContext* aCx)
2455 : {
2456 : // Unpack the aVal to nsIVariant. This uses the JS context.
2457 0 : nsCOMPtr<nsIVariant> unpackedVal;
2458 : nsresult rv =
2459 0 : nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
2460 0 : if (NS_FAILED(rv)) {
2461 0 : internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
2462 0 : return NS_OK;
2463 : }
2464 :
2465 : ScalarResult sr;
2466 : {
2467 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2468 0 : sr = internal_UpdateKeyedScalar(locker, aName, aKey,
2469 : ScalarActionType::eSet,
2470 0 : unpackedVal);
2471 : }
2472 :
2473 : // Warn the user about the error if we need to.
2474 0 : if (sr != ScalarResult::Ok) {
2475 0 : internal_LogScalarError(aName, sr);
2476 : }
2477 :
2478 : return NS_OK;
2479 : }
2480 :
2481 : /**
2482 : * Sets the scalar to the given numeric value.
2483 : *
2484 : * @param aId The scalar enum id.
2485 : * @param aValue The numeric, unsigned value to set the scalar to.
2486 : */
2487 : void
2488 0 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
2489 : {
2490 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2491 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2492 : return;
2493 : }
2494 :
2495 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2496 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2497 :
2498 0 : if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
2499 : // We can't record this scalar. Bail out.
2500 0 : return;
2501 : }
2502 :
2503 : // Accumulate in the child process if needed.
2504 0 : if (!XRE_IsParentProcess()) {
2505 0 : TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
2506 : ScalarActionType::eSet,
2507 0 : ScalarVariant(aValue));
2508 0 : return;
2509 : }
2510 :
2511 0 : if (internal_IsScalarDeserializing(locker)) {
2512 0 : internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2513 : ScalarActionType::eSet,
2514 0 : ScalarVariant(aValue));
2515 0 : return;
2516 : }
2517 :
2518 0 : ScalarBase* scalar = nullptr;
2519 : nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
2520 0 : &scalar);
2521 0 : if (NS_FAILED(rv)) {
2522 : return;
2523 : }
2524 :
2525 0 : scalar->SetValue(aValue);
2526 : }
2527 :
2528 : /**
2529 : * Sets the scalar to the given string value.
2530 : *
2531 : * @param aId The scalar enum id.
2532 : * @param aValue The string value to set the scalar to.
2533 : */
2534 : void
2535 0 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue)
2536 : {
2537 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2538 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2539 : return;
2540 : }
2541 :
2542 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2543 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2544 :
2545 0 : if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
2546 : // We can't record this scalar. Bail out.
2547 0 : return;
2548 : }
2549 :
2550 : // Accumulate in the child process if needed.
2551 0 : if (!XRE_IsParentProcess()) {
2552 0 : TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
2553 : ScalarActionType::eSet,
2554 0 : ScalarVariant(nsString(aValue)));
2555 0 : return;
2556 : }
2557 :
2558 0 : if (internal_IsScalarDeserializing(locker)) {
2559 0 : internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2560 : ScalarActionType::eSet,
2561 0 : ScalarVariant(nsString(aValue)));
2562 0 : return;
2563 : }
2564 :
2565 0 : ScalarBase* scalar = nullptr;
2566 : nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
2567 0 : &scalar);
2568 0 : if (NS_FAILED(rv)) {
2569 : return;
2570 : }
2571 :
2572 0 : scalar->SetValue(aValue);
2573 : }
2574 :
2575 : /**
2576 : * Sets the scalar to the given boolean value.
2577 : *
2578 : * @param aId The scalar enum id.
2579 : * @param aValue The boolean value to set the scalar to.
2580 : */
2581 : void
2582 2 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue)
2583 : {
2584 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2585 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2586 : return;
2587 : }
2588 :
2589 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2590 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2591 :
2592 2 : if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
2593 : // We can't record this scalar. Bail out.
2594 0 : return;
2595 : }
2596 :
2597 : // Accumulate in the child process if needed.
2598 2 : if (!XRE_IsParentProcess()) {
2599 0 : TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
2600 : ScalarActionType::eSet,
2601 0 : ScalarVariant(aValue));
2602 0 : return;
2603 : }
2604 :
2605 0 : if (internal_IsScalarDeserializing(locker)) {
2606 0 : internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2607 : ScalarActionType::eSet,
2608 0 : ScalarVariant(aValue));
2609 0 : return;
2610 : }
2611 :
2612 2 : ScalarBase* scalar = nullptr;
2613 : nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
2614 0 : &scalar);
2615 2 : if (NS_FAILED(rv)) {
2616 : return;
2617 : }
2618 :
2619 0 : scalar->SetValue(aValue);
2620 : }
2621 :
2622 : /**
2623 : * Sets the keyed scalar to the given numeric value.
2624 : *
2625 : * @param aId The scalar enum id.
2626 : * @param aKey The scalar key.
2627 : * @param aValue The numeric, unsigned value to set the scalar to.
2628 : */
2629 : void
2630 0 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2631 : uint32_t aValue)
2632 : {
2633 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2634 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2635 : return;
2636 : }
2637 :
2638 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2639 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2640 :
2641 0 : if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
2642 : // We can't record this scalar. Bail out.
2643 0 : return;
2644 : }
2645 :
2646 : // Accumulate in the child process if needed.
2647 0 : if (!XRE_IsParentProcess()) {
2648 0 : TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
2649 0 : aKey, ScalarActionType::eSet, ScalarVariant(aValue));
2650 0 : return;
2651 : }
2652 :
2653 0 : if (internal_IsScalarDeserializing(locker)) {
2654 0 : internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2655 : aKey,
2656 : ScalarActionType::eSet,
2657 0 : ScalarVariant(aValue));
2658 0 : return;
2659 : }
2660 :
2661 0 : KeyedScalar* scalar = nullptr;
2662 : nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
2663 : ProcessID::Parent,
2664 0 : &scalar);
2665 0 : if (NS_FAILED(rv)) {
2666 : return;
2667 : }
2668 :
2669 0 : scalar->SetValue(aKey, aValue);
2670 : }
2671 :
2672 : /**
2673 : * Sets the scalar to the given boolean value.
2674 : *
2675 : * @param aId The scalar enum id.
2676 : * @param aKey The scalar key.
2677 : * @param aValue The boolean value to set the scalar to.
2678 : */
2679 : void
2680 1 : TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2681 : bool aValue)
2682 : {
2683 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2684 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2685 : return;
2686 : }
2687 :
2688 1 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2689 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2690 :
2691 1 : if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
2692 : // We can't record this scalar. Bail out.
2693 0 : return;
2694 : }
2695 :
2696 : // Accumulate in the child process if needed.
2697 1 : if (!XRE_IsParentProcess()) {
2698 0 : TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
2699 0 : aKey, ScalarActionType::eSet, ScalarVariant(aValue));
2700 0 : return;
2701 : }
2702 :
2703 2 : if (internal_IsScalarDeserializing(locker)) {
2704 0 : internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2705 : aKey,
2706 : ScalarActionType::eSet,
2707 0 : ScalarVariant(aValue));
2708 0 : return;
2709 : }
2710 :
2711 1 : KeyedScalar* scalar = nullptr;
2712 : nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
2713 : ProcessID::Parent,
2714 1 : &scalar);
2715 1 : if (NS_FAILED(rv)) {
2716 : return;
2717 : }
2718 :
2719 1 : scalar->SetValue(aKey, aValue);
2720 : }
2721 :
2722 : /**
2723 : * Sets the scalar to the maximum of the current and the passed value.
2724 : *
2725 : * @param aName The scalar name.
2726 : * @param aVal The numeric value to set the scalar to.
2727 : * @param aCx The JS context.
2728 : * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
2729 : * a warning level message is printed in the browser console.
2730 : */
2731 : nsresult
2732 2 : TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
2733 : {
2734 : // Unpack the aVal to nsIVariant. This uses the JS context.
2735 4 : nsCOMPtr<nsIVariant> unpackedVal;
2736 : nsresult rv =
2737 4 : nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
2738 2 : if (NS_FAILED(rv)) {
2739 0 : internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
2740 0 : return NS_OK;
2741 : }
2742 :
2743 : ScalarResult sr;
2744 : {
2745 4 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2746 2 : sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSetMaximum,
2747 2 : unpackedVal);
2748 : }
2749 :
2750 : // Warn the user about the error if we need to.
2751 0 : if (sr != ScalarResult::Ok) {
2752 0 : internal_LogScalarError(aName, sr);
2753 : }
2754 :
2755 : return NS_OK;
2756 : }
2757 :
2758 : /**
2759 : * Sets the scalar to the maximum of the current and the passed value.
2760 : *
2761 : * @param aName The scalar name.
2762 : * @param aKey The key name.
2763 : * @param aVal The numeric value to set the scalar to.
2764 : * @param aCx The JS context.
2765 : * @return NS_OK (always) so that the JS API call doesn't throw. In case of errors,
2766 : * a warning level message is printed in the browser console.
2767 : */
2768 : nsresult
2769 0 : TelemetryScalar::SetMaximum(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal,
2770 : JSContext* aCx)
2771 : {
2772 : // Unpack the aVal to nsIVariant. This uses the JS context.
2773 0 : nsCOMPtr<nsIVariant> unpackedVal;
2774 : nsresult rv =
2775 0 : nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal));
2776 0 : if (NS_FAILED(rv)) {
2777 0 : internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
2778 0 : return NS_OK;
2779 : }
2780 :
2781 : ScalarResult sr;
2782 : {
2783 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2784 0 : sr = internal_UpdateKeyedScalar(locker, aName, aKey,
2785 : ScalarActionType::eSetMaximum,
2786 0 : unpackedVal);
2787 : }
2788 :
2789 : // Warn the user about the error if we need to.
2790 0 : if (sr != ScalarResult::Ok) {
2791 0 : internal_LogScalarError(aName, sr);
2792 : }
2793 :
2794 : return NS_OK;
2795 : }
2796 :
2797 : /**
2798 : * Sets the scalar to the maximum of the current and the passed value.
2799 : *
2800 : * @param aId The scalar enum id.
2801 : * @param aValue The numeric value to set the scalar to.
2802 : */
2803 : void
2804 0 : TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
2805 : {
2806 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2807 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2808 : return;
2809 : }
2810 :
2811 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2812 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2813 :
2814 0 : if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
2815 : // We can't record this scalar. Bail out.
2816 0 : return;
2817 : }
2818 :
2819 : // Accumulate in the child process if needed.
2820 0 : if (!XRE_IsParentProcess()) {
2821 0 : TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
2822 : ScalarActionType::eSetMaximum,
2823 0 : ScalarVariant(aValue));
2824 0 : return;
2825 : }
2826 :
2827 0 : if (internal_IsScalarDeserializing(locker)) {
2828 0 : internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2829 : ScalarActionType::eSetMaximum,
2830 0 : ScalarVariant(aValue));
2831 0 : return;
2832 : }
2833 :
2834 0 : ScalarBase* scalar = nullptr;
2835 : nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
2836 0 : &scalar);
2837 0 : if (NS_FAILED(rv)) {
2838 : return;
2839 : }
2840 :
2841 0 : scalar->SetMaximum(aValue);
2842 : }
2843 :
2844 : /**
2845 : * Sets the keyed scalar to the maximum of the current and the passed value.
2846 : *
2847 : * @param aId The scalar enum id.
2848 : * @param aKey The key name.
2849 : * @param aValue The numeric value to set the scalar to.
2850 : */
2851 : void
2852 0 : TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
2853 : uint32_t aValue)
2854 : {
2855 0 : if (NS_WARN_IF(!IsValidEnumId(aId))) {
2856 0 : MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2857 : return;
2858 : }
2859 :
2860 0 : ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
2861 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2862 :
2863 0 : if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
2864 : // We can't record this scalar. Bail out.
2865 0 : return;
2866 : }
2867 :
2868 : // Accumulate in the child process if needed.
2869 0 : if (!XRE_IsParentProcess()) {
2870 0 : TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
2871 0 : aKey, ScalarActionType::eSetMaximum, ScalarVariant(aValue));
2872 0 : return;
2873 : }
2874 :
2875 0 : if (internal_IsScalarDeserializing(locker)) {
2876 0 : internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
2877 : aKey,
2878 : ScalarActionType::eSetMaximum,
2879 0 : ScalarVariant(aValue));
2880 0 : return;
2881 : }
2882 :
2883 0 : KeyedScalar* scalar = nullptr;
2884 : nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, ProcessID::Parent,
2885 0 : &scalar);
2886 0 : if (NS_FAILED(rv)) {
2887 : return;
2888 : }
2889 :
2890 0 : scalar->SetMaximum(aKey, aValue);
2891 : }
2892 :
2893 : /**
2894 : * Serializes the scalars from the given dataset to a json-style object and resets them.
2895 : * The returned structure looks like:
2896 : * {"process": {"category1.probe":1,"category1.other_probe":false,...}, ... }.
2897 : *
2898 : * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
2899 : * @param aClear Whether to clear out the scalars after snapshotting.
2900 : */
2901 : nsresult
2902 0 : TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
2903 : uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
2904 : {
2905 0 : MOZ_ASSERT(XRE_IsParentProcess(),
2906 : "Snapshotting scalars should only happen in the parent processes.");
2907 : // If no arguments were passed in, apply the default value.
2908 0 : if (!optional_argc) {
2909 0 : aClearScalars = false;
2910 : }
2911 :
2912 0 : JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
2913 0 : if (!root_obj) {
2914 : return NS_ERROR_FAILURE;
2915 : }
2916 0 : aResult.setObject(*root_obj);
2917 :
2918 : // Return `{}` in child processes.
2919 0 : if (!XRE_IsParentProcess()) {
2920 : return NS_OK;
2921 : }
2922 :
2923 : // Only lock the mutex while accessing our data, without locking any JS related code.
2924 0 : ScalarSnapshotTable scalarsToReflect;
2925 : {
2926 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2927 :
2928 : nsresult rv =
2929 0 : internal_GetScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
2930 0 : if (NS_FAILED(rv)) {
2931 0 : return rv;
2932 : }
2933 : }
2934 :
2935 : // Reflect it to JS.
2936 0 : for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
2937 0 : ScalarTupleArray& processScalars = iter.Data();
2938 0 : const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
2939 :
2940 : // Create the object that will hold the scalars for this process and add it
2941 : // to the returned root object.
2942 0 : JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
2943 0 : if (!processObj ||
2944 0 : !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
2945 0 : return NS_ERROR_FAILURE;
2946 : }
2947 :
2948 0 : for (ScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
2949 0 : const ScalarDataTuple& scalar = processScalars[i];
2950 :
2951 : // Convert it to a JS Val.
2952 0 : JS::Rooted<JS::Value> scalarJsValue(aCx);
2953 : nsresult rv =
2954 0 : nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, mozilla::Get<1>(scalar), &scalarJsValue);
2955 0 : if (NS_FAILED(rv)) {
2956 0 : return rv;
2957 : }
2958 :
2959 : // Add it to the scalar object.
2960 0 : if (!JS_DefineProperty(aCx, processObj, mozilla::Get<0>(scalar), scalarJsValue, JSPROP_ENUMERATE)) {
2961 : return NS_ERROR_FAILURE;
2962 : }
2963 : }
2964 : }
2965 :
2966 0 : return NS_OK;
2967 : }
2968 :
2969 : /**
2970 : * Serializes the scalars from the given dataset to a json-style object and resets them.
2971 : * The returned structure looks like:
2972 : * { "process": { "category1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
2973 : *
2974 : * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
2975 : * @param aClear Whether to clear out the keyed scalars after snapshotting.
2976 : */
2977 : nsresult
2978 0 : TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
2979 : uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
2980 : {
2981 0 : MOZ_ASSERT(XRE_IsParentProcess(),
2982 : "Snapshotting scalars should only happen in the parent processes.");
2983 : // If no arguments were passed in, apply the default value.
2984 0 : if (!optional_argc) {
2985 0 : aClearScalars = false;
2986 : }
2987 :
2988 0 : JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
2989 0 : if (!root_obj) {
2990 : return NS_ERROR_FAILURE;
2991 : }
2992 0 : aResult.setObject(*root_obj);
2993 :
2994 : // Return `{}` in child processes.
2995 0 : if (!XRE_IsParentProcess()) {
2996 : return NS_OK;
2997 : }
2998 :
2999 : // Only lock the mutex while accessing our data, without locking any JS related code.
3000 0 : KeyedScalarSnapshotTable scalarsToReflect;
3001 : {
3002 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3003 :
3004 : nsresult rv =
3005 0 : internal_GetKeyedScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
3006 0 : if (NS_FAILED(rv)) {
3007 0 : return rv;
3008 : }
3009 : }
3010 :
3011 : // Reflect it to JS.
3012 0 : for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
3013 0 : KeyedScalarTupleArray& processScalars = iter.Data();
3014 0 : const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
3015 :
3016 : // Create the object that will hold the scalars for this process and add it
3017 : // to the returned root object.
3018 0 : JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
3019 0 : if (!processObj ||
3020 0 : !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
3021 0 : return NS_ERROR_FAILURE;
3022 : }
3023 :
3024 0 : for (KeyedScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
3025 0 : const KeyedScalarDataTuple& keyedScalarData = processScalars[i];
3026 :
3027 : // Go through each keyed scalar and create a keyed scalar object.
3028 : // This object will hold the values for all the keyed scalar keys.
3029 0 : JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
3030 :
3031 : // Define a property for each scalar key, then add it to the keyed scalar
3032 : // object.
3033 0 : const nsTArray<KeyedScalar::KeyValuePair>& keyProps = mozilla::Get<1>(keyedScalarData);
3034 0 : for (uint32_t i = 0; i < keyProps.Length(); i++) {
3035 0 : const KeyedScalar::KeyValuePair& keyData = keyProps[i];
3036 :
3037 : // Convert the value for the key to a JSValue.
3038 0 : JS::Rooted<JS::Value> keyJsValue(aCx);
3039 : nsresult rv =
3040 0 : nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
3041 0 : if (NS_FAILED(rv)) {
3042 0 : return rv;
3043 : }
3044 :
3045 : // Add the key to the scalar representation.
3046 0 : const NS_ConvertUTF8toUTF16 key(keyData.first());
3047 0 : if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
3048 0 : return NS_ERROR_FAILURE;
3049 : }
3050 : }
3051 :
3052 : // Add the scalar to the root object.
3053 0 : if (!JS_DefineProperty(aCx, processObj, mozilla::Get<0>(keyedScalarData), keyedScalarObj, JSPROP_ENUMERATE)) {
3054 : return NS_ERROR_FAILURE;
3055 : }
3056 : }
3057 : }
3058 :
3059 0 : return NS_OK;
3060 : }
3061 :
3062 : nsresult
3063 0 : TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
3064 : JS::Handle<JS::Value> aScalarData,
3065 : bool aBuiltin,
3066 : JSContext* cx)
3067 : {
3068 0 : MOZ_ASSERT(XRE_IsParentProcess(),
3069 : "Dynamic scalars should only be created in the parent process.");
3070 :
3071 0 : if (!IsValidIdentifierString(aCategoryName, kMaximumCategoryNameLength, true, false)) {
3072 0 : JS_ReportErrorASCII(cx, "Invalid category name %s.",
3073 0 : PromiseFlatCString(aCategoryName).get());
3074 0 : return NS_ERROR_INVALID_ARG;
3075 : }
3076 :
3077 0 : if (!aScalarData.isObject()) {
3078 0 : JS_ReportErrorASCII(cx, "Scalar data parameter should be an object");
3079 0 : return NS_ERROR_INVALID_ARG;
3080 : }
3081 :
3082 0 : JS::RootedObject obj(cx, &aScalarData.toObject());
3083 0 : JS::Rooted<JS::IdVector> scalarPropertyIds(cx, JS::IdVector(cx));
3084 0 : if (!JS_Enumerate(cx, obj, &scalarPropertyIds)) {
3085 : return NS_ERROR_FAILURE;
3086 : }
3087 :
3088 : // Collect the scalar data into local storage first.
3089 : // Only after successfully validating all contained scalars will we register
3090 : // them into global storage.
3091 0 : nsTArray<DynamicScalarInfo> newScalarInfos;
3092 :
3093 0 : for (size_t i = 0, n = scalarPropertyIds.length(); i < n; i++) {
3094 0 : nsAutoJSString scalarName;
3095 0 : if (!scalarName.init(cx, scalarPropertyIds[i])) {
3096 0 : return NS_ERROR_FAILURE;
3097 : }
3098 :
3099 0 : if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(scalarName), kMaximumScalarNameLength,
3100 : false, true)) {
3101 0 : JS_ReportErrorASCII(cx, "Invalid scalar name %s.",
3102 0 : PromiseFlatCString(NS_ConvertUTF16toUTF8(scalarName)).get());
3103 0 : return NS_ERROR_INVALID_ARG;
3104 : }
3105 :
3106 : // Join the category and the probe names.
3107 : nsPrintfCString fullName("%s.%s",
3108 0 : PromiseFlatCString(aCategoryName).get(),
3109 0 : NS_ConvertUTF16toUTF8(scalarName).get());
3110 :
3111 0 : JS::RootedValue value(cx);
3112 0 : if (!JS_GetPropertyById(cx, obj, scalarPropertyIds[i], &value) || !value.isObject()) {
3113 0 : return NS_ERROR_FAILURE;
3114 : }
3115 0 : JS::RootedObject scalarDef(cx, &value.toObject());
3116 :
3117 : // Get the scalar's kind.
3118 0 : if (!JS_GetProperty(cx, scalarDef, "kind", &value)
3119 0 : || !value.isInt32()) {
3120 0 : JS_ReportErrorASCII(cx, "Invalid or missing 'kind' for scalar %s.",
3121 0 : PromiseFlatCString(fullName).get());
3122 0 : return NS_ERROR_FAILURE;
3123 : }
3124 0 : uint32_t kind = static_cast<uint32_t>(value.toInt32());
3125 :
3126 : // Get the optional scalar's recording policy (default to false).
3127 0 : bool hasProperty = false;
3128 0 : bool recordOnRelease = false;
3129 0 : if (JS_HasProperty(cx, scalarDef, "record_on_release", &hasProperty) && hasProperty) {
3130 0 : if (!JS_GetProperty(cx, scalarDef, "record_on_release", &value) || !value.isBoolean()) {
3131 0 : JS_ReportErrorASCII(cx, "Invalid 'record_on_release' for scalar %s.",
3132 0 : PromiseFlatCString(fullName).get());
3133 0 : return NS_ERROR_FAILURE;
3134 : }
3135 0 : recordOnRelease = static_cast<bool>(value.toBoolean());
3136 : }
3137 :
3138 : // Get the optional scalar's keyed (default to false).
3139 0 : bool keyed = false;
3140 0 : if (JS_HasProperty(cx, scalarDef, "keyed", &hasProperty) && hasProperty) {
3141 0 : if (!JS_GetProperty(cx, scalarDef, "keyed", &value) || !value.isBoolean()) {
3142 0 : JS_ReportErrorASCII(cx, "Invalid 'keyed' for scalar %s.",
3143 0 : PromiseFlatCString(fullName).get());
3144 0 : return NS_ERROR_FAILURE;
3145 : }
3146 0 : keyed = static_cast<bool>(value.toBoolean());
3147 : }
3148 :
3149 :
3150 : // Get the optional scalar's expired state (default to false).
3151 0 : bool expired = false;
3152 0 : if (JS_HasProperty(cx, scalarDef, "expired", &hasProperty) && hasProperty) {
3153 0 : if (!JS_GetProperty(cx, scalarDef, "expired", &value) || !value.isBoolean()) {
3154 0 : JS_ReportErrorASCII(cx, "Invalid 'expired' for scalar %s.",
3155 0 : PromiseFlatCString(fullName).get());
3156 0 : return NS_ERROR_FAILURE;
3157 : }
3158 0 : expired = static_cast<bool>(value.toBoolean());
3159 : }
3160 :
3161 : // We defer the actual registration here in case any other event description is invalid.
3162 : // In that case we don't need to roll back any partial registration.
3163 0 : newScalarInfos.AppendElement(DynamicScalarInfo{
3164 : kind, recordOnRelease, expired, fullName, keyed, aBuiltin
3165 0 : });
3166 : }
3167 :
3168 : // Register the dynamic definition on the parent process.
3169 : {
3170 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3171 0 : ::internal_RegisterScalars(locker, newScalarInfos);
3172 :
3173 : // Propagate the registration to all the content-processes. Please note that
3174 : // this does not require to hold the mutex.
3175 0 : ::internal_BroadcastDefinitions(locker, newScalarInfos);
3176 : }
3177 0 : return NS_OK;
3178 : }
3179 :
3180 : /**
3181 : * Count in Scalars how many of which events were recorded. See bug 1440673
3182 : *
3183 : * Event Telemetry unfortunately cannot use vanilla ScalarAdd because it needs
3184 : * to summarize events recorded in different processes to the
3185 : * telemetry.event_counts of the same process. Including "dynamic".
3186 : *
3187 : * @param aUniqueEventName - expected to be category#object#method
3188 : * @param aProcessType - the process of the event being summarized
3189 : * @param aDynamic - whether the event being summarized was dynamic
3190 : */
3191 : void
3192 0 : TelemetryScalar::SummarizeEvent(const nsCString& aUniqueEventName,
3193 : ProcessID aProcessType, bool aDynamic)
3194 : {
3195 0 : MOZ_ASSERT(XRE_IsParentProcess(), "Only summarize events in the parent process");
3196 0 : if (!XRE_IsParentProcess()) {
3197 0 : return;
3198 : }
3199 :
3200 0 : StaticMutexAutoLock lock(gTelemetryScalarsMutex);
3201 :
3202 0 : ScalarKey scalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_EVENT_COUNTS), aDynamic};
3203 0 : if (aDynamic) {
3204 : nsresult rv = internal_GetEnumByScalarName(lock,
3205 0 : nsAutoCString("telemetry.dynamic_event_counts"),
3206 0 : &scalarKey);
3207 0 : if (NS_FAILED(rv)) {
3208 0 : NS_WARNING("NS_FAILED getting ScalarKey for telemetry.dynamic_event_counts");
3209 0 : return;
3210 : }
3211 : }
3212 :
3213 0 : KeyedScalar* scalar = nullptr;
3214 0 : nsresult rv = internal_GetKeyedScalarByEnum(lock, scalarKey, aProcessType, &scalar);
3215 :
3216 0 : if (NS_FAILED(rv)) {
3217 0 : NS_WARNING("NS_FAILED getting keyed scalar for event summary. Wut.");
3218 0 : return;
3219 : }
3220 :
3221 : static uint32_t sMaxEventSummaryKeys =
3222 0 : Preferences::GetUint("toolkit.telemetry.maxEventSummaryKeys", 500);
3223 :
3224 : // Set this each time as it may have been cleared and recreated between calls
3225 0 : scalar->SetMaximumNumberOfKeys(sMaxEventSummaryKeys);
3226 :
3227 0 : scalar->AddValue(NS_ConvertASCIItoUTF16(aUniqueEventName), 1);
3228 : }
3229 :
3230 : /**
3231 : * Resets all the stored scalars. This is intended to be only used in tests.
3232 : */
3233 : void
3234 0 : TelemetryScalar::ClearScalars()
3235 : {
3236 0 : MOZ_ASSERT(XRE_IsParentProcess(), "Scalars should only be cleared in the parent process.");
3237 0 : if (!XRE_IsParentProcess()) {
3238 0 : return;
3239 : }
3240 :
3241 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3242 0 : gScalarStorageMap.Clear();
3243 0 : gKeyedScalarStorageMap.Clear();
3244 0 : gDynamicBuiltinScalarStorageMap.Clear();
3245 0 : gDynamicBuiltinKeyedScalarStorageMap.Clear();
3246 0 : gScalarsActions = nullptr;
3247 0 : gKeyedScalarsActions = nullptr;
3248 : }
3249 :
3250 : size_t
3251 0 : TelemetryScalar::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
3252 : {
3253 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3254 0 : return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
3255 : }
3256 :
3257 : size_t
3258 0 : TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
3259 : {
3260 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3261 0 : size_t n = 0;
3262 :
3263 0 : auto getSizeOf = [aMallocSizeOf](auto &storageMap)
3264 0 : {
3265 0 : size_t partial = 0;
3266 0 : for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
3267 0 : auto scalarStorage = iter.UserData();
3268 0 : for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
3269 0 : auto scalar = childIter.UserData();
3270 0 : partial += scalar->SizeOfIncludingThis(aMallocSizeOf);
3271 : }
3272 : }
3273 0 : return partial;
3274 0 : };
3275 :
3276 : // Account for all the storage used for the different scalar types.
3277 0 : n += getSizeOf(gScalarStorageMap);
3278 0 : n += getSizeOf(gKeyedScalarStorageMap);
3279 0 : n += getSizeOf(gDynamicBuiltinScalarStorageMap);
3280 0 : n += getSizeOf(gDynamicBuiltinKeyedScalarStorageMap);
3281 :
3282 0 : return n;
3283 : }
3284 :
3285 : void
3286 0 : TelemetryScalar::UpdateChildData(ProcessID aProcessType,
3287 : const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions)
3288 : {
3289 0 : MOZ_ASSERT(XRE_IsParentProcess(),
3290 : "The stored child processes scalar data must be updated from the parent process.");
3291 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3292 :
3293 : // If scalars are still being deserialized, we need to record the incoming
3294 : // operations as well.
3295 0 : if (internal_IsScalarDeserializing(locker)) {
3296 0 : for (const ScalarAction& action : aScalarActions) {
3297 : // We're only getting immutable access, so let's copy it
3298 0 : ScalarAction copy = action;
3299 : // Fix up the process type
3300 0 : copy.mProcessType = aProcessType;
3301 0 : internal_RecordScalarAction(locker, copy);
3302 : }
3303 :
3304 0 : return;
3305 : }
3306 :
3307 0 : internal_ApplyScalarActions(locker, aScalarActions, Some(aProcessType));
3308 : }
3309 :
3310 : void
3311 0 : TelemetryScalar::UpdateChildKeyedData(ProcessID aProcessType,
3312 : const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions)
3313 : {
3314 0 : MOZ_ASSERT(XRE_IsParentProcess(),
3315 : "The stored child processes keyed scalar data must be updated from the parent process.");
3316 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3317 :
3318 : // If scalars are still being deserialized, we need to record the incoming
3319 : // operations as well.
3320 0 : if (internal_IsScalarDeserializing(locker)) {
3321 0 : for (const KeyedScalarAction& action : aScalarActions) {
3322 : // We're only getting immutable access, so let's copy it
3323 0 : KeyedScalarAction copy = action;
3324 : // Fix up the process type
3325 0 : copy.mProcessType = aProcessType;
3326 0 : internal_RecordKeyedScalarAction(locker, copy);
3327 : }
3328 :
3329 0 : return;
3330 : }
3331 :
3332 0 : internal_ApplyKeyedScalarActions(locker, aScalarActions, Some(aProcessType));
3333 : }
3334 :
3335 : void
3336 0 : TelemetryScalar::RecordDiscardedData(ProcessID aProcessType,
3337 : const mozilla::Telemetry::DiscardedData& aDiscardedData)
3338 : {
3339 0 : MOZ_ASSERT(XRE_IsParentProcess(),
3340 : "Discarded Data must be updated from the parent process.");
3341 2 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3342 0 : if (!internal_CanRecordBase(locker)) {
3343 0 : return;
3344 : }
3345 :
3346 0 : ScalarBase* scalar = nullptr;
3347 2 : mozilla::DebugOnly<nsresult> rv;
3348 :
3349 0 : rv = internal_GetScalarByEnum(locker,
3350 0 : ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_ACCUMULATIONS), false},
3351 0 : aProcessType, &scalar);
3352 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
3353 1 : scalar->AddValue(aDiscardedData.mDiscardedHistogramAccumulations);
3354 :
3355 1 : rv = internal_GetScalarByEnum(locker,
3356 2 : ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_KEYED_ACCUMULATIONS), false},
3357 2 : aProcessType, &scalar);
3358 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
3359 1 : scalar->AddValue(aDiscardedData.mDiscardedKeyedHistogramAccumulations);
3360 :
3361 1 : rv = internal_GetScalarByEnum(locker,
3362 2 : ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_SCALAR_ACTIONS), false},
3363 0 : aProcessType, &scalar);
3364 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
3365 1 : scalar->AddValue(aDiscardedData.mDiscardedScalarActions);
3366 :
3367 1 : rv = internal_GetScalarByEnum(locker,
3368 2 : ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_KEYED_SCALAR_ACTIONS), false},
3369 0 : aProcessType, &scalar);
3370 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
3371 1 : scalar->AddValue(aDiscardedData.mDiscardedKeyedScalarActions);
3372 :
3373 1 : rv = internal_GetScalarByEnum(locker,
3374 2 : ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_CHILD_EVENTS), false},
3375 2 : aProcessType, &scalar);
3376 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
3377 1 : scalar->AddValue(aDiscardedData.mDiscardedChildEvents);
3378 : }
3379 :
3380 : /**
3381 : * Get the dynamic scalar definitions in an IPC-friendly
3382 : * structure.
3383 : */
3384 : void
3385 1 : TelemetryScalar::GetDynamicScalarDefinitions(
3386 : nsTArray<DynamicScalarDefinition> &aDefArray)
3387 : {
3388 1 : MOZ_ASSERT(XRE_IsParentProcess());
3389 0 : if (!gDynamicScalarInfo) {
3390 : // Don't have dynamic scalar definitions. Bail out!
3391 0 : return;
3392 : }
3393 :
3394 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3395 1 : internal_DynamicScalarToIPC(locker, *gDynamicScalarInfo, aDefArray);
3396 : }
3397 :
3398 : /**
3399 : * This adds the dynamic scalar definitions coming from
3400 : * the parent process to this child process. If a dynamic
3401 : * scalar definition is already defined, check if the new definition
3402 : * makes the scalar expired and eventually update the expiration
3403 : * state.
3404 : */
3405 : void
3406 0 : TelemetryScalar::AddDynamicScalarDefinitions(
3407 : const nsTArray<DynamicScalarDefinition>& aDefs)
3408 : {
3409 0 : MOZ_ASSERT(!XRE_IsParentProcess());
3410 :
3411 0 : nsTArray<DynamicScalarInfo> dynamicStubs;
3412 :
3413 : // Populate the definitions array before acquiring the lock.
3414 0 : for (auto def : aDefs) {
3415 0 : bool recordOnRelease = def.dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT;
3416 0 : dynamicStubs.AppendElement(DynamicScalarInfo{
3417 : def.type,
3418 : recordOnRelease,
3419 0 : def.expired,
3420 : def.name,
3421 0 : def.keyed,
3422 0 : false /* builtin */});
3423 : }
3424 :
3425 : {
3426 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3427 0 : internal_RegisterScalars(locker, dynamicStubs);
3428 : }
3429 0 : }
3430 :
3431 : ////////////////////////////////////////////////////////////////////////
3432 : ////////////////////////////////////////////////////////////////////////
3433 : //
3434 : // PUBLIC: GeckoView serialization/deserialization functions.
3435 :
3436 : /**
3437 : * Write the scalar data to the provided Json object, for
3438 : * GeckoView measurement persistence. The output format is the same one used
3439 : * for snapshotting the scalars.
3440 : *
3441 : * @param {aWriter} The JSON object to write to.
3442 : * @returns NS_OK or a failure value explaining why persistence failed.
3443 : */
3444 : nsresult
3445 0 : TelemetryScalar::SerializeScalars(mozilla::JSONWriter& aWriter)
3446 : {
3447 : // Get a copy of the data, without clearing.
3448 0 : ScalarSnapshotTable scalarsToReflect;
3449 : {
3450 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3451 : // For persistence, we care about all the datasets. Worst case, they
3452 : // will be empty.
3453 : nsresult rv = internal_GetScalarSnapshot(locker,
3454 : scalarsToReflect,
3455 : nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
3456 0 : false /*aClearScalars*/);
3457 0 : if (NS_FAILED(rv)) {
3458 0 : return rv;
3459 : }
3460 : }
3461 :
3462 : // Persist the scalars to the JSON object.
3463 0 : for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
3464 0 : ScalarTupleArray& processScalars = iter.Data();
3465 0 : const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
3466 :
3467 0 : aWriter.StartObjectProperty(processName);
3468 :
3469 0 : for (const ScalarDataTuple& scalar : processScalars) {
3470 0 : nsresult rv = WriteVariantToJSONWriter(mozilla::Get<2>(scalar) /*aScalarType*/,
3471 0 : mozilla::Get<1>(scalar) /*aInputValue*/,
3472 0 : mozilla::Get<0>(scalar) /*aPropertyName*/,
3473 0 : aWriter /*aWriter*/);
3474 0 : if (NS_FAILED(rv)) {
3475 : // Skip this scalar if we failed to write it. We don't bail out just
3476 : // yet as we may salvage other scalars. We eventually need to call EndObject.
3477 : continue;
3478 : }
3479 : }
3480 :
3481 0 : aWriter.EndObject();
3482 : }
3483 :
3484 0 : return NS_OK;
3485 : }
3486 :
3487 : /**
3488 : * Write the keyed scalar data to the provided Json object, for
3489 : * GeckoView measurement persistence. The output format is the same
3490 : * one used for snapshotting the keyed scalars.
3491 : *
3492 : * @param {aWriter} The JSON object to write to.
3493 : * @returns NS_OK or a failure value explaining why persistence failed.
3494 : */
3495 : nsresult
3496 0 : TelemetryScalar::SerializeKeyedScalars(mozilla::JSONWriter& aWriter)
3497 : {
3498 : // Get a copy of the data, without clearing.
3499 0 : KeyedScalarSnapshotTable keyedScalarsToReflect;
3500 : {
3501 0 : StaticMutexAutoLock locker(gTelemetryScalarsMutex);
3502 : // For persistence, we care about all the datasets. Worst case, they
3503 : // will be empty.
3504 : nsresult rv = internal_GetKeyedScalarSnapshot(locker,
3505 : keyedScalarsToReflect,
3506 : nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
3507 0 : false /*aClearScalars*/);
3508 0 : if (NS_FAILED(rv)) {
3509 0 : return rv;
3510 : }
3511 : }
3512 :
3513 : // Persist the scalars to the JSON object.
3514 0 : for (auto iter = keyedScalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
3515 0 : KeyedScalarTupleArray& processScalars = iter.Data();
3516 0 : const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
3517 :
3518 0 : aWriter.StartObjectProperty(processName);
3519 :
3520 0 : for (const KeyedScalarDataTuple& keyedScalarData : processScalars) {
3521 0 : aWriter.StartObjectProperty(mozilla::Get<0>(keyedScalarData));
3522 :
3523 : // Define a property for each scalar key, then add it to the keyed scalar
3524 : // object.
3525 0 : const nsTArray<KeyedScalar::KeyValuePair>& keyProps = mozilla::Get<1>(keyedScalarData);
3526 0 : for (const KeyedScalar::KeyValuePair& keyData : keyProps) {
3527 0 : nsresult rv = WriteVariantToJSONWriter(mozilla::Get<2>(keyedScalarData) /*aScalarType*/,
3528 0 : keyData.second() /*aInputValue*/,
3529 0 : PromiseFlatCString(keyData.first()).get() /*aOutKey*/,
3530 0 : aWriter /*aWriter*/);
3531 0 : if (NS_FAILED(rv)) {
3532 : // Skip this scalar if we failed to write it. We don't bail out just
3533 : // yet as we may salvage other scalars. We eventually need to call EndObject.
3534 : continue;
3535 : }
3536 : }
3537 0 : aWriter.EndObject();
3538 : }
3539 0 : aWriter.EndObject();
3540 : }
3541 :
3542 0 : return NS_OK;
3543 : }
3544 :
3545 : /**
3546 : * Load the persisted measurements from a Json object and inject them
3547 : * in the relevant process storage.
3548 : *
3549 : * @param {aData} The input Json object.
3550 : * @returns NS_OK if loading was performed, an error code explaining the
3551 : * failure reason otherwise.
3552 : */
3553 : nsresult
3554 0 : TelemetryScalar::DeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData)
3555 : {
3556 0 : MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
3557 0 : if (!XRE_IsParentProcess()) {
3558 : return NS_ERROR_FAILURE;
3559 : }
3560 :
3561 : typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> PersistedScalarPair;
3562 : typedef nsTArray<PersistedScalarPair> PersistedScalarArray;
3563 : typedef nsDataHashtable<ProcessIDHashKey, PersistedScalarArray> PeristedScalarStorage;
3564 :
3565 0 : PeristedScalarStorage scalarsToUpdate;
3566 :
3567 : // Before updating the scalars, we need to get the data out of the JS
3568 : // wrappers. We can't hold the scalars mutex while handling JS stuff.
3569 : // Build a <scalar name, value> map.
3570 0 : JS::RootedObject scalarDataObj(aCx, &aData.toObject());
3571 0 : JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx));
3572 0 : if (!JS_Enumerate(aCx, scalarDataObj, &processes)) {
3573 : // We can't even enumerate the processes in the loaded data, so
3574 : // there is nothing we could recover from the persistence file. Bail out.
3575 0 : JS_ClearPendingException(aCx);
3576 0 : return NS_ERROR_FAILURE;
3577 : }
3578 :
3579 : // The following block of code attempts to extract as much data as possible
3580 : // from the serialized JSON, even in case of light data corruptions: if, for example,
3581 : // the data for a single process is corrupted or is in an unexpected form, we press on
3582 : // and attempt to load the data for the other processes.
3583 0 : JS::RootedId process(aCx);
3584 0 : for (auto& processVal : processes) {
3585 : // This is required as JS API calls require an Handle<jsid> and not a
3586 : // plain jsid.
3587 0 : process = processVal;
3588 : // Get the process name.
3589 0 : nsAutoJSString processNameJS;
3590 0 : if (!processNameJS.init(aCx, process)) {
3591 0 : JS_ClearPendingException(aCx);
3592 0 : continue;
3593 : }
3594 :
3595 : // Make sure it's valid. Note that this is safe to call outside
3596 : // of a locked section.
3597 0 : NS_ConvertUTF16toUTF8 processName(processNameJS);
3598 0 : ProcessID processID = GetIDForProcessName(processName.get());
3599 0 : if (processID == ProcessID::Count) {
3600 0 : NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.get()).get());
3601 0 : continue;
3602 : }
3603 :
3604 : // And its probes.
3605 0 : JS::RootedValue processData(aCx);
3606 0 : if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) {
3607 0 : JS_ClearPendingException(aCx);
3608 0 : continue;
3609 : }
3610 :
3611 0 : if (!processData.isObject()) {
3612 : // |processData| should be an object containing scalars. If this is
3613 : // not the case, silently skip and try to load the data for the other
3614 : // processes.
3615 : continue;
3616 : }
3617 :
3618 : // Iterate through each scalar.
3619 0 : JS::RootedObject processDataObj(aCx, &processData.toObject());
3620 0 : JS::Rooted<JS::IdVector> scalars(aCx, JS::IdVector(aCx));
3621 0 : if (!JS_Enumerate(aCx, processDataObj, &scalars)) {
3622 0 : JS_ClearPendingException(aCx);
3623 0 : continue;
3624 : }
3625 :
3626 0 : JS::RootedId scalar(aCx);
3627 0 : for (auto& scalarVal : scalars) {
3628 0 : scalar = scalarVal;
3629 : // Get the scalar name.
3630 0 : nsAutoJSString scalarName;
3631 0 : if (!scalarName.init(aCx, scalar)) {
3632 0 : JS_ClearPendingException(aCx);
3633 0 : continue;
3634 : }
3635 :
3636 : // Get the scalar value as a JS value.
3637 0 : JS::RootedValue scalarValue(aCx);
3638 0 : if (!JS_GetPropertyById(aCx, processDataObj, scalar, &scalarValue)) {
3639 0 : JS_ClearPendingException(aCx);
3640 0 : continue;
3641 : }
3642 :
3643 0 : if (scalarValue.isNullOrUndefined()) {
3644 : // We can't set scalars to null or undefined values, skip this
3645 : // and try to load other scalars.
3646 : continue;
3647 : }
3648 :
3649 : // Unpack the aVal to nsIVariant.
3650 0 : nsCOMPtr<nsIVariant> unpackedVal;
3651 : nsresult rv =
3652 0 : nsContentUtils::XPConnect()->JSToVariant(aCx, scalarValue, getter_AddRefs(unpackedVal));
3653 0 : if (NS_FAILED(rv)) {
3654 0 : JS_ClearPendingException(aCx);
3655 0 : continue;
3656 : }
3657 :
3658 : // Add the scalar to the map.
3659 : PersistedScalarArray& processScalars =
3660 0 : scalarsToUpdate.GetOrInsert(static_cast<uint32_t>(processID));
3661 0 : processScalars.AppendElement(
3662 0 : mozilla::MakePair(nsCString(NS_ConvertUTF16toUTF8(scalarName)), unpackedVal));
3663 : }
3664 : }
3665 :
3666 : // Now that all the JS specific operations are finished, update the scalars.
3667 : {
3668 0 : StaticMutexAutoLock lock(gTelemetryScalarsMutex);
3669 :
3670 0 : for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
3671 0 : PersistedScalarArray& processScalars = iter.Data();
3672 0 : for (PersistedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
3673 0 : mozilla::Unused << internal_UpdateScalar(lock,
3674 0 : processScalars[i].first(),
3675 : ScalarActionType::eSet,
3676 0 : processScalars[i].second(),
3677 0 : ProcessID(iter.Key()),
3678 : true /* aForce */);
3679 : }
3680 : }
3681 : }
3682 :
3683 : return NS_OK;
3684 : }
3685 :
3686 : /**
3687 : * Load the persisted measurements from a Json object and injects them
3688 : * in the relevant process storage.
3689 : *
3690 : * @param {aData} The input Json object.
3691 : * @returns NS_OK if loading was performed, an error code explaining the
3692 : * failure reason otherwise.
3693 : */
3694 : nsresult
3695 0 : TelemetryScalar::DeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData)
3696 : {
3697 0 : MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
3698 0 : if (!XRE_IsParentProcess()) {
3699 : return NS_ERROR_FAILURE;
3700 : }
3701 :
3702 : typedef mozilla::Tuple<nsCString, nsString, nsCOMPtr<nsIVariant>> PersistedKeyedScalarTuple;
3703 : typedef nsTArray<PersistedKeyedScalarTuple> PersistedKeyedScalarArray;
3704 : typedef nsDataHashtable<ProcessIDHashKey, PersistedKeyedScalarArray> PeristedKeyedScalarStorage;
3705 :
3706 0 : PeristedKeyedScalarStorage scalarsToUpdate;
3707 :
3708 : // Before updating the keyed scalars, we need to get the data out of the JS
3709 : // wrappers. We can't hold the scalars mutex while handling JS stuff.
3710 : // Build a <scalar name, value> map.
3711 0 : JS::RootedObject scalarDataObj(aCx, &aData.toObject());
3712 0 : JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx));
3713 0 : if (!JS_Enumerate(aCx, scalarDataObj, &processes)) {
3714 : // We can't even enumerate the processes in the loaded data, so
3715 : // there is nothing we could recover from the persistence file. Bail out.
3716 0 : JS_ClearPendingException(aCx);
3717 0 : return NS_ERROR_FAILURE;
3718 : }
3719 :
3720 : // The following block of code attempts to extract as much data as possible
3721 : // from the serialized JSON, even in case of light data corruptions: if, for example,
3722 : // the data for a single process is corrupted or is in an unexpected form, we press on
3723 : // and attempt to load the data for the other processes.
3724 0 : JS::RootedId process(aCx);
3725 0 : for (auto& processVal : processes) {
3726 0 : process = processVal;
3727 : // Get the process name.
3728 0 : nsAutoJSString processNameJS;
3729 0 : if (!processNameJS.init(aCx, process)) {
3730 0 : JS_ClearPendingException(aCx);
3731 0 : continue;
3732 : }
3733 :
3734 : // Make sure it's valid. Note that this is safe to call outside
3735 : // of a locked section.
3736 0 : NS_ConvertUTF16toUTF8 processName(processNameJS);
3737 0 : ProcessID processID = GetIDForProcessName(processName.get());
3738 0 : if (processID == ProcessID::Count) {
3739 0 : NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.get()).get());
3740 0 : continue;
3741 : }
3742 :
3743 : // And its probes.
3744 0 : JS::RootedValue processData(aCx);
3745 0 : if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) {
3746 0 : JS_ClearPendingException(aCx);
3747 0 : continue;
3748 : }
3749 :
3750 0 : if (!processData.isObject()) {
3751 : // |processData| should be an object containing scalars. If this is
3752 : // not the case, silently skip and try to load the data for the other
3753 : // processes.
3754 : continue;
3755 : }
3756 :
3757 : // Iterate through each keyed scalar.
3758 0 : JS::RootedObject processDataObj(aCx, &processData.toObject());
3759 0 : JS::Rooted<JS::IdVector> keyedScalars(aCx, JS::IdVector(aCx));
3760 0 : if (!JS_Enumerate(aCx, processDataObj, &keyedScalars)) {
3761 0 : JS_ClearPendingException(aCx);
3762 0 : continue;
3763 : }
3764 :
3765 0 : JS::RootedId keyedScalar(aCx);
3766 0 : for (auto& keyedScalarVal : keyedScalars) {
3767 0 : keyedScalar = keyedScalarVal;
3768 : // Get the scalar name.
3769 0 : nsAutoJSString scalarName;
3770 0 : if (!scalarName.init(aCx, keyedScalar)) {
3771 0 : JS_ClearPendingException(aCx);
3772 0 : continue;
3773 : }
3774 :
3775 : // Get the data for this keyed scalar.
3776 0 : JS::RootedValue keyedScalarData(aCx);
3777 0 : if (!JS_GetPropertyById(aCx, processDataObj, keyedScalar, &keyedScalarData)) {
3778 0 : JS_ClearPendingException(aCx);
3779 0 : continue;
3780 : }
3781 :
3782 0 : if (!keyedScalarData.isObject()) {
3783 : // Keyed scalar data need to be an object. If that's not the case, skip it
3784 : // and try to load the rest of the data.
3785 : continue;
3786 : }
3787 :
3788 : // Get the keys in the keyed scalar.
3789 0 : JS::RootedObject keyedScalarDataObj(aCx, &keyedScalarData.toObject());
3790 0 : JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
3791 0 : if (!JS_Enumerate(aCx, keyedScalarDataObj, &keys)) {
3792 0 : JS_ClearPendingException(aCx);
3793 0 : continue;
3794 : }
3795 :
3796 0 : JS::RootedId key(aCx);
3797 0 : for (auto keyVal : keys) {
3798 0 : key = keyVal;
3799 : // Get the process name.
3800 0 : nsAutoJSString keyName;
3801 0 : if (!keyName.init(aCx, key)) {
3802 0 : JS_ClearPendingException(aCx);
3803 0 : continue;
3804 : }
3805 :
3806 : // Get the scalar value as a JS value.
3807 0 : JS::RootedValue scalarValue(aCx);
3808 0 : if (!JS_GetPropertyById(aCx, keyedScalarDataObj, key, &scalarValue)) {
3809 0 : JS_ClearPendingException(aCx);
3810 0 : continue;
3811 : }
3812 :
3813 0 : if (scalarValue.isNullOrUndefined()) {
3814 : // We can't set scalars to null or undefined values, skip this
3815 : // and try to load other scalars.
3816 : continue;
3817 : }
3818 :
3819 : // Unpack the aVal to nsIVariant.
3820 0 : nsCOMPtr<nsIVariant> unpackedVal;
3821 : nsresult rv =
3822 0 : nsContentUtils::XPConnect()->JSToVariant(aCx, scalarValue, getter_AddRefs(unpackedVal));
3823 0 : if (NS_FAILED(rv)) {
3824 0 : JS_ClearPendingException(aCx);
3825 0 : continue;
3826 : }
3827 :
3828 : // Add the scalar to the map.
3829 : PersistedKeyedScalarArray& processScalars =
3830 0 : scalarsToUpdate.GetOrInsert(static_cast<uint32_t>(processID));
3831 0 : processScalars.AppendElement(
3832 0 : mozilla::MakeTuple(nsCString(NS_ConvertUTF16toUTF8(scalarName)),
3833 : nsString(keyName), unpackedVal));
3834 : }
3835 : }
3836 : }
3837 :
3838 : // Now that all the JS specific operations are finished, update the scalars.
3839 : {
3840 : StaticMutexAutoLock lock(gTelemetryScalarsMutex);
3841 :
3842 : for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
3843 : PersistedKeyedScalarArray& processScalars = iter.Data();
3844 : for (PersistedKeyedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
3845 : mozilla::Unused << internal_UpdateKeyedScalar(lock,
3846 : mozilla::Get<0>(processScalars[i]),
3847 : mozilla::Get<1>(processScalars[i]),
3848 : ScalarActionType::eSet,
3849 : mozilla::Get<2>(processScalars[i]),
3850 : ProcessID(iter.Key()),
3851 : true /* aForce */);
3852 : }
3853 : }
3854 : }
3855 :
3856 : return NS_OK;
3857 : }
|