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/. */
7 #include "TelemetryScalar.h"
9 #include "ipc/TelemetryIPCAccumulator.h"
10 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
11 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefineUCProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasProperty
12 #include "mozilla/dom/ContentParent.h"
13 #include "mozilla/JSONWriter.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/StaticMutex.h"
16 #include "mozilla/StaticPtr.h"
17 #include "mozilla/TelemetryComms.h"
18 #include "mozilla/Unused.h"
19 #include "nsBaseHashtable.h"
20 #include "nsClassHashtable.h"
21 #include "nsContentUtils.h"
22 #include "nsHashKeys.h"
23 #include "nsITelemetry.h"
24 #include "nsIVariant.h"
25 #include "nsIXPConnect.h"
26 #include "nsJSUtils.h"
27 #include "nsPrintfCString.h"
28 #include "nsVariant.h"
29 #include "TelemetryCommon.h"
30 #include "TelemetryScalarData.h"
32 using mozilla::MakeUnique
;
33 using mozilla::Nothing
;
34 using mozilla::Preferences
;
36 using mozilla::StaticAutoPtr
;
37 using mozilla::StaticMutex
;
38 using mozilla::StaticMutexAutoLock
;
39 using mozilla::UniquePtr
;
40 using mozilla::Telemetry::DynamicScalarDefinition
;
41 using mozilla::Telemetry::KeyedScalarAction
;
42 using mozilla::Telemetry::ProcessID
;
43 using mozilla::Telemetry::ScalarAction
;
44 using mozilla::Telemetry::ScalarActionType
;
45 using mozilla::Telemetry::ScalarID
;
46 using mozilla::Telemetry::ScalarVariant
;
47 using mozilla::Telemetry::Common::AutoHashtable
;
48 using mozilla::Telemetry::Common::CanRecordDataset
;
49 using mozilla::Telemetry::Common::CanRecordProduct
;
50 using mozilla::Telemetry::Common::GetCurrentProduct
;
51 using mozilla::Telemetry::Common::GetIDForProcessName
;
52 using mozilla::Telemetry::Common::GetNameForProcessID
;
53 using mozilla::Telemetry::Common::IsExpiredVersion
;
54 using mozilla::Telemetry::Common::IsInDataset
;
55 using mozilla::Telemetry::Common::IsValidIdentifierString
;
56 using mozilla::Telemetry::Common::LogToBrowserConsole
;
57 using mozilla::Telemetry::Common::RecordedProcessType
;
58 using mozilla::Telemetry::Common::StringHashSet
;
59 using mozilla::Telemetry::Common::SupportedProduct
;
61 namespace TelemetryIPCAccumulator
= mozilla::TelemetryIPCAccumulator
;
63 namespace geckoprofiler::markers
{
66 static constexpr mozilla::Span
<const char> MarkerTypeName() {
67 return mozilla::MakeStringSpan("Scalar");
69 static void StreamJSONMarkerData(
70 mozilla::baseprofiler::SpliceableJSONWriter
& aWriter
,
71 const mozilla::ProfilerString8View
& aName
, const uint32_t& aKind
,
72 const nsCString
& aKey
, const ScalarVariant
& aValue
) {
73 aWriter
.UniqueStringProperty("id", aName
);
74 if (!aKey
.IsEmpty()) {
75 aWriter
.StringProperty("key", mozilla::MakeStringSpan(aKey
.get()));
77 if (aKind
== nsITelemetry::SCALAR_TYPE_COUNT
) {
78 aWriter
.UniqueStringProperty("scalarType", "uint");
79 aWriter
.IntProperty("val", aValue
.as
<uint32_t>());
80 } else if (aKind
== nsITelemetry::SCALAR_TYPE_STRING
) {
81 aWriter
.UniqueStringProperty("scalarType", "string");
82 aWriter
.StringProperty(
83 "val", mozilla::MakeStringSpan(
84 NS_ConvertUTF16toUTF8(aValue
.as
<nsString
>()).get()));
86 aWriter
.UniqueStringProperty("scalarType", "bool");
87 aWriter
.BoolProperty("val", aValue
.as
<bool>());
90 using MS
= mozilla::MarkerSchema
;
91 static MS
MarkerTypeDisplay() {
92 MS schema
{MS::Location::MarkerChart
, MS::Location::MarkerTable
};
93 schema
.AddKeyLabelFormatSearchable("id", "Scalar Name",
94 MS::Format::UniqueString
,
95 MS::Searchable::Searchable
);
96 schema
.AddKeyLabelFormatSearchable("key", "Key", MS::Format::String
,
97 MS::Searchable::Searchable
);
98 schema
.AddKeyLabelFormatSearchable("scalarType", "Type",
99 MS::Format::UniqueString
,
100 MS::Searchable::Searchable
);
101 schema
.AddKeyLabelFormatSearchable("val", "Value", MS::Format::String
,
102 MS::Searchable::Searchable
);
103 schema
.SetTooltipLabel(
104 "{marker.data.id}[{marker.data.key}] {marker.data.val}");
105 schema
.SetTableLabel(
106 "{marker.name} - {marker.data.id}[{marker.data.key}]: "
107 "{marker.data.val}");
112 } // namespace geckoprofiler::markers
114 ////////////////////////////////////////////////////////////////////////
115 ////////////////////////////////////////////////////////////////////////
117 // Naming: there are two kinds of functions in this file:
119 // * Functions named internal_*: these can only be reached via an
120 // interface function (TelemetryScalar::*). If they access shared
121 // state, they require the interface function to have acquired
122 // |gTelemetryScalarMutex| to ensure thread safety.
124 // * Functions named TelemetryScalar::*. This is the external interface.
125 // Entries and exits to these functions are serialised using
126 // |gTelemetryScalarsMutex|.
128 // Avoiding races and deadlocks:
130 // All functions in the external interface (TelemetryScalar::*) are
131 // serialised using the mutex |gTelemetryScalarsMutex|. This means
132 // that the external interface is thread-safe. But it also brings
133 // a danger of deadlock if any function in the external interface can
134 // get back to that interface. That is, we will deadlock on any call
137 // TelemetryScalar::* -> .. any functions .. -> TelemetryScalar::*
139 // To reduce the danger of that happening, observe the following rules:
141 // * No function in TelemetryScalar::* may directly call, nor take the
142 // address of, any other function in TelemetryScalar::*.
144 // * No internal function internal_* may call, nor take the address
145 // of, any function in TelemetryScalar::*.
147 ////////////////////////////////////////////////////////////////////////
148 ////////////////////////////////////////////////////////////////////////
154 const uint32_t kMaximumNumberOfKeys
= 100;
155 const uint32_t kMaxEventSummaryKeys
= 500;
156 const uint32_t kMaximumKeyStringLength
= 72;
157 const uint32_t kMaximumStringValueLength
= 50;
158 // The category and scalar name maximum lengths are used by the dynamic
159 // scalar registration function and must match the constants used by
160 // the 'parse_scalars.py' script for static scalars.
161 const uint32_t kMaximumCategoryNameLength
= 40;
162 const uint32_t kMaximumScalarNameLength
= 40;
163 const uint32_t kScalarCount
=
164 static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount
);
166 const char* TEST_SCALAR_PREFIX
= "telemetry.test.";
168 // The max offset supported by gScalarStoresTable for static scalars' stores.
169 // Also the sentinel value (with store_count == 0) for just the sole "main"
171 const uint32_t kMaxStaticStoreOffset
= UINT16_MAX
;
173 enum class ScalarResult
: uint8_t {
174 // Nothing went wrong.
176 // General Scalar Errors
179 CannotRecordInProcess
,
183 OperationNotSupported
,
186 // Keyed Scalar Errors
191 // String Scalar Errors
193 // Unsigned Scalar Errors
194 UnsignedNegativeValue
,
195 UnsignedTruncatedValue
,
198 // A common identifier for both built-in and dynamic scalars.
204 // Dynamic scalar store names.
205 StaticAutoPtr
<nsTArray
<RefPtr
<nsAtom
>>> gDynamicStoreNames
;
208 * Scalar information for dynamic definitions.
210 struct DynamicScalarInfo
: BaseScalarInfo
{
211 nsCString mDynamicName
;
212 bool mDynamicExpiration
;
213 uint32_t store_count
;
214 uint32_t store_offset
;
216 DynamicScalarInfo(uint32_t aKind
, bool aRecordOnRelease
, bool aExpired
,
217 const nsACString
& aName
, bool aKeyed
,
218 const nsTArray
<nsCString
>& aStores
)
221 aRecordOnRelease
? nsITelemetry::DATASET_ALL_CHANNELS
222 : nsITelemetry::DATASET_PRERELEASE_CHANNELS
,
223 RecordedProcessType::All
, aKeyed
, 0, 0, GetCurrentProduct()),
225 mDynamicExpiration(aExpired
) {
226 store_count
= aStores
.Length();
227 if (store_count
== 0) {
229 store_offset
= kMaxStaticStoreOffset
;
231 store_offset
= kMaxStaticStoreOffset
+ 1 + gDynamicStoreNames
->Length();
232 for (const auto& storeName
: aStores
) {
233 gDynamicStoreNames
->AppendElement(NS_Atomize(storeName
));
236 gDynamicStoreNames
->Length() < UINT32_MAX
- kMaxStaticStoreOffset
- 1,
237 "Too many dynamic scalar store names. Overflow.");
241 // The following functions will read the stored text
242 // instead of looking it up in the statically generated
244 const char* name() const override
;
245 const char* expiration() const override
;
247 uint32_t storeCount() const override
;
248 uint32_t storeOffset() const override
;
251 const char* DynamicScalarInfo::name() const { return mDynamicName
.get(); }
253 const char* DynamicScalarInfo::expiration() const {
254 // Dynamic scalars can either be expired or not (boolean flag).
255 // Return an appropriate version string to leverage the scalar expiration
257 return mDynamicExpiration
? "1.0" : "never";
260 uint32_t DynamicScalarInfo::storeOffset() const { return store_offset
; }
261 uint32_t DynamicScalarInfo::storeCount() const { return store_count
; }
263 typedef nsBaseHashtableET
<nsDepCharHashKey
, ScalarKey
> CharPtrEntryType
;
264 typedef AutoHashtable
<CharPtrEntryType
> ScalarMapType
;
266 // Dynamic scalar definitions.
267 StaticAutoPtr
<nsTArray
<DynamicScalarInfo
>> gDynamicScalarInfo
;
269 const BaseScalarInfo
& internal_GetScalarInfo(const StaticMutexAutoLock
& lock
,
270 const ScalarKey
& aId
) {
272 return gScalars
[aId
.id
];
275 return (*gDynamicScalarInfo
)[aId
.id
];
278 bool IsValidEnumId(mozilla::Telemetry::ScalarID aID
) {
279 return aID
< mozilla::Telemetry::ScalarID::ScalarCount
;
282 bool internal_IsValidId(const StaticMutexAutoLock
& lock
, const ScalarKey
& aId
) {
283 // Please note that this function needs to be called with the scalar
284 // mutex being acquired: other functions might be messing with
285 // |gDynamicScalarInfo|.
287 ? (aId
.id
< gDynamicScalarInfo
->Length())
288 : IsValidEnumId(static_cast<mozilla::Telemetry::ScalarID
>(aId
.id
));
291 // Implements the methods for ScalarInfo.
292 const char* ScalarInfo::name() const {
293 return &gScalarsStringTable
[this->name_offset
];
296 const char* ScalarInfo::expiration() const {
297 return &gScalarsStringTable
[this->expiration_offset
];
301 * The base scalar object, that serves as a common ancestor for storage
306 explicit ScalarBase(const BaseScalarInfo
& aInfo
)
307 : mStoreCount(aInfo
.storeCount()),
308 mStoreOffset(aInfo
.storeOffset()),
309 mStoreHasValue(mStoreCount
),
310 mName(aInfo
.name()) {
311 mStoreHasValue
.SetLength(mStoreCount
);
312 for (auto& val
: mStoreHasValue
) {
316 virtual ~ScalarBase() = default;
318 // Convenience methods used by the C++ API.
319 virtual void SetValue(uint32_t aValue
) {
320 mozilla::Unused
<< HandleUnsupported();
322 virtual ScalarResult
SetValue(const nsAString
& aValue
) {
323 return HandleUnsupported();
325 virtual void SetValue(bool aValue
) { mozilla::Unused
<< HandleUnsupported(); }
326 virtual void AddValue(uint32_t aValue
) {
327 mozilla::Unused
<< HandleUnsupported();
330 // GetValue is used to get the value of the scalar when persisting it to JS.
331 virtual nsresult
GetValue(const nsACString
& aStoreName
, bool aClearStore
,
332 nsCOMPtr
<nsIVariant
>& aResult
) = 0;
334 // To measure the memory stats.
335 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const;
336 virtual size_t SizeOfIncludingThis(
337 mozilla::MallocSizeOf aMallocSizeOf
) const = 0;
340 bool HasValueInStore(size_t aStoreIndex
) const;
341 void ClearValueInStore(size_t aStoreIndex
);
342 void SetValueInStores();
343 nsresult
StoreIndex(const nsACString
& aStoreName
, size_t* aStoreIndex
) const;
346 ScalarResult
HandleUnsupported() const;
348 const uint32_t mStoreCount
;
349 const uint32_t mStoreOffset
;
350 nsTArray
<bool> mStoreHasValue
;
353 const nsCString mName
;
356 ScalarResult
ScalarBase::HandleUnsupported() const {
357 MOZ_ASSERT(false, "This operation is not support for this scalar type.");
358 return ScalarResult::OperationNotSupported
;
361 bool ScalarBase::HasValueInStore(size_t aStoreIndex
) const {
362 MOZ_ASSERT(aStoreIndex
< mStoreHasValue
.Length(),
363 "Invalid scalar store index.");
364 return mStoreHasValue
[aStoreIndex
];
367 void ScalarBase::ClearValueInStore(size_t aStoreIndex
) {
368 MOZ_ASSERT(aStoreIndex
< mStoreHasValue
.Length(),
369 "Invalid scalar store index to clear.");
370 mStoreHasValue
[aStoreIndex
] = false;
373 void ScalarBase::SetValueInStores() {
374 for (auto& val
: mStoreHasValue
) {
379 nsresult
ScalarBase::StoreIndex(const nsACString
& aStoreName
,
380 size_t* aStoreIndex
) const {
381 if (mStoreCount
== 1 && mStoreOffset
== kMaxStaticStoreOffset
) {
382 // This Scalar is only in the "main" store.
383 if (aStoreName
.EqualsLiteral("main")) {
387 return NS_ERROR_NO_CONTENT
;
390 // Multiple stores. Linear scan to find one that matches aStoreName.
391 // Dynamic Scalars start at kMaxStaticStoreOffset + 1
392 if (mStoreOffset
> kMaxStaticStoreOffset
) {
393 auto dynamicOffset
= mStoreOffset
- kMaxStaticStoreOffset
- 1;
394 for (uint32_t i
= 0; i
< mStoreCount
; ++i
) {
395 auto scalarStore
= (*gDynamicStoreNames
)[dynamicOffset
+ i
];
396 if (nsAtomCString(scalarStore
).Equals(aStoreName
)) {
401 return NS_ERROR_NO_CONTENT
;
404 // Static Scalars are similar.
405 for (uint32_t i
= 0; i
< mStoreCount
; ++i
) {
406 uint32_t stringIndex
= gScalarStoresTable
[mStoreOffset
+ i
];
407 if (aStoreName
.EqualsASCII(&gScalarsStringTable
[stringIndex
])) {
412 return NS_ERROR_NO_CONTENT
;
415 size_t ScalarBase::SizeOfExcludingThis(
416 mozilla::MallocSizeOf aMallocSizeOf
) const {
417 return mStoreHasValue
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
421 * The implementation for the unsigned int scalar type.
423 class ScalarUnsigned
: public ScalarBase
{
425 using ScalarBase::SetValue
;
427 explicit ScalarUnsigned(const BaseScalarInfo
& aInfo
)
428 : ScalarBase(aInfo
), mStorage(aInfo
.storeCount()) {
429 mStorage
.SetLength(aInfo
.storeCount());
430 for (auto& val
: mStorage
) {
435 ~ScalarUnsigned() override
= default;
437 void SetValue(uint32_t aValue
) final
;
438 void AddValue(uint32_t aValue
) final
;
439 nsresult
GetValue(const nsACString
& aStoreName
, bool aClearStore
,
440 nsCOMPtr
<nsIVariant
>& aResult
) final
;
441 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const final
;
444 nsTArray
<uint32_t> mStorage
;
447 ScalarUnsigned(const ScalarUnsigned
& aOther
) = delete;
448 void operator=(const ScalarUnsigned
& aOther
) = delete;
451 void ScalarUnsigned::SetValue(uint32_t aValue
) {
452 for (auto& val
: mStorage
) {
458 void ScalarUnsigned::AddValue(uint32_t aValue
) {
459 for (auto& val
: mStorage
) {
465 nsresult
ScalarUnsigned::GetValue(const nsACString
& aStoreName
,
467 nsCOMPtr
<nsIVariant
>& aResult
) {
468 size_t storeIndex
= 0;
469 nsresult rv
= StoreIndex(aStoreName
, &storeIndex
);
473 if (!HasValueInStore(storeIndex
)) {
474 return NS_ERROR_NO_CONTENT
;
476 nsCOMPtr
<nsIWritableVariant
> outVar(new nsVariant());
477 rv
= outVar
->SetAsUint32(mStorage
[storeIndex
]);
481 aResult
= std::move(outVar
);
483 mStorage
[storeIndex
] = 0;
484 ClearValueInStore(storeIndex
);
489 size_t ScalarUnsigned::SizeOfIncludingThis(
490 mozilla::MallocSizeOf aMallocSizeOf
) const {
491 size_t n
= aMallocSizeOf(this);
492 n
+= ScalarBase::SizeOfExcludingThis(aMallocSizeOf
);
493 n
+= mStorage
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
498 * The implementation for the string scalar type.
500 class ScalarString
: public ScalarBase
{
502 using ScalarBase::SetValue
;
504 explicit ScalarString(const BaseScalarInfo
& aInfo
)
505 : ScalarBase(aInfo
), mStorage(aInfo
.storeCount()) {
506 mStorage
.SetLength(aInfo
.storeCount());
509 ~ScalarString() override
= default;
511 ScalarResult
SetValue(const nsAString
& aValue
) final
;
512 nsresult
GetValue(const nsACString
& aStoreName
, bool aClearStore
,
513 nsCOMPtr
<nsIVariant
>& aResult
) final
;
514 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const final
;
517 nsTArray
<nsString
> mStorage
;
520 ScalarString(const ScalarString
& aOther
) = delete;
521 void operator=(const ScalarString
& aOther
) = delete;
524 ScalarResult
ScalarString::SetValue(const nsAString
& aValue
) {
525 auto str
= Substring(aValue
, 0, kMaximumStringValueLength
);
526 for (auto& val
: mStorage
) {
530 if (aValue
.Length() > kMaximumStringValueLength
) {
531 return ScalarResult::StringTooLong
;
533 return ScalarResult::Ok
;
536 nsresult
ScalarString::GetValue(const nsACString
& aStoreName
, bool aClearStore
,
537 nsCOMPtr
<nsIVariant
>& aResult
) {
538 nsCOMPtr
<nsIWritableVariant
> outVar(new nsVariant());
539 size_t storeIndex
= 0;
540 nsresult rv
= StoreIndex(aStoreName
, &storeIndex
);
544 if (!HasValueInStore(storeIndex
)) {
545 return NS_ERROR_NO_CONTENT
;
547 rv
= outVar
->SetAsAString(mStorage
[storeIndex
]);
552 ClearValueInStore(storeIndex
);
554 aResult
= std::move(outVar
);
558 size_t ScalarString::SizeOfIncludingThis(
559 mozilla::MallocSizeOf aMallocSizeOf
) const {
560 size_t n
= aMallocSizeOf(this);
561 n
+= ScalarBase::SizeOfExcludingThis(aMallocSizeOf
);
562 n
+= mStorage
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
563 for (auto& val
: mStorage
) {
564 n
+= val
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
570 * The implementation for the boolean scalar type.
572 class ScalarBoolean
: public ScalarBase
{
574 using ScalarBase::SetValue
;
576 explicit ScalarBoolean(const BaseScalarInfo
& aInfo
)
577 : ScalarBase(aInfo
), mStorage(aInfo
.storeCount()) {
578 mStorage
.SetLength(aInfo
.storeCount());
579 for (auto& val
: mStorage
) {
584 ~ScalarBoolean() override
= default;
586 void SetValue(bool aValue
) final
;
587 nsresult
GetValue(const nsACString
& aStoreName
, bool aClearStore
,
588 nsCOMPtr
<nsIVariant
>& aResult
) final
;
589 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const final
;
592 nsTArray
<bool> mStorage
;
595 ScalarBoolean(const ScalarBoolean
& aOther
) = delete;
596 void operator=(const ScalarBoolean
& aOther
) = delete;
599 void ScalarBoolean::SetValue(bool aValue
) {
600 for (auto& val
: mStorage
) {
606 nsresult
ScalarBoolean::GetValue(const nsACString
& aStoreName
, bool aClearStore
,
607 nsCOMPtr
<nsIVariant
>& aResult
) {
608 nsCOMPtr
<nsIWritableVariant
> outVar(new nsVariant());
609 size_t storeIndex
= 0;
610 nsresult rv
= StoreIndex(aStoreName
, &storeIndex
);
614 if (!HasValueInStore(storeIndex
)) {
615 return NS_ERROR_NO_CONTENT
;
618 ClearValueInStore(storeIndex
);
620 rv
= outVar
->SetAsBool(mStorage
[storeIndex
]);
624 aResult
= std::move(outVar
);
628 size_t ScalarBoolean::SizeOfIncludingThis(
629 mozilla::MallocSizeOf aMallocSizeOf
) const {
630 size_t n
= aMallocSizeOf(this);
631 n
+= ScalarBase::SizeOfExcludingThis(aMallocSizeOf
);
632 n
+= mStorage
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
637 * Allocate a scalar class given the scalar info.
639 * @param aInfo The informations for the scalar coming from the definition file.
640 * @return nullptr if the scalar type is unknown, otherwise a valid pointer to
643 ScalarBase
* internal_ScalarAllocate(const BaseScalarInfo
& aInfo
) {
644 ScalarBase
* scalar
= nullptr;
645 switch (aInfo
.kind
) {
646 case nsITelemetry::SCALAR_TYPE_COUNT
:
647 scalar
= new ScalarUnsigned(aInfo
);
649 case nsITelemetry::SCALAR_TYPE_STRING
:
650 scalar
= new ScalarString(aInfo
);
652 case nsITelemetry::SCALAR_TYPE_BOOLEAN
:
653 scalar
= new ScalarBoolean(aInfo
);
656 MOZ_ASSERT(false, "Invalid scalar type");
662 * The implementation for the keyed scalar type.
666 typedef std::pair
<nsCString
, nsCOMPtr
<nsIVariant
>> KeyValuePair
;
668 // We store the name instead of a reference to the BaseScalarInfo because
669 // the BaseScalarInfo can move if it's from a dynamic scalar.
670 explicit KeyedScalar(const BaseScalarInfo
& info
)
671 : mScalarName(info
.name()),
672 mScalarKeyCount(info
.key_count
),
673 mScalarKeyOffset(info
.key_offset
),
674 mMaximumNumberOfKeys(kMaximumNumberOfKeys
) {};
675 ~KeyedScalar() = default;
677 // Convenience methods used by the C++ API.
678 void SetValue(const StaticMutexAutoLock
& locker
, const nsAString
& aKey
,
680 void SetValue(const StaticMutexAutoLock
& locker
, const nsAString
& aKey
,
682 void AddValue(const StaticMutexAutoLock
& locker
, const nsAString
& aKey
,
685 // GetValue is used to get the key-value pairs stored in the keyed scalar
686 // when persisting it to JS.
687 nsresult
GetValue(const nsACString
& aStoreName
, bool aClearStorage
,
688 nsTArray
<KeyValuePair
>& aValues
);
690 // To measure the memory stats.
691 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
);
693 // To permit more keys than normal.
694 void SetMaximumNumberOfKeys(uint32_t aMaximumNumberOfKeys
) {
695 mMaximumNumberOfKeys
= aMaximumNumberOfKeys
;
699 typedef nsClassHashtable
<nsCStringHashKey
, ScalarBase
> ScalarKeysMapType
;
701 const nsCString mScalarName
;
702 ScalarKeysMapType mScalarKeys
;
703 uint32_t mScalarKeyCount
;
704 uint32_t mScalarKeyOffset
;
705 uint32_t mMaximumNumberOfKeys
;
707 ScalarResult
GetScalarForKey(const StaticMutexAutoLock
& locker
,
708 const nsAString
& aKey
, ScalarBase
** aRet
);
710 bool AllowsKey(const nsAString
& aKey
) const;
713 void KeyedScalar::SetValue(const StaticMutexAutoLock
& locker
,
714 const nsAString
& aKey
, uint32_t aValue
) {
715 ScalarBase
* scalar
= nullptr;
716 ScalarResult sr
= GetScalarForKey(locker
, aKey
, &scalar
);
718 if (sr
!= ScalarResult::Ok
) {
719 // Bug 1451813 - We now report which scalars exceed the key limit in
720 // telemetry.keyed_scalars_exceed_limit.
724 return scalar
->SetValue(aValue
);
727 void KeyedScalar::SetValue(const StaticMutexAutoLock
& locker
,
728 const nsAString
& aKey
, bool aValue
) {
729 ScalarBase
* scalar
= nullptr;
730 ScalarResult sr
= GetScalarForKey(locker
, aKey
, &scalar
);
732 if (sr
!= ScalarResult::Ok
) {
733 // Bug 1451813 - We now report which scalars exceed the key limit in
734 // telemetry.keyed_scalars_exceed_limit.
738 return scalar
->SetValue(aValue
);
741 void KeyedScalar::AddValue(const StaticMutexAutoLock
& locker
,
742 const nsAString
& aKey
, uint32_t aValue
) {
743 ScalarBase
* scalar
= nullptr;
744 ScalarResult sr
= GetScalarForKey(locker
, aKey
, &scalar
);
746 if (sr
!= ScalarResult::Ok
) {
747 // Bug 1451813 - We now report which scalars exceed the key limit in
748 // telemetry.keyed_scalars_exceed_limit.
752 return scalar
->AddValue(aValue
);
756 * Get a key-value array with the values for the Keyed Scalar.
757 * @param aValue The array that will hold the key-value pairs.
758 * @return {nsresult} NS_OK or an error value as reported by the
759 * the specific scalar objects implementations (e.g.
762 nsresult
KeyedScalar::GetValue(const nsACString
& aStoreName
, bool aClearStorage
,
763 nsTArray
<KeyValuePair
>& aValues
) {
764 for (const auto& entry
: mScalarKeys
) {
765 ScalarBase
* scalar
= entry
.GetWeak();
767 // Get the scalar value.
768 nsCOMPtr
<nsIVariant
> scalarValue
;
769 nsresult rv
= scalar
->GetValue(aStoreName
, aClearStorage
, scalarValue
);
770 if (rv
== NS_ERROR_NO_CONTENT
) {
771 // No value for this store.
778 // Append it to value list.
779 aValues
.AppendElement(
780 std::make_pair(nsCString(entry
.GetKey()), scalarValue
));
786 // Forward declaration
787 nsresult
internal_GetKeyedScalarByEnum(const StaticMutexAutoLock
& lock
,
788 const ScalarKey
& aId
,
789 ProcessID aProcessStorage
,
792 // Forward declaration
793 nsresult
internal_GetEnumByScalarName(const StaticMutexAutoLock
& lock
,
794 const nsACString
& aName
, ScalarKey
* aId
);
797 * Get the scalar for the referenced key.
798 * If there's no such key, instantiate a new Scalar object with the
799 * same type of the Keyed scalar and create the key.
801 ScalarResult
KeyedScalar::GetScalarForKey(const StaticMutexAutoLock
& locker
,
802 const nsAString
& aKey
,
804 if (aKey
.IsEmpty()) {
805 return ScalarResult::KeyIsEmpty
;
808 if (!AllowsKey(aKey
)) {
809 KeyedScalar
* scalarUnknown
= nullptr;
810 ScalarKey scalarUnknownUniqueId
{
811 static_cast<uint32_t>(
812 mozilla::Telemetry::ScalarID::TELEMETRY_KEYED_SCALARS_UNKNOWN_KEYS
),
814 ProcessID process
= ProcessID::Parent
;
815 nsresult rv
= internal_GetKeyedScalarByEnum(locker
, scalarUnknownUniqueId
,
816 process
, &scalarUnknown
);
818 return ScalarResult::TooManyKeys
;
820 scalarUnknown
->AddValue(locker
, NS_ConvertUTF8toUTF16(mScalarName
), 1);
822 return ScalarResult::KeyNotAllowed
;
825 if (aKey
.Length() > kMaximumKeyStringLength
) {
826 return ScalarResult::KeyTooLong
;
829 NS_ConvertUTF16toUTF8
utf8Key(aKey
);
831 ScalarBase
* scalar
= nullptr;
832 if (mScalarKeys
.Get(utf8Key
, &scalar
)) {
834 return ScalarResult::Ok
;
838 nsresult rv
= internal_GetEnumByScalarName(locker
, mScalarName
, &uniqueId
);
840 return (rv
== NS_ERROR_FAILURE
) ? ScalarResult::NotInitialized
841 : ScalarResult::UnknownScalar
;
844 const BaseScalarInfo
& info
= internal_GetScalarInfo(locker
, uniqueId
);
845 if (mScalarKeys
.Count() >= mMaximumNumberOfKeys
) {
846 if (aKey
.EqualsLiteral("telemetry.keyed_scalars_exceed_limit")) {
847 return ScalarResult::TooManyKeys
;
850 KeyedScalar
* scalarExceed
= nullptr;
853 static_cast<uint32_t>(
854 mozilla::Telemetry::ScalarID::TELEMETRY_KEYED_SCALARS_EXCEED_LIMIT
),
857 ProcessID process
= ProcessID::Parent
;
859 internal_GetKeyedScalarByEnum(locker
, uniqueId
, process
, &scalarExceed
);
862 return ScalarResult::TooManyKeys
;
865 scalarExceed
->AddValue(locker
, NS_ConvertUTF8toUTF16(info
.name()), 1);
867 return ScalarResult::TooManyKeys
;
870 scalar
= internal_ScalarAllocate(info
);
872 return ScalarResult::InvalidType
;
875 mScalarKeys
.InsertOrUpdate(utf8Key
, UniquePtr
<ScalarBase
>(scalar
));
878 return ScalarResult::Ok
;
881 size_t KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) {
882 size_t n
= aMallocSizeOf(this);
883 for (const auto& scalar
: mScalarKeys
.Values()) {
884 n
+= scalar
->SizeOfIncludingThis(aMallocSizeOf
);
889 bool KeyedScalar::AllowsKey(const nsAString
& aKey
) const {
890 // If we didn't specify a list of allowed keys, just return true.
891 if (mScalarKeyCount
== 0) {
895 for (uint32_t i
= 0; i
< mScalarKeyCount
; ++i
) {
896 uint32_t stringIndex
= gScalarKeysTable
[mScalarKeyOffset
+ i
];
897 if (aKey
.EqualsASCII(&gScalarsStringTable
[stringIndex
])) {
905 typedef nsUint32HashKey ScalarIDHashKey
;
906 typedef nsUint32HashKey ProcessIDHashKey
;
907 typedef nsClassHashtable
<ScalarIDHashKey
, ScalarBase
> ScalarStorageMapType
;
908 typedef nsClassHashtable
<ScalarIDHashKey
, KeyedScalar
>
909 KeyedScalarStorageMapType
;
910 typedef nsClassHashtable
<ProcessIDHashKey
, ScalarStorageMapType
>
911 ProcessesScalarsMapType
;
912 typedef nsClassHashtable
<ProcessIDHashKey
, KeyedScalarStorageMapType
>
913 ProcessesKeyedScalarsMapType
;
915 typedef std::tuple
<const char*, nsCOMPtr
<nsIVariant
>, uint32_t> ScalarDataTuple
;
916 typedef nsTArray
<ScalarDataTuple
> ScalarTupleArray
;
917 typedef nsTHashMap
<ProcessIDHashKey
, ScalarTupleArray
> ScalarSnapshotTable
;
919 typedef std::tuple
<const char*, nsTArray
<KeyedScalar::KeyValuePair
>, uint32_t>
920 KeyedScalarDataTuple
;
921 typedef nsTArray
<KeyedScalarDataTuple
> KeyedScalarTupleArray
;
922 typedef nsTHashMap
<ProcessIDHashKey
, KeyedScalarTupleArray
>
923 KeyedScalarSnapshotTable
;
927 ////////////////////////////////////////////////////////////////////////
928 ////////////////////////////////////////////////////////////////////////
930 // PRIVATE STATE, SHARED BY ALL THREADS
934 // Set to true once this global state has been initialized.
935 bool gTelemetryScalarInitDone
= false;
937 bool gTelemetryScalarCanRecordBase
;
938 bool gTelemetryScalarCanRecordExtended
;
940 // The Name -> ID cache map.
941 MOZ_RUNINIT ScalarMapType
gScalarNameIDMap(kScalarCount
);
943 // The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a
944 // nsClassHashtable, it owns the scalar instances and takes care of deallocating
945 // them when they are removed from the map.
946 MOZ_RUNINIT ProcessesScalarsMapType gScalarStorageMap
;
947 // As above, for the keyed scalars.
948 MOZ_RUNINIT ProcessesKeyedScalarsMapType gKeyedScalarStorageMap
;
949 // Provide separate storage for "dynamic builtin" plain and keyed scalars,
950 // needed to support "build faster" in local developer builds.
951 MOZ_RUNINIT ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap
;
952 MOZ_RUNINIT ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap
;
955 ////////////////////////////////////////////////////////////////////////
956 ////////////////////////////////////////////////////////////////////////
958 // PRIVATE: helpers for the external interface
962 bool internal_CanRecordBase(const StaticMutexAutoLock
& lock
) {
963 return gTelemetryScalarCanRecordBase
;
966 bool internal_CanRecordExtended(const StaticMutexAutoLock
& lock
) {
967 return gTelemetryScalarCanRecordExtended
;
971 * Check if the given scalar is a keyed scalar.
973 * @param lock Instance of a lock locking gTelemetryHistogramMutex
974 * @param aId The scalar identifier.
975 * @return true if aId refers to a keyed scalar, false otherwise.
977 bool internal_IsKeyedScalar(const StaticMutexAutoLock
& lock
,
978 const ScalarKey
& aId
) {
979 return internal_GetScalarInfo(lock
, aId
).keyed
;
983 * Check if we're allowed to record the given scalar in the current
986 * @param lock Instance of a lock locking gTelemetryHistogramMutex
987 * @param aId The scalar identifier.
988 * @return true if the scalar is allowed to be recorded in the current process,
991 bool internal_CanRecordProcess(const StaticMutexAutoLock
& lock
,
992 const ScalarKey
& aId
) {
993 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
994 return CanRecordInProcess(info
.record_in_processes
, XRE_GetProcessType());
997 bool internal_CanRecordProduct(const StaticMutexAutoLock
& lock
,
998 const ScalarKey
& aId
) {
999 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
1000 return CanRecordProduct(info
.products
);
1003 bool internal_CanRecordForScalarID(const StaticMutexAutoLock
& lock
,
1004 const ScalarKey
& aId
) {
1005 // Get the scalar info from the id.
1006 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
1008 // Can we record at all?
1009 bool canRecordBase
= internal_CanRecordBase(lock
);
1010 if (!canRecordBase
) {
1014 bool canRecordDataset
= CanRecordDataset(info
.dataset
, canRecordBase
,
1015 internal_CanRecordExtended(lock
));
1016 if (!canRecordDataset
) {
1024 * Check if we are allowed to record the provided scalar.
1026 * @param lock Instance of a lock locking gTelemetryHistogramMutex
1027 * @param aId The scalar identifier.
1028 * @param aKeyed Are we attempting to write a keyed scalar?
1029 * @param aForce Whether to allow recording even if the probe is not allowed on
1030 * the current process.
1031 * @return ScalarResult::Ok if we can record, an error code otherwise.
1033 ScalarResult
internal_CanRecordScalar(const StaticMutexAutoLock
& lock
,
1034 const ScalarKey
& aId
, bool aKeyed
,
1035 bool aForce
= false) {
1036 // Make sure that we have a keyed scalar if we are trying to change one.
1037 if (internal_IsKeyedScalar(lock
, aId
) != aKeyed
) {
1038 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
1039 PROFILER_MARKER_TEXT(
1040 "ScalarError", TELEMETRY
, mozilla::MarkerStack::Capture(),
1041 nsPrintfCString("KeyedTypeMismatch for %s", info
.name()));
1042 return ScalarResult::KeyedTypeMismatch
;
1045 // Are we allowed to record this scalar based on the current Telemetry
1047 if (!internal_CanRecordForScalarID(lock
, aId
)) {
1048 return ScalarResult::CannotRecordDataset
;
1051 // Can we record in this process?
1052 if (!aForce
&& !internal_CanRecordProcess(lock
, aId
)) {
1053 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
1054 PROFILER_MARKER_TEXT(
1055 "ScalarError", TELEMETRY
, mozilla::MarkerStack::Capture(),
1056 nsPrintfCString("CannotRecordInProcess for %s", info
.name()));
1057 return ScalarResult::CannotRecordInProcess
;
1060 // Can we record on this product?
1061 if (!internal_CanRecordProduct(lock
, aId
)) {
1062 return ScalarResult::CannotRecordDataset
;
1065 return ScalarResult::Ok
;
1069 * Get the scalar enum id from the scalar name.
1071 * @param lock Instance of a lock locking gTelemetryHistogramMutex
1072 * @param aName The scalar name.
1073 * @param aId The output variable to contain the enum.
1075 * NS_ERROR_FAILURE if this was called before init is completed.
1076 * NS_ERROR_INVALID_ARG if the name can't be found in the scalar definitions.
1077 * NS_OK if the scalar was found and aId contains a valid enum id.
1079 nsresult
internal_GetEnumByScalarName(const StaticMutexAutoLock
& lock
,
1080 const nsACString
& aName
, ScalarKey
* aId
) {
1081 if (!gTelemetryScalarInitDone
) {
1082 return NS_ERROR_FAILURE
;
1085 CharPtrEntryType
* entry
=
1086 gScalarNameIDMap
.GetEntry(PromiseFlatCString(aName
).get());
1088 return NS_ERROR_INVALID_ARG
;
1090 *aId
= entry
->GetData();
1095 * Get a scalar object by its enum id. This implicitly allocates the scalar
1096 * object in the storage if it wasn't previously allocated.
1098 * @param lock Instance of a lock locking gTelemetryHistogramMutex
1099 * @param aId The scalar identifier.
1100 * @param aProcessStorage This drives the selection of the map to use to store
1101 * the scalar data coming from child processes. This is only meaningful
1102 * when this function is called in parent process. If that's the case,
1103 * if this is not |GeckoProcessType_Default|, the process id is used to
1104 * allocate and store the scalars.
1105 * @param aRes The output variable that stores scalar object.
1107 * NS_ERROR_INVALID_ARG if the scalar id is unknown.
1108 * NS_ERROR_NOT_AVAILABLE if the scalar is expired.
1109 * NS_OK if the scalar was found. If that's the case, aResult contains a
1110 * valid pointer to a scalar type.
1112 nsresult
internal_GetScalarByEnum(const StaticMutexAutoLock
& lock
,
1113 const ScalarKey
& aId
,
1114 ProcessID aProcessStorage
,
1115 ScalarBase
** aRet
) {
1116 if (!internal_IsValidId(lock
, aId
)) {
1117 MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
1118 return NS_ERROR_INVALID_ARG
;
1121 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
1123 ScalarBase
* scalar
= nullptr;
1124 // Initialize the scalar storage to the parent storage. This will get
1125 // set to the child storage if needed.
1126 uint32_t storageId
= static_cast<uint32_t>(aProcessStorage
);
1128 // Put dynamic-builtin scalars (used to support "build faster") in a
1129 // separate storage.
1130 ProcessesScalarsMapType
& processStorage
=
1131 aId
.dynamic
? gDynamicBuiltinScalarStorageMap
: gScalarStorageMap
;
1133 // Get the process-specific storage or create one if it's not
1135 ScalarStorageMapType
* const scalarStorage
=
1136 processStorage
.GetOrInsertNew(storageId
);
1138 // Check if the scalar is already allocated in the parent or in the child
1140 if (scalarStorage
->Get(aId
.id
, &scalar
)) {
1141 // Dynamic scalars can expire at any time during the session (e.g. an
1142 // add-on was updated). Check if it expired.
1144 const DynamicScalarInfo
& dynInfo
=
1145 static_cast<const DynamicScalarInfo
&>(info
);
1146 if (dynInfo
.mDynamicExpiration
) {
1147 // The Dynamic scalar is expired.
1148 PROFILER_MARKER_TEXT(
1149 "ScalarError", TELEMETRY
, mozilla::MarkerStack::Capture(),
1150 nsPrintfCString("ExpiredDynamicScalar: %s", info
.name()));
1151 return NS_ERROR_NOT_AVAILABLE
;
1154 // This was not a dynamic scalar or was not expired.
1159 // The scalar storage wasn't already allocated. Check if the scalar is expired
1160 // and then allocate the storage, if needed.
1161 if (IsExpiredVersion(info
.expiration())) {
1162 PROFILER_MARKER_TEXT("ScalarError", TELEMETRY
,
1163 mozilla::MarkerStack::Capture(),
1164 nsPrintfCString("ExpiredScalar: %s", info
.name()));
1165 return NS_ERROR_NOT_AVAILABLE
;
1168 scalar
= internal_ScalarAllocate(info
);
1170 return NS_ERROR_INVALID_ARG
;
1173 scalarStorage
->InsertOrUpdate(aId
.id
, UniquePtr
<ScalarBase
>(scalar
));
1178 // For C++ consummers.
1179 static void internal_profilerMarker_impl(
1180 const StaticMutexAutoLock
& lock
, ScalarActionType aType
,
1181 const ScalarVariant
& aValue
, ScalarKey uniqueId
,
1182 const nsAString
& aKey
= EmptyString()) {
1183 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, uniqueId
);
1185 aType
== ScalarActionType::eSet
1186 ? mozilla::ProfilerString8View("Scalar::Set")
1187 : mozilla::ProfilerString8View("Scalar::Add"),
1188 TELEMETRY
, {}, ScalarMarker
,
1189 mozilla::ProfilerString8View::WrapNullTerminatedString(info
.name()),
1190 info
.kind
, NS_ConvertUTF16toUTF8(aKey
), aValue
);
1193 // For child process data coming from IPCs
1194 static void internal_profilerMarker_impl(
1195 const StaticMutexAutoLock
& lock
, const ScalarAction
& aAction
,
1196 const nsCString
& aKey
= EmptyCString()) {
1197 ScalarKey uniqueId
{aAction
.mId
, aAction
.mDynamic
};
1198 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, uniqueId
);
1201 aAction
.mActionType
== ScalarActionType::eSet
1202 ? mozilla::ProfilerString8View("ChildScalar::Set")
1203 : mozilla::ProfilerString8View("ChildScalar::Add"),
1204 TELEMETRY
, {}, ScalarMarker
,
1205 mozilla::ProfilerString8View::WrapNullTerminatedString(info
.name()),
1206 info
.kind
, aKey
, *aAction
.mData
);
1209 #define internal_profilerMarker(...) \
1211 if (profiler_thread_is_being_profiled_for_markers()) { \
1212 internal_profilerMarker_impl(__VA_ARGS__); \
1218 ////////////////////////////////////////////////////////////////////////
1219 ////////////////////////////////////////////////////////////////////////
1221 // PRIVATE: thread-unsafe helpers for the keyed scalars
1226 * Get a keyed scalar object by its enum id. This implicitly allocates the keyed
1227 * scalar object in the storage if it wasn't previously allocated.
1229 * @param lock Instance of a lock locking gTelemetryHistogramMutex
1230 * @param aId The scalar identifier.
1231 * @param aProcessStorage This drives the selection of the map to use to store
1232 * the scalar data coming from child processes. This is only meaningful
1233 * when this function is called in parent process. If that's the case,
1234 * if this is not |GeckoProcessType_Default|, the process id is used to
1235 * allocate and store the scalars.
1236 * @param aRet The output variable that stores scalar object.
1238 * NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed
1240 * NS_ERROR_NOT_AVAILABLE if the scalar is expired.
1241 * NS_OK if the scalar was found. If that's the case, aResult contains a
1242 * valid pointer to a scalar type.
1244 nsresult
internal_GetKeyedScalarByEnum(const StaticMutexAutoLock
& lock
,
1245 const ScalarKey
& aId
,
1246 ProcessID aProcessStorage
,
1247 KeyedScalar
** aRet
) {
1248 if (!internal_IsValidId(lock
, aId
)) {
1249 MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
1250 return NS_ERROR_INVALID_ARG
;
1253 const BaseScalarInfo
& info
= internal_GetScalarInfo(lock
, aId
);
1255 KeyedScalar
* scalar
= nullptr;
1256 // Initialize the scalar storage to the parent storage. This will get
1257 // set to the child storage if needed.
1258 uint32_t storageId
= static_cast<uint32_t>(aProcessStorage
);
1260 // Put dynamic-builtin scalars (used to support "build faster") in a
1261 // separate storage.
1262 ProcessesKeyedScalarsMapType
& processStorage
=
1263 aId
.dynamic
? gDynamicBuiltinKeyedScalarStorageMap
1264 : gKeyedScalarStorageMap
;
1266 // Get the process-specific storage or create one if it's not
1268 KeyedScalarStorageMapType
* const scalarStorage
=
1269 processStorage
.GetOrInsertNew(storageId
);
1271 if (scalarStorage
->Get(aId
.id
, &scalar
)) {
1276 if (IsExpiredVersion(info
.expiration())) {
1277 return NS_ERROR_NOT_AVAILABLE
;
1280 // We don't currently support keyed string scalars. Disable them.
1281 if (info
.kind
== nsITelemetry::SCALAR_TYPE_STRING
) {
1282 MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
1283 return NS_ERROR_INVALID_ARG
;
1286 scalar
= new KeyedScalar(info
);
1288 scalarStorage
->InsertOrUpdate(aId
.id
, UniquePtr
<KeyedScalar
>(scalar
));
1294 * Helper function to convert an array of |DynamicScalarInfo|
1295 * to |DynamicScalarDefinition| used by the IPC calls.
1297 void internal_DynamicScalarToIPC(
1298 const StaticMutexAutoLock
& lock
,
1299 const nsTArray
<DynamicScalarInfo
>& aDynamicScalarInfos
,
1300 nsTArray
<DynamicScalarDefinition
>& aIPCDefs
) {
1301 for (auto& info
: aDynamicScalarInfos
) {
1302 DynamicScalarDefinition stubDefinition
;
1303 stubDefinition
.type
= info
.kind
;
1304 stubDefinition
.dataset
= info
.dataset
;
1305 stubDefinition
.expired
= info
.mDynamicExpiration
;
1306 stubDefinition
.keyed
= info
.keyed
;
1307 stubDefinition
.name
= info
.mDynamicName
;
1308 aIPCDefs
.AppendElement(stubDefinition
);
1313 * Broadcasts the dynamic scalar definitions to all the other
1314 * content processes.
1316 void internal_BroadcastDefinitions(
1317 const nsTArray
<DynamicScalarDefinition
>& scalarDefs
) {
1318 nsTArray
<mozilla::dom::ContentParent
*> parents
;
1319 mozilla::dom::ContentParent::GetAll(parents
);
1320 if (!parents
.Length()) {
1324 // Broadcast the definitions to the other content processes.
1325 for (auto parent
: parents
) {
1326 mozilla::Unused
<< parent
->SendAddDynamicScalars(scalarDefs
);
1330 void internal_RegisterScalars(const StaticMutexAutoLock
& lock
,
1331 const nsTArray
<DynamicScalarInfo
>& scalarInfos
) {
1332 // Register the new scalars.
1333 if (!gDynamicScalarInfo
) {
1334 gDynamicScalarInfo
= new nsTArray
<DynamicScalarInfo
>();
1336 if (!gDynamicStoreNames
) {
1337 gDynamicStoreNames
= new nsTArray
<RefPtr
<nsAtom
>>();
1340 for (auto& scalarInfo
: scalarInfos
) {
1341 // Allow expiring scalars that were already registered.
1342 CharPtrEntryType
* existingKey
=
1343 gScalarNameIDMap
.GetEntry(scalarInfo
.name());
1348 gDynamicScalarInfo
->AppendElement(scalarInfo
);
1349 uint32_t scalarId
= gDynamicScalarInfo
->Length() - 1;
1350 CharPtrEntryType
* entry
= gScalarNameIDMap
.PutEntry(scalarInfo
.name());
1351 entry
->SetData(ScalarKey
{scalarId
, true});
1356 * Creates a snapshot of the desired scalar storage.
1357 * @param {aLock} The proof of lock to access scalar data.
1358 * @param {aScalarsToReflect} The table that will contain the snapshot.
1359 * @param {aDataset} The dataset we're asking the snapshot for.
1360 * @param {aProcessStorage} The scalar storage to take a snapshot of.
1361 * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin
1363 * @return NS_OK or the error code describing the failure reason.
1365 nsresult
internal_ScalarSnapshotter(const StaticMutexAutoLock
& aLock
,
1366 ScalarSnapshotTable
& aScalarsToReflect
,
1367 unsigned int aDataset
,
1368 ProcessesScalarsMapType
& aProcessStorage
,
1369 bool aIsBuiltinDynamic
, bool aClearScalars
,
1370 const nsACString
& aStoreName
) {
1371 // Iterate the scalars in aProcessStorage. The storage may contain empty or
1372 // yet to be initialized scalars from all the supported processes.
1373 for (const auto& entry
: aProcessStorage
) {
1374 ScalarStorageMapType
* scalarStorage
= entry
.GetWeak();
1375 ScalarTupleArray
& processScalars
=
1376 aScalarsToReflect
.LookupOrInsert(entry
.GetKey());
1378 // Are we in the "Dynamic" process?
1379 bool isDynamicProcess
=
1380 ProcessID::Dynamic
== static_cast<ProcessID
>(entry
.GetKey());
1382 // Iterate each available child storage.
1383 for (const auto& childEntry
: *scalarStorage
) {
1384 ScalarBase
* scalar
= childEntry
.GetWeak();
1386 // Get the informations for this scalar.
1387 const BaseScalarInfo
& info
= internal_GetScalarInfo(
1388 aLock
, ScalarKey
{childEntry
.GetKey(),
1389 aIsBuiltinDynamic
? true : isDynamicProcess
});
1391 // Serialize the scalar if it's in the desired dataset.
1392 if (IsInDataset(info
.dataset
, aDataset
)) {
1393 // Get the scalar value.
1394 nsCOMPtr
<nsIVariant
> scalarValue
;
1395 nsresult rv
= scalar
->GetValue(aStoreName
, aClearScalars
, scalarValue
);
1396 if (rv
== NS_ERROR_NO_CONTENT
) {
1397 // No value for this store. Proceed.
1400 if (NS_FAILED(rv
)) {
1403 // Append it to our list.
1404 processScalars
.AppendElement(
1405 std::make_tuple(info
.name(), scalarValue
, info
.kind
));
1408 if (processScalars
.Length() == 0) {
1409 aScalarsToReflect
.Remove(entry
.GetKey());
1416 * Creates a snapshot of the desired keyed scalar storage.
1417 * @param {aLock} The proof of lock to access scalar data.
1418 * @param {aScalarsToReflect} The table that will contain the snapshot.
1419 * @param {aDataset} The dataset we're asking the snapshot for.
1420 * @param {aProcessStorage} The scalar storage to take a snapshot of.
1421 * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin
1423 * @return NS_OK or the error code describing the failure reason.
1425 nsresult
internal_KeyedScalarSnapshotter(
1426 const StaticMutexAutoLock
& aLock
,
1427 KeyedScalarSnapshotTable
& aScalarsToReflect
, unsigned int aDataset
,
1428 ProcessesKeyedScalarsMapType
& aProcessStorage
, bool aIsBuiltinDynamic
,
1429 bool aClearScalars
, const nsACString
& aStoreName
) {
1430 // Iterate the scalars in aProcessStorage. The storage may contain empty or
1431 // yet to be initialized scalars from all the supported processes.
1432 for (const auto& entry
: aProcessStorage
) {
1433 KeyedScalarStorageMapType
* scalarStorage
= entry
.GetWeak();
1434 KeyedScalarTupleArray
& processScalars
=
1435 aScalarsToReflect
.LookupOrInsert(entry
.GetKey());
1437 // Are we in the "Dynamic" process?
1438 bool isDynamicProcess
=
1439 ProcessID::Dynamic
== static_cast<ProcessID
>(entry
.GetKey());
1441 for (const auto& childEntry
: *scalarStorage
) {
1442 KeyedScalar
* scalar
= childEntry
.GetWeak();
1444 // Get the informations for this scalar.
1445 const BaseScalarInfo
& info
= internal_GetScalarInfo(
1446 aLock
, ScalarKey
{childEntry
.GetKey(),
1447 aIsBuiltinDynamic
? true : isDynamicProcess
});
1449 // Serialize the scalar if it's in the desired dataset.
1450 if (IsInDataset(info
.dataset
, aDataset
)) {
1451 // Get the keys for this scalar.
1452 nsTArray
<KeyedScalar::KeyValuePair
> scalarKeyedData
;
1454 scalar
->GetValue(aStoreName
, aClearScalars
, scalarKeyedData
);
1455 if (NS_FAILED(rv
)) {
1458 if (scalarKeyedData
.Length() == 0) {
1459 // Don't bother with empty keyed scalars.
1462 // Append it to our list.
1463 processScalars
.AppendElement(std::make_tuple(
1464 info
.name(), std::move(scalarKeyedData
), info
.kind
));
1467 if (processScalars
.Length() == 0) {
1468 aScalarsToReflect
.Remove(entry
.GetKey());
1475 * Helper function to get a snapshot of the scalars.
1477 * @param {aLock} The proof of lock to access scalar data.
1478 * @param {aScalarsToReflect} The table that will contain the snapshot.
1479 * @param {aDataset} The dataset we're asking the snapshot for.
1480 * @param {aClearScalars} Whether or not to clear the scalar storage.
1481 * @param {aStoreName} The name of the store to snapshot.
1482 * @return NS_OK or the error code describing the failure reason.
1484 nsresult
internal_GetScalarSnapshot(const StaticMutexAutoLock
& aLock
,
1485 ScalarSnapshotTable
& aScalarsToReflect
,
1486 unsigned int aDataset
, bool aClearScalars
,
1487 const nsACString
& aStoreName
) {
1488 // Take a snapshot of the scalars.
1490 internal_ScalarSnapshotter(aLock
, aScalarsToReflect
, aDataset
,
1491 gScalarStorageMap
, false, /*aIsBuiltinDynamic*/
1492 aClearScalars
, aStoreName
);
1493 if (NS_FAILED(rv
)) {
1497 // And a snapshot of the dynamic builtin ones.
1498 rv
= internal_ScalarSnapshotter(aLock
, aScalarsToReflect
, aDataset
,
1499 gDynamicBuiltinScalarStorageMap
,
1500 true, /*aIsBuiltinDynamic*/
1501 aClearScalars
, aStoreName
);
1502 if (NS_FAILED(rv
)) {
1510 * Helper function to get a snapshot of the keyed scalars.
1512 * @param {aLock} The proof of lock to access scalar data.
1513 * @param {aScalarsToReflect} The table that will contain the snapshot.
1514 * @param {aDataset} The dataset we're asking the snapshot for.
1515 * @param {aClearScalars} Whether or not to clear the scalar storage.
1516 * @param {aStoreName} The name of the store to snapshot.
1517 * @return NS_OK or the error code describing the failure reason.
1519 nsresult
internal_GetKeyedScalarSnapshot(
1520 const StaticMutexAutoLock
& aLock
,
1521 KeyedScalarSnapshotTable
& aScalarsToReflect
, unsigned int aDataset
,
1522 bool aClearScalars
, const nsACString
& aStoreName
) {
1523 // Take a snapshot of the scalars.
1524 nsresult rv
= internal_KeyedScalarSnapshotter(
1525 aLock
, aScalarsToReflect
, aDataset
, gKeyedScalarStorageMap
,
1526 false, /*aIsBuiltinDynamic*/
1527 aClearScalars
, aStoreName
);
1528 if (NS_FAILED(rv
)) {
1532 // And a snapshot of the dynamic builtin ones.
1533 rv
= internal_KeyedScalarSnapshotter(aLock
, aScalarsToReflect
, aDataset
,
1534 gDynamicBuiltinKeyedScalarStorageMap
,
1535 true, /*aIsBuiltinDynamic*/
1536 aClearScalars
, aStoreName
);
1537 if (NS_FAILED(rv
)) {
1546 // helpers for recording/applying scalar operations
1549 void internal_ApplyScalarActions(
1550 const StaticMutexAutoLock
& lock
,
1551 const nsTArray
<mozilla::Telemetry::ScalarAction
>& aScalarActions
,
1552 const mozilla::Maybe
<ProcessID
>& aProcessType
= Nothing()) {
1553 if (!internal_CanRecordBase(lock
)) {
1557 for (auto& upd
: aScalarActions
) {
1558 ScalarKey uniqueId
{upd
.mId
, upd
.mDynamic
};
1559 if (NS_WARN_IF(!internal_IsValidId(lock
, uniqueId
))) {
1560 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1564 if (internal_IsKeyedScalar(lock
, uniqueId
)) {
1568 // Are we allowed to record this scalar? We don't need to check for
1569 // allowed processes here, that's taken care of when recording
1570 // in child processes.
1571 if (!internal_CanRecordForScalarID(lock
, uniqueId
)) {
1575 // Either we got passed a process type or it was explicitely set on the
1576 // recorded action. It should never happen that it is set to an invalid
1577 // value (such as ProcessID::Count)
1578 ProcessID processType
= aProcessType
.valueOr(upd
.mProcessType
);
1579 MOZ_ASSERT(processType
!= ProcessID::Count
);
1581 // Refresh the data in the parent process with the data coming from the
1583 ScalarBase
* scalar
= nullptr;
1585 internal_GetScalarByEnum(lock
, uniqueId
, processType
, &scalar
);
1586 if (NS_FAILED(rv
)) {
1587 // Bug 1513496 - We no longer log a warning if the scalar is expired.
1588 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
1589 NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
1594 if (upd
.mData
.isNothing()) {
1595 MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
1599 internal_profilerMarker(lock
, upd
);
1601 // Get the type of this scalar from the scalar ID. We already checked
1602 // for its validity a few lines above.
1603 const uint32_t scalarType
= internal_GetScalarInfo(lock
, uniqueId
).kind
;
1605 // Extract the data from the mozilla::Variant.
1606 switch (upd
.mActionType
) {
1607 case ScalarActionType::eSet
: {
1608 switch (scalarType
) {
1609 case nsITelemetry::SCALAR_TYPE_COUNT
:
1610 if (!upd
.mData
->is
<uint32_t>()) {
1611 NS_WARNING("Attempting to set a count scalar to a non-integer.");
1614 scalar
->SetValue(upd
.mData
->as
<uint32_t>());
1616 case nsITelemetry::SCALAR_TYPE_BOOLEAN
:
1617 if (!upd
.mData
->is
<bool>()) {
1619 "Attempting to set a boolean scalar to a non-boolean.");
1622 scalar
->SetValue(upd
.mData
->as
<bool>());
1624 case nsITelemetry::SCALAR_TYPE_STRING
:
1625 if (!upd
.mData
->is
<nsString
>()) {
1626 NS_WARNING("Attempting to set a string scalar to a non-string.");
1629 scalar
->SetValue(upd
.mData
->as
<nsString
>());
1634 case ScalarActionType::eAdd
: {
1635 if (scalarType
!= nsITelemetry::SCALAR_TYPE_COUNT
) {
1636 NS_WARNING("Attempting to add on a non count scalar.");
1639 // We only support adding uint32_t.
1640 if (!upd
.mData
->is
<uint32_t>()) {
1641 NS_WARNING("Attempting to add to a count scalar with a non-integer.");
1644 scalar
->AddValue(upd
.mData
->as
<uint32_t>());
1648 NS_WARNING("Unsupported action coming from scalar child updates.");
1653 void internal_ApplyKeyedScalarActions(
1654 const StaticMutexAutoLock
& lock
,
1655 const nsTArray
<mozilla::Telemetry::KeyedScalarAction
>& aScalarActions
,
1656 const mozilla::Maybe
<ProcessID
>& aProcessType
= Nothing()) {
1657 if (!internal_CanRecordBase(lock
)) {
1661 for (auto& upd
: aScalarActions
) {
1662 ScalarKey uniqueId
{upd
.mId
, upd
.mDynamic
};
1663 if (NS_WARN_IF(!internal_IsValidId(lock
, uniqueId
))) {
1664 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1668 if (!internal_IsKeyedScalar(lock
, uniqueId
)) {
1672 // Are we allowed to record this scalar? We don't need to check for
1673 // allowed processes here, that's taken care of when recording
1674 // in child processes.
1675 if (!internal_CanRecordForScalarID(lock
, uniqueId
)) {
1679 // Either we got passed a process type or it was explicitely set on the
1680 // recorded action. It should never happen that it is set to an invalid
1681 // value (such as ProcessID::Count)
1682 ProcessID processType
= aProcessType
.valueOr(upd
.mProcessType
);
1683 MOZ_ASSERT(processType
!= ProcessID::Count
);
1685 // Refresh the data in the parent process with the data coming from the
1687 KeyedScalar
* scalar
= nullptr;
1689 internal_GetKeyedScalarByEnum(lock
, uniqueId
, processType
, &scalar
);
1690 if (NS_FAILED(rv
)) {
1691 // Bug 1513496 - We no longer log a warning if the scalar is expired.
1692 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
1693 NS_WARNING("NS_FAILED internal_GetKeyedScalarByEnum for CHILD");
1698 if (upd
.mData
.isNothing()) {
1699 MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
1703 // Get the type of this scalar from the scalar ID. We already checked
1704 // for its validity a few lines above.
1705 const uint32_t scalarType
= internal_GetScalarInfo(lock
, uniqueId
).kind
;
1707 internal_profilerMarker(lock
, upd
, upd
.mKey
);
1709 // Extract the data from the mozilla::Variant.
1710 switch (upd
.mActionType
) {
1711 case ScalarActionType::eSet
: {
1712 switch (scalarType
) {
1713 case nsITelemetry::SCALAR_TYPE_COUNT
:
1714 if (!upd
.mData
->is
<uint32_t>()) {
1715 NS_WARNING("Attempting to set a count scalar to a non-integer.");
1718 scalar
->SetValue(lock
, NS_ConvertUTF8toUTF16(upd
.mKey
),
1719 upd
.mData
->as
<uint32_t>());
1721 case nsITelemetry::SCALAR_TYPE_BOOLEAN
:
1722 if (!upd
.mData
->is
<bool>()) {
1724 "Attempting to set a boolean scalar to a non-boolean.");
1727 scalar
->SetValue(lock
, NS_ConvertUTF8toUTF16(upd
.mKey
),
1728 upd
.mData
->as
<bool>());
1731 NS_WARNING("Unsupported type coming from scalar child updates.");
1735 case ScalarActionType::eAdd
: {
1736 if (scalarType
!= nsITelemetry::SCALAR_TYPE_COUNT
) {
1737 NS_WARNING("Attempting to add on a non count scalar.");
1740 // We only support adding on uint32_t.
1741 if (!upd
.mData
->is
<uint32_t>()) {
1742 NS_WARNING("Attempting to add to a count scalar with a non-integer.");
1745 scalar
->AddValue(lock
, NS_ConvertUTF8toUTF16(upd
.mKey
),
1746 upd
.mData
->as
<uint32_t>());
1751 "Unsupported action coming from keyed scalar child updates.");
1758 ////////////////////////////////////////////////////////////////////////
1759 ////////////////////////////////////////////////////////////////////////
1761 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars::
1763 // This is a StaticMutex rather than a plain Mutex (1) so that
1764 // it gets initialised in a thread-safe manner the first time
1765 // it is used, and (2) because it is never de-initialised, and
1766 // a normal Mutex would show up as a leak in BloatView. StaticMutex
1767 // also has the "OffTheBooks" property, so it won't show as a leak
1769 // Another reason to use a StaticMutex instead of a plain Mutex is
1770 // that, due to the nature of Telemetry, we cannot rely on having a
1771 // mutex initialized in InitializeGlobalState. Unfortunately, we
1772 // cannot make sure that no other function is called before this point.
1773 static StaticMutex gTelemetryScalarsMutex MOZ_UNANNOTATED
;
1775 void TelemetryScalar::InitializeGlobalState(bool aCanRecordBase
,
1776 bool aCanRecordExtended
) {
1777 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1778 MOZ_ASSERT(!gTelemetryScalarInitDone
,
1779 "TelemetryScalar::InitializeGlobalState "
1780 "may only be called once");
1782 gTelemetryScalarCanRecordBase
= aCanRecordBase
;
1783 gTelemetryScalarCanRecordExtended
= aCanRecordExtended
;
1785 // Populate the static scalar name->id cache. Note that the scalar names are
1786 // statically allocated and come from the automatically generated
1787 // TelemetryScalarData.h.
1788 uint32_t scalarCount
=
1789 static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount
);
1790 for (uint32_t i
= 0; i
< scalarCount
; i
++) {
1791 CharPtrEntryType
* entry
= gScalarNameIDMap
.PutEntry(gScalars
[i
].name());
1792 entry
->SetData(ScalarKey
{i
, false});
1795 // To summarize dynamic events we need a dynamic scalar.
1796 const nsTArray
<DynamicScalarInfo
> initialDynamicScalars({
1798 nsITelemetry::SCALAR_TYPE_COUNT
,
1799 true /* recordOnRelease */,
1800 false /* expired */,
1801 nsAutoCString("telemetry.dynamic_event_counts"),
1806 internal_RegisterScalars(locker
, initialDynamicScalars
);
1808 gTelemetryScalarInitDone
= true;
1811 void TelemetryScalar::DeInitializeGlobalState() {
1812 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1813 gTelemetryScalarCanRecordBase
= false;
1814 gTelemetryScalarCanRecordExtended
= false;
1815 gScalarNameIDMap
.Clear();
1816 gScalarStorageMap
.Clear();
1817 gKeyedScalarStorageMap
.Clear();
1818 gDynamicBuiltinScalarStorageMap
.Clear();
1819 gDynamicBuiltinKeyedScalarStorageMap
.Clear();
1820 gDynamicScalarInfo
= nullptr;
1821 gDynamicStoreNames
= nullptr;
1822 gTelemetryScalarInitDone
= false;
1825 void TelemetryScalar::SetCanRecordBase(bool b
) {
1826 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1827 gTelemetryScalarCanRecordBase
= b
;
1830 void TelemetryScalar::SetCanRecordExtended(bool b
) {
1831 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1832 gTelemetryScalarCanRecordExtended
= b
;
1836 * Adds the value to the given scalar.
1838 * @param aId The scalar enum id.
1839 * @param aVal The numeric value to add to the scalar.
1841 void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId
, uint32_t aValue
) {
1842 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
1843 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1847 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
1848 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1850 if (internal_CanRecordScalar(locker
, uniqueId
, false) != ScalarResult::Ok
) {
1851 // We can't record this scalar. Bail out.
1855 internal_profilerMarker(locker
, ScalarActionType::eAdd
, ScalarVariant(aValue
),
1858 // Accumulate in the child process if needed.
1859 if (!XRE_IsParentProcess()) {
1860 TelemetryIPCAccumulator::RecordChildScalarAction(
1861 uniqueId
.id
, uniqueId
.dynamic
, ScalarActionType::eAdd
,
1862 ScalarVariant(aValue
));
1866 ScalarBase
* scalar
= nullptr;
1868 internal_GetScalarByEnum(locker
, uniqueId
, ProcessID::Parent
, &scalar
);
1869 if (NS_FAILED(rv
)) {
1873 scalar
->AddValue(aValue
);
1877 * Adds the value to the given keyed scalar.
1879 * @param aId The scalar enum id.
1880 * @param aKey The key name.
1881 * @param aVal The numeric value to add to the scalar.
1883 void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId
,
1884 const nsAString
& aKey
, uint32_t aValue
) {
1885 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
1886 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1890 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
1891 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1893 if (internal_CanRecordScalar(locker
, uniqueId
, true) != ScalarResult::Ok
) {
1894 // We can't record this scalar. Bail out.
1898 internal_profilerMarker(locker
, ScalarActionType::eAdd
, ScalarVariant(aValue
),
1901 // Accumulate in the child process if needed.
1902 if (!XRE_IsParentProcess()) {
1903 TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
1904 uniqueId
.id
, uniqueId
.dynamic
, aKey
, ScalarActionType::eAdd
,
1905 ScalarVariant(aValue
));
1909 KeyedScalar
* scalar
= nullptr;
1910 nsresult rv
= internal_GetKeyedScalarByEnum(locker
, uniqueId
,
1911 ProcessID::Parent
, &scalar
);
1912 if (NS_FAILED(rv
)) {
1916 scalar
->AddValue(locker
, aKey
, aValue
);
1920 * Sets the scalar to the given numeric value.
1922 * @param aId The scalar enum id.
1923 * @param aValue The numeric, unsigned value to set the scalar to.
1925 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId
, uint32_t aValue
) {
1926 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
1927 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1931 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
1932 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1934 if (internal_CanRecordScalar(locker
, uniqueId
, false) != ScalarResult::Ok
) {
1935 // We can't record this scalar. Bail out.
1939 internal_profilerMarker(locker
, ScalarActionType::eSet
, ScalarVariant(aValue
),
1942 // Accumulate in the child process if needed.
1943 if (!XRE_IsParentProcess()) {
1944 TelemetryIPCAccumulator::RecordChildScalarAction(
1945 uniqueId
.id
, uniqueId
.dynamic
, ScalarActionType::eSet
,
1946 ScalarVariant(aValue
));
1950 ScalarBase
* scalar
= nullptr;
1952 internal_GetScalarByEnum(locker
, uniqueId
, ProcessID::Parent
, &scalar
);
1953 if (NS_FAILED(rv
)) {
1957 scalar
->SetValue(aValue
);
1961 * Sets the scalar to the given string value.
1963 * @param aId The scalar enum id.
1964 * @param aValue The string value to set the scalar to.
1966 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId
,
1967 const nsAString
& aValue
) {
1968 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
1969 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
1973 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
1974 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
1976 if (internal_CanRecordScalar(locker
, uniqueId
, false) != ScalarResult::Ok
) {
1977 // We can't record this scalar. Bail out.
1981 internal_profilerMarker(locker
, ScalarActionType::eSet
,
1982 ScalarVariant(nsString(aValue
)), uniqueId
);
1984 // Accumulate in the child process if needed.
1985 if (!XRE_IsParentProcess()) {
1986 TelemetryIPCAccumulator::RecordChildScalarAction(
1987 uniqueId
.id
, uniqueId
.dynamic
, ScalarActionType::eSet
,
1988 ScalarVariant(nsString(aValue
)));
1992 ScalarBase
* scalar
= nullptr;
1994 internal_GetScalarByEnum(locker
, uniqueId
, ProcessID::Parent
, &scalar
);
1995 if (NS_FAILED(rv
)) {
1999 scalar
->SetValue(aValue
);
2003 * Sets the scalar to the given boolean value.
2005 * @param aId The scalar enum id.
2006 * @param aValue The boolean value to set the scalar to.
2008 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId
, bool aValue
) {
2009 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
2010 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2014 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
2015 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2017 if (internal_CanRecordScalar(locker
, uniqueId
, false) != ScalarResult::Ok
) {
2018 // We can't record this scalar. Bail out.
2022 internal_profilerMarker(locker
, ScalarActionType::eSet
, ScalarVariant(aValue
),
2025 // Accumulate in the child process if needed.
2026 if (!XRE_IsParentProcess()) {
2027 TelemetryIPCAccumulator::RecordChildScalarAction(
2028 uniqueId
.id
, uniqueId
.dynamic
, ScalarActionType::eSet
,
2029 ScalarVariant(aValue
));
2033 ScalarBase
* scalar
= nullptr;
2035 internal_GetScalarByEnum(locker
, uniqueId
, ProcessID::Parent
, &scalar
);
2036 if (NS_FAILED(rv
)) {
2040 scalar
->SetValue(aValue
);
2044 * Sets the keyed scalar to the given numeric value.
2046 * @param aId The scalar enum id.
2047 * @param aKey The scalar key.
2048 * @param aValue The numeric, unsigned value to set the scalar to.
2050 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId
,
2051 const nsAString
& aKey
, uint32_t aValue
) {
2052 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
2053 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2057 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
2058 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2060 if (internal_CanRecordScalar(locker
, uniqueId
, true) != ScalarResult::Ok
) {
2061 // We can't record this scalar. Bail out.
2065 internal_profilerMarker(locker
, ScalarActionType::eSet
, ScalarVariant(aValue
),
2068 // Accumulate in the child process if needed.
2069 if (!XRE_IsParentProcess()) {
2070 TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
2071 uniqueId
.id
, uniqueId
.dynamic
, aKey
, ScalarActionType::eSet
,
2072 ScalarVariant(aValue
));
2076 KeyedScalar
* scalar
= nullptr;
2077 nsresult rv
= internal_GetKeyedScalarByEnum(locker
, uniqueId
,
2078 ProcessID::Parent
, &scalar
);
2079 if (NS_FAILED(rv
)) {
2083 scalar
->SetValue(locker
, aKey
, aValue
);
2087 * Sets the scalar to the given boolean value.
2089 * @param aId The scalar enum id.
2090 * @param aKey The scalar key.
2091 * @param aValue The boolean value to set the scalar to.
2093 void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId
,
2094 const nsAString
& aKey
, bool aValue
) {
2095 if (NS_WARN_IF(!IsValidEnumId(aId
))) {
2096 MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
2100 ScalarKey uniqueId
{static_cast<uint32_t>(aId
), false};
2101 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2103 if (internal_CanRecordScalar(locker
, uniqueId
, true) != ScalarResult::Ok
) {
2104 // We can't record this scalar. Bail out.
2108 internal_profilerMarker(locker
, ScalarActionType::eSet
, ScalarVariant(aValue
),
2111 // Accumulate in the child process if needed.
2112 if (!XRE_IsParentProcess()) {
2113 TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
2114 uniqueId
.id
, uniqueId
.dynamic
, aKey
, ScalarActionType::eSet
,
2115 ScalarVariant(aValue
));
2119 KeyedScalar
* scalar
= nullptr;
2120 nsresult rv
= internal_GetKeyedScalarByEnum(locker
, uniqueId
,
2121 ProcessID::Parent
, &scalar
);
2122 if (NS_FAILED(rv
)) {
2126 scalar
->SetValue(locker
, aKey
, aValue
);
2129 nsresult
TelemetryScalar::CreateSnapshots(unsigned int aDataset
,
2130 bool aClearScalars
, JSContext
* aCx
,
2131 uint8_t optional_argc
,
2132 JS::MutableHandle
<JS::Value
> aResult
,
2134 const nsACString
& aStoreName
) {
2136 XRE_IsParentProcess(),
2137 "Snapshotting scalars should only happen in the parent processes.");
2138 // If no arguments were passed in, apply the default value.
2139 if (!optional_argc
) {
2140 aClearScalars
= false;
2143 JS::Rooted
<JSObject
*> root_obj(aCx
, JS_NewPlainObject(aCx
));
2145 return NS_ERROR_FAILURE
;
2147 aResult
.setObject(*root_obj
);
2149 // Return `{}` in child processes.
2150 if (!XRE_IsParentProcess()) {
2154 // Only lock the mutex while accessing our data, without locking any JS
2156 ScalarSnapshotTable scalarsToReflect
;
2158 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2160 nsresult rv
= internal_GetScalarSnapshot(locker
, scalarsToReflect
, aDataset
,
2161 aClearScalars
, aStoreName
);
2162 if (NS_FAILED(rv
)) {
2167 // Reflect it to JS.
2168 for (const auto& entry
: scalarsToReflect
) {
2169 const ScalarTupleArray
& processScalars
= entry
.GetData();
2170 const char* processName
= GetNameForProcessID(ProcessID(entry
.GetKey()));
2172 // Create the object that will hold the scalars for this process and add it
2173 // to the returned root object.
2174 JS::Rooted
<JSObject
*> processObj(aCx
, JS_NewPlainObject(aCx
));
2175 if (!processObj
|| !JS_DefineProperty(aCx
, root_obj
, processName
,
2176 processObj
, JSPROP_ENUMERATE
)) {
2177 return NS_ERROR_FAILURE
;
2180 for (ScalarTupleArray::size_type i
= 0; i
< processScalars
.Length(); i
++) {
2181 const ScalarDataTuple
& scalar
= processScalars
[i
];
2183 const char* scalarName
= std::get
<0>(scalar
);
2184 if (aFilterTest
&& strncmp(TEST_SCALAR_PREFIX
, scalarName
,
2185 strlen(TEST_SCALAR_PREFIX
)) == 0) {
2189 // Convert it to a JS Val.
2190 JS::Rooted
<JS::Value
> scalarJsValue(aCx
);
2191 nsresult rv
= nsContentUtils::XPConnect()->VariantToJS(
2192 aCx
, processObj
, std::get
<1>(scalar
), &scalarJsValue
);
2193 if (NS_FAILED(rv
)) {
2197 // Add it to the scalar object.
2198 if (!JS_DefineProperty(aCx
, processObj
, scalarName
, scalarJsValue
,
2199 JSPROP_ENUMERATE
)) {
2200 return NS_ERROR_FAILURE
;
2208 nsresult
TelemetryScalar::CreateKeyedSnapshots(
2209 unsigned int aDataset
, bool aClearScalars
, JSContext
* aCx
,
2210 uint8_t optional_argc
, JS::MutableHandle
<JS::Value
> aResult
,
2211 bool aFilterTest
, const nsACString
& aStoreName
) {
2213 XRE_IsParentProcess(),
2214 "Snapshotting scalars should only happen in the parent processes.");
2215 // If no arguments were passed in, apply the default value.
2216 if (!optional_argc
) {
2217 aClearScalars
= false;
2220 JS::Rooted
<JSObject
*> root_obj(aCx
, JS_NewPlainObject(aCx
));
2222 return NS_ERROR_FAILURE
;
2224 aResult
.setObject(*root_obj
);
2226 // Return `{}` in child processes.
2227 if (!XRE_IsParentProcess()) {
2231 // Only lock the mutex while accessing our data, without locking any JS
2233 KeyedScalarSnapshotTable scalarsToReflect
;
2235 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2237 nsresult rv
= internal_GetKeyedScalarSnapshot(
2238 locker
, scalarsToReflect
, aDataset
, aClearScalars
, aStoreName
);
2239 if (NS_FAILED(rv
)) {
2244 // Reflect it to JS.
2245 for (const auto& entry
: scalarsToReflect
) {
2246 const KeyedScalarTupleArray
& processScalars
= entry
.GetData();
2247 const char* processName
= GetNameForProcessID(ProcessID(entry
.GetKey()));
2249 // Create the object that will hold the scalars for this process and add it
2250 // to the returned root object.
2251 JS::Rooted
<JSObject
*> processObj(aCx
, JS_NewPlainObject(aCx
));
2252 if (!processObj
|| !JS_DefineProperty(aCx
, root_obj
, processName
,
2253 processObj
, JSPROP_ENUMERATE
)) {
2254 return NS_ERROR_FAILURE
;
2257 for (KeyedScalarTupleArray::size_type i
= 0; i
< processScalars
.Length();
2259 const KeyedScalarDataTuple
& keyedScalarData
= processScalars
[i
];
2261 const char* scalarName
= std::get
<0>(keyedScalarData
);
2262 if (aFilterTest
&& strncmp(TEST_SCALAR_PREFIX
, scalarName
,
2263 strlen(TEST_SCALAR_PREFIX
)) == 0) {
2267 // Go through each keyed scalar and create a keyed scalar object.
2268 // This object will hold the values for all the keyed scalar keys.
2269 JS::Rooted
<JSObject
*> keyedScalarObj(aCx
, JS_NewPlainObject(aCx
));
2271 // Define a property for each scalar key, then add it to the keyed scalar
2273 const nsTArray
<KeyedScalar::KeyValuePair
>& keyProps
=
2274 std::get
<1>(keyedScalarData
);
2275 for (uint32_t i
= 0; i
< keyProps
.Length(); i
++) {
2276 const KeyedScalar::KeyValuePair
& keyData
= keyProps
[i
];
2278 // Convert the value for the key to a JSValue.
2279 JS::Rooted
<JS::Value
> keyJsValue(aCx
);
2280 nsresult rv
= nsContentUtils::XPConnect()->VariantToJS(
2281 aCx
, keyedScalarObj
, keyData
.second
, &keyJsValue
);
2282 if (NS_FAILED(rv
)) {
2286 // Add the key to the scalar representation.
2287 const NS_ConvertUTF8toUTF16
key(keyData
.first
);
2288 if (!JS_DefineUCProperty(aCx
, keyedScalarObj
, key
.Data(), key
.Length(),
2289 keyJsValue
, JSPROP_ENUMERATE
)) {
2290 return NS_ERROR_FAILURE
;
2294 // Add the scalar to the root object.
2295 if (!JS_DefineProperty(aCx
, processObj
, scalarName
, keyedScalarObj
,
2296 JSPROP_ENUMERATE
)) {
2297 return NS_ERROR_FAILURE
;
2305 nsresult
TelemetryScalar::RegisterScalars(const nsACString
& aCategoryName
,
2306 JS::Handle
<JS::Value
> aScalarData
,
2308 MOZ_ASSERT(XRE_IsParentProcess(),
2309 "Dynamic scalars should only be created in the parent process.");
2311 if (!IsValidIdentifierString(aCategoryName
, kMaximumCategoryNameLength
, true,
2313 JS_ReportErrorASCII(cx
, "Invalid category name %s.",
2314 PromiseFlatCString(aCategoryName
).get());
2315 return NS_ERROR_INVALID_ARG
;
2318 if (!aScalarData
.isObject()) {
2319 JS_ReportErrorASCII(cx
, "Scalar data parameter should be an object");
2320 return NS_ERROR_INVALID_ARG
;
2323 JS::Rooted
<JSObject
*> obj(cx
, &aScalarData
.toObject());
2324 JS::Rooted
<JS::IdVector
> scalarPropertyIds(cx
, JS::IdVector(cx
));
2325 if (!JS_Enumerate(cx
, obj
, &scalarPropertyIds
)) {
2326 return NS_ERROR_FAILURE
;
2329 // Collect the scalar data into local storage first.
2330 // Only after successfully validating all contained scalars will we register
2331 // them into global storage.
2332 nsTArray
<DynamicScalarInfo
> newScalarInfos
;
2334 for (size_t i
= 0, n
= scalarPropertyIds
.length(); i
< n
; i
++) {
2335 nsAutoJSString scalarName
;
2336 if (!scalarName
.init(cx
, scalarPropertyIds
[i
])) {
2337 return NS_ERROR_FAILURE
;
2340 if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(scalarName
),
2341 kMaximumScalarNameLength
, false, true)) {
2342 JS_ReportErrorASCII(
2343 cx
, "Invalid scalar name %s.",
2344 PromiseFlatCString(NS_ConvertUTF16toUTF8(scalarName
)).get());
2345 return NS_ERROR_INVALID_ARG
;
2348 // Join the category and the probe names.
2349 nsPrintfCString
fullName("%s.%s", PromiseFlatCString(aCategoryName
).get(),
2350 NS_ConvertUTF16toUTF8(scalarName
).get());
2352 JS::Rooted
<JS::Value
> value(cx
);
2353 if (!JS_GetPropertyById(cx
, obj
, scalarPropertyIds
[i
], &value
) ||
2354 !value
.isObject()) {
2355 return NS_ERROR_FAILURE
;
2357 JS::Rooted
<JSObject
*> scalarDef(cx
, &value
.toObject());
2359 // Get the scalar's kind.
2360 if (!JS_GetProperty(cx
, scalarDef
, "kind", &value
) || !value
.isInt32()) {
2361 JS_ReportErrorASCII(cx
, "Invalid or missing 'kind' for scalar %s.",
2362 PromiseFlatCString(fullName
).get());
2363 return NS_ERROR_FAILURE
;
2365 uint32_t kind
= static_cast<uint32_t>(value
.toInt32());
2367 // Get the optional scalar's recording policy (default to false).
2368 bool hasProperty
= false;
2369 bool recordOnRelease
= false;
2370 if (JS_HasProperty(cx
, scalarDef
, "record_on_release", &hasProperty
) &&
2372 if (!JS_GetProperty(cx
, scalarDef
, "record_on_release", &value
) ||
2373 !value
.isBoolean()) {
2374 JS_ReportErrorASCII(cx
, "Invalid 'record_on_release' for scalar %s.",
2375 PromiseFlatCString(fullName
).get());
2376 return NS_ERROR_FAILURE
;
2378 recordOnRelease
= static_cast<bool>(value
.toBoolean());
2381 // Get the optional scalar's keyed (default to false).
2383 if (JS_HasProperty(cx
, scalarDef
, "keyed", &hasProperty
) && hasProperty
) {
2384 if (!JS_GetProperty(cx
, scalarDef
, "keyed", &value
) ||
2385 !value
.isBoolean()) {
2386 JS_ReportErrorASCII(cx
, "Invalid 'keyed' for scalar %s.",
2387 PromiseFlatCString(fullName
).get());
2388 return NS_ERROR_FAILURE
;
2390 keyed
= static_cast<bool>(value
.toBoolean());
2393 // Get the optional scalar's expired state (default to false).
2394 bool expired
= false;
2395 if (JS_HasProperty(cx
, scalarDef
, "expired", &hasProperty
) && hasProperty
) {
2396 if (!JS_GetProperty(cx
, scalarDef
, "expired", &value
) ||
2397 !value
.isBoolean()) {
2398 JS_ReportErrorASCII(cx
, "Invalid 'expired' for scalar %s.",
2399 PromiseFlatCString(fullName
).get());
2400 return NS_ERROR_FAILURE
;
2402 expired
= static_cast<bool>(value
.toBoolean());
2405 // Get the scalar's optional stores list (default to ["main"]).
2406 nsTArray
<nsCString
> stores
;
2407 if (JS_HasProperty(cx
, scalarDef
, "stores", &hasProperty
) && hasProperty
) {
2408 bool isArray
= false;
2409 if (!JS_GetProperty(cx
, scalarDef
, "stores", &value
) ||
2410 !JS::IsArrayObject(cx
, value
, &isArray
) || !isArray
) {
2411 JS_ReportErrorASCII(cx
, "Invalid 'stores' for scalar %s.",
2412 PromiseFlatCString(fullName
).get());
2413 return NS_ERROR_FAILURE
;
2416 JS::Rooted
<JSObject
*> arrayObj(cx
, &value
.toObject());
2417 uint32_t storesLength
= 0;
2418 if (!JS::GetArrayLength(cx
, arrayObj
, &storesLength
)) {
2419 JS_ReportErrorASCII(cx
,
2420 "Can't get 'stores' array length for scalar %s.",
2421 PromiseFlatCString(fullName
).get());
2422 return NS_ERROR_FAILURE
;
2425 for (uint32_t i
= 0; i
< storesLength
; ++i
) {
2426 JS::Rooted
<JS::Value
> elt(cx
);
2427 if (!JS_GetElement(cx
, arrayObj
, i
, &elt
)) {
2428 JS_ReportErrorASCII(
2429 cx
, "Can't get element from scalar %s 'stores' array.",
2430 PromiseFlatCString(fullName
).get());
2431 return NS_ERROR_FAILURE
;
2433 if (!elt
.isString()) {
2434 JS_ReportErrorASCII(cx
,
2435 "Element in scalar %s 'stores' array isn't a "
2437 PromiseFlatCString(fullName
).get());
2438 return NS_ERROR_FAILURE
;
2441 nsAutoJSString jsStr
;
2442 if (!jsStr
.init(cx
, elt
)) {
2443 return NS_ERROR_FAILURE
;
2446 stores
.AppendElement(NS_ConvertUTF16toUTF8(jsStr
));
2448 // In the event of the usual case (just "main"), save the storage.
2449 if (stores
.Length() == 1 && stores
[0].EqualsLiteral("main")) {
2450 stores
.TruncateLength(0);
2454 // We defer the actual registration here in case any other event description
2455 // is invalid. In that case we don't need to roll back any partial
2457 newScalarInfos
.AppendElement(DynamicScalarInfo
{
2458 kind
, recordOnRelease
, expired
, fullName
, keyed
, std::move(stores
)});
2461 // Register the dynamic definition on the parent process.
2462 nsTArray
<DynamicScalarDefinition
> ipcDefinitions
;
2464 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2465 ::internal_RegisterScalars(locker
, newScalarInfos
);
2467 // Convert the internal scalar representation to a stripped down IPC one.
2468 ::internal_DynamicScalarToIPC(locker
, newScalarInfos
, ipcDefinitions
);
2471 // Propagate the registration to all the content-processes.
2472 // Do not hold the mutex while calling IPC.
2473 ::internal_BroadcastDefinitions(ipcDefinitions
);
2479 * Count in Scalars how many of which events were recorded. See bug 1440673
2481 * Event Telemetry unfortunately cannot use vanilla ScalarAdd because it needs
2482 * to summarize events recorded in different processes to the
2483 * telemetry.event_counts of the same process. Including "dynamic".
2485 * @param aUniqueEventName - expected to be category#object#method
2486 * @param aProcessType - the process of the event being summarized
2488 void TelemetryScalar::SummarizeEvent(const nsCString
& aUniqueEventName
,
2489 ProcessID aProcessType
) {
2490 MOZ_ASSERT(XRE_IsParentProcess(),
2491 "Only summarize events in the parent process");
2492 if (!XRE_IsParentProcess()) {
2496 StaticMutexAutoLock
lock(gTelemetryScalarsMutex
);
2498 ScalarKey scalarKey
{static_cast<uint32_t>(ScalarID::TELEMETRY_EVENT_COUNTS
)};
2499 KeyedScalar
* scalar
= nullptr;
2501 internal_GetKeyedScalarByEnum(lock
, scalarKey
, aProcessType
, &scalar
);
2503 if (NS_FAILED(rv
)) {
2504 NS_WARNING("NS_FAILED getting keyed scalar for event summary. Wut.");
2508 // Set this each time as it may have been cleared and recreated between calls
2509 scalar
->SetMaximumNumberOfKeys(kMaxEventSummaryKeys
);
2511 scalar
->AddValue(lock
, NS_ConvertASCIItoUTF16(aUniqueEventName
), 1);
2515 * Resets all the stored scalars. This is intended to be only used in tests.
2517 void TelemetryScalar::ClearScalars() {
2518 MOZ_ASSERT(XRE_IsParentProcess(),
2519 "Scalars should only be cleared in the parent process.");
2520 if (!XRE_IsParentProcess()) {
2524 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2525 gScalarStorageMap
.Clear();
2526 gKeyedScalarStorageMap
.Clear();
2527 gDynamicBuiltinScalarStorageMap
.Clear();
2528 gDynamicBuiltinKeyedScalarStorageMap
.Clear();
2531 size_t TelemetryScalar::GetMapShallowSizesOfExcludingThis(
2532 mozilla::MallocSizeOf aMallocSizeOf
) {
2533 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2534 return gScalarNameIDMap
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
2537 size_t TelemetryScalar::GetScalarSizesOfIncludingThis(
2538 mozilla::MallocSizeOf aMallocSizeOf
) {
2539 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2542 auto getSizeOf
= [aMallocSizeOf
](auto& storageMap
) {
2544 for (const auto& scalarStorage
: storageMap
.Values()) {
2545 for (const auto& scalar
: scalarStorage
->Values()) {
2546 partial
+= scalar
->SizeOfIncludingThis(aMallocSizeOf
);
2552 // Account for all the storage used for the different scalar types.
2553 n
+= getSizeOf(gScalarStorageMap
);
2554 n
+= getSizeOf(gKeyedScalarStorageMap
);
2555 n
+= getSizeOf(gDynamicBuiltinScalarStorageMap
);
2556 n
+= getSizeOf(gDynamicBuiltinKeyedScalarStorageMap
);
2561 void TelemetryScalar::UpdateChildData(
2562 ProcessID aProcessType
,
2563 const nsTArray
<mozilla::Telemetry::ScalarAction
>& aScalarActions
) {
2564 MOZ_ASSERT(XRE_IsParentProcess(),
2565 "The stored child processes scalar data must be updated from the "
2567 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2569 internal_ApplyScalarActions(locker
, aScalarActions
, Some(aProcessType
));
2572 void TelemetryScalar::UpdateChildKeyedData(
2573 ProcessID aProcessType
,
2574 const nsTArray
<mozilla::Telemetry::KeyedScalarAction
>& aScalarActions
) {
2575 MOZ_ASSERT(XRE_IsParentProcess(),
2576 "The stored child processes keyed scalar data must be updated "
2577 "from the parent process.");
2578 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2580 internal_ApplyKeyedScalarActions(locker
, aScalarActions
, Some(aProcessType
));
2583 void TelemetryScalar::RecordDiscardedData(
2584 ProcessID aProcessType
,
2585 const mozilla::Telemetry::DiscardedData
& aDiscardedData
) {
2586 MOZ_ASSERT(XRE_IsParentProcess(),
2587 "Discarded Data must be updated from the parent process.");
2588 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2589 if (!internal_CanRecordBase(locker
)) {
2593 ScalarBase
* scalar
= nullptr;
2594 mozilla::DebugOnly
<nsresult
> rv
;
2596 #define REPORT_DISCARDED(id, member) \
2597 if (aDiscardedData.member) { \
2598 ScalarKey uniqueId = \
2599 ScalarKey{static_cast<uint32_t>(ScalarID::id), false}; \
2600 rv = internal_GetScalarByEnum(locker, uniqueId, aProcessType, &scalar); \
2601 MOZ_ASSERT(NS_SUCCEEDED(rv)); \
2602 internal_profilerMarker(locker, ScalarActionType::eAdd, \
2603 ScalarVariant(aDiscardedData.member), uniqueId); \
2604 scalar->AddValue(aDiscardedData.member); \
2607 REPORT_DISCARDED(TELEMETRY_DISCARDED_ACCUMULATIONS
,
2608 mDiscardedHistogramAccumulations
)
2609 REPORT_DISCARDED(TELEMETRY_DISCARDED_KEYED_ACCUMULATIONS
,
2610 mDiscardedKeyedHistogramAccumulations
)
2611 REPORT_DISCARDED(TELEMETRY_DISCARDED_SCALAR_ACTIONS
, mDiscardedScalarActions
)
2612 REPORT_DISCARDED(TELEMETRY_DISCARDED_KEYED_SCALAR_ACTIONS
,
2613 mDiscardedKeyedScalarActions
)
2614 REPORT_DISCARDED(TELEMETRY_DISCARDED_CHILD_EVENTS
, mDiscardedChildEvents
)
2618 * Get the dynamic scalar definitions in an IPC-friendly
2621 void TelemetryScalar::GetDynamicScalarDefinitions(
2622 nsTArray
<DynamicScalarDefinition
>& aDefArray
) {
2623 MOZ_ASSERT(XRE_IsParentProcess());
2624 if (!gDynamicScalarInfo
) {
2625 // Don't have dynamic scalar definitions. Bail out!
2629 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2630 internal_DynamicScalarToIPC(locker
, *gDynamicScalarInfo
, aDefArray
);
2634 * This adds the dynamic scalar definitions coming from
2635 * the parent process to this child process. If a dynamic
2636 * scalar definition is already defined, check if the new definition
2637 * makes the scalar expired and eventually update the expiration
2640 void TelemetryScalar::AddDynamicScalarDefinitions(
2641 const nsTArray
<DynamicScalarDefinition
>& aDefs
) {
2642 MOZ_ASSERT(!XRE_IsParentProcess());
2644 nsTArray
<DynamicScalarInfo
> dynamicStubs
;
2646 // Populate the definitions array before acquiring the lock.
2647 for (auto& def
: aDefs
) {
2648 bool recordOnRelease
= def
.dataset
== nsITelemetry::DATASET_ALL_CHANNELS
;
2649 dynamicStubs
.AppendElement(DynamicScalarInfo
{def
.type
,
2658 StaticMutexAutoLock
locker(gTelemetryScalarsMutex
);
2659 internal_RegisterScalars(locker
, dynamicStubs
);
2663 nsresult
TelemetryScalar::GetAllStores(StringHashSet
& set
) {
2665 for (uint32_t storeIdx
: gScalarStoresTable
) {
2666 const char* name
= &gScalarsStringTable
[storeIdx
];
2667 nsAutoCString store
;
2668 store
.AssignASCII(name
);
2669 if (!set
.Insert(store
, mozilla::fallible
)) {
2670 return NS_ERROR_FAILURE
;
2675 for (auto& ptr
: *gDynamicStoreNames
) {
2676 nsAutoCString store
;
2677 ptr
->ToUTF8String(store
);
2678 if (!set
.Insert(store
, mozilla::fallible
)) {
2679 return NS_ERROR_FAILURE
;