/* -*- 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/. */
using mozilla::MakeUnique; using mozilla::Nothing; using mozilla::Preferences; using mozilla::Some; using mozilla::StaticAutoPtr; using mozilla::StaticMutex; using mozilla::StaticMutexAutoLock; using mozilla::UniquePtr; using mozilla::Telemetry::DynamicScalarDefinition; using mozilla::Telemetry::KeyedScalarAction; using mozilla::Telemetry::ProcessID; using mozilla::Telemetry::ScalarAction; using mozilla::Telemetry::ScalarActionType; using mozilla::Telemetry::ScalarID; using mozilla::Telemetry::ScalarVariant; using mozilla::Telemetry::Common::AutoHashtable; using mozilla::Telemetry::Common::CanRecordDataset; using mozilla::Telemetry::Common::CanRecordProduct; using mozilla::Telemetry::Common::GetCurrentProduct; using mozilla::Telemetry::Common::GetIDForProcessName; using mozilla::Telemetry::Common::GetNameForProcessID; using mozilla::Telemetry::Common::IsExpiredVersion; using mozilla::Telemetry::Common::IsInDataset; using mozilla::Telemetry::Common::IsValidIdentifierString; using mozilla::Telemetry::Common::LogToBrowserConsole; using mozilla::Telemetry::Common::RecordedProcessType; using mozilla::Telemetry::Common::StringHashSet; using mozilla::Telemetry::Common::SupportedProduct;
//////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // Naming: there are two kinds of functions in this file: // // * Functions named internal_*: these can only be reached via an // interface function (TelemetryScalar::*). If they access shared // state, they require the interface function to have acquired // |gTelemetryScalarMutex| to ensure thread safety. // // * 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. 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::*.
const uint32_t kMaximumNumberOfKeys = 100; const uint32_t kMaxEventSummaryKeys = 500; const uint32_t kMaximumKeyStringLength = 72; const uint32_t kMaximumStringValueLength = 50; // The category and scalar name maximum lengths are used by the dynamic // scalar registration function and must match the constants used by // the 'parse_scalars.py' script for static scalars. const uint32_t kMaximumCategoryNameLength = 40; const uint32_t kMaximumScalarNameLength = 40; const uint32_t kScalarCount = static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
// The max offset supported by gScalarStoresTable for static scalars' stores. // Also the sentinel value (with store_count == 0) for just the sole "main" // store. const uint32_t kMaxStaticStoreOffset = UINT16_MAX;
// The following functions will read the stored text // instead of looking it up in the statically generated // tables. constchar* name() const override; constchar* expiration() const override;
constchar* DynamicScalarInfo::expiration() const { // Dynamic scalars can either be expired or not (boolean flag). // Return an appropriate version string to leverage the scalar expiration // logic. return mDynamicExpiration ? "1.0" : "never";
}
bool IsValidEnumId(mozilla::Telemetry::ScalarID aID) { return aID < mozilla::Telemetry::ScalarID::ScalarCount;
}
bool internal_IsValidId(const StaticMutexAutoLock& lock, const ScalarKey& aId) { // Please note that this function needs to be called with the scalar // mutex being acquired: other functions might be messing with // |gDynamicScalarInfo|. return aId.dynamic
? (aId.id < gDynamicScalarInfo->Length())
: IsValidEnumId(static_cast<mozilla::Telemetry::ScalarID>(aId.id));
}
// Implements the methods for ScalarInfo. constchar* ScalarInfo::name() const { return &gScalarsStringTable[this->name_offset];
}
/** * The base scalar object, that serves as a common ancestor for storage * purposes.
*/ class ScalarBase { public: explicit ScalarBase(const BaseScalarInfo& aInfo)
: mStoreCount(aInfo.storeCount()),
mStoreOffset(aInfo.storeOffset()),
mStoreHasValue(mStoreCount),
mName(aInfo.name()) {
mStoreHasValue.SetLength(mStoreCount); for (auto& val : mStoreHasValue) {
val = false;
}
}; virtual ~ScalarBase() = default;
// GetValue is used to get the value of the scalar when persisting it to JS. virtual nsresult GetValue(const nsACString& aStoreName, bool aClearStore,
nsCOMPtr<nsIVariant>& aResult) = 0;
// To measure the memory stats.
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; virtual size_t SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const = 0;
ScalarResult ScalarBase::HandleUnsupported() const {
MOZ_ASSERT(false, "This operation is not support for this scalar type."); return ScalarResult::OperationNotSupported;
}
size_t ScalarString::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
n += ScalarBase::SizeOfExcludingThis(aMallocSizeOf);
n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); for (auto& val : mStorage) {
n += val.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
} return n;
}
/** * The implementation for the boolean scalar type.
*/ class ScalarBoolean : public ScalarBase { public: using ScalarBase::SetValue;
explicit ScalarBoolean(const BaseScalarInfo& aInfo)
: ScalarBase(aInfo), mStorage(aInfo.storeCount()) {
mStorage.SetLength(aInfo.storeCount()); for (auto& val : mStorage) {
val = false;
}
};
size_t ScalarBoolean::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
n += ScalarBase::SizeOfExcludingThis(aMallocSizeOf);
n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); return n;
}
/** * 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(const BaseScalarInfo& aInfo) {
ScalarBase* scalar = nullptr; switch (aInfo.kind) { case nsITelemetry::SCALAR_TYPE_COUNT:
scalar = new ScalarUnsigned(aInfo); break; case nsITelemetry::SCALAR_TYPE_STRING:
scalar = new ScalarString(aInfo); break; case nsITelemetry::SCALAR_TYPE_BOOLEAN:
scalar = new ScalarBoolean(aInfo); break; default:
MOZ_ASSERT(false, "Invalid scalar type");
} return scalar;
}
/** * The implementation for the keyed scalar type.
*/ class KeyedScalar { public: typedef std::pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair;
// We store the name instead of a reference to the BaseScalarInfo because // the BaseScalarInfo can move if it's from a dynamic scalar. explicit KeyedScalar(const BaseScalarInfo& info)
: mScalarName(info.name()),
mScalarKeyCount(info.key_count),
mScalarKeyOffset(info.key_offset),
mMaximumNumberOfKeys(kMaximumNumberOfKeys) {};
~KeyedScalar() = default;
// Convenience methods used by the C++ API. void SetValue(const StaticMutexAutoLock& locker, const nsAString& aKey,
uint32_t aValue); void SetValue(const StaticMutexAutoLock& locker, const nsAString& aKey, bool aValue); void AddValue(const StaticMutexAutoLock& locker, 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(const nsACString& aStoreName, bool aClearStorage,
nsTArray<KeyValuePair>& aValues);
// To measure the memory stats.
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
// To permit more keys than normal. void SetMaximumNumberOfKeys(uint32_t aMaximumNumberOfKeys) {
mMaximumNumberOfKeys = aMaximumNumberOfKeys;
};
if (sr != ScalarResult::Ok) { // Bug 1451813 - We now report which scalars exceed the key limit in // telemetry.keyed_scalars_exceed_limit. return;
}
return scalar->AddValue(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(const nsACString& aStoreName, bool aClearStorage,
nsTArray<KeyValuePair>& aValues) { for (constauto& entry : mScalarKeys) {
ScalarBase* scalar = entry.GetWeak();
// Get the scalar value.
nsCOMPtr<nsIVariant> scalarValue;
nsresult rv = scalar->GetValue(aStoreName, aClearStorage, scalarValue); if (rv == NS_ERROR_NO_CONTENT) { // No value for this store. continue;
} if (NS_FAILED(rv)) { return rv;
}
// Append it to value list.
aValues.AppendElement(
std::make_pair(nsCString(entry.GetKey()), scalarValue));
}
/** * 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 StaticMutexAutoLock& locker, const nsAString& aKey,
ScalarBase** aRet) { if (aKey.IsEmpty()) { return ScalarResult::KeyIsEmpty;
}
size_t KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
size_t n = aMallocSizeOf(this); for (constauto& scalar : mScalarKeys.Values()) {
n += scalar->SizeOfIncludingThis(aMallocSizeOf);
} return n;
}
bool KeyedScalar::AllowsKey(const nsAString& aKey) const { // If we didn't specify a list of allowed keys, just return true. if (mScalarKeyCount == 0) { returntrue;
}
for (uint32_t i = 0; i < mScalarKeyCount; ++i) {
uint32_t stringIndex = gScalarKeysTable[mScalarKeyOffset + i]; if (aKey.EqualsASCII(&gScalarsStringTable[stringIndex])) { returntrue;
}
}
//////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE STATE, SHARED BY ALL THREADS
namespace {
// Set to true once this global state has been initialized. bool gTelemetryScalarInitDone = false;
// The Name -> ID cache map.
MOZ_RUNINIT ScalarMapType gScalarNameIDMap(kScalarCount);
// The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a // nsClassHashtable, it owns the scalar instances and takes care of deallocating // them when they are removed from the map.
MOZ_RUNINIT ProcessesScalarsMapType gScalarStorageMap; // As above, for the keyed scalars.
MOZ_RUNINIT ProcessesKeyedScalarsMapType gKeyedScalarStorageMap; // Provide separate storage for "dynamic builtin" plain and keyed scalars, // needed to support "build faster" in local developer builds.
MOZ_RUNINIT ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap;
MOZ_RUNINIT ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap;
} // namespace
//////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: helpers for the external interface
/** * Check if the given scalar is a keyed scalar. * * @param lock Instance of a lock locking gTelemetryHistogramMutex * @param aId The scalar identifier. * @return true if aId refers to a keyed scalar, false otherwise.
*/ bool internal_IsKeyedScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId) { return internal_GetScalarInfo(lock, aId).keyed;
}
/** * Check if we're allowed to record the given scalar in the current * process. * * @param lock Instance of a lock locking gTelemetryHistogramMutex * @param aId The scalar identifier. * @return true if the scalar is allowed to be recorded in the current process, * false otherwise.
*/ bool internal_CanRecordProcess(const StaticMutexAutoLock& lock, const ScalarKey& aId) { const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId); return CanRecordInProcess(info.record_in_processes, XRE_GetProcessType());
}
bool internal_CanRecordForScalarID(const StaticMutexAutoLock& lock, const ScalarKey& aId) { // Get the scalar info from the id. const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
// Can we record at all? bool canRecordBase = internal_CanRecordBase(lock); if (!canRecordBase) { returnfalse;
}
/** * Check if we are allowed to record the provided scalar. * * @param lock Instance of a lock locking gTelemetryHistogramMutex * @param aId The scalar identifier. * @param aKeyed Are we attempting to write a keyed scalar? * @param aForce Whether to allow recording even if the probe is not allowed on * the current process. * @return ScalarResult::Ok if we can record, an error code otherwise.
*/
ScalarResult internal_CanRecordScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId, bool aKeyed, bool aForce = false) { // Make sure that we have a keyed scalar if we are trying to change one. if (internal_IsKeyedScalar(lock, aId) != aKeyed) { const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
PROFILER_MARKER_TEXT( "ScalarError", TELEMETRY, mozilla::MarkerStack::Capture(),
nsPrintfCString("KeyedTypeMismatch for %s", info.name())); return ScalarResult::KeyedTypeMismatch;
}
// Are we allowed to record this scalar based on the current Telemetry // settings? if (!internal_CanRecordForScalarID(lock, aId)) { return ScalarResult::CannotRecordDataset;
}
// Can we record in this process? if (!aForce && !internal_CanRecordProcess(lock, aId)) { const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
PROFILER_MARKER_TEXT( "ScalarError", TELEMETRY, mozilla::MarkerStack::Capture(),
nsPrintfCString("CannotRecordInProcess for %s", info.name())); return ScalarResult::CannotRecordInProcess;
}
// Can we record on this product? if (!internal_CanRecordProduct(lock, aId)) { return ScalarResult::CannotRecordDataset;
}
return ScalarResult::Ok;
}
/** * Get the scalar enum id from the scalar name. * * @param lock Instance of a lock locking gTelemetryHistogramMutex * @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 StaticMutexAutoLock& lock, const nsACString& aName, ScalarKey* aId) { if (!gTelemetryScalarInitDone) { return NS_ERROR_FAILURE;
}
/** * Get a scalar object by its enum id. This implicitly allocates the scalar * object in the storage if it wasn't previously allocated. * * @param lock Instance of a lock locking gTelemetryHistogramMutex * @param aId The scalar identifier. * @param aProcessStorage This drives the selection of the map to use to store * the scalar data coming from child processes. This is only meaningful * when this function is called in parent process. If that's the case, * if this is not |GeckoProcessType_Default|, the process id is used to * allocate and store the scalars. * @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(const StaticMutexAutoLock& lock, const ScalarKey& aId,
ProcessID aProcessStorage,
ScalarBase** aRet) { if (!internal_IsValidId(lock, aId)) {
MOZ_ASSERT(false, "Requested a scalar with an invalid id."); return NS_ERROR_INVALID_ARG;
}
const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
ScalarBase* scalar = nullptr; // Initialize the scalar storage to the parent storage. This will get // set to the child storage if needed.
uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
// Put dynamic-builtin scalars (used to support "build faster") in a // separate storage.
ProcessesScalarsMapType& processStorage =
aId.dynamic ? gDynamicBuiltinScalarStorageMap : gScalarStorageMap;
// Get the process-specific storage or create one if it's not // available.
ScalarStorageMapType* const scalarStorage =
processStorage.GetOrInsertNew(storageId);
// Check if the scalar is already allocated in the parent or in the child // storage. if (scalarStorage->Get(aId.id, &scalar)) { // Dynamic scalars can expire at any time during the session (e.g. an // add-on was updated). Check if it expired. if (aId.dynamic) { const DynamicScalarInfo& dynInfo = static_cast<const DynamicScalarInfo&>(info); if (dynInfo.mDynamicExpiration) { // The Dynamic scalar is expired.
PROFILER_MARKER_TEXT( "ScalarError", TELEMETRY, mozilla::MarkerStack::Capture(),
nsPrintfCString("ExpiredDynamicScalar: %s", info.name())); return NS_ERROR_NOT_AVAILABLE;
}
} // This was not a dynamic scalar or was not expired.
*aRet = scalar; return NS_OK;
}
// The scalar storage wasn't already allocated. Check if the scalar is expired // and then allocate the storage, if needed. if (IsExpiredVersion(info.expiration())) {
PROFILER_MARKER_TEXT("ScalarError", TELEMETRY,
mozilla::MarkerStack::Capture(),
nsPrintfCString("ExpiredScalar: %s", info.name())); return NS_ERROR_NOT_AVAILABLE;
}
scalar = internal_ScalarAllocate(info); if (!scalar) { return NS_ERROR_INVALID_ARG;
}
#define internal_profilerMarker(...) \ do { \ if (profiler_thread_is_being_profiled_for_markers()) { \
internal_profilerMarker_impl(__VA_ARGS__); \
} \
} while (false)
} // 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 lock Instance of a lock locking gTelemetryHistogramMutex * @param aId The scalar identifier. * @param aProcessStorage This drives the selection of the map to use to store * the scalar data coming from child processes. This is only meaningful * when this function is called in parent process. If that's the case, * if this is not |GeckoProcessType_Default|, the process id is used to * allocate and store the scalars. * @param aRet 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(const StaticMutexAutoLock& lock, const ScalarKey& aId,
ProcessID aProcessStorage,
KeyedScalar** aRet) { if (!internal_IsValidId(lock, aId)) {
MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id."); return NS_ERROR_INVALID_ARG;
}
const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId);
KeyedScalar* scalar = nullptr; // Initialize the scalar storage to the parent storage. This will get // set to the child storage if needed.
uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
// Put dynamic-builtin scalars (used to support "build faster") in a // separate storage.
ProcessesKeyedScalarsMapType& processStorage =
aId.dynamic ? gDynamicBuiltinKeyedScalarStorageMap
: gKeyedScalarStorageMap;
// Get the process-specific storage or create one if it's not // available.
KeyedScalarStorageMapType* const scalarStorage =
processStorage.GetOrInsertNew(storageId);
if (scalarStorage->Get(aId.id, &scalar)) {
*aRet = scalar; return NS_OK;
}
if (IsExpiredVersion(info.expiration())) { return NS_ERROR_NOT_AVAILABLE;
}
// We don't currently support keyed string scalars. Disable them. if (info.kind == nsITelemetry::SCALAR_TYPE_STRING) {
MOZ_ASSERT(false, "Keyed string scalars are not currently supported."); return NS_ERROR_INVALID_ARG;
}
/** * Helper function to convert an array of |DynamicScalarInfo| * to |DynamicScalarDefinition| used by the IPC calls.
*/ void internal_DynamicScalarToIPC( const StaticMutexAutoLock& lock, const nsTArray<DynamicScalarInfo>& aDynamicScalarInfos,
nsTArray<DynamicScalarDefinition>& aIPCDefs) { for (auto& info : aDynamicScalarInfos) {
DynamicScalarDefinition stubDefinition;
stubDefinition.type = info.kind;
stubDefinition.dataset = info.dataset;
stubDefinition.expired = info.mDynamicExpiration;
stubDefinition.keyed = info.keyed;
stubDefinition.name = info.mDynamicName;
aIPCDefs.AppendElement(stubDefinition);
}
}
/** * Broadcasts the dynamic scalar definitions to all the other * content processes.
*/ void internal_BroadcastDefinitions( const nsTArray<DynamicScalarDefinition>& scalarDefs) {
nsTArray<mozilla::dom::ContentParent*> parents;
mozilla::dom::ContentParent::GetAll(parents); if (!parents.Length()) { return;
}
// Broadcast the definitions to the other content processes. for (auto parent : parents) {
mozilla::Unused << parent->SendAddDynamicScalars(scalarDefs);
}
}
void internal_RegisterScalars(const StaticMutexAutoLock& lock, const nsTArray<DynamicScalarInfo>& scalarInfos) { // Register the new scalars. if (!gDynamicScalarInfo) {
gDynamicScalarInfo = new nsTArray<DynamicScalarInfo>();
} if (!gDynamicStoreNames) {
gDynamicStoreNames = new nsTArray<RefPtr<nsAtom>>();
}
for (auto& scalarInfo : scalarInfos) { // Allow expiring scalars that were already registered.
CharPtrEntryType* existingKey =
gScalarNameIDMap.GetEntry(scalarInfo.name()); if (existingKey) { continue;
}
/** * Creates a snapshot of the desired scalar storage. * @param {aLock} The proof of lock to access scalar data. * @param {aScalarsToReflect} The table that will contain the snapshot. * @param {aDataset} The dataset we're asking the snapshot for. * @param {aProcessStorage} The scalar storage to take a snapshot of. * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin * scalars. * @return NS_OK or the error code describing the failure reason.
*/
nsresult internal_ScalarSnapshotter(const StaticMutexAutoLock& aLock,
ScalarSnapshotTable& aScalarsToReflect, unsignedint aDataset,
ProcessesScalarsMapType& aProcessStorage, bool aIsBuiltinDynamic, bool aClearScalars, const nsACString& aStoreName) { // Iterate the scalars in aProcessStorage. The storage may contain empty or // yet to be initialized scalars from all the supported processes. for (constauto& entry : aProcessStorage) {
ScalarStorageMapType* scalarStorage = entry.GetWeak();
ScalarTupleArray& processScalars =
aScalarsToReflect.LookupOrInsert(entry.GetKey());
// Are we in the "Dynamic" process? bool isDynamicProcess =
ProcessID::Dynamic == static_cast<ProcessID>(entry.GetKey());
// Iterate each available child storage. for (constauto& childEntry : *scalarStorage) {
ScalarBase* scalar = childEntry.GetWeak();
// Get the informations for this scalar. const BaseScalarInfo& info = internal_GetScalarInfo(
aLock, ScalarKey{childEntry.GetKey(),
aIsBuiltinDynamic ? true : isDynamicProcess});
// 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(aStoreName, aClearScalars, scalarValue); if (rv == NS_ERROR_NO_CONTENT) { // No value for this store. Proceed. continue;
} if (NS_FAILED(rv)) { return rv;
} // Append it to our list.
processScalars.AppendElement(
std::make_tuple(info.name(), scalarValue, info.kind));
}
} if (processScalars.Length() == 0) {
aScalarsToReflect.Remove(entry.GetKey());
}
} return NS_OK;
}
/** * Creates a snapshot of the desired keyed scalar storage. * @param {aLock} The proof of lock to access scalar data. * @param {aScalarsToReflect} The table that will contain the snapshot. * @param {aDataset} The dataset we're asking the snapshot for. * @param {aProcessStorage} The scalar storage to take a snapshot of. * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin * scalars. * @return NS_OK or the error code describing the failure reason.
*/
nsresult internal_KeyedScalarSnapshotter( const StaticMutexAutoLock& aLock,
KeyedScalarSnapshotTable& aScalarsToReflect, unsignedint aDataset,
ProcessesKeyedScalarsMapType& aProcessStorage, bool aIsBuiltinDynamic, bool aClearScalars, const nsACString& aStoreName) { // Iterate the scalars in aProcessStorage. The storage may contain empty or // yet to be initialized scalars from all the supported processes. for (constauto& entry : aProcessStorage) {
KeyedScalarStorageMapType* scalarStorage = entry.GetWeak();
KeyedScalarTupleArray& processScalars =
aScalarsToReflect.LookupOrInsert(entry.GetKey());
// Are we in the "Dynamic" process? bool isDynamicProcess =
ProcessID::Dynamic == static_cast<ProcessID>(entry.GetKey());
for (constauto& childEntry : *scalarStorage) {
KeyedScalar* scalar = childEntry.GetWeak();
// Get the informations for this scalar. const BaseScalarInfo& info = internal_GetScalarInfo(
aLock, ScalarKey{childEntry.GetKey(),
aIsBuiltinDynamic ? true : isDynamicProcess});
// 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(aStoreName, aClearScalars, scalarKeyedData); if (NS_FAILED(rv)) { return rv;
} if (scalarKeyedData.Length() == 0) { // Don't bother with empty keyed scalars. continue;
} // Append it to our list.
processScalars.AppendElement(std::make_tuple(
info.name(), std::move(scalarKeyedData), info.kind));
}
} if (processScalars.Length() == 0) {
aScalarsToReflect.Remove(entry.GetKey());
}
} return NS_OK;
}
/** * Helper function to get a snapshot of the scalars. * * @param {aLock} The proof of lock to access scalar data. * @param {aScalarsToReflect} The table that will contain the snapshot. * @param {aDataset} The dataset we're asking the snapshot for. * @param {aClearScalars} Whether or not to clear the scalar storage. * @param {aStoreName} The name of the store to snapshot. * @return NS_OK or the error code describing the failure reason.
*/
nsresult internal_GetScalarSnapshot(const StaticMutexAutoLock& aLock,
ScalarSnapshotTable& aScalarsToReflect, unsignedint aDataset, bool aClearScalars, const nsACString& aStoreName) { // Take a snapshot of the scalars.
nsresult rv =
internal_ScalarSnapshotter(aLock, aScalarsToReflect, aDataset,
gScalarStorageMap, false, /*aIsBuiltinDynamic*/
aClearScalars, aStoreName); if (NS_FAILED(rv)) { return rv;
}
// And a snapshot of the dynamic builtin ones.
rv = internal_ScalarSnapshotter(aLock, aScalarsToReflect, aDataset,
gDynamicBuiltinScalarStorageMap, true, /*aIsBuiltinDynamic*/
aClearScalars, aStoreName); if (NS_FAILED(rv)) { return rv;
}
return NS_OK;
}
/** * Helper function to get a snapshot of the keyed scalars. * * @param {aLock} The proof of lock to access scalar data. * @param {aScalarsToReflect} The table that will contain the snapshot. * @param {aDataset} The dataset we're asking the snapshot for. * @param {aClearScalars} Whether or not to clear the scalar storage. * @param {aStoreName} The name of the store to snapshot. * @return NS_OK or the error code describing the failure reason.
*/
nsresult internal_GetKeyedScalarSnapshot( const StaticMutexAutoLock& aLock,
KeyedScalarSnapshotTable& aScalarsToReflect, unsignedint aDataset, bool aClearScalars, const nsACString& aStoreName) { // Take a snapshot of the scalars.
nsresult rv = internal_KeyedScalarSnapshotter(
aLock, aScalarsToReflect, aDataset, gKeyedScalarStorageMap, false, /*aIsBuiltinDynamic*/
aClearScalars, aStoreName); if (NS_FAILED(rv)) { return rv;
}
// And a snapshot of the dynamic builtin ones.
rv = internal_KeyedScalarSnapshotter(aLock, aScalarsToReflect, aDataset,
gDynamicBuiltinKeyedScalarStorageMap, true, /*aIsBuiltinDynamic*/
aClearScalars, aStoreName); if (NS_FAILED(rv)) { return rv;
}
return NS_OK;
}
} // namespace
// helpers for recording/applying scalar operations namespace {
for (auto& upd : aScalarActions) {
ScalarKey uniqueId{upd.mId, upd.mDynamic}; if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); continue;
}
if (internal_IsKeyedScalar(lock, uniqueId)) { continue;
}
// Are we allowed to record this scalar? We don't need to check for // allowed processes here, that's taken care of when recording // in child processes. if (!internal_CanRecordForScalarID(lock, uniqueId)) { continue;
}
// Either we got passed a process type or it was explicitely set on the // recorded action. It should never happen that it is set to an invalid // value (such as ProcessID::Count)
ProcessID processType = aProcessType.valueOr(upd.mProcessType);
MOZ_ASSERT(processType != ProcessID::Count);
// Refresh the data in the parent process with the data coming from the // child processes.
ScalarBase* scalar = nullptr;
nsresult rv =
internal_GetScalarByEnum(lock, uniqueId, processType, &scalar); if (NS_FAILED(rv)) { // Bug 1513496 - We no longer log a warning if the scalar is expired. if (rv != NS_ERROR_NOT_AVAILABLE) {
NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
} continue;
}
if (upd.mData.isNothing()) {
MOZ_ASSERT(false, "There is no data in the ScalarActionType."); continue;
}
internal_profilerMarker(lock, upd);
// Get the type of this scalar from the scalar ID. We already checked // for its validity a few lines above. const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
// Extract the data from the mozilla::Variant. switch (upd.mActionType) { case ScalarActionType::eSet: { switch (scalarType) { case nsITelemetry::SCALAR_TYPE_COUNT: if (!upd.mData->is<uint32_t>()) {
NS_WARNING("Attempting to set a count scalar to a non-integer."); continue;
}
scalar->SetValue(upd.mData->as<uint32_t>()); break; case nsITelemetry::SCALAR_TYPE_BOOLEAN: if (!upd.mData->is<bool>()) {
NS_WARNING( "Attempting to set a boolean scalar to a non-boolean."); continue;
}
scalar->SetValue(upd.mData->as<bool>()); break; case nsITelemetry::SCALAR_TYPE_STRING: if (!upd.mData->is<nsString>()) {
NS_WARNING("Attempting to set a string scalar to a non-string."); continue;
}
scalar->SetValue(upd.mData->as<nsString>()); break;
} break;
} case ScalarActionType::eAdd: { if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
NS_WARNING("Attempting to add on a non count scalar."); continue;
} // We only support adding uint32_t. if (!upd.mData->is<uint32_t>()) {
NS_WARNING("Attempting to add to a count scalar with a non-integer."); continue;
}
scalar->AddValue(upd.mData->as<uint32_t>()); break;
} default:
NS_WARNING("Unsupported action coming from scalar child updates.");
}
}
}
for (auto& upd : aScalarActions) {
ScalarKey uniqueId{upd.mId, upd.mDynamic}; if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) {
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); continue;
}
if (!internal_IsKeyedScalar(lock, uniqueId)) { continue;
}
// Are we allowed to record this scalar? We don't need to check for // allowed processes here, that's taken care of when recording // in child processes. if (!internal_CanRecordForScalarID(lock, uniqueId)) { continue;
}
// Either we got passed a process type or it was explicitely set on the // recorded action. It should never happen that it is set to an invalid // value (such as ProcessID::Count)
ProcessID processType = aProcessType.valueOr(upd.mProcessType);
MOZ_ASSERT(processType != ProcessID::Count);
// Refresh the data in the parent process with the data coming from the // child processes.
KeyedScalar* scalar = nullptr;
nsresult rv =
internal_GetKeyedScalarByEnum(lock, uniqueId, processType, &scalar); if (NS_FAILED(rv)) { // Bug 1513496 - We no longer log a warning if the scalar is expired. if (rv != NS_ERROR_NOT_AVAILABLE) {
NS_WARNING("NS_FAILED internal_GetKeyedScalarByEnum for CHILD");
} continue;
}
if (upd.mData.isNothing()) {
MOZ_ASSERT(false, "There is no data in the KeyedScalarAction."); continue;
}
// Get the type of this scalar from the scalar ID. We already checked // for its validity a few lines above. const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind;
internal_profilerMarker(lock, upd, upd.mKey);
// Extract the data from the mozilla::Variant. switch (upd.mActionType) { case ScalarActionType::eSet: { switch (scalarType) { case nsITelemetry::SCALAR_TYPE_COUNT: if (!upd.mData->is<uint32_t>()) {
NS_WARNING("Attempting to set a count scalar to a non-integer."); continue;
}
scalar->SetValue(lock, NS_ConvertUTF8toUTF16(upd.mKey),
upd.mData->as<uint32_t>()); break; case nsITelemetry::SCALAR_TYPE_BOOLEAN: if (!upd.mData->is<bool>()) {
NS_WARNING( "Attempting to set a boolean scalar to a non-boolean."); continue;
}
scalar->SetValue(lock, NS_ConvertUTF8toUTF16(upd.mKey),
upd.mData->as<bool>()); break; default:
NS_WARNING("Unsupported type coming from scalar child updates.");
} break;
} case ScalarActionType::eAdd: { if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
NS_WARNING("Attempting to add on a non count scalar."); continue;
} // We only support adding on uint32_t. if (!upd.mData->is<uint32_t>()) {
NS_WARNING("Attempting to add to a count scalar with a non-integer."); continue;
}
scalar->AddValue(lock, NS_ConvertUTF8toUTF16(upd.mKey),
upd.mData->as<uint32_t>()); break;
} default:
NS_WARNING( "Unsupported action coming from keyed scalar child updates.");
}
}
}
} // 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 MOZ_UNANNOTATED;
void TelemetryScalar::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtended) {
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
MOZ_ASSERT(!gTelemetryScalarInitDone, "TelemetryScalar::InitializeGlobalState " "may only be called once");
// 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->SetData(ScalarKey{i, false});
}
// To summarize dynamic events we need a dynamic scalar. const nsTArray<DynamicScalarInfo> initialDynamicScalars({
DynamicScalarInfo{
nsITelemetry::SCALAR_TYPE_COUNT, true/* recordOnRelease */, false/* expired */,
nsAutoCString("telemetry.dynamic_event_counts"), true/* keyed */,
{} /* stores */,
},
});
internal_RegisterScalars(locker, initialDynamicScalars);
void TelemetryScalar::SetCanRecordBase(bool b) {
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
gTelemetryScalarCanRecordBase = b;
}
void TelemetryScalar::SetCanRecordExtended(bool b) {
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
gTelemetryScalarCanRecordExtended = b;
}
/** * 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) { if (NS_WARN_IF(!IsValidEnumId(aId))) {
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); return;
}
// Accumulate in the child process if needed. if (!XRE_IsParentProcess()) {
TelemetryIPCAccumulator::RecordChildScalarAction(
uniqueId.id, uniqueId.dynamic, ScalarActionType::eAdd,
ScalarVariant(aValue)); return;
}
/** * 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) { if (NS_WARN_IF(!IsValidEnumId(aId))) {
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.