Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / toolkit / components / telemetry / core / TelemetryScalar.cpp
blob16b4439e17bcaf0f1373397cdff9e4d5bd2d3776
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;
35 using mozilla::Some;
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 {
65 struct ScalarMarker {
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()));
85 } else {
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}");
108 return schema;
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
135 // chain like this
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 ////////////////////////////////////////////////////////////////////////
150 // PRIVATE TYPES
152 namespace {
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"
170 // store.
171 const uint32_t kMaxStaticStoreOffset = UINT16_MAX;
173 enum class ScalarResult : uint8_t {
174 // Nothing went wrong.
176 // General Scalar Errors
177 NotInitialized,
178 CannotUnpackVariant,
179 CannotRecordInProcess,
180 CannotRecordDataset,
181 KeyedTypeMismatch,
182 UnknownScalar,
183 OperationNotSupported,
184 InvalidType,
185 InvalidValue,
186 // Keyed Scalar Errors
187 KeyIsEmpty,
188 KeyTooLong,
189 TooManyKeys,
190 KeyNotAllowed,
191 // String Scalar Errors
192 StringTooLong,
193 // Unsigned Scalar Errors
194 UnsignedNegativeValue,
195 UnsignedTruncatedValue,
198 // A common identifier for both built-in and dynamic scalars.
199 struct ScalarKey {
200 uint32_t id;
201 bool dynamic;
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)
219 : BaseScalarInfo(
220 aKind,
221 aRecordOnRelease ? nsITelemetry::DATASET_ALL_CHANNELS
222 : nsITelemetry::DATASET_PRERELEASE_CHANNELS,
223 RecordedProcessType::All, aKeyed, 0, 0, GetCurrentProduct()),
224 mDynamicName(aName),
225 mDynamicExpiration(aExpired) {
226 store_count = aStores.Length();
227 if (store_count == 0) {
228 store_count = 1;
229 store_offset = kMaxStaticStoreOffset;
230 } else {
231 store_offset = kMaxStaticStoreOffset + 1 + gDynamicStoreNames->Length();
232 for (const auto& storeName : aStores) {
233 gDynamicStoreNames->AppendElement(NS_Atomize(storeName));
235 MOZ_ASSERT(
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
243 // tables.
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
256 // logic.
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) {
271 if (!aId.dynamic) {
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|.
286 return aId.dynamic
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
302 * purposes.
304 class ScalarBase {
305 public:
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) {
313 val = false;
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;
339 protected:
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;
345 private:
346 ScalarResult HandleUnsupported() const;
348 const uint32_t mStoreCount;
349 const uint32_t mStoreOffset;
350 nsTArray<bool> mStoreHasValue;
352 protected:
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) {
375 val = true;
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")) {
384 *aStoreIndex = 0;
385 return NS_OK;
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)) {
397 *aStoreIndex = i;
398 return NS_OK;
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])) {
408 *aStoreIndex = i;
409 return NS_OK;
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 {
424 public:
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) {
431 val = 0;
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;
443 private:
444 nsTArray<uint32_t> mStorage;
446 // Prevent copying.
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) {
453 val = aValue;
455 SetValueInStores();
458 void ScalarUnsigned::AddValue(uint32_t aValue) {
459 for (auto& val : mStorage) {
460 val += aValue;
462 SetValueInStores();
465 nsresult ScalarUnsigned::GetValue(const nsACString& aStoreName,
466 bool aClearStore,
467 nsCOMPtr<nsIVariant>& aResult) {
468 size_t storeIndex = 0;
469 nsresult rv = StoreIndex(aStoreName, &storeIndex);
470 if (NS_FAILED(rv)) {
471 return rv;
473 if (!HasValueInStore(storeIndex)) {
474 return NS_ERROR_NO_CONTENT;
476 nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
477 rv = outVar->SetAsUint32(mStorage[storeIndex]);
478 if (NS_FAILED(rv)) {
479 return rv;
481 aResult = std::move(outVar);
482 if (aClearStore) {
483 mStorage[storeIndex] = 0;
484 ClearValueInStore(storeIndex);
486 return NS_OK;
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);
494 return n;
498 * The implementation for the string scalar type.
500 class ScalarString : public ScalarBase {
501 public:
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;
516 private:
517 nsTArray<nsString> mStorage;
519 // Prevent copying.
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) {
527 val.Assign(str);
529 SetValueInStores();
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);
541 if (NS_FAILED(rv)) {
542 return rv;
544 if (!HasValueInStore(storeIndex)) {
545 return NS_ERROR_NO_CONTENT;
547 rv = outVar->SetAsAString(mStorage[storeIndex]);
548 if (NS_FAILED(rv)) {
549 return rv;
551 if (aClearStore) {
552 ClearValueInStore(storeIndex);
554 aResult = std::move(outVar);
555 return NS_OK;
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);
566 return n;
570 * The implementation for the boolean scalar type.
572 class ScalarBoolean : public ScalarBase {
573 public:
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) {
580 val = false;
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;
591 private:
592 nsTArray<bool> mStorage;
594 // Prevent copying.
595 ScalarBoolean(const ScalarBoolean& aOther) = delete;
596 void operator=(const ScalarBoolean& aOther) = delete;
599 void ScalarBoolean::SetValue(bool aValue) {
600 for (auto& val : mStorage) {
601 val = aValue;
603 SetValueInStores();
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);
611 if (NS_FAILED(rv)) {
612 return rv;
614 if (!HasValueInStore(storeIndex)) {
615 return NS_ERROR_NO_CONTENT;
617 if (aClearStore) {
618 ClearValueInStore(storeIndex);
620 rv = outVar->SetAsBool(mStorage[storeIndex]);
621 if (NS_FAILED(rv)) {
622 return rv;
624 aResult = std::move(outVar);
625 return NS_OK;
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);
633 return n;
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
641 * the scalar type.
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);
648 break;
649 case nsITelemetry::SCALAR_TYPE_STRING:
650 scalar = new ScalarString(aInfo);
651 break;
652 case nsITelemetry::SCALAR_TYPE_BOOLEAN:
653 scalar = new ScalarBoolean(aInfo);
654 break;
655 default:
656 MOZ_ASSERT(false, "Invalid scalar type");
658 return scalar;
662 * The implementation for the keyed scalar type.
664 class KeyedScalar {
665 public:
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,
679 uint32_t aValue);
680 void SetValue(const StaticMutexAutoLock& locker, const nsAString& aKey,
681 bool aValue);
682 void AddValue(const StaticMutexAutoLock& locker, const nsAString& aKey,
683 uint32_t aValue);
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;
698 private:
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.
721 return;
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.
735 return;
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.
749 return;
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.
760 * ScalarUnsigned).
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.
772 continue;
774 if (NS_FAILED(rv)) {
775 return rv;
778 // Append it to value list.
779 aValues.AppendElement(
780 std::make_pair(nsCString(entry.GetKey()), scalarValue));
783 return NS_OK;
786 // Forward declaration
787 nsresult internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock,
788 const ScalarKey& aId,
789 ProcessID aProcessStorage,
790 KeyedScalar** aRet);
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,
803 ScalarBase** aRet) {
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),
813 false};
814 ProcessID process = ProcessID::Parent;
815 nsresult rv = internal_GetKeyedScalarByEnum(locker, scalarUnknownUniqueId,
816 process, &scalarUnknown);
817 if (NS_FAILED(rv)) {
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)) {
833 *aRet = scalar;
834 return ScalarResult::Ok;
837 ScalarKey uniqueId;
838 nsresult rv = internal_GetEnumByScalarName(locker, mScalarName, &uniqueId);
839 if (NS_FAILED(rv)) {
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;
852 ScalarKey uniqueId{
853 static_cast<uint32_t>(
854 mozilla::Telemetry::ScalarID::TELEMETRY_KEYED_SCALARS_EXCEED_LIMIT),
855 false};
857 ProcessID process = ProcessID::Parent;
858 nsresult rv =
859 internal_GetKeyedScalarByEnum(locker, uniqueId, process, &scalarExceed);
861 if (NS_FAILED(rv)) {
862 return ScalarResult::TooManyKeys;
865 scalarExceed->AddValue(locker, NS_ConvertUTF8toUTF16(info.name()), 1);
867 return ScalarResult::TooManyKeys;
870 scalar = internal_ScalarAllocate(info);
871 if (!scalar) {
872 return ScalarResult::InvalidType;
875 mScalarKeys.InsertOrUpdate(utf8Key, UniquePtr<ScalarBase>(scalar));
877 *aRet = 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);
886 return n;
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) {
892 return true;
895 for (uint32_t i = 0; i < mScalarKeyCount; ++i) {
896 uint32_t stringIndex = gScalarKeysTable[mScalarKeyOffset + i];
897 if (aKey.EqualsASCII(&gScalarsStringTable[stringIndex])) {
898 return true;
902 return false;
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;
925 } // namespace
927 ////////////////////////////////////////////////////////////////////////
928 ////////////////////////////////////////////////////////////////////////
930 // PRIVATE STATE, SHARED BY ALL THREADS
932 namespace {
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;
953 } // namespace
955 ////////////////////////////////////////////////////////////////////////
956 ////////////////////////////////////////////////////////////////////////
958 // PRIVATE: helpers for the external interface
960 namespace {
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
984 * process.
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,
989 * false otherwise.
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) {
1011 return false;
1014 bool canRecordDataset = CanRecordDataset(info.dataset, canRecordBase,
1015 internal_CanRecordExtended(lock));
1016 if (!canRecordDataset) {
1017 return false;
1020 return true;
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
1046 // settings?
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.
1074 * @return
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());
1087 if (!entry) {
1088 return NS_ERROR_INVALID_ARG;
1090 *aId = entry->GetData();
1091 return NS_OK;
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.
1106 * @return
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
1134 // available.
1135 ScalarStorageMapType* const scalarStorage =
1136 processStorage.GetOrInsertNew(storageId);
1138 // Check if the scalar is already allocated in the parent or in the child
1139 // storage.
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.
1143 if (aId.dynamic) {
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.
1155 *aRet = scalar;
1156 return NS_OK;
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);
1169 if (!scalar) {
1170 return NS_ERROR_INVALID_ARG;
1173 scalarStorage->InsertOrUpdate(aId.id, UniquePtr<ScalarBase>(scalar));
1174 *aRet = scalar;
1175 return NS_OK;
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);
1184 PROFILER_MARKER(
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);
1200 PROFILER_MARKER(
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(...) \
1210 do { \
1211 if (profiler_thread_is_being_profiled_for_markers()) { \
1212 internal_profilerMarker_impl(__VA_ARGS__); \
1214 } while (false)
1216 } // namespace
1218 ////////////////////////////////////////////////////////////////////////
1219 ////////////////////////////////////////////////////////////////////////
1221 // PRIVATE: thread-unsafe helpers for the keyed scalars
1223 namespace {
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.
1237 * @return
1238 * NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed
1239 * string scalar.
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
1267 // available.
1268 KeyedScalarStorageMapType* const scalarStorage =
1269 processStorage.GetOrInsertNew(storageId);
1271 if (scalarStorage->Get(aId.id, &scalar)) {
1272 *aRet = scalar;
1273 return NS_OK;
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));
1289 *aRet = scalar;
1290 return NS_OK;
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()) {
1321 return;
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());
1344 if (existingKey) {
1345 continue;
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
1362 * scalars.
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.
1398 continue;
1400 if (NS_FAILED(rv)) {
1401 return 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());
1412 return NS_OK;
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
1422 * scalars.
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;
1453 nsresult rv =
1454 scalar->GetValue(aStoreName, aClearScalars, scalarKeyedData);
1455 if (NS_FAILED(rv)) {
1456 return rv;
1458 if (scalarKeyedData.Length() == 0) {
1459 // Don't bother with empty keyed scalars.
1460 continue;
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());
1471 return NS_OK;
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.
1489 nsresult rv =
1490 internal_ScalarSnapshotter(aLock, aScalarsToReflect, aDataset,
1491 gScalarStorageMap, false, /*aIsBuiltinDynamic*/
1492 aClearScalars, aStoreName);
1493 if (NS_FAILED(rv)) {
1494 return 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)) {
1503 return rv;
1506 return NS_OK;
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)) {
1529 return 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)) {
1538 return rv;
1541 return NS_OK;
1544 } // namespace
1546 // helpers for recording/applying scalar operations
1547 namespace {
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)) {
1554 return;
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.");
1561 continue;
1564 if (internal_IsKeyedScalar(lock, uniqueId)) {
1565 continue;
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)) {
1572 continue;
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
1582 // child processes.
1583 ScalarBase* scalar = nullptr;
1584 nsresult rv =
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");
1591 continue;
1594 if (upd.mData.isNothing()) {
1595 MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
1596 continue;
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.");
1612 continue;
1614 scalar->SetValue(upd.mData->as<uint32_t>());
1615 break;
1616 case nsITelemetry::SCALAR_TYPE_BOOLEAN:
1617 if (!upd.mData->is<bool>()) {
1618 NS_WARNING(
1619 "Attempting to set a boolean scalar to a non-boolean.");
1620 continue;
1622 scalar->SetValue(upd.mData->as<bool>());
1623 break;
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.");
1627 continue;
1629 scalar->SetValue(upd.mData->as<nsString>());
1630 break;
1632 break;
1634 case ScalarActionType::eAdd: {
1635 if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
1636 NS_WARNING("Attempting to add on a non count scalar.");
1637 continue;
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.");
1642 continue;
1644 scalar->AddValue(upd.mData->as<uint32_t>());
1645 break;
1647 default:
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)) {
1658 return;
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.");
1665 continue;
1668 if (!internal_IsKeyedScalar(lock, uniqueId)) {
1669 continue;
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)) {
1676 continue;
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
1686 // child processes.
1687 KeyedScalar* scalar = nullptr;
1688 nsresult rv =
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");
1695 continue;
1698 if (upd.mData.isNothing()) {
1699 MOZ_ASSERT(false, "There is no data in the KeyedScalarAction.");
1700 continue;
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.");
1716 continue;
1718 scalar->SetValue(lock, NS_ConvertUTF8toUTF16(upd.mKey),
1719 upd.mData->as<uint32_t>());
1720 break;
1721 case nsITelemetry::SCALAR_TYPE_BOOLEAN:
1722 if (!upd.mData->is<bool>()) {
1723 NS_WARNING(
1724 "Attempting to set a boolean scalar to a non-boolean.");
1725 continue;
1727 scalar->SetValue(lock, NS_ConvertUTF8toUTF16(upd.mKey),
1728 upd.mData->as<bool>());
1729 break;
1730 default:
1731 NS_WARNING("Unsupported type coming from scalar child updates.");
1733 break;
1735 case ScalarActionType::eAdd: {
1736 if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
1737 NS_WARNING("Attempting to add on a non count scalar.");
1738 continue;
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.");
1743 continue;
1745 scalar->AddValue(lock, NS_ConvertUTF8toUTF16(upd.mKey),
1746 upd.mData->as<uint32_t>());
1747 break;
1749 default:
1750 NS_WARNING(
1751 "Unsupported action coming from keyed scalar child updates.");
1756 } // namespace
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
1768 // in BloatView.
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({
1797 DynamicScalarInfo{
1798 nsITelemetry::SCALAR_TYPE_COUNT,
1799 true /* recordOnRelease */,
1800 false /* expired */,
1801 nsAutoCString("telemetry.dynamic_event_counts"),
1802 true /* keyed */,
1803 {} /* stores */,
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.");
1844 return;
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.
1852 return;
1855 internal_profilerMarker(locker, ScalarActionType::eAdd, ScalarVariant(aValue),
1856 uniqueId);
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));
1863 return;
1866 ScalarBase* scalar = nullptr;
1867 nsresult rv =
1868 internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1869 if (NS_FAILED(rv)) {
1870 return;
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.");
1887 return;
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.
1895 return;
1898 internal_profilerMarker(locker, ScalarActionType::eAdd, ScalarVariant(aValue),
1899 uniqueId, aKey);
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));
1906 return;
1909 KeyedScalar* scalar = nullptr;
1910 nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
1911 ProcessID::Parent, &scalar);
1912 if (NS_FAILED(rv)) {
1913 return;
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.");
1928 return;
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.
1936 return;
1939 internal_profilerMarker(locker, ScalarActionType::eSet, ScalarVariant(aValue),
1940 uniqueId);
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));
1947 return;
1950 ScalarBase* scalar = nullptr;
1951 nsresult rv =
1952 internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1953 if (NS_FAILED(rv)) {
1954 return;
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.");
1970 return;
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.
1978 return;
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)));
1989 return;
1992 ScalarBase* scalar = nullptr;
1993 nsresult rv =
1994 internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
1995 if (NS_FAILED(rv)) {
1996 return;
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.");
2011 return;
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.
2019 return;
2022 internal_profilerMarker(locker, ScalarActionType::eSet, ScalarVariant(aValue),
2023 uniqueId);
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));
2030 return;
2033 ScalarBase* scalar = nullptr;
2034 nsresult rv =
2035 internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar);
2036 if (NS_FAILED(rv)) {
2037 return;
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.");
2054 return;
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.
2062 return;
2065 internal_profilerMarker(locker, ScalarActionType::eSet, ScalarVariant(aValue),
2066 uniqueId, aKey);
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));
2073 return;
2076 KeyedScalar* scalar = nullptr;
2077 nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
2078 ProcessID::Parent, &scalar);
2079 if (NS_FAILED(rv)) {
2080 return;
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.");
2097 return;
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.
2105 return;
2108 internal_profilerMarker(locker, ScalarActionType::eSet, ScalarVariant(aValue),
2109 uniqueId, aKey);
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));
2116 return;
2119 KeyedScalar* scalar = nullptr;
2120 nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
2121 ProcessID::Parent, &scalar);
2122 if (NS_FAILED(rv)) {
2123 return;
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,
2133 bool aFilterTest,
2134 const nsACString& aStoreName) {
2135 MOZ_ASSERT(
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));
2144 if (!root_obj) {
2145 return NS_ERROR_FAILURE;
2147 aResult.setObject(*root_obj);
2149 // Return `{}` in child processes.
2150 if (!XRE_IsParentProcess()) {
2151 return NS_OK;
2154 // Only lock the mutex while accessing our data, without locking any JS
2155 // related code.
2156 ScalarSnapshotTable scalarsToReflect;
2158 StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2160 nsresult rv = internal_GetScalarSnapshot(locker, scalarsToReflect, aDataset,
2161 aClearScalars, aStoreName);
2162 if (NS_FAILED(rv)) {
2163 return 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) {
2186 continue;
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)) {
2194 return rv;
2197 // Add it to the scalar object.
2198 if (!JS_DefineProperty(aCx, processObj, scalarName, scalarJsValue,
2199 JSPROP_ENUMERATE)) {
2200 return NS_ERROR_FAILURE;
2205 return NS_OK;
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) {
2212 MOZ_ASSERT(
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));
2221 if (!root_obj) {
2222 return NS_ERROR_FAILURE;
2224 aResult.setObject(*root_obj);
2226 // Return `{}` in child processes.
2227 if (!XRE_IsParentProcess()) {
2228 return NS_OK;
2231 // Only lock the mutex while accessing our data, without locking any JS
2232 // related code.
2233 KeyedScalarSnapshotTable scalarsToReflect;
2235 StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2237 nsresult rv = internal_GetKeyedScalarSnapshot(
2238 locker, scalarsToReflect, aDataset, aClearScalars, aStoreName);
2239 if (NS_FAILED(rv)) {
2240 return 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();
2258 i++) {
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) {
2264 continue;
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
2272 // object.
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)) {
2283 return 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;
2302 return NS_OK;
2305 nsresult TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
2306 JS::Handle<JS::Value> aScalarData,
2307 JSContext* cx) {
2308 MOZ_ASSERT(XRE_IsParentProcess(),
2309 "Dynamic scalars should only be created in the parent process.");
2311 if (!IsValidIdentifierString(aCategoryName, kMaximumCategoryNameLength, true,
2312 false)) {
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) &&
2371 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).
2382 bool keyed = 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 "
2436 "string.",
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
2456 // registration.
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);
2475 return NS_OK;
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()) {
2493 return;
2496 StaticMutexAutoLock lock(gTelemetryScalarsMutex);
2498 ScalarKey scalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_EVENT_COUNTS)};
2499 KeyedScalar* scalar = nullptr;
2500 nsresult rv =
2501 internal_GetKeyedScalarByEnum(lock, scalarKey, aProcessType, &scalar);
2503 if (NS_FAILED(rv)) {
2504 NS_WARNING("NS_FAILED getting keyed scalar for event summary. Wut.");
2505 return;
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()) {
2521 return;
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);
2540 size_t n = 0;
2542 auto getSizeOf = [aMallocSizeOf](auto& storageMap) {
2543 size_t partial = 0;
2544 for (const auto& scalarStorage : storageMap.Values()) {
2545 for (const auto& scalar : scalarStorage->Values()) {
2546 partial += scalar->SizeOfIncludingThis(aMallocSizeOf);
2549 return partial;
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);
2558 return n;
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 "
2566 "parent process.");
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)) {
2590 return;
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
2619 * structure.
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!
2626 return;
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
2638 * state.
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,
2650 recordOnRelease,
2651 def.expired,
2652 def.name,
2653 def.keyed,
2654 {} /* stores */});
2658 StaticMutexAutoLock locker(gTelemetryScalarsMutex);
2659 internal_RegisterScalars(locker, dynamicStubs);
2663 nsresult TelemetryScalar::GetAllStores(StringHashSet& set) {
2664 // Static stores
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;
2674 // Dynamic stores
2675 for (auto& ptr : *gDynamicStoreNames) {
2676 nsAutoCString store;
2677 ptr->ToUTF8String(store);
2678 if (!set.Insert(store, mozilla::fallible)) {
2679 return NS_ERROR_FAILURE;
2683 return NS_OK;