/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsITelemetry.h" #include "nsIVariant.h" #include "nsVariant.h" #include "nsHashKeys.h" #include "nsBaseHashtable.h" #include "nsClassHashtable.h" #include "nsIXPConnect.h" #include "nsContentUtils.h" #include "nsThreadUtils.h" #include "mozilla/StaticMutex.h" #include "mozilla/Unused.h" #include "TelemetryCommon.h" #include "TelemetryScalar.h" #include "TelemetryScalarData.h" using mozilla::StaticMutex; using mozilla::StaticMutexAutoLock; using mozilla::Telemetry::Common::AutoHashtable; using mozilla::Telemetry::Common::IsExpiredVersion; using mozilla::Telemetry::Common::CanRecordDataset; using mozilla::Telemetry::Common::IsInDataset; using mozilla::Telemetry::Common::LogToBrowserConsole; //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // Naming: there are two kinds of functions in this file: // // * Functions named internal_*: these can only be reached via an // interface function (TelemetryScalar::*). They expect the interface // function to have acquired |gTelemetryScalarsMutex|, so they do not // have to be thread-safe. // // * Functions named TelemetryScalar::*. This is the external interface. // Entries and exits to these functions are serialised using // |gTelemetryScalarsMutex|. // // Avoiding races and deadlocks: // // All functions in the external interface (TelemetryScalar::*) are // serialised using the mutex |gTelemetryScalarsMutex|. This means // that the external interface is thread-safe, and many of the // internal_* functions can ignore thread safety. But it also brings // a danger of deadlock if any function in the external interface can // get back to that interface. That is, we will deadlock on any call // chain like this // // TelemetryScalar::* -> .. any functions .. -> TelemetryScalar::* // // To reduce the danger of that happening, observe the following rules: // // * No function in TelemetryScalar::* may directly call, nor take the // address of, any other function in TelemetryScalar::*. // // * No internal function internal_* may call, nor take the address // of, any function in TelemetryScalar::*. //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE TYPES namespace { const uint32_t kMaximumNumberOfKeys = 100; const uint32_t kMaximumKeyStringLength = 70; const uint32_t kMaximumStringValueLength = 50; const uint32_t kScalarCount = static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount); enum class ScalarResult : uint8_t { // Nothing went wrong. Ok, // General Scalar Errors OperationNotSupported, InvalidType, InvalidValue, // Keyed Scalar Errors KeyTooLong, TooManyKeys, // String Scalar Errors StringTooLong, // Unsigned Scalar Errors UnsignedNegativeValue, UnsignedTruncatedValue }; typedef nsBaseHashtableET<nsDepCharHashKey, mozilla::Telemetry::ScalarID> CharPtrEntryType; typedef AutoHashtable<CharPtrEntryType> ScalarMapType; /** * Map the error codes used internally to NS_* error codes. * @param aSr The error code used internally in this module. * @return {nsresult} A NS_* error code. */ nsresult MapToNsResult(ScalarResult aSr) { switch (aSr) { case ScalarResult::Ok: return NS_OK; case ScalarResult::OperationNotSupported: return NS_ERROR_NOT_AVAILABLE; case ScalarResult::StringTooLong: // We don't want to throw if we're setting a string that is too long. return NS_OK; case ScalarResult::InvalidType: case ScalarResult::InvalidValue: case ScalarResult::KeyTooLong: return NS_ERROR_ILLEGAL_VALUE; case ScalarResult::TooManyKeys: return NS_ERROR_FAILURE; case ScalarResult::UnsignedNegativeValue: case ScalarResult::UnsignedTruncatedValue: // We shouldn't throw if trying to set a negative number or are truncated, // only warn the user. return NS_OK; } return NS_ERROR_FAILURE; } bool IsValidEnumId(mozilla::Telemetry::ScalarID aID) { return aID < mozilla::Telemetry::ScalarID::ScalarCount; } // Implements the methods for ScalarInfo. const char * ScalarInfo::name() const { return &gScalarsStringTable[this->name_offset]; } const char * ScalarInfo::expiration() const { return &gScalarsStringTable[this->expiration_offset]; } /** * The base scalar object, that servers as a common ancestor for storage * purposes. */ class ScalarBase { public: virtual ~ScalarBase() {}; // Set, Add and SetMaximum functions as described in the Telemetry IDL. virtual ScalarResult SetValue(nsIVariant* aValue) = 0; virtual ScalarResult AddValue(nsIVariant* aValue) { return ScalarResult::OperationNotSupported; } virtual ScalarResult SetMaximum(nsIVariant* aValue) { return ScalarResult::OperationNotSupported; } // Convenience methods used by the C++ API. virtual void SetValue(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); } virtual ScalarResult SetValue(const nsAString& aValue) { return HandleUnsupported(); } virtual void SetValue(bool aValue) { mozilla::Unused << HandleUnsupported(); } virtual void AddValue(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); } virtual void SetMaximum(uint32_t aValue) { mozilla::Unused << HandleUnsupported(); } // GetValue is used to get the value of the scalar when persisting it to JS. virtual nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const = 0; // To measure the memory stats. virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; private: ScalarResult HandleUnsupported() const; }; ScalarResult ScalarBase::HandleUnsupported() const { MOZ_ASSERT(false, "This operation is not support for this scalar type."); return ScalarResult::OperationNotSupported; } /** * The implementation for the unsigned int scalar type. */ class ScalarUnsigned : public ScalarBase { public: using ScalarBase::SetValue; ScalarUnsigned() : mStorage(0) {}; ~ScalarUnsigned() {}; ScalarResult SetValue(nsIVariant* aValue) final; void SetValue(uint32_t aValue) final; ScalarResult AddValue(nsIVariant* aValue) final; void AddValue(uint32_t aValue) final; ScalarResult SetMaximum(nsIVariant* aValue) final; void SetMaximum(uint32_t aValue) final; nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final; size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final; private: uint32_t mStorage; ScalarResult CheckInput(nsIVariant* aValue); // Prevent copying. ScalarUnsigned(const ScalarUnsigned& aOther) = delete; void operator=(const ScalarUnsigned& aOther) = delete; }; ScalarResult ScalarUnsigned::SetValue(nsIVariant* aValue) { ScalarResult sr = CheckInput(aValue); if (sr == ScalarResult::UnsignedNegativeValue) { return sr; } if (NS_FAILED(aValue->GetAsUint32(&mStorage))) { return ScalarResult::InvalidValue; } return sr; } void ScalarUnsigned::SetValue(uint32_t aValue) { mStorage = aValue; } ScalarResult ScalarUnsigned::AddValue(nsIVariant* aValue) { ScalarResult sr = CheckInput(aValue); if (sr == ScalarResult::UnsignedNegativeValue) { return sr; } uint32_t newAddend = 0; nsresult rv = aValue->GetAsUint32(&newAddend); if (NS_FAILED(rv)) { return ScalarResult::InvalidValue; } mStorage += newAddend; return sr; } void ScalarUnsigned::AddValue(uint32_t aValue) { mStorage += aValue; } ScalarResult ScalarUnsigned::SetMaximum(nsIVariant* aValue) { ScalarResult sr = CheckInput(aValue); if (sr == ScalarResult::UnsignedNegativeValue) { return sr; } uint32_t newValue = 0; nsresult rv = aValue->GetAsUint32(&newValue); if (NS_FAILED(rv)) { return ScalarResult::InvalidValue; } if (newValue > mStorage) { mStorage = newValue; } return sr; } void ScalarUnsigned::SetMaximum(uint32_t aValue) { if (aValue > mStorage) { mStorage = aValue; } } nsresult ScalarUnsigned::GetValue(nsCOMPtr<nsIVariant>& aResult) const { nsCOMPtr<nsIWritableVariant> outVar(new nsVariant()); nsresult rv = outVar->SetAsUint32(mStorage); if (NS_FAILED(rv)) { return rv; } aResult = outVar.forget(); return NS_OK; } size_t ScalarUnsigned::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this); } ScalarResult ScalarUnsigned::CheckInput(nsIVariant* aValue) { // If this is a floating point value/double, we will probably get truncated. uint16_t type; aValue->GetDataType(&type); if (type == nsIDataType::VTYPE_FLOAT || type == nsIDataType::VTYPE_DOUBLE) { return ScalarResult::UnsignedTruncatedValue; } int32_t signedTest; // If we're able to cast the number to an int, check its sign. // Warn the user if he's trying to set the unsigned scalar to a negative // number. if (NS_SUCCEEDED(aValue->GetAsInt32(&signedTest)) && signedTest < 0) { return ScalarResult::UnsignedNegativeValue; } return ScalarResult::Ok; } /** * The implementation for the string scalar type. */ class ScalarString : public ScalarBase { public: using ScalarBase::SetValue; ScalarString() : mStorage(EmptyString()) {}; ~ScalarString() {}; ScalarResult SetValue(nsIVariant* aValue) final; ScalarResult SetValue(const nsAString& aValue) final; nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final; size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final; private: nsString mStorage; // Prevent copying. ScalarString(const ScalarString& aOther) = delete; void operator=(const ScalarString& aOther) = delete; }; ScalarResult ScalarString::SetValue(nsIVariant* aValue) { // Check that we got the correct data type. uint16_t type; aValue->GetDataType(&type); if (type != nsIDataType::VTYPE_CHAR && type != nsIDataType::VTYPE_WCHAR && type != nsIDataType::VTYPE_DOMSTRING && type != nsIDataType::VTYPE_CHAR_STR && type != nsIDataType::VTYPE_WCHAR_STR && type != nsIDataType::VTYPE_STRING_SIZE_IS && type != nsIDataType::VTYPE_WSTRING_SIZE_IS && type != nsIDataType::VTYPE_UTF8STRING && type != nsIDataType::VTYPE_CSTRING && type != nsIDataType::VTYPE_ASTRING) { return ScalarResult::InvalidType; } nsAutoString convertedString; nsresult rv = aValue->GetAsAString(convertedString); if (NS_FAILED(rv)) { return ScalarResult::InvalidValue; } return SetValue(convertedString); }; ScalarResult ScalarString::SetValue(const nsAString& aValue) { mStorage = Substring(aValue, 0, kMaximumStringValueLength); if (aValue.Length() > kMaximumStringValueLength) { return ScalarResult::StringTooLong; } return ScalarResult::Ok; } nsresult ScalarString::GetValue(nsCOMPtr<nsIVariant>& aResult) const { nsCOMPtr<nsIWritableVariant> outVar(new nsVariant()); nsresult rv = outVar->SetAsAString(mStorage); if (NS_FAILED(rv)) { return rv; } aResult = outVar.forget(); return NS_OK; } size_t ScalarString::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); n+= mStorage.SizeOfExcludingThisIfUnshared(aMallocSizeOf); return n; } /** * The implementation for the boolean scalar type. */ class ScalarBoolean : public ScalarBase { public: using ScalarBase::SetValue; ScalarBoolean() : mStorage(false) {}; ~ScalarBoolean() {}; ScalarResult SetValue(nsIVariant* aValue) final; void SetValue(bool aValue) final; nsresult GetValue(nsCOMPtr<nsIVariant>& aResult) const final; size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final; private: bool mStorage; // Prevent copying. ScalarBoolean(const ScalarBoolean& aOther) = delete; void operator=(const ScalarBoolean& aOther) = delete; }; ScalarResult ScalarBoolean::SetValue(nsIVariant* aValue) { // Check that we got the correct data type. uint16_t type; aValue->GetDataType(&type); if (type != nsIDataType::VTYPE_BOOL && type != nsIDataType::VTYPE_INT8 && type != nsIDataType::VTYPE_INT16 && type != nsIDataType::VTYPE_INT32 && type != nsIDataType::VTYPE_INT64 && type != nsIDataType::VTYPE_UINT8 && type != nsIDataType::VTYPE_UINT16 && type != nsIDataType::VTYPE_UINT32 && type != nsIDataType::VTYPE_UINT64) { return ScalarResult::InvalidType; } if (NS_FAILED(aValue->GetAsBool(&mStorage))) { return ScalarResult::InvalidValue; } return ScalarResult::Ok; }; void ScalarBoolean::SetValue(bool aValue) { mStorage = aValue; } nsresult ScalarBoolean::GetValue(nsCOMPtr<nsIVariant>& aResult) const { nsCOMPtr<nsIWritableVariant> outVar(new nsVariant()); nsresult rv = outVar->SetAsBool(mStorage); if (NS_FAILED(rv)) { return rv; } aResult = outVar.forget(); return NS_OK; } size_t ScalarBoolean::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this); } /** * Allocate a scalar class given the scalar info. * * @param aInfo The informations for the scalar coming from the definition file. * @return nullptr if the scalar type is unknown, otherwise a valid pointer to the * scalar type. */ ScalarBase* internal_ScalarAllocate(uint32_t aScalarKind) { ScalarBase* scalar = nullptr; switch (aScalarKind) { case nsITelemetry::SCALAR_COUNT: scalar = new ScalarUnsigned(); break; case nsITelemetry::SCALAR_STRING: scalar = new ScalarString(); break; case nsITelemetry::SCALAR_BOOLEAN: scalar = new ScalarBoolean(); break; default: MOZ_ASSERT(false, "Invalid scalar type"); } return scalar; } /** * The implementation for the keyed scalar type. */ class KeyedScalar { public: typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair; explicit KeyedScalar(uint32_t aScalarKind) : mScalarKind(aScalarKind) {}; ~KeyedScalar() {}; // Set, Add and SetMaximum functions as described in the Telemetry IDL. // These methods implicitly instantiate a Scalar[*] for each key. ScalarResult SetValue(const nsAString& aKey, nsIVariant* aValue); ScalarResult AddValue(const nsAString& aKey, nsIVariant* aValue); ScalarResult SetMaximum(const nsAString& aKey, nsIVariant* aValue); // Convenience methods used by the C++ API. void SetValue(const nsAString& aKey, uint32_t aValue); void SetValue(const nsAString& aKey, bool aValue); void AddValue(const nsAString& aKey, uint32_t aValue); void SetMaximum(const nsAString& aKey, uint32_t aValue); // GetValue is used to get the key-value pairs stored in the keyed scalar // when persisting it to JS. nsresult GetValue(nsTArray<KeyValuePair>& aValues) const; // To measure the memory stats. size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); private: typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType; ScalarKeysMapType mScalarKeys; const uint32_t mScalarKind; ScalarResult GetScalarForKey(const nsAString& aKey, ScalarBase** aRet); }; ScalarResult KeyedScalar::SetValue(const nsAString& aKey, nsIVariant* aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { return sr; } return scalar->SetValue(aValue); } ScalarResult KeyedScalar::AddValue(const nsAString& aKey, nsIVariant* aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { return sr; } return scalar->AddValue(aValue); } ScalarResult KeyedScalar::SetMaximum(const nsAString& aKey, nsIVariant* aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { return sr; } return scalar->SetMaximum(aValue); } void KeyedScalar::SetValue(const nsAString& aKey, uint32_t aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar."); return; } return scalar->SetValue(aValue); } void KeyedScalar::SetValue(const nsAString& aKey, bool aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar."); return; } return scalar->SetValue(aValue); } void KeyedScalar::AddValue(const nsAString& aKey, uint32_t aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar."); return; } return scalar->AddValue(aValue); } void KeyedScalar::SetMaximum(const nsAString& aKey, uint32_t aValue) { ScalarBase* scalar = nullptr; ScalarResult sr = GetScalarForKey(aKey, &scalar); if (sr != ScalarResult::Ok) { MOZ_ASSERT(false, "Key too long or too many keys are recorded in the scalar."); return; } return scalar->SetMaximum(aValue); } /** * Get a key-value array with the values for the Keyed Scalar. * @param aValue The array that will hold the key-value pairs. * @return {nsresult} NS_OK or an error value as reported by the * the specific scalar objects implementations (e.g. * ScalarUnsigned). */ nsresult KeyedScalar::GetValue(nsTArray<KeyValuePair>& aValues) const { for (auto iter = mScalarKeys.ConstIter(); !iter.Done(); iter.Next()) { ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data()); // Get the scalar value. nsCOMPtr<nsIVariant> scalarValue; nsresult rv = scalar->GetValue(scalarValue); if (NS_FAILED(rv)) { return rv; } // Append it to value list. aValues.AppendElement(mozilla::MakePair(nsCString(iter.Key()), scalarValue)); } return NS_OK; } /** * Get the scalar for the referenced key. * If there's no such key, instantiate a new Scalar object with the * same type of the Keyed scalar and create the key. */ ScalarResult KeyedScalar::GetScalarForKey(const nsAString& aKey, ScalarBase** aRet) { if (aKey.Length() >= kMaximumKeyStringLength) { return ScalarResult::KeyTooLong; } if (mScalarKeys.Count() >= kMaximumNumberOfKeys) { return ScalarResult::TooManyKeys; } NS_ConvertUTF16toUTF8 utf8Key(aKey); ScalarBase* scalar = nullptr; if (mScalarKeys.Get(utf8Key, &scalar)) { *aRet = scalar; return ScalarResult::Ok; } scalar = internal_ScalarAllocate(mScalarKind); if (!scalar) { return ScalarResult::InvalidType; } mScalarKeys.Put(utf8Key, scalar); *aRet = scalar; return ScalarResult::Ok; } size_t KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { size_t n = aMallocSizeOf(this); for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) { ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data()); n += scalar->SizeOfIncludingThis(aMallocSizeOf); } return n; } typedef nsUint32HashKey ScalarIDHashKey; typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType; typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType; } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE STATE, SHARED BY ALL THREADS namespace { // Set to true once this global state has been initialized. bool gInitDone = false; bool gCanRecordBase; bool gCanRecordExtended; // The Name -> ID cache map. ScalarMapType gScalarNameIDMap(kScalarCount); // The ID -> Scalar Object map. This is a nsClassHashtable, it owns // the scalar instance and takes care of deallocating them when they // get removed from the map. ScalarStorageMapType gScalarStorageMap; // The ID -> Keyed Scalar Object map. As for plain scalars, this is // nsClassHashtable. See above. KeyedScalarStorageMapType gKeyedScalarStorageMap; } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: Function that may call JS code. // NOTE: the functions in this section all run without protection from // |gTelemetryScalarsMutex|. If they held the mutex, there would be the // possibility of deadlock because the JS_ calls that they make may call // back into the TelemetryScalar interface, hence trying to re-acquire the mutex. // // This means that these functions potentially race against threads, but // that seems preferable to risking deadlock. namespace { /** * Checks if the error should be logged. * * @param aSr The error code. * @return true if the error should be logged, false otherwise. */ bool internal_ShouldLogError(ScalarResult aSr) { switch (aSr) { case ScalarResult::StringTooLong: MOZ_FALLTHROUGH; case ScalarResult::KeyTooLong: MOZ_FALLTHROUGH; case ScalarResult::TooManyKeys: MOZ_FALLTHROUGH; case ScalarResult::UnsignedNegativeValue: MOZ_FALLTHROUGH; case ScalarResult::UnsignedTruncatedValue: // Intentional fall-through. return true; default: return false; } // It should never reach this point. return false; } /** * Converts the error code to a human readable error message and prints it to the * browser console. * * @param aScalarName The name of the scalar that raised the error. * @param aSr The error code. */ void internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr) { nsAutoString errorMessage; AppendUTF8toUTF16(aScalarName, errorMessage); switch (aSr) { case ScalarResult::StringTooLong: errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters.")); break; case ScalarResult::KeyTooLong: errorMessage.Append(NS_LITERAL_STRING(" - The key length must be limited to 70 characters.")); break; case ScalarResult::TooManyKeys: errorMessage.Append(NS_LITERAL_STRING(" - Keyed scalars cannot have more than 100 keys.")); break; case ScalarResult::UnsignedNegativeValue: errorMessage.Append(NS_LITERAL_STRING(" - Trying to set an unsigned scalar to a negative number.")); break; case ScalarResult::UnsignedTruncatedValue: errorMessage.Append(NS_LITERAL_STRING(" - Truncating float/double number.")); break; default: // Nothing. return; } LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage); } } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: thread-unsafe helpers for the external interface namespace { bool internal_CanRecordBase() { return gCanRecordBase; } bool internal_CanRecordExtended() { return gCanRecordExtended; } const ScalarInfo& internal_InfoForScalarID(mozilla::Telemetry::ScalarID aId) { return gScalars[static_cast<uint32_t>(aId)]; } /** * Check if the given scalar is a keyed scalar. * * @param aId The scalar enum. * @return true if aId refers to a keyed scalar, false otherwise. */ bool internal_IsKeyedScalar(mozilla::Telemetry::ScalarID aId) { return internal_InfoForScalarID(aId).keyed; } bool internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId) { // Get the scalar info from the id. const ScalarInfo &info = internal_InfoForScalarID(aId); // Can we record at all? bool canRecordBase = internal_CanRecordBase(); if (!canRecordBase) { return false; } bool canRecordDataset = CanRecordDataset(info.dataset, canRecordBase, internal_CanRecordExtended()); if (!canRecordDataset) { return false; } return true; } /** * Get the scalar enum id from the scalar name. * * @param aName The scalar name. * @param aId The output variable to contain the enum. * @return * NS_ERROR_FAILURE if this was called before init is completed. * NS_ERROR_INVALID_ARG if the name can't be found in the scalar definitions. * NS_OK if the scalar was found and aId contains a valid enum id. */ nsresult internal_GetEnumByScalarName(const nsACString& aName, mozilla::Telemetry::ScalarID* aId) { if (!gInitDone) { return NS_ERROR_FAILURE; } CharPtrEntryType *entry = gScalarNameIDMap.GetEntry(PromiseFlatCString(aName).get()); if (!entry) { return NS_ERROR_INVALID_ARG; } *aId = entry->mData; return NS_OK; } /** * Get a scalar object by its enum id. This implicitly allocates the scalar * object in the storage if it wasn't previously allocated. * * @param aId The scalar id. * @param aRes The output variable that stores scalar object. * @return * NS_ERROR_INVALID_ARG if the scalar id is unknown. * NS_ERROR_NOT_AVAILABLE if the scalar is expired. * NS_OK if the scalar was found. If that's the case, aResult contains a * valid pointer to a scalar type. */ nsresult internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet) { if (!IsValidEnumId(aId)) { MOZ_ASSERT(false, "Requested a scalar with an invalid id."); return NS_ERROR_INVALID_ARG; } const uint32_t id = static_cast<uint32_t>(aId); ScalarBase* scalar = nullptr; if (gScalarStorageMap.Get(id, &scalar)) { *aRet = scalar; return NS_OK; } const ScalarInfo &info = gScalars[id]; if (IsExpiredVersion(info.expiration())) { return NS_ERROR_NOT_AVAILABLE; } scalar = internal_ScalarAllocate(info.kind); if (!scalar) { return NS_ERROR_INVALID_ARG; } gScalarStorageMap.Put(id, scalar); *aRet = scalar; return NS_OK; } /** * Get a scalar object by its enum id, if we're allowed to record it. * * @param aId The scalar id. * @return The ScalarBase instance or nullptr if we're not allowed to record * the scalar. */ ScalarBase* internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId) { // Get the scalar by the enum (it also internally checks for aId validity). ScalarBase* scalar = nullptr; nsresult rv = internal_GetScalarByEnum(aId, &scalar); if (NS_FAILED(rv)) { return nullptr; } if (internal_IsKeyedScalar(aId)) { return nullptr; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(aId)) { return nullptr; } return scalar; } } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: thread-unsafe helpers for the keyed scalars namespace { /** * Get a keyed scalar object by its enum id. This implicitly allocates the keyed * scalar object in the storage if it wasn't previously allocated. * * @param aId The scalar id. * @param aRes The output variable that stores scalar object. * @return * NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string * scalar. * NS_ERROR_NOT_AVAILABLE if the scalar is expired. * NS_OK if the scalar was found. If that's the case, aResult contains a * valid pointer to a scalar type. */ nsresult internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aRet) { if (!IsValidEnumId(aId)) { MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id."); return NS_ERROR_INVALID_ARG; } const uint32_t id = static_cast<uint32_t>(aId); KeyedScalar* scalar = nullptr; if (gKeyedScalarStorageMap.Get(id, &scalar)) { *aRet = scalar; return NS_OK; } const ScalarInfo &info = gScalars[id]; if (IsExpiredVersion(info.expiration())) { return NS_ERROR_NOT_AVAILABLE; } // We don't currently support keyed string scalars. Disable them. if (info.kind == nsITelemetry::SCALAR_STRING) { MOZ_ASSERT(false, "Keyed string scalars are not currently supported."); return NS_ERROR_INVALID_ARG; } scalar = new KeyedScalar(info.kind); if (!scalar) { return NS_ERROR_INVALID_ARG; } gKeyedScalarStorageMap.Put(id, scalar); *aRet = scalar; return NS_OK; } /** * Get a keyed scalar object by its enum id, if we're allowed to record it. * * @param aId The scalar id. * @return The KeyedScalar instance or nullptr if we're not allowed to record * the scalar. */ KeyedScalar* internal_GetRecordableKeyedScalar(mozilla::Telemetry::ScalarID aId) { // Get the scalar by the enum (it also internally checks for aId validity). KeyedScalar* scalar = nullptr; nsresult rv = internal_GetKeyedScalarByEnum(aId, &scalar); if (NS_FAILED(rv)) { return nullptr; } if (!internal_IsKeyedScalar(aId)) { return nullptr; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(aId)) { return nullptr; } return scalar; } } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars:: // This is a StaticMutex rather than a plain Mutex (1) so that // it gets initialised in a thread-safe manner the first time // it is used, and (2) because it is never de-initialised, and // a normal Mutex would show up as a leak in BloatView. StaticMutex // also has the "OffTheBooks" property, so it won't show as a leak // in BloatView. // Another reason to use a StaticMutex instead of a plain Mutex is // that, due to the nature of Telemetry, we cannot rely on having a // mutex initialized in InitializeGlobalState. Unfortunately, we // cannot make sure that no other function is called before this point. static StaticMutex gTelemetryScalarsMutex; void TelemetryScalar::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtended) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); MOZ_ASSERT(!gInitDone, "TelemetryScalar::InitializeGlobalState " "may only be called once"); gCanRecordBase = aCanRecordBase; gCanRecordExtended = aCanRecordExtended; // Populate the static scalar name->id cache. Note that the scalar names are // statically allocated and come from the automatically generated TelemetryScalarData.h. uint32_t scalarCount = static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount); for (uint32_t i = 0; i < scalarCount; i++) { CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(gScalars[i].name()); entry->mData = static_cast<mozilla::Telemetry::ScalarID>(i); } #ifdef DEBUG gScalarNameIDMap.MarkImmutable(); #endif gInitDone = true; } void TelemetryScalar::DeInitializeGlobalState() { StaticMutexAutoLock locker(gTelemetryScalarsMutex); gCanRecordBase = false; gCanRecordExtended = false; gScalarNameIDMap.Clear(); gScalarStorageMap.Clear(); gKeyedScalarStorageMap.Clear(); gInitDone = false; } void TelemetryScalar::SetCanRecordBase(bool b) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); gCanRecordBase = b; } void TelemetryScalar::SetCanRecordExtended(bool b) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); gCanRecordExtended = b; } /** * Adds the value to the given scalar. * * @param aName The scalar name. * @param aVal The numeric value to add to the scalar. * @param aCx The JS context. * @return NS_OK if the value was added or if we're not allowed to record to this * dataset. Otherwise, return an error. */ nsresult TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx) { // Unpack the aVal to nsIVariant. This uses the JS context. nsCOMPtr<nsIVariant> unpackedVal; nsresult rv = nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal)); if (NS_FAILED(rv)) { return rv; } ScalarResult sr; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); mozilla::Telemetry::ScalarID id; rv = internal_GetEnumByScalarName(aName, &id); if (NS_FAILED(rv)) { return rv; } // We're trying to set a plain scalar, so make sure this is one. if (internal_IsKeyedScalar(id)) { return NS_ERROR_ILLEGAL_VALUE; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(id)) { return NS_OK; } // Finally get the scalar. ScalarBase* scalar = nullptr; rv = internal_GetScalarByEnum(id, &scalar); if (NS_FAILED(rv)) { // Don't throw on expired scalars. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK; } return rv; } sr = scalar->AddValue(unpackedVal); } // Warn the user about the error if we need to. if (internal_ShouldLogError(sr)) { internal_LogScalarError(aName, sr); } return MapToNsResult(sr); } /** * Adds the value to the given scalar. * * @param aName The scalar name. * @param aKey The key name. * @param aVal The numeric value to add to the scalar. * @param aCx The JS context. * @return NS_OK if the value was added or if we're not allow to record to this * dataset. Otherwise, return an error. */ nsresult TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal, JSContext* aCx) { // Unpack the aVal to nsIVariant. This uses the JS context. nsCOMPtr<nsIVariant> unpackedVal; nsresult rv = nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal)); if (NS_FAILED(rv)) { return rv; } ScalarResult sr; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); mozilla::Telemetry::ScalarID id; rv = internal_GetEnumByScalarName(aName, &id); if (NS_FAILED(rv)) { return rv; } // Make sure this is a keyed scalar. if (!internal_IsKeyedScalar(id)) { return NS_ERROR_ILLEGAL_VALUE; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(id)) { return NS_OK; } // Finally get the scalar. KeyedScalar* scalar = nullptr; rv = internal_GetKeyedScalarByEnum(id, &scalar); if (NS_FAILED(rv)) { // Don't throw on expired scalars. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK; } return rv; } sr = scalar->AddValue(aKey, unpackedVal); } // Warn the user about the error if we need to. if (internal_ShouldLogError(sr)) { internal_LogScalarError(aName, sr); } return MapToNsResult(sr); } /** * Adds the value to the given scalar. * * @param aId The scalar enum id. * @param aVal The numeric value to add to the scalar. */ void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; } scalar->AddValue(aValue); } /** * Adds the value to the given keyed scalar. * * @param aId The scalar enum id. * @param aKey The key name. * @param aVal The numeric value to add to the scalar. */ void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; } scalar->AddValue(aKey, aValue); } /** * Sets the scalar to the given value. * * @param aName The scalar name. * @param aVal The value to set the scalar to. * @param aCx The JS context. * @return NS_OK if the value was added or if we're not allow to record to this * dataset. Otherwise, return an error. */ nsresult TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx) { // Unpack the aVal to nsIVariant. This uses the JS context. nsCOMPtr<nsIVariant> unpackedVal; nsresult rv = nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal)); if (NS_FAILED(rv)) { return rv; } ScalarResult sr; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); mozilla::Telemetry::ScalarID id; rv = internal_GetEnumByScalarName(aName, &id); if (NS_FAILED(rv)) { return rv; } // We're trying to set a plain scalar, so make sure this is one. if (internal_IsKeyedScalar(id)) { return NS_ERROR_ILLEGAL_VALUE; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(id)) { return NS_OK; } // Finally get the scalar. ScalarBase* scalar = nullptr; rv = internal_GetScalarByEnum(id, &scalar); if (NS_FAILED(rv)) { // Don't throw on expired scalars. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK; } return rv; } sr = scalar->SetValue(unpackedVal); } // Warn the user about the error if we need to. if (internal_ShouldLogError(sr)) { internal_LogScalarError(aName, sr); } return MapToNsResult(sr); } /** * Sets the keyed scalar to the given value. * * @param aName The scalar name. * @param aKey The key name. * @param aVal The value to set the scalar to. * @param aCx The JS context. * @return NS_OK if the value was added or if we're not allow to record to this * dataset. Otherwise, return an error. */ nsresult TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal, JSContext* aCx) { // Unpack the aVal to nsIVariant. This uses the JS context. nsCOMPtr<nsIVariant> unpackedVal; nsresult rv = nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal)); if (NS_FAILED(rv)) { return rv; } ScalarResult sr; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); mozilla::Telemetry::ScalarID id; rv = internal_GetEnumByScalarName(aName, &id); if (NS_FAILED(rv)) { return rv; } // We're trying to set a keyed scalar. Report an error if this isn't one. if (!internal_IsKeyedScalar(id)) { return NS_ERROR_ILLEGAL_VALUE; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(id)) { return NS_OK; } // Finally get the scalar. KeyedScalar* scalar = nullptr; rv = internal_GetKeyedScalarByEnum(id, &scalar); if (NS_FAILED(rv)) { // Don't throw on expired scalars. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK; } return rv; } sr = scalar->SetValue(aKey, unpackedVal); } // Warn the user about the error if we need to. if (internal_ShouldLogError(sr)) { internal_LogScalarError(aName, sr); } return MapToNsResult(sr); } /** * Sets the scalar to the given numeric value. * * @param aId The scalar enum id. * @param aValue The numeric, unsigned value to set the scalar to. */ void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; } scalar->SetValue(aValue); } /** * Sets the scalar to the given string value. * * @param aId The scalar enum id. * @param aValue The string value to set the scalar to. */ void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; } scalar->SetValue(aValue); } /** * Sets the scalar to the given boolean value. * * @param aId The scalar enum id. * @param aValue The boolean value to set the scalar to. */ void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; } scalar->SetValue(aValue); } /** * Sets the keyed scalar to the given numeric value. * * @param aId The scalar enum id. * @param aKey The scalar key. * @param aValue The numeric, unsigned value to set the scalar to. */ void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; } scalar->SetValue(aKey, aValue); } /** * Sets the scalar to the given boolean value. * * @param aId The scalar enum id. * @param aKey The scalar key. * @param aValue The boolean value to set the scalar to. */ void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; } scalar->SetValue(aKey, aValue); } /** * Sets the scalar to the maximum of the current and the passed value. * * @param aName The scalar name. * @param aVal The numeric value to set the scalar to. * @param aCx The JS context. * @return NS_OK if the value was added or if we're not allow to record to this * dataset. Otherwise, return an error. */ nsresult TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx) { // Unpack the aVal to nsIVariant. This uses the JS context. nsCOMPtr<nsIVariant> unpackedVal; nsresult rv = nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal)); if (NS_FAILED(rv)) { return rv; } ScalarResult sr; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); mozilla::Telemetry::ScalarID id; rv = internal_GetEnumByScalarName(aName, &id); if (NS_FAILED(rv)) { return rv; } // Make sure this is not a keyed scalar. if (internal_IsKeyedScalar(id)) { return NS_ERROR_ILLEGAL_VALUE; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(id)) { return NS_OK; } // Finally get the scalar. ScalarBase* scalar = nullptr; rv = internal_GetScalarByEnum(id, &scalar); if (NS_FAILED(rv)) { // Don't throw on expired scalars. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK; } return rv; } sr = scalar->SetMaximum(unpackedVal); } // Warn the user about the error if we need to. if (internal_ShouldLogError(sr)) { internal_LogScalarError(aName, sr); } return MapToNsResult(sr); } /** * Sets the scalar to the maximum of the current and the passed value. * * @param aName The scalar name. * @param aKey The key name. * @param aVal The numeric value to set the scalar to. * @param aCx The JS context. * @return NS_OK if the value was added or if we're not allow to record to this * dataset. Otherwise, return an error. */ nsresult TelemetryScalar::SetMaximum(const nsACString& aName, const nsAString& aKey, JS::HandleValue aVal, JSContext* aCx) { // Unpack the aVal to nsIVariant. This uses the JS context. nsCOMPtr<nsIVariant> unpackedVal; nsresult rv = nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(unpackedVal)); if (NS_FAILED(rv)) { return rv; } ScalarResult sr; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); mozilla::Telemetry::ScalarID id; rv = internal_GetEnumByScalarName(aName, &id); if (NS_FAILED(rv)) { return rv; } // Make sure this is a keyed scalar. if (!internal_IsKeyedScalar(id)) { return NS_ERROR_ILLEGAL_VALUE; } // Are we allowed to record this scalar? if (!internal_CanRecordForScalarID(id)) { return NS_OK; } // Finally get the scalar. KeyedScalar* scalar = nullptr; rv = internal_GetKeyedScalarByEnum(id, &scalar); if (NS_FAILED(rv)) { // Don't throw on expired scalars. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK; } return rv; } sr = scalar->SetMaximum(aKey, unpackedVal); } // Warn the user about the error if we need to. if (internal_ShouldLogError(sr)) { internal_LogScalarError(aName, sr); } return MapToNsResult(sr); } /** * Sets the scalar to the maximum of the current and the passed value. * * @param aId The scalar enum id. * @param aValue The numeric value to set the scalar to. */ void TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; } scalar->SetMaximum(aValue); } /** * Sets the keyed scalar to the maximum of the current and the passed value. * * @param aId The scalar enum id. * @param aKey The key name. * @param aValue The numeric value to set the scalar to. */ void TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; } scalar->SetMaximum(aKey, aValue); } /** * Serializes the scalars from the given dataset to a json-style object and resets them. * The returned structure looks like {"group1.probe":1,"group1.other_probe":false,...}. * * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN. * @param aClear Whether to clear out the scalars after snapshotting. */ nsresult TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx, uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult) { // If no arguments were passed in, apply the default value. if (!optional_argc) { aClearScalars = false; } JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx)); if (!root_obj) { return NS_ERROR_FAILURE; } aResult.setObject(*root_obj); // Only lock the mutex while accessing our data, without locking any JS related code. typedef mozilla::Pair<const char*, nsCOMPtr<nsIVariant>> DataPair; nsTArray<DataPair> scalarsToReflect; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); // Iterate the scalars in gScalarStorageMap. The storage may contain empty or yet to be // initialized scalars. for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data()); // Get the informations for this scalar. const ScalarInfo& info = gScalars[iter.Key()]; // Serialize the scalar if it's in the desired dataset. if (IsInDataset(info.dataset, aDataset)) { // Get the scalar value. nsCOMPtr<nsIVariant> scalarValue; nsresult rv = scalar->GetValue(scalarValue); if (NS_FAILED(rv)) { return rv; } // Append it to our list. scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarValue)); } } if (aClearScalars) { // The map already takes care of freeing the allocated memory. gScalarStorageMap.Clear(); } } // Reflect it to JS. for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) { const DataPair& scalar = scalarsToReflect[i]; // Convert it to a JS Val. JS::Rooted<JS::Value> scalarJsValue(aCx); nsresult rv = nsContentUtils::XPConnect()->VariantToJS(aCx, root_obj, scalar.second(), &scalarJsValue); if (NS_FAILED(rv)) { return rv; } // Add it to the scalar object. if (!JS_DefineProperty(aCx, root_obj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) { return NS_ERROR_FAILURE; } } return NS_OK; } /** * Serializes the scalars from the given dataset to a json-style object and resets them. * The returned structure looks like: * { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... } * * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN. * @param aClear Whether to clear out the keyed scalars after snapshotting. */ nsresult TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx, uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult) { // If no arguments were passed in, apply the default value. if (!optional_argc) { aClearScalars = false; } JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx)); if (!root_obj) { return NS_ERROR_FAILURE; } aResult.setObject(*root_obj); // Only lock the mutex while accessing our data, without locking any JS related code. typedef mozilla::Pair<const char*, nsTArray<KeyedScalar::KeyValuePair>> DataPair; nsTArray<DataPair> scalarsToReflect; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); // Iterate the scalars in gKeyedScalarStorageMap. The storage may contain empty or yet // to be initialized scalars. for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data()); // Get the informations for this scalar. const ScalarInfo& info = gScalars[iter.Key()]; // Serialize the scalar if it's in the desired dataset. if (IsInDataset(info.dataset, aDataset)) { // Get the keys for this scalar. nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData; nsresult rv = scalar->GetValue(scalarKeyedData); if (NS_FAILED(rv)) { return rv; } // Append it to our list. scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData)); } } if (aClearScalars) { // The map already takes care of freeing the allocated memory. gKeyedScalarStorageMap.Clear(); } } // Reflect it to JS. for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) { const DataPair& keyedScalarData = scalarsToReflect[i]; // Go through each keyed scalar and create a keyed scalar object. // This object will hold the values for all the keyed scalar keys. JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx)); // Define a property for each scalar key, then add it to the keyed scalar // object. const nsTArray<KeyedScalar::KeyValuePair>& keyProps = keyedScalarData.second(); for (uint32_t i = 0; i < keyProps.Length(); i++) { const KeyedScalar::KeyValuePair& keyData = keyProps[i]; // Convert the value for the key to a JSValue. JS::Rooted<JS::Value> keyJsValue(aCx); nsresult rv = nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue); if (NS_FAILED(rv)) { return rv; } // Add the key to the scalar representation. const NS_ConvertUTF8toUTF16 key(keyData.first()); if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) { return NS_ERROR_FAILURE; } } // Add the scalar to the root object. if (!JS_DefineProperty(aCx, root_obj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) { return NS_ERROR_FAILURE; } } return NS_OK; } /** * Resets all the stored scalars. This is intended to be only used in tests. */ void TelemetryScalar::ClearScalars() { StaticMutexAutoLock locker(gTelemetryScalarsMutex); gScalarStorageMap.Clear(); gKeyedScalarStorageMap.Clear(); } size_t TelemetryScalar::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf); } size_t TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); size_t n = 0; // For the plain scalars... for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data()); n += scalar->SizeOfIncludingThis(aMallocSizeOf); } // ...and for the keyed scalars. for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data()); n += scalar->SizeOfIncludingThis(aMallocSizeOf); } return n; }