mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-12 14:20:14 +02:00
MozReview-Commit-ID: L3GnUTXHf3C --HG-- extra : rebase_source : 4127109f262bcc2e0330fd83e1bf1d092cdcbbbd
3857 lines
126 KiB
C++
3857 lines
126 KiB
C++
/* -*- 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 "nsDataHashtable.h"
|
|
#include "nsIXPConnect.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/PContent.h"
|
|
#include "mozilla/JSONWriter.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/StaticMutex.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
#include "TelemetryCommon.h"
|
|
#include "TelemetryScalar.h"
|
|
#include "TelemetryScalarData.h"
|
|
#include "ipc/TelemetryComms.h"
|
|
#include "ipc/TelemetryIPCAccumulator.h"
|
|
|
|
using mozilla::Preferences;
|
|
using mozilla::StaticAutoPtr;
|
|
using mozilla::StaticMutex;
|
|
using mozilla::StaticMutexAutoLock;
|
|
using mozilla::Some;
|
|
using mozilla::Nothing;
|
|
using mozilla::Telemetry::Common::AutoHashtable;
|
|
using mozilla::Telemetry::Common::IsExpiredVersion;
|
|
using mozilla::Telemetry::Common::CanRecordDataset;
|
|
using mozilla::Telemetry::Common::CanRecordProduct;
|
|
using mozilla::Telemetry::Common::IsInDataset;
|
|
using mozilla::Telemetry::Common::LogToBrowserConsole;
|
|
using mozilla::Telemetry::Common::GetNameForProcessID;
|
|
using mozilla::Telemetry::Common::GetIDForProcessName;
|
|
using mozilla::Telemetry::Common::RecordedProcessType;
|
|
using mozilla::Telemetry::Common::IsValidIdentifierString;
|
|
using mozilla::Telemetry::Common::GetCurrentProduct;
|
|
using mozilla::Telemetry::Common::SupportedProduct;
|
|
using mozilla::Telemetry::ScalarActionType;
|
|
using mozilla::Telemetry::ScalarAction;
|
|
using mozilla::Telemetry::KeyedScalarAction;
|
|
using mozilla::Telemetry::ScalarID;
|
|
using mozilla::Telemetry::DynamicScalarDefinition;
|
|
using mozilla::Telemetry::ScalarVariant;
|
|
using mozilla::Telemetry::ProcessID;
|
|
|
|
namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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::*.
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE TYPES
|
|
|
|
namespace {
|
|
|
|
const uint32_t kMaximumNumberOfKeys = 100;
|
|
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);
|
|
|
|
// To stop growing unbounded in memory while waiting for scalar deserialization
|
|
// to finish, we immediately apply pending operations if the array reaches
|
|
// a certain high water mark of elements.
|
|
const size_t kScalarActionsArrayHighWaterMark = 10000;
|
|
|
|
enum class ScalarResult : uint8_t {
|
|
// Nothing went wrong.
|
|
Ok,
|
|
// General Scalar Errors
|
|
NotInitialized,
|
|
CannotUnpackVariant,
|
|
CannotRecordInProcess,
|
|
CannotRecordDataset,
|
|
KeyedTypeMismatch,
|
|
UnknownScalar,
|
|
OperationNotSupported,
|
|
InvalidType,
|
|
InvalidValue,
|
|
// Keyed Scalar Errors
|
|
KeyIsEmpty,
|
|
KeyTooLong,
|
|
TooManyKeys,
|
|
// String Scalar Errors
|
|
StringTooLong,
|
|
// Unsigned Scalar Errors
|
|
UnsignedNegativeValue,
|
|
UnsignedTruncatedValue,
|
|
};
|
|
|
|
// A common identifier for both built-in and dynamic scalars.
|
|
struct ScalarKey {
|
|
uint32_t id;
|
|
bool dynamic;
|
|
};
|
|
|
|
/**
|
|
* Scalar information for dynamic definitions.
|
|
*/
|
|
struct DynamicScalarInfo : BaseScalarInfo {
|
|
nsCString mDynamicName;
|
|
bool mDynamicExpiration;
|
|
|
|
DynamicScalarInfo(uint32_t aKind, bool aRecordOnRelease,
|
|
bool aExpired, const nsACString& aName,
|
|
bool aKeyed, bool aBuiltin)
|
|
: BaseScalarInfo(aKind,
|
|
aRecordOnRelease ?
|
|
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT :
|
|
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
|
|
RecordedProcessType::All,
|
|
aKeyed,
|
|
GetCurrentProduct(),
|
|
aBuiltin)
|
|
, mDynamicName(aName)
|
|
, mDynamicExpiration(aExpired)
|
|
{}
|
|
|
|
// The following functions will read the stored text
|
|
// instead of looking it up in the statically generated
|
|
// tables.
|
|
const char *name() const override;
|
|
const char *expiration() const override;
|
|
};
|
|
|
|
const char *
|
|
DynamicScalarInfo::name() const
|
|
{
|
|
return mDynamicName.get();
|
|
}
|
|
|
|
const char *
|
|
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";
|
|
}
|
|
|
|
typedef nsBaseHashtableET<nsDepCharHashKey, ScalarKey> CharPtrEntryType;
|
|
typedef AutoHashtable<CharPtrEntryType> ScalarMapType;
|
|
|
|
// Dynamic scalar definitions.
|
|
StaticAutoPtr<nsTArray<DynamicScalarInfo>> gDynamicScalarInfo;
|
|
|
|
const BaseScalarInfo&
|
|
internal_GetScalarInfo(const StaticMutexAutoLock& lock, const ScalarKey& aId)
|
|
{
|
|
if (!aId.dynamic) {
|
|
return gScalars[aId.id];
|
|
}
|
|
|
|
return (*gDynamicScalarInfo)[aId.id];
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
/**
|
|
* Convert a nsIVariant to a mozilla::Variant, which is used for
|
|
* accumulating child process scalars.
|
|
*/
|
|
ScalarResult
|
|
GetVariantFromIVariant(nsIVariant* aInput, uint32_t aScalarKind,
|
|
mozilla::Maybe<ScalarVariant>& aOutput)
|
|
{
|
|
switch (aScalarKind) {
|
|
case nsITelemetry::SCALAR_TYPE_COUNT:
|
|
{
|
|
uint32_t val = 0;
|
|
nsresult rv = aInput->GetAsUint32(&val);
|
|
if (NS_FAILED(rv)) {
|
|
return ScalarResult::CannotUnpackVariant;
|
|
}
|
|
aOutput = mozilla::Some(mozilla::AsVariant(val));
|
|
break;
|
|
}
|
|
case nsITelemetry::SCALAR_TYPE_STRING:
|
|
{
|
|
nsString val;
|
|
nsresult rv = aInput->GetAsAString(val);
|
|
if (NS_FAILED(rv)) {
|
|
return ScalarResult::CannotUnpackVariant;
|
|
}
|
|
aOutput = mozilla::Some(mozilla::AsVariant(val));
|
|
break;
|
|
}
|
|
case nsITelemetry::SCALAR_TYPE_BOOLEAN:
|
|
{
|
|
bool val = false;
|
|
nsresult rv = aInput->GetAsBool(&val);
|
|
if (NS_FAILED(rv)) {
|
|
return ScalarResult::CannotUnpackVariant;
|
|
}
|
|
aOutput = mozilla::Some(mozilla::AsVariant(val));
|
|
break;
|
|
}
|
|
default:
|
|
MOZ_ASSERT(false, "Unknown scalar kind.");
|
|
return ScalarResult::UnknownScalar;
|
|
}
|
|
return ScalarResult::Ok;
|
|
}
|
|
|
|
/**
|
|
* Write a nsIVariant with a JSONWriter, used for GeckoView persistence.
|
|
*/
|
|
nsresult
|
|
WriteVariantToJSONWriter(uint32_t aScalarType, nsIVariant* aInputValue,
|
|
const char* aPropertyName, mozilla::JSONWriter& aWriter)
|
|
{
|
|
MOZ_ASSERT(aInputValue);
|
|
|
|
switch (aScalarType) {
|
|
case nsITelemetry::SCALAR_TYPE_COUNT:
|
|
{
|
|
uint32_t val = 0;
|
|
nsresult rv = aInputValue->GetAsUint32(&val);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
aWriter.IntProperty(aPropertyName, val);
|
|
break;
|
|
}
|
|
case nsITelemetry::SCALAR_TYPE_STRING:
|
|
{
|
|
nsCString val;
|
|
nsresult rv = aInputValue->GetAsACString(val);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
aWriter.StringProperty(aPropertyName, val.get());
|
|
break;
|
|
}
|
|
case nsITelemetry::SCALAR_TYPE_BOOLEAN:
|
|
{
|
|
bool val = false;
|
|
nsresult rv = aInputValue->GetAsBool(&val);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
aWriter.BoolProperty(aPropertyName, val);
|
|
break;
|
|
}
|
|
default:
|
|
MOZ_ASSERT(false, "Unknown scalar kind.");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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 serves as a common ancestor for storage
|
|
* purposes.
|
|
*/
|
|
class ScalarBase
|
|
{
|
|
public:
|
|
virtual ~ScalarBase() = default;
|
|
|
|
// 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() override = default;
|
|
|
|
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() override = default;
|
|
|
|
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() override = default;
|
|
|
|
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_TYPE_COUNT:
|
|
scalar = new ScalarUnsigned();
|
|
break;
|
|
case nsITelemetry::SCALAR_TYPE_STRING:
|
|
scalar = new ScalarString();
|
|
break;
|
|
case nsITelemetry::SCALAR_TYPE_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)
|
|
, mMaximumNumberOfKeys(kMaximumNumberOfKeys)
|
|
{ };
|
|
~KeyedScalar() = default;
|
|
|
|
// 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);
|
|
|
|
// To permit more keys than normal.
|
|
void SetMaximumNumberOfKeys(uint32_t aMaximumNumberOfKeys)
|
|
{
|
|
mMaximumNumberOfKeys = aMaximumNumberOfKeys;
|
|
};
|
|
|
|
private:
|
|
typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType;
|
|
|
|
ScalarKeysMapType mScalarKeys;
|
|
const uint32_t mScalarKind;
|
|
uint32_t mMaximumNumberOfKeys;
|
|
|
|
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.IsEmpty()) {
|
|
return ScalarResult::KeyIsEmpty;
|
|
}
|
|
|
|
if (aKey.Length() > kMaximumKeyStringLength) {
|
|
return ScalarResult::KeyTooLong;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 utf8Key(aKey);
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
if (mScalarKeys.Get(utf8Key, &scalar)) {
|
|
*aRet = scalar;
|
|
return ScalarResult::Ok;
|
|
}
|
|
|
|
if (mScalarKeys.Count() >= mMaximumNumberOfKeys) {
|
|
return ScalarResult::TooManyKeys;
|
|
}
|
|
|
|
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 nsUint32HashKey ProcessIDHashKey;
|
|
typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
|
|
typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
|
|
typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> ProcessesScalarsMapType;
|
|
typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> ProcessesKeyedScalarsMapType;
|
|
|
|
typedef mozilla::Tuple<const char*, nsCOMPtr<nsIVariant>, uint32_t> ScalarDataTuple;
|
|
typedef nsTArray<ScalarDataTuple> ScalarTupleArray;
|
|
typedef nsDataHashtable<ProcessIDHashKey, ScalarTupleArray> ScalarSnapshotTable;
|
|
|
|
typedef mozilla::Tuple<const char*, nsTArray<KeyedScalar::KeyValuePair>, uint32_t> KeyedScalarDataTuple;
|
|
typedef nsTArray<KeyedScalarDataTuple> KeyedScalarTupleArray;
|
|
typedef nsDataHashtable<ProcessIDHashKey, KeyedScalarTupleArray> KeyedScalarSnapshotTable;
|
|
|
|
} // 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 (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.
|
|
ProcessesScalarsMapType gScalarStorageMap;
|
|
// As above, for the keyed scalars.
|
|
ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
|
|
// Provide separate storage for "dynamic builtin" plain and keyed scalars,
|
|
// needed to support "build faster" in local developer builds.
|
|
ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap;
|
|
ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap;
|
|
|
|
// Whether or not the deserialization of persisted scalars is still in progress.
|
|
// This is never the case on Desktop or Fennec.
|
|
// Only GeckoView restores persisted scalars.
|
|
bool gIsDeserializing = false;
|
|
// This batches scalar accumulations that should be applied once loading finished.
|
|
StaticAutoPtr<nsTArray<ScalarAction>> gScalarsActions;
|
|
StaticAutoPtr<nsTArray<KeyedScalarAction>> gKeyedScalarsActions;
|
|
|
|
bool
|
|
internal_IsScalarDeserializing(const StaticMutexAutoLock& lock)
|
|
{
|
|
return gIsDeserializing;
|
|
}
|
|
|
|
} // 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 {
|
|
|
|
/**
|
|
* 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::NotInitialized:
|
|
errorMessage.AppendLiteral(u" - Telemetry was not yet initialized.");
|
|
break;
|
|
case ScalarResult::CannotUnpackVariant:
|
|
errorMessage.AppendLiteral(u" - Cannot convert the provided JS value to nsIVariant.");
|
|
break;
|
|
case ScalarResult::CannotRecordInProcess:
|
|
errorMessage.AppendLiteral(u" - Cannot record the scalar in the current process.");
|
|
break;
|
|
case ScalarResult::KeyedTypeMismatch:
|
|
errorMessage.AppendLiteral(u" - Attempting to manage a keyed scalar as a scalar (or vice-versa).");
|
|
break;
|
|
case ScalarResult::UnknownScalar:
|
|
errorMessage.AppendLiteral(u" - Unknown scalar.");
|
|
break;
|
|
case ScalarResult::OperationNotSupported:
|
|
errorMessage.AppendLiteral(u" - The requested operation is not supported on this scalar.");
|
|
break;
|
|
case ScalarResult::InvalidType:
|
|
errorMessage.AppendLiteral(u" - Attempted to set the scalar to an invalid data type.");
|
|
break;
|
|
case ScalarResult::InvalidValue:
|
|
errorMessage.AppendLiteral(u" - Attempted to set the scalar to an incompatible value.");
|
|
break;
|
|
case ScalarResult::StringTooLong:
|
|
AppendUTF8toUTF16(nsPrintfCString(" - Truncating scalar value to %d characters.", kMaximumStringValueLength), errorMessage);
|
|
break;
|
|
case ScalarResult::KeyIsEmpty:
|
|
errorMessage.AppendLiteral(u" - The key must not be empty.");
|
|
break;
|
|
case ScalarResult::KeyTooLong:
|
|
AppendUTF8toUTF16(nsPrintfCString(" - The key length must be limited to %d characters.", kMaximumKeyStringLength), errorMessage);
|
|
break;
|
|
case ScalarResult::TooManyKeys:
|
|
AppendUTF8toUTF16(nsPrintfCString(" - Keyed scalars cannot have more than %d keys.", kMaximumNumberOfKeys), errorMessage);
|
|
break;
|
|
case ScalarResult::UnsignedNegativeValue:
|
|
errorMessage.AppendLiteral(u" - Trying to set an unsigned scalar to a negative number.");
|
|
break;
|
|
case ScalarResult::UnsignedTruncatedValue:
|
|
errorMessage.AppendLiteral(u" - Truncating float/double number.");
|
|
break;
|
|
default:
|
|
// Nothing.
|
|
return;
|
|
}
|
|
|
|
LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: helpers for the external interface
|
|
|
|
namespace {
|
|
|
|
bool
|
|
internal_CanRecordBase(const StaticMutexAutoLock& lock)
|
|
{
|
|
return gCanRecordBase;
|
|
}
|
|
|
|
bool
|
|
internal_CanRecordExtended(const StaticMutexAutoLock& lock)
|
|
{
|
|
return gCanRecordExtended;
|
|
}
|
|
|
|
/**
|
|
* 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_CanRecordProduct(const StaticMutexAutoLock& lock,
|
|
const ScalarKey& aId)
|
|
{
|
|
const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
|
|
return CanRecordProduct(info.products);
|
|
}
|
|
|
|
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) {
|
|
return false;
|
|
}
|
|
|
|
bool canRecordDataset = CanRecordDataset(info.dataset,
|
|
canRecordBase,
|
|
internal_CanRecordExtended(lock));
|
|
if (!canRecordDataset) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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?
|
|
* @return ScalarResult::Ok if we can record, an error code otherwise.
|
|
*/
|
|
ScalarResult
|
|
internal_CanRecordScalar(const StaticMutexAutoLock& lock, const ScalarKey& aId,
|
|
bool aKeyed)
|
|
{
|
|
// Make sure that we have a keyed scalar if we are trying to change one.
|
|
if (internal_IsKeyedScalar(lock, aId) != aKeyed) {
|
|
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 (!internal_CanRecordProcess(lock, aId)) {
|
|
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 (!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 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);
|
|
|
|
// Dynamic scalars fixup: they are always stored in the "dynamic" process,
|
|
// unless they are part of the "builtin" Firefox probes. Please note that
|
|
// "dynamic builtin" probes are meant to support "artifact" and "build faster"
|
|
// builds.
|
|
if (aId.dynamic && !info.builtin) {
|
|
aProcessStorage = ProcessID::Dynamic;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
ScalarStorageMapType* scalarStorage = 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 && info.builtin) ? gDynamicBuiltinScalarStorageMap : gScalarStorageMap;
|
|
|
|
// Get the process-specific storage or create one if it's not
|
|
// available.
|
|
if (!processStorage.Get(storageId, &scalarStorage)) {
|
|
scalarStorage = new ScalarStorageMapType();
|
|
processStorage.Put(storageId, scalarStorage);
|
|
}
|
|
|
|
// 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.
|
|
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())) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
scalar = internal_ScalarAllocate(info.kind);
|
|
if (!scalar) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
scalarStorage->Put(aId.id, scalar);
|
|
*aRet = scalar;
|
|
return NS_OK;
|
|
}
|
|
|
|
void internal_ApplyPendingOperations(const StaticMutexAutoLock& lock);
|
|
|
|
/**
|
|
* Record that the high-water mark for the pending operations list was reached once.
|
|
*
|
|
* Important:
|
|
* This appends one additional operation.
|
|
* This needs to happen while still in deserialization mode.
|
|
*/
|
|
void internal_RecordHighwatermarkReached(const StaticMutexAutoLock& lock)
|
|
{
|
|
MOZ_ASSERT(gIsDeserializing);
|
|
MOZ_ASSERT(gScalarsActions);
|
|
|
|
// We can't call `internal_RecordScalarAction` here, because we are already
|
|
// getting called from there after the high-water mark check.
|
|
// But we know that `gScalarsActions` is a valid array and can append directly.
|
|
ScalarID id = ScalarID::TELEMETRY_PENDING_OPERATIONS_HIGHWATERMARK_REACHED;
|
|
ScalarAction action{
|
|
static_cast<uint32_t>(id), false, ScalarActionType::eAdd,
|
|
Some(ScalarVariant(1u)), ProcessID::Parent
|
|
};
|
|
gScalarsActions->AppendElement(action);
|
|
}
|
|
|
|
/**
|
|
* Record the given action on a scalar into the pending actions list.
|
|
*
|
|
* If the pending actions list overflows the high water mark length
|
|
* all operations are immediately applied, including the passed action.
|
|
*
|
|
* @param aScalarAction The action to record.
|
|
*/
|
|
void
|
|
internal_RecordScalarAction(const StaticMutexAutoLock& lock,
|
|
const ScalarAction& aScalarAction)
|
|
{
|
|
// Make sure to have the storage.
|
|
if (!gScalarsActions) {
|
|
gScalarsActions = new nsTArray<ScalarAction>();
|
|
}
|
|
|
|
// Store the action.
|
|
gScalarsActions->AppendElement(aScalarAction);
|
|
|
|
// If this action overflows the pending actions array, we immediately apply pending operations
|
|
// and assume loading is over.
|
|
// If loading still happens afterwards, some scalar values might be
|
|
// overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
|
|
if (gScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
|
|
internal_RecordHighwatermarkReached(lock);
|
|
internal_ApplyPendingOperations(lock);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record the given action on a scalar on the main process into the pending actions list.
|
|
*
|
|
* If the pending actions list overflows the high water mark length
|
|
* all operations are immediately applied, including the passed action.
|
|
*
|
|
* @param aId The scalar's ID this action applies to
|
|
* @param aDynamic Determines if the scalar is dynamic
|
|
* @param aAction The action to record
|
|
* @param aValue The additional data for the recorded action
|
|
*/
|
|
void
|
|
internal_RecordScalarAction(const StaticMutexAutoLock& lock,
|
|
uint32_t aId, bool aDynamic,
|
|
ScalarActionType aAction, const ScalarVariant& aValue)
|
|
{
|
|
internal_RecordScalarAction(lock, ScalarAction{
|
|
aId, aDynamic, aAction,
|
|
Some(aValue), ProcessID::Parent
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Record the given action on a keyed scalar into the pending actions list.
|
|
*
|
|
* If the pending actions list overflows the high water mark length
|
|
* all operations are immediately applied, including the passed action.
|
|
*
|
|
* @param aScalarAction The action to record.
|
|
*/
|
|
void
|
|
internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
|
|
const KeyedScalarAction& aScalarAction)
|
|
{
|
|
// Make sure to have the storage.
|
|
if (!gKeyedScalarsActions) {
|
|
gKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
|
|
}
|
|
|
|
// Store the action.
|
|
gKeyedScalarsActions->AppendElement(aScalarAction);
|
|
|
|
// If this action overflows the pending actions array, we immediately apply pending operations
|
|
// and assume loading is over.
|
|
// If loading still happens afterwards, some scalar values might be
|
|
// overwritten and inconsistent, but we won't lose operations on otherwise untouched probes.
|
|
if (gKeyedScalarsActions->Length() > kScalarActionsArrayHighWaterMark) {
|
|
internal_RecordHighwatermarkReached(lock);
|
|
internal_ApplyPendingOperations(lock);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record the given action on a keyed scalar on the main process into the pending actions list.
|
|
*
|
|
* If the pending actions list overflows the high water mark length
|
|
* all operations are immediately applied, including the passed action.
|
|
*
|
|
* @param aId The scalar's ID this action applies to
|
|
* @param aDynamic Determines if the scalar is dynamic
|
|
* @param aKey The scalar's key
|
|
* @param aAction The action to record
|
|
* @param aValue The additional data for the recorded action
|
|
*/
|
|
void
|
|
internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock,
|
|
uint32_t aId, bool aDynamic,
|
|
const nsAString& aKey,
|
|
ScalarActionType aAction,
|
|
const ScalarVariant& aValue)
|
|
{
|
|
internal_RecordKeyedScalarAction(lock, KeyedScalarAction{
|
|
aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey),
|
|
Some(aValue), ProcessID::Parent
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update the scalar with the provided value. This is used by the JS API.
|
|
*
|
|
* @param lock Instance of a lock locking gTelemetryHistogramMutex
|
|
* @param aName The scalar name.
|
|
* @param aType The action type for updating the scalar.
|
|
* @param aValue The value to use for updating the scalar.
|
|
* @param aProcessOverride The process for which the scalar must be updated.
|
|
* This must only be used for GeckoView persistence. It must be
|
|
* set to the ProcessID::Parent for all the other cases.
|
|
* @param aForce Whether to force updating even if load is in progress.
|
|
* @return a ScalarResult error value.
|
|
*/
|
|
ScalarResult
|
|
internal_UpdateScalar(const StaticMutexAutoLock& lock, const nsACString& aName,
|
|
ScalarActionType aType, nsIVariant* aValue,
|
|
ProcessID aProcessOverride = ProcessID::Parent,
|
|
bool aForce = false)
|
|
{
|
|
ScalarKey uniqueId;
|
|
nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
|
|
if (NS_FAILED(rv)) {
|
|
return (rv == NS_ERROR_FAILURE) ?
|
|
ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
|
|
}
|
|
|
|
ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, false);
|
|
if (sr != ScalarResult::Ok) {
|
|
if (sr == ScalarResult::CannotRecordDataset) {
|
|
return ScalarResult::Ok;
|
|
}
|
|
return sr;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
|
|
// Convert the nsIVariant to a Variant.
|
|
mozilla::Maybe<ScalarVariant> variantValue;
|
|
sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
|
|
if (sr != ScalarResult::Ok) {
|
|
MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
|
|
return sr;
|
|
}
|
|
TelemetryIPCAccumulator::RecordChildScalarAction(
|
|
uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
|
|
return ScalarResult::Ok;
|
|
}
|
|
|
|
if (!aForce && internal_IsScalarDeserializing(lock)) {
|
|
const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
|
|
// Convert the nsIVariant to a Variant.
|
|
mozilla::Maybe<ScalarVariant> variantValue;
|
|
sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
|
|
if (sr != ScalarResult::Ok) {
|
|
MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
|
|
return sr;
|
|
}
|
|
internal_RecordScalarAction(lock, uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
|
|
return ScalarResult::Ok;
|
|
}
|
|
|
|
// Finally get the scalar.
|
|
ScalarBase* scalar = nullptr;
|
|
rv = internal_GetScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
|
|
if (NS_FAILED(rv)) {
|
|
// Don't throw on expired scalars.
|
|
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
|
return ScalarResult::Ok;
|
|
}
|
|
return ScalarResult::UnknownScalar;
|
|
}
|
|
|
|
if (aType == ScalarActionType::eAdd) {
|
|
return scalar->AddValue(aValue);
|
|
}
|
|
if (aType == ScalarActionType::eSet) {
|
|
return scalar->SetValue(aValue);
|
|
}
|
|
|
|
return scalar->SetMaximum(aValue);
|
|
}
|
|
|
|
} // 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);
|
|
|
|
// Dynamic scalars fixup: they are always stored in the "dynamic" process,
|
|
// unless they are part of the "builtin" Firefox probes. Please note that
|
|
// "dynamic builtin" probes are meant to support "artifact" and "build faster"
|
|
// builds.
|
|
if (aId.dynamic && !info.builtin) {
|
|
aProcessStorage = ProcessID::Dynamic;
|
|
}
|
|
|
|
KeyedScalar* scalar = nullptr;
|
|
KeyedScalarStorageMapType* scalarStorage = 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 && info.builtin) ? gDynamicBuiltinKeyedScalarStorageMap : gKeyedScalarStorageMap;
|
|
|
|
// Get the process-specific storage or create one if it's not
|
|
// available.
|
|
if (!processStorage.Get(storageId, &scalarStorage)) {
|
|
scalarStorage = new KeyedScalarStorageMapType();
|
|
processStorage.Put(storageId, scalarStorage);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
scalar = new KeyedScalar(info.kind);
|
|
if (!scalar) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
scalarStorage->Put(aId.id, scalar);
|
|
*aRet = scalar;
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Update the keyed scalar with the provided value. This is used by the JS API.
|
|
*
|
|
* @param lock Instance of a lock locking gTelemetryHistogramMutex
|
|
* @param aName The scalar name.
|
|
* @param aKey The key name.
|
|
* @param aType The action type for updating the scalar.
|
|
* @param aValue The value to use for updating the scalar.
|
|
* @param aProcessOverride The process for which the scalar must be updated.
|
|
* This must only be used for GeckoView persistence. It must be
|
|
* set to the ProcessID::Parent for all the other cases.
|
|
* @return a ScalarResult error value.
|
|
*/
|
|
ScalarResult
|
|
internal_UpdateKeyedScalar(const StaticMutexAutoLock& lock,
|
|
const nsACString& aName, const nsAString& aKey,
|
|
ScalarActionType aType, nsIVariant* aValue,
|
|
ProcessID aProcessOverride = ProcessID::Parent,
|
|
bool aForce = false)
|
|
{
|
|
ScalarKey uniqueId;
|
|
nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
|
|
if (NS_FAILED(rv)) {
|
|
return (rv == NS_ERROR_FAILURE) ?
|
|
ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
|
|
}
|
|
|
|
ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, true);
|
|
if (sr != ScalarResult::Ok) {
|
|
if (sr == ScalarResult::CannotRecordDataset) {
|
|
return ScalarResult::Ok;
|
|
}
|
|
return sr;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
|
|
// Convert the nsIVariant to a Variant.
|
|
mozilla::Maybe<ScalarVariant> variantValue;
|
|
sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
|
|
if (sr != ScalarResult::Ok) {
|
|
MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
|
|
return sr;
|
|
}
|
|
TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
|
|
uniqueId.id, uniqueId.dynamic, aKey, aType, variantValue.ref());
|
|
return ScalarResult::Ok;
|
|
}
|
|
|
|
if (!aForce && internal_IsScalarDeserializing(lock)) {
|
|
const BaseScalarInfo &info = internal_GetScalarInfo(lock, uniqueId);
|
|
// Convert the nsIVariant to a Variant.
|
|
mozilla::Maybe<ScalarVariant> variantValue;
|
|
sr = GetVariantFromIVariant(aValue, info.kind, variantValue);
|
|
if (sr != ScalarResult::Ok) {
|
|
MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant.");
|
|
return sr;
|
|
}
|
|
internal_RecordKeyedScalarAction(lock,
|
|
uniqueId.id, uniqueId.dynamic,
|
|
aKey, aType, variantValue.ref());
|
|
return ScalarResult::Ok;
|
|
}
|
|
|
|
// Finally get the scalar.
|
|
KeyedScalar* scalar = nullptr;
|
|
rv = internal_GetKeyedScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
|
|
if (NS_FAILED(rv)) {
|
|
// Don't throw on expired scalars.
|
|
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
|
return ScalarResult::Ok;
|
|
}
|
|
return ScalarResult::UnknownScalar;
|
|
}
|
|
|
|
if (aType == ScalarActionType::eAdd) {
|
|
return scalar->AddValue(aKey, aValue);
|
|
}
|
|
if (aType == ScalarActionType::eSet) {
|
|
return scalar->SetValue(aKey, aValue);
|
|
}
|
|
|
|
return scalar->SetMaximum(aKey, aValue);
|
|
}
|
|
|
|
/**
|
|
* 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 StaticMutexAutoLock& lock,
|
|
const nsTArray<DynamicScalarInfo>& scalarInfos)
|
|
{
|
|
nsTArray<mozilla::dom::ContentParent*> parents;
|
|
mozilla::dom::ContentParent::GetAll(parents);
|
|
if (!parents.Length()) {
|
|
return;
|
|
}
|
|
|
|
// Convert the internal scalar representation to a stripped down IPC one.
|
|
nsTArray<DynamicScalarDefinition> ipcDefinitions;
|
|
internal_DynamicScalarToIPC(lock, scalarInfos, ipcDefinitions);
|
|
|
|
// Broadcast the definitions to the other content processes.
|
|
for (auto parent : parents) {
|
|
mozilla::Unused << parent->SendAddDynamicScalars(ipcDefinitions);
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_RegisterScalars(const StaticMutexAutoLock& lock,
|
|
const nsTArray<DynamicScalarInfo>& scalarInfos)
|
|
{
|
|
// Register the new scalars.
|
|
if (!gDynamicScalarInfo) {
|
|
gDynamicScalarInfo = new nsTArray<DynamicScalarInfo>();
|
|
}
|
|
|
|
for (auto scalarInfo : scalarInfos) {
|
|
// Allow expiring scalars that were already registered.
|
|
CharPtrEntryType *existingKey = gScalarNameIDMap.GetEntry(scalarInfo.name());
|
|
if (existingKey) {
|
|
// Change the scalar to expired if needed.
|
|
if (scalarInfo.mDynamicExpiration && !scalarInfo.builtin) {
|
|
DynamicScalarInfo& scalarData = (*gDynamicScalarInfo)[existingKey->mData.id];
|
|
scalarData.mDynamicExpiration = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
gDynamicScalarInfo->AppendElement(scalarInfo);
|
|
uint32_t scalarId = gDynamicScalarInfo->Length() - 1;
|
|
CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(scalarInfo.name());
|
|
entry->mData = ScalarKey{scalarId, true};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
unsigned int aDataset,
|
|
ProcessesScalarsMapType& aProcessStorage,
|
|
bool aIsBuiltinDynamic)
|
|
{
|
|
// Iterate the scalars in aProcessStorage. The storage may contain empty or yet to be
|
|
// initialized scalars from all the supported processes.
|
|
for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
|
|
ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
|
|
ScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
|
|
|
|
// Are we in the "Dynamic" process?
|
|
bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
|
|
|
|
// Iterate each available child storage.
|
|
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
|
|
ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
|
|
|
|
// Get the informations for this scalar.
|
|
const BaseScalarInfo& info =
|
|
internal_GetScalarInfo(aLock, ScalarKey{childIter.Key(),
|
|
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(scalarValue);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
// Append it to our list.
|
|
processScalars.AppendElement(mozilla::MakeTuple(info.name(), scalarValue, info.kind));
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
unsigned int aDataset,
|
|
ProcessesKeyedScalarsMapType& aProcessStorage,
|
|
bool aIsBuiltinDynamic)
|
|
{
|
|
// Iterate the scalars in aProcessStorage. The storage may contain empty or yet
|
|
// to be initialized scalars from all the supported processes.
|
|
for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
|
|
KeyedScalarStorageMapType* scalarStorage =
|
|
static_cast<KeyedScalarStorageMapType*>(iter.Data());
|
|
KeyedScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
|
|
|
|
// Are we in the "Dynamic" process?
|
|
bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
|
|
|
|
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
|
|
KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
|
|
|
|
// Get the informations for this scalar.
|
|
const BaseScalarInfo& info =
|
|
internal_GetScalarInfo(aLock, ScalarKey{childIter.Key(),
|
|
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(scalarKeyedData);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
// Append it to our list.
|
|
processScalars.AppendElement(
|
|
mozilla::MakeTuple(info.name(), scalarKeyedData, info.kind));
|
|
}
|
|
}
|
|
}
|
|
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.
|
|
* @return NS_OK or the error code describing the failure reason.
|
|
*/
|
|
nsresult
|
|
internal_GetScalarSnapshot(const StaticMutexAutoLock& aLock,
|
|
ScalarSnapshotTable& aScalarsToReflect,
|
|
unsigned int aDataset, bool aClearScalars)
|
|
{
|
|
// Take a snapshot of the scalars.
|
|
nsresult rv = internal_ScalarSnapshotter(aLock,
|
|
aScalarsToReflect,
|
|
aDataset,
|
|
gScalarStorageMap,
|
|
false /*aIsBuiltinDynamic*/);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// And a snapshot of the dynamic builtin ones.
|
|
rv = internal_ScalarSnapshotter(aLock,
|
|
aScalarsToReflect,
|
|
aDataset,
|
|
gDynamicBuiltinScalarStorageMap,
|
|
true /*aIsBuiltinDynamic*/);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (aClearScalars) {
|
|
// The map already takes care of freeing the allocated memory.
|
|
gScalarStorageMap.Clear();
|
|
gDynamicBuiltinScalarStorageMap.Clear();
|
|
}
|
|
|
|
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.
|
|
* @return NS_OK or the error code describing the failure reason.
|
|
*/
|
|
nsresult
|
|
internal_GetKeyedScalarSnapshot(const StaticMutexAutoLock& aLock,
|
|
KeyedScalarSnapshotTable& aScalarsToReflect,
|
|
unsigned int aDataset, bool aClearScalars)
|
|
{
|
|
// Take a snapshot of the scalars.
|
|
nsresult rv = internal_KeyedScalarSnapshotter(aLock,
|
|
aScalarsToReflect,
|
|
aDataset,
|
|
gKeyedScalarStorageMap,
|
|
false /*aIsBuiltinDynamic*/);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// And a snapshot of the dynamic builtin ones.
|
|
rv = internal_KeyedScalarSnapshotter(aLock,
|
|
aScalarsToReflect,
|
|
aDataset,
|
|
gDynamicBuiltinKeyedScalarStorageMap,
|
|
true /*aIsBuiltinDynamic*/);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (aClearScalars) {
|
|
// The map already takes care of freeing the allocated memory.
|
|
gKeyedScalarStorageMap.Clear();
|
|
gDynamicBuiltinKeyedScalarStorageMap.Clear();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// helpers for recording/applying scalar operations
|
|
namespace {
|
|
|
|
void
|
|
internal_ApplyScalarActions(const StaticMutexAutoLock& lock,
|
|
const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions,
|
|
const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
|
|
{
|
|
if (!internal_CanRecordBase(lock)) {
|
|
return;
|
|
}
|
|
|
|
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)) {
|
|
NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
|
|
continue;
|
|
}
|
|
|
|
if (upd.mData.isNothing()) {
|
|
MOZ_ASSERT(false, "There is no data in the ScalarActionType.");
|
|
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;
|
|
|
|
// Extract the data from the mozilla::Variant.
|
|
switch (upd.mActionType)
|
|
{
|
|
case ScalarActionType::eSet:
|
|
{
|
|
switch (scalarType)
|
|
{
|
|
case nsITelemetry::SCALAR_TYPE_COUNT:
|
|
scalar->SetValue(upd.mData->as<uint32_t>());
|
|
break;
|
|
case nsITelemetry::SCALAR_TYPE_BOOLEAN:
|
|
scalar->SetValue(upd.mData->as<bool>());
|
|
break;
|
|
case nsITelemetry::SCALAR_TYPE_STRING:
|
|
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.
|
|
scalar->AddValue(upd.mData->as<uint32_t>());
|
|
break;
|
|
}
|
|
case ScalarActionType::eSetMaximum:
|
|
{
|
|
if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
|
|
NS_WARNING("Attempting to add on a non count scalar.");
|
|
continue;
|
|
}
|
|
// We only support SetMaximum on uint32_t.
|
|
scalar->SetMaximum(upd.mData->as<uint32_t>());
|
|
break;
|
|
}
|
|
default:
|
|
NS_WARNING("Unsupported action coming from scalar child updates.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_ApplyKeyedScalarActions(const StaticMutexAutoLock& lock,
|
|
const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions,
|
|
const mozilla::Maybe<ProcessID>& aProcessType = Nothing())
|
|
{
|
|
if (!internal_CanRecordBase(lock)) {
|
|
return;
|
|
}
|
|
|
|
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)) {
|
|
NS_WARNING("NS_FAILED internal_GetScalarByEnum 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;
|
|
|
|
// Extract the data from the mozilla::Variant.
|
|
switch (upd.mActionType)
|
|
{
|
|
case ScalarActionType::eSet:
|
|
{
|
|
switch (scalarType)
|
|
{
|
|
case nsITelemetry::SCALAR_TYPE_COUNT:
|
|
scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
|
|
break;
|
|
case nsITelemetry::SCALAR_TYPE_BOOLEAN:
|
|
scalar->SetValue(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.
|
|
scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
|
|
break;
|
|
}
|
|
case ScalarActionType::eSetMaximum:
|
|
{
|
|
if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) {
|
|
NS_WARNING("Attempting to add on a non count scalar.");
|
|
continue;
|
|
}
|
|
// We only support SetMaximum on uint32_t.
|
|
scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData->as<uint32_t>());
|
|
break;
|
|
}
|
|
default:
|
|
NS_WARNING("Unsupported action coming from keyed scalar child updates.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_ApplyPendingOperations(const StaticMutexAutoLock& lock)
|
|
{
|
|
if (gScalarsActions && gScalarsActions->Length() > 0) {
|
|
internal_ApplyScalarActions(lock, *gScalarsActions);
|
|
gScalarsActions->Clear();
|
|
}
|
|
|
|
if (gKeyedScalarsActions && gKeyedScalarsActions->Length() > 0) {
|
|
internal_ApplyKeyedScalarActions(lock, *gKeyedScalarsActions);
|
|
gKeyedScalarsActions->Clear();
|
|
}
|
|
|
|
// After all pending operations are applied deserialization is done
|
|
gIsDeserializing = false;
|
|
}
|
|
|
|
} // 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 = 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 */,
|
|
false /* built-in */,
|
|
},
|
|
});
|
|
internal_RegisterScalars(locker, initialDynamicScalars);
|
|
|
|
gInitDone = true;
|
|
}
|
|
|
|
void
|
|
TelemetryScalar::DeInitializeGlobalState()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
gCanRecordBase = false;
|
|
gCanRecordExtended = false;
|
|
gScalarNameIDMap.Clear();
|
|
gScalarStorageMap.Clear();
|
|
gKeyedScalarStorageMap.Clear();
|
|
gDynamicBuiltinScalarStorageMap.Clear();
|
|
gDynamicBuiltinKeyedScalarStorageMap.Clear();
|
|
gDynamicScalarInfo = nullptr;
|
|
gInitDone = false;
|
|
}
|
|
|
|
void
|
|
TelemetryScalar::DeserializationStarted()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
gIsDeserializing = true;
|
|
}
|
|
|
|
void
|
|
TelemetryScalar::ApplyPendingOperations()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
internal_ApplyPendingOperations(locker);
|
|
}
|
|
|
|
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 (always) so that the JS API call doesn't throw. In case of errors,
|
|
* a warning level message is printed in the browser console.
|
|
*/
|
|
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)) {
|
|
internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
|
|
return NS_OK;
|
|
}
|
|
|
|
ScalarResult sr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
sr = internal_UpdateScalar(locker, aName, ScalarActionType::eAdd,
|
|
unpackedVal);
|
|
}
|
|
|
|
// Warn the user about the error if we need to.
|
|
if (sr != ScalarResult::Ok) {
|
|
internal_LogScalarError(aName, sr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* 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 (always) so that the JS API call doesn't throw. In case of errors,
|
|
* a warning level message is printed in the browser console.
|
|
*/
|
|
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)) {
|
|
internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
|
|
return NS_OK;
|
|
}
|
|
|
|
ScalarResult sr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
sr = internal_UpdateKeyedScalar(locker, aName, aKey,
|
|
ScalarActionType::eAdd,
|
|
unpackedVal);
|
|
}
|
|
|
|
// Warn the user about the error if we need to.
|
|
if (sr != ScalarResult::Ok) {
|
|
internal_LogScalarError(aName, sr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eAdd,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eAdd,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
aKey, ScalarActionType::eAdd, ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
aKey,
|
|
ScalarActionType::eAdd,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
KeyedScalar* scalar = nullptr;
|
|
nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
|
|
ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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 (always) so that the JS API call doesn't throw. In case of errors,
|
|
* a warning level message is printed in the browser console.
|
|
*/
|
|
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)) {
|
|
internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
|
|
return NS_OK;
|
|
}
|
|
|
|
ScalarResult sr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSet,
|
|
unpackedVal);
|
|
}
|
|
|
|
// Warn the user about the error if we need to.
|
|
if (sr != ScalarResult::Ok) {
|
|
internal_LogScalarError(aName, sr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* 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 (always) so that the JS API call doesn't throw. In case of errors,
|
|
* a warning level message is printed in the browser console.
|
|
*/
|
|
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)) {
|
|
internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
|
|
return NS_OK;
|
|
}
|
|
|
|
ScalarResult sr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
sr = internal_UpdateKeyedScalar(locker, aName, aKey,
|
|
ScalarActionType::eSet,
|
|
unpackedVal);
|
|
}
|
|
|
|
// Warn the user about the error if we need to.
|
|
if (sr != ScalarResult::Ok) {
|
|
internal_LogScalarError(aName, sr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(nsString(aValue)));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(nsString(aValue)));
|
|
return;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
aKey, ScalarActionType::eSet, ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
aKey,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
KeyedScalar* scalar = nullptr;
|
|
nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
|
|
ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
aKey, ScalarActionType::eSet, ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
aKey,
|
|
ScalarActionType::eSet,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
KeyedScalar* scalar = nullptr;
|
|
nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId,
|
|
ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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 (always) so that the JS API call doesn't throw. In case of errors,
|
|
* a warning level message is printed in the browser console.
|
|
*/
|
|
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)) {
|
|
internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
|
|
return NS_OK;
|
|
}
|
|
|
|
ScalarResult sr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSetMaximum,
|
|
unpackedVal);
|
|
}
|
|
|
|
// Warn the user about the error if we need to.
|
|
if (sr != ScalarResult::Ok) {
|
|
internal_LogScalarError(aName, sr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* 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 (always) so that the JS API call doesn't throw. In case of errors,
|
|
* a warning level message is printed in the browser console.
|
|
*/
|
|
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)) {
|
|
internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant);
|
|
return NS_OK;
|
|
}
|
|
|
|
ScalarResult sr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
sr = internal_UpdateKeyedScalar(locker, aName, aKey,
|
|
ScalarActionType::eSetMaximum,
|
|
unpackedVal);
|
|
}
|
|
|
|
// Warn the user about the error if we need to.
|
|
if (sr != ScalarResult::Ok) {
|
|
internal_LogScalarError(aName, sr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSetMaximum,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
ScalarActionType::eSetMaximum,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
nsresult rv = internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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)
|
|
{
|
|
if (NS_WARN_IF(!IsValidEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
ScalarKey uniqueId{static_cast<uint32_t>(aId), false};
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) {
|
|
// We can't record this scalar. Bail out.
|
|
return;
|
|
}
|
|
|
|
// Accumulate in the child process if needed.
|
|
if (!XRE_IsParentProcess()) {
|
|
TelemetryIPCAccumulator::RecordChildKeyedScalarAction(uniqueId.id, uniqueId.dynamic,
|
|
aKey, ScalarActionType::eSetMaximum, ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic,
|
|
aKey,
|
|
ScalarActionType::eSetMaximum,
|
|
ScalarVariant(aValue));
|
|
return;
|
|
}
|
|
|
|
KeyedScalar* scalar = nullptr;
|
|
nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, ProcessID::Parent,
|
|
&scalar);
|
|
if (NS_FAILED(rv)) {
|
|
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:
|
|
* {"process": {"category1.probe":1,"category1.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)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"Snapshotting scalars should only happen in the parent processes.");
|
|
// 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);
|
|
|
|
// Return `{}` in child processes.
|
|
if (!XRE_IsParentProcess()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Only lock the mutex while accessing our data, without locking any JS related code.
|
|
ScalarSnapshotTable scalarsToReflect;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
nsresult rv =
|
|
internal_GetScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Reflect it to JS.
|
|
for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
|
|
ScalarTupleArray& processScalars = iter.Data();
|
|
const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
|
|
|
|
// Create the object that will hold the scalars for this process and add it
|
|
// to the returned root object.
|
|
JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
|
|
if (!processObj ||
|
|
!JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
for (ScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
|
|
const ScalarDataTuple& scalar = processScalars[i];
|
|
|
|
// Convert it to a JS Val.
|
|
JS::Rooted<JS::Value> scalarJsValue(aCx);
|
|
nsresult rv =
|
|
nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, mozilla::Get<1>(scalar), &scalarJsValue);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// Add it to the scalar object.
|
|
if (!JS_DefineProperty(aCx, processObj, mozilla::Get<0>(scalar), 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:
|
|
* { "process": { "category1.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)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"Snapshotting scalars should only happen in the parent processes.");
|
|
// 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);
|
|
|
|
// Return `{}` in child processes.
|
|
if (!XRE_IsParentProcess()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Only lock the mutex while accessing our data, without locking any JS related code.
|
|
KeyedScalarSnapshotTable scalarsToReflect;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
nsresult rv =
|
|
internal_GetKeyedScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Reflect it to JS.
|
|
for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
|
|
KeyedScalarTupleArray& processScalars = iter.Data();
|
|
const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
|
|
|
|
// Create the object that will hold the scalars for this process and add it
|
|
// to the returned root object.
|
|
JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
|
|
if (!processObj ||
|
|
!JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
for (KeyedScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
|
|
const KeyedScalarDataTuple& keyedScalarData = processScalars[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 = mozilla::Get<1>(keyedScalarData);
|
|
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, processObj, mozilla::Get<0>(keyedScalarData), keyedScalarObj, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
|
|
JS::Handle<JS::Value> aScalarData,
|
|
bool aBuiltin,
|
|
JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"Dynamic scalars should only be created in the parent process.");
|
|
|
|
if (!IsValidIdentifierString(aCategoryName, kMaximumCategoryNameLength, true, false)) {
|
|
JS_ReportErrorASCII(cx, "Invalid category name %s.",
|
|
PromiseFlatCString(aCategoryName).get());
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (!aScalarData.isObject()) {
|
|
JS_ReportErrorASCII(cx, "Scalar data parameter should be an object");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
JS::RootedObject obj(cx, &aScalarData.toObject());
|
|
JS::Rooted<JS::IdVector> scalarPropertyIds(cx, JS::IdVector(cx));
|
|
if (!JS_Enumerate(cx, obj, &scalarPropertyIds)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Collect the scalar data into local storage first.
|
|
// Only after successfully validating all contained scalars will we register
|
|
// them into global storage.
|
|
nsTArray<DynamicScalarInfo> newScalarInfos;
|
|
|
|
for (size_t i = 0, n = scalarPropertyIds.length(); i < n; i++) {
|
|
nsAutoJSString scalarName;
|
|
if (!scalarName.init(cx, scalarPropertyIds[i])) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(scalarName), kMaximumScalarNameLength,
|
|
false, true)) {
|
|
JS_ReportErrorASCII(cx, "Invalid scalar name %s.",
|
|
PromiseFlatCString(NS_ConvertUTF16toUTF8(scalarName)).get());
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// Join the category and the probe names.
|
|
nsPrintfCString fullName("%s.%s",
|
|
PromiseFlatCString(aCategoryName).get(),
|
|
NS_ConvertUTF16toUTF8(scalarName).get());
|
|
|
|
JS::RootedValue value(cx);
|
|
if (!JS_GetPropertyById(cx, obj, scalarPropertyIds[i], &value) || !value.isObject()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
JS::RootedObject scalarDef(cx, &value.toObject());
|
|
|
|
// Get the scalar's kind.
|
|
if (!JS_GetProperty(cx, scalarDef, "kind", &value)
|
|
|| !value.isInt32()) {
|
|
JS_ReportErrorASCII(cx, "Invalid or missing 'kind' for scalar %s.",
|
|
PromiseFlatCString(fullName).get());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
uint32_t kind = static_cast<uint32_t>(value.toInt32());
|
|
|
|
// Get the optional scalar's recording policy (default to false).
|
|
bool hasProperty = false;
|
|
bool recordOnRelease = false;
|
|
if (JS_HasProperty(cx, scalarDef, "record_on_release", &hasProperty) && hasProperty) {
|
|
if (!JS_GetProperty(cx, scalarDef, "record_on_release", &value) || !value.isBoolean()) {
|
|
JS_ReportErrorASCII(cx, "Invalid 'record_on_release' for scalar %s.",
|
|
PromiseFlatCString(fullName).get());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
recordOnRelease = static_cast<bool>(value.toBoolean());
|
|
}
|
|
|
|
// Get the optional scalar's keyed (default to false).
|
|
bool keyed = false;
|
|
if (JS_HasProperty(cx, scalarDef, "keyed", &hasProperty) && hasProperty) {
|
|
if (!JS_GetProperty(cx, scalarDef, "keyed", &value) || !value.isBoolean()) {
|
|
JS_ReportErrorASCII(cx, "Invalid 'keyed' for scalar %s.",
|
|
PromiseFlatCString(fullName).get());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
keyed = static_cast<bool>(value.toBoolean());
|
|
}
|
|
|
|
|
|
// Get the optional scalar's expired state (default to false).
|
|
bool expired = false;
|
|
if (JS_HasProperty(cx, scalarDef, "expired", &hasProperty) && hasProperty) {
|
|
if (!JS_GetProperty(cx, scalarDef, "expired", &value) || !value.isBoolean()) {
|
|
JS_ReportErrorASCII(cx, "Invalid 'expired' for scalar %s.",
|
|
PromiseFlatCString(fullName).get());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
expired = static_cast<bool>(value.toBoolean());
|
|
}
|
|
|
|
// We defer the actual registration here in case any other event description is invalid.
|
|
// In that case we don't need to roll back any partial registration.
|
|
newScalarInfos.AppendElement(DynamicScalarInfo{
|
|
kind, recordOnRelease, expired, fullName, keyed, aBuiltin
|
|
});
|
|
}
|
|
|
|
// Register the dynamic definition on the parent process.
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
::internal_RegisterScalars(locker, newScalarInfos);
|
|
|
|
// Propagate the registration to all the content-processes. Please note that
|
|
// this does not require to hold the mutex.
|
|
::internal_BroadcastDefinitions(locker, newScalarInfos);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Count in Scalars how many of which events were recorded. See bug 1440673
|
|
*
|
|
* Event Telemetry unfortunately cannot use vanilla ScalarAdd because it needs
|
|
* to summarize events recorded in different processes to the
|
|
* telemetry.event_counts of the same process. Including "dynamic".
|
|
*
|
|
* @param aUniqueEventName - expected to be category#object#method
|
|
* @param aProcessType - the process of the event being summarized
|
|
* @param aDynamic - whether the event being summarized was dynamic
|
|
*/
|
|
void
|
|
TelemetryScalar::SummarizeEvent(const nsCString& aUniqueEventName,
|
|
ProcessID aProcessType, bool aDynamic)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(), "Only summarize events in the parent process");
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock lock(gTelemetryScalarsMutex);
|
|
|
|
ScalarKey scalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_EVENT_COUNTS), aDynamic};
|
|
if (aDynamic) {
|
|
nsresult rv = internal_GetEnumByScalarName(lock,
|
|
nsAutoCString("telemetry.dynamic_event_counts"),
|
|
&scalarKey);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("NS_FAILED getting ScalarKey for telemetry.dynamic_event_counts");
|
|
return;
|
|
}
|
|
}
|
|
|
|
KeyedScalar* scalar = nullptr;
|
|
nsresult rv = internal_GetKeyedScalarByEnum(lock, scalarKey, aProcessType, &scalar);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("NS_FAILED getting keyed scalar for event summary. Wut.");
|
|
return;
|
|
}
|
|
|
|
static uint32_t sMaxEventSummaryKeys =
|
|
Preferences::GetUint("toolkit.telemetry.maxEventSummaryKeys", 500);
|
|
|
|
// Set this each time as it may have been cleared and recreated between calls
|
|
scalar->SetMaximumNumberOfKeys(sMaxEventSummaryKeys);
|
|
|
|
scalar->AddValue(NS_ConvertASCIItoUTF16(aUniqueEventName), 1);
|
|
}
|
|
|
|
/**
|
|
* Resets all the stored scalars. This is intended to be only used in tests.
|
|
*/
|
|
void
|
|
TelemetryScalar::ClearScalars()
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(), "Scalars should only be cleared in the parent process.");
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
gScalarStorageMap.Clear();
|
|
gKeyedScalarStorageMap.Clear();
|
|
gDynamicBuiltinScalarStorageMap.Clear();
|
|
gDynamicBuiltinKeyedScalarStorageMap.Clear();
|
|
gScalarsActions = nullptr;
|
|
gKeyedScalarsActions = nullptr;
|
|
}
|
|
|
|
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;
|
|
|
|
auto getSizeOf = [aMallocSizeOf](auto &storageMap)
|
|
{
|
|
size_t partial = 0;
|
|
for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
|
|
auto scalarStorage = iter.UserData();
|
|
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
|
|
auto scalar = childIter.UserData();
|
|
partial += scalar->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
}
|
|
return partial;
|
|
};
|
|
|
|
// Account for all the storage used for the different scalar types.
|
|
n += getSizeOf(gScalarStorageMap);
|
|
n += getSizeOf(gKeyedScalarStorageMap);
|
|
n += getSizeOf(gDynamicBuiltinScalarStorageMap);
|
|
n += getSizeOf(gDynamicBuiltinKeyedScalarStorageMap);
|
|
|
|
return n;
|
|
}
|
|
|
|
void
|
|
TelemetryScalar::UpdateChildData(ProcessID aProcessType,
|
|
const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"The stored child processes scalar data must be updated from the parent process.");
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
// If scalars are still being deserialized, we need to record the incoming
|
|
// operations as well.
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
for (const ScalarAction& action : aScalarActions) {
|
|
// We're only getting immutable access, so let's copy it
|
|
ScalarAction copy = action;
|
|
// Fix up the process type
|
|
copy.mProcessType = aProcessType;
|
|
internal_RecordScalarAction(locker, copy);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
internal_ApplyScalarActions(locker, aScalarActions, Some(aProcessType));
|
|
}
|
|
|
|
void
|
|
TelemetryScalar::UpdateChildKeyedData(ProcessID aProcessType,
|
|
const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"The stored child processes keyed scalar data must be updated from the parent process.");
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
|
|
// If scalars are still being deserialized, we need to record the incoming
|
|
// operations as well.
|
|
if (internal_IsScalarDeserializing(locker)) {
|
|
for (const KeyedScalarAction& action : aScalarActions) {
|
|
// We're only getting immutable access, so let's copy it
|
|
KeyedScalarAction copy = action;
|
|
// Fix up the process type
|
|
copy.mProcessType = aProcessType;
|
|
internal_RecordKeyedScalarAction(locker, copy);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
internal_ApplyKeyedScalarActions(locker, aScalarActions, Some(aProcessType));
|
|
}
|
|
|
|
void
|
|
TelemetryScalar::RecordDiscardedData(ProcessID aProcessType,
|
|
const mozilla::Telemetry::DiscardedData& aDiscardedData)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"Discarded Data must be updated from the parent process.");
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
if (!internal_CanRecordBase(locker)) {
|
|
return;
|
|
}
|
|
|
|
ScalarBase* scalar = nullptr;
|
|
mozilla::DebugOnly<nsresult> rv;
|
|
|
|
rv = internal_GetScalarByEnum(locker,
|
|
ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_ACCUMULATIONS), false},
|
|
aProcessType, &scalar);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
scalar->AddValue(aDiscardedData.mDiscardedHistogramAccumulations);
|
|
|
|
rv = internal_GetScalarByEnum(locker,
|
|
ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_KEYED_ACCUMULATIONS), false},
|
|
aProcessType, &scalar);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
scalar->AddValue(aDiscardedData.mDiscardedKeyedHistogramAccumulations);
|
|
|
|
rv = internal_GetScalarByEnum(locker,
|
|
ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_SCALAR_ACTIONS), false},
|
|
aProcessType, &scalar);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
scalar->AddValue(aDiscardedData.mDiscardedScalarActions);
|
|
|
|
rv = internal_GetScalarByEnum(locker,
|
|
ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_KEYED_SCALAR_ACTIONS), false},
|
|
aProcessType, &scalar);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
scalar->AddValue(aDiscardedData.mDiscardedKeyedScalarActions);
|
|
|
|
rv = internal_GetScalarByEnum(locker,
|
|
ScalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_CHILD_EVENTS), false},
|
|
aProcessType, &scalar);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
scalar->AddValue(aDiscardedData.mDiscardedChildEvents);
|
|
}
|
|
|
|
/**
|
|
* Get the dynamic scalar definitions in an IPC-friendly
|
|
* structure.
|
|
*/
|
|
void
|
|
TelemetryScalar::GetDynamicScalarDefinitions(
|
|
nsTArray<DynamicScalarDefinition> &aDefArray)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
if (!gDynamicScalarInfo) {
|
|
// Don't have dynamic scalar definitions. Bail out!
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
internal_DynamicScalarToIPC(locker, *gDynamicScalarInfo, aDefArray);
|
|
}
|
|
|
|
/**
|
|
* This adds the dynamic scalar definitions coming from
|
|
* the parent process to this child process. If a dynamic
|
|
* scalar definition is already defined, check if the new definition
|
|
* makes the scalar expired and eventually update the expiration
|
|
* state.
|
|
*/
|
|
void
|
|
TelemetryScalar::AddDynamicScalarDefinitions(
|
|
const nsTArray<DynamicScalarDefinition>& aDefs)
|
|
{
|
|
MOZ_ASSERT(!XRE_IsParentProcess());
|
|
|
|
nsTArray<DynamicScalarInfo> dynamicStubs;
|
|
|
|
// Populate the definitions array before acquiring the lock.
|
|
for (auto def : aDefs) {
|
|
bool recordOnRelease = def.dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT;
|
|
dynamicStubs.AppendElement(DynamicScalarInfo{
|
|
def.type,
|
|
recordOnRelease,
|
|
def.expired,
|
|
def.name,
|
|
def.keyed,
|
|
false /* builtin */});
|
|
}
|
|
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
internal_RegisterScalars(locker, dynamicStubs);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PUBLIC: GeckoView serialization/deserialization functions.
|
|
|
|
/**
|
|
* Write the scalar data to the provided Json object, for
|
|
* GeckoView measurement persistence. The output format is the same one used
|
|
* for snapshotting the scalars.
|
|
*
|
|
* @param {aWriter} The JSON object to write to.
|
|
* @returns NS_OK or a failure value explaining why persistence failed.
|
|
*/
|
|
nsresult
|
|
TelemetryScalar::SerializeScalars(mozilla::JSONWriter& aWriter)
|
|
{
|
|
// Get a copy of the data, without clearing.
|
|
ScalarSnapshotTable scalarsToReflect;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
// For persistence, we care about all the datasets. Worst case, they
|
|
// will be empty.
|
|
nsresult rv = internal_GetScalarSnapshot(locker,
|
|
scalarsToReflect,
|
|
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
|
|
false /*aClearScalars*/);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Persist the scalars to the JSON object.
|
|
for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
|
|
ScalarTupleArray& processScalars = iter.Data();
|
|
const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
|
|
|
|
aWriter.StartObjectProperty(processName);
|
|
|
|
for (const ScalarDataTuple& scalar : processScalars) {
|
|
nsresult rv = WriteVariantToJSONWriter(mozilla::Get<2>(scalar) /*aScalarType*/,
|
|
mozilla::Get<1>(scalar) /*aInputValue*/,
|
|
mozilla::Get<0>(scalar) /*aPropertyName*/,
|
|
aWriter /*aWriter*/);
|
|
if (NS_FAILED(rv)) {
|
|
// Skip this scalar if we failed to write it. We don't bail out just
|
|
// yet as we may salvage other scalars. We eventually need to call EndObject.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
aWriter.EndObject();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Write the keyed scalar data to the provided Json object, for
|
|
* GeckoView measurement persistence. The output format is the same
|
|
* one used for snapshotting the keyed scalars.
|
|
*
|
|
* @param {aWriter} The JSON object to write to.
|
|
* @returns NS_OK or a failure value explaining why persistence failed.
|
|
*/
|
|
nsresult
|
|
TelemetryScalar::SerializeKeyedScalars(mozilla::JSONWriter& aWriter)
|
|
{
|
|
// Get a copy of the data, without clearing.
|
|
KeyedScalarSnapshotTable keyedScalarsToReflect;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
|
|
// For persistence, we care about all the datasets. Worst case, they
|
|
// will be empty.
|
|
nsresult rv = internal_GetKeyedScalarSnapshot(locker,
|
|
keyedScalarsToReflect,
|
|
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
|
|
false /*aClearScalars*/);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Persist the scalars to the JSON object.
|
|
for (auto iter = keyedScalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
|
|
KeyedScalarTupleArray& processScalars = iter.Data();
|
|
const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
|
|
|
|
aWriter.StartObjectProperty(processName);
|
|
|
|
for (const KeyedScalarDataTuple& keyedScalarData : processScalars) {
|
|
aWriter.StartObjectProperty(mozilla::Get<0>(keyedScalarData));
|
|
|
|
// Define a property for each scalar key, then add it to the keyed scalar
|
|
// object.
|
|
const nsTArray<KeyedScalar::KeyValuePair>& keyProps = mozilla::Get<1>(keyedScalarData);
|
|
for (const KeyedScalar::KeyValuePair& keyData : keyProps) {
|
|
nsresult rv = WriteVariantToJSONWriter(mozilla::Get<2>(keyedScalarData) /*aScalarType*/,
|
|
keyData.second() /*aInputValue*/,
|
|
PromiseFlatCString(keyData.first()).get() /*aOutKey*/,
|
|
aWriter /*aWriter*/);
|
|
if (NS_FAILED(rv)) {
|
|
// Skip this scalar if we failed to write it. We don't bail out just
|
|
// yet as we may salvage other scalars. We eventually need to call EndObject.
|
|
continue;
|
|
}
|
|
}
|
|
aWriter.EndObject();
|
|
}
|
|
aWriter.EndObject();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Load the persisted measurements from a Json object and inject them
|
|
* in the relevant process storage.
|
|
*
|
|
* @param {aData} The input Json object.
|
|
* @returns NS_OK if loading was performed, an error code explaining the
|
|
* failure reason otherwise.
|
|
*/
|
|
nsresult
|
|
TelemetryScalar::DeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
|
|
if (!XRE_IsParentProcess()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
typedef mozilla::Pair<nsCString, nsCOMPtr<nsIVariant>> PersistedScalarPair;
|
|
typedef nsTArray<PersistedScalarPair> PersistedScalarArray;
|
|
typedef nsDataHashtable<ProcessIDHashKey, PersistedScalarArray> PeristedScalarStorage;
|
|
|
|
PeristedScalarStorage scalarsToUpdate;
|
|
|
|
// Before updating the scalars, we need to get the data out of the JS
|
|
// wrappers. We can't hold the scalars mutex while handling JS stuff.
|
|
// Build a <scalar name, value> map.
|
|
JS::RootedObject scalarDataObj(aCx, &aData.toObject());
|
|
JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx));
|
|
if (!JS_Enumerate(aCx, scalarDataObj, &processes)) {
|
|
// We can't even enumerate the processes in the loaded data, so
|
|
// there is nothing we could recover from the persistence file. Bail out.
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// The following block of code attempts to extract as much data as possible
|
|
// from the serialized JSON, even in case of light data corruptions: if, for example,
|
|
// the data for a single process is corrupted or is in an unexpected form, we press on
|
|
// and attempt to load the data for the other processes.
|
|
JS::RootedId process(aCx);
|
|
for (auto& processVal : processes) {
|
|
// This is required as JS API calls require an Handle<jsid> and not a
|
|
// plain jsid.
|
|
process = processVal;
|
|
// Get the process name.
|
|
nsAutoJSString processNameJS;
|
|
if (!processNameJS.init(aCx, process)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Make sure it's valid. Note that this is safe to call outside
|
|
// of a locked section.
|
|
NS_ConvertUTF16toUTF8 processName(processNameJS);
|
|
ProcessID processID = GetIDForProcessName(processName.get());
|
|
if (processID == ProcessID::Count) {
|
|
NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.get()).get());
|
|
continue;
|
|
}
|
|
|
|
// And its probes.
|
|
JS::RootedValue processData(aCx);
|
|
if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
if (!processData.isObject()) {
|
|
// |processData| should be an object containing scalars. If this is
|
|
// not the case, silently skip and try to load the data for the other
|
|
// processes.
|
|
continue;
|
|
}
|
|
|
|
// Iterate through each scalar.
|
|
JS::RootedObject processDataObj(aCx, &processData.toObject());
|
|
JS::Rooted<JS::IdVector> scalars(aCx, JS::IdVector(aCx));
|
|
if (!JS_Enumerate(aCx, processDataObj, &scalars)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
JS::RootedId scalar(aCx);
|
|
for (auto& scalarVal : scalars) {
|
|
scalar = scalarVal;
|
|
// Get the scalar name.
|
|
nsAutoJSString scalarName;
|
|
if (!scalarName.init(aCx, scalar)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Get the scalar value as a JS value.
|
|
JS::RootedValue scalarValue(aCx);
|
|
if (!JS_GetPropertyById(aCx, processDataObj, scalar, &scalarValue)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
if (scalarValue.isNullOrUndefined()) {
|
|
// We can't set scalars to null or undefined values, skip this
|
|
// and try to load other scalars.
|
|
continue;
|
|
}
|
|
|
|
// Unpack the aVal to nsIVariant.
|
|
nsCOMPtr<nsIVariant> unpackedVal;
|
|
nsresult rv =
|
|
nsContentUtils::XPConnect()->JSToVariant(aCx, scalarValue, getter_AddRefs(unpackedVal));
|
|
if (NS_FAILED(rv)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Add the scalar to the map.
|
|
PersistedScalarArray& processScalars =
|
|
scalarsToUpdate.GetOrInsert(static_cast<uint32_t>(processID));
|
|
processScalars.AppendElement(
|
|
mozilla::MakePair(nsCString(NS_ConvertUTF16toUTF8(scalarName)), unpackedVal));
|
|
}
|
|
}
|
|
|
|
// Now that all the JS specific operations are finished, update the scalars.
|
|
{
|
|
StaticMutexAutoLock lock(gTelemetryScalarsMutex);
|
|
|
|
for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
|
|
PersistedScalarArray& processScalars = iter.Data();
|
|
for (PersistedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
|
|
mozilla::Unused << internal_UpdateScalar(lock,
|
|
processScalars[i].first(),
|
|
ScalarActionType::eSet,
|
|
processScalars[i].second(),
|
|
ProcessID(iter.Key()),
|
|
true /* aForce */);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Load the persisted measurements from a Json object and injects them
|
|
* in the relevant process storage.
|
|
*
|
|
* @param {aData} The input Json object.
|
|
* @returns NS_OK if loading was performed, an error code explaining the
|
|
* failure reason otherwise.
|
|
*/
|
|
nsresult
|
|
TelemetryScalar::DeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
|
|
if (!XRE_IsParentProcess()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
typedef mozilla::Tuple<nsCString, nsString, nsCOMPtr<nsIVariant>> PersistedKeyedScalarTuple;
|
|
typedef nsTArray<PersistedKeyedScalarTuple> PersistedKeyedScalarArray;
|
|
typedef nsDataHashtable<ProcessIDHashKey, PersistedKeyedScalarArray> PeristedKeyedScalarStorage;
|
|
|
|
PeristedKeyedScalarStorage scalarsToUpdate;
|
|
|
|
// Before updating the keyed scalars, we need to get the data out of the JS
|
|
// wrappers. We can't hold the scalars mutex while handling JS stuff.
|
|
// Build a <scalar name, value> map.
|
|
JS::RootedObject scalarDataObj(aCx, &aData.toObject());
|
|
JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx));
|
|
if (!JS_Enumerate(aCx, scalarDataObj, &processes)) {
|
|
// We can't even enumerate the processes in the loaded data, so
|
|
// there is nothing we could recover from the persistence file. Bail out.
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// The following block of code attempts to extract as much data as possible
|
|
// from the serialized JSON, even in case of light data corruptions: if, for example,
|
|
// the data for a single process is corrupted or is in an unexpected form, we press on
|
|
// and attempt to load the data for the other processes.
|
|
JS::RootedId process(aCx);
|
|
for (auto& processVal : processes) {
|
|
process = processVal;
|
|
// Get the process name.
|
|
nsAutoJSString processNameJS;
|
|
if (!processNameJS.init(aCx, process)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Make sure it's valid. Note that this is safe to call outside
|
|
// of a locked section.
|
|
NS_ConvertUTF16toUTF8 processName(processNameJS);
|
|
ProcessID processID = GetIDForProcessName(processName.get());
|
|
if (processID == ProcessID::Count) {
|
|
NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.get()).get());
|
|
continue;
|
|
}
|
|
|
|
// And its probes.
|
|
JS::RootedValue processData(aCx);
|
|
if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
if (!processData.isObject()) {
|
|
// |processData| should be an object containing scalars. If this is
|
|
// not the case, silently skip and try to load the data for the other
|
|
// processes.
|
|
continue;
|
|
}
|
|
|
|
// Iterate through each keyed scalar.
|
|
JS::RootedObject processDataObj(aCx, &processData.toObject());
|
|
JS::Rooted<JS::IdVector> keyedScalars(aCx, JS::IdVector(aCx));
|
|
if (!JS_Enumerate(aCx, processDataObj, &keyedScalars)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
JS::RootedId keyedScalar(aCx);
|
|
for (auto& keyedScalarVal : keyedScalars) {
|
|
keyedScalar = keyedScalarVal;
|
|
// Get the scalar name.
|
|
nsAutoJSString scalarName;
|
|
if (!scalarName.init(aCx, keyedScalar)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Get the data for this keyed scalar.
|
|
JS::RootedValue keyedScalarData(aCx);
|
|
if (!JS_GetPropertyById(aCx, processDataObj, keyedScalar, &keyedScalarData)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
if (!keyedScalarData.isObject()) {
|
|
// Keyed scalar data need to be an object. If that's not the case, skip it
|
|
// and try to load the rest of the data.
|
|
continue;
|
|
}
|
|
|
|
// Get the keys in the keyed scalar.
|
|
JS::RootedObject keyedScalarDataObj(aCx, &keyedScalarData.toObject());
|
|
JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
|
|
if (!JS_Enumerate(aCx, keyedScalarDataObj, &keys)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
JS::RootedId key(aCx);
|
|
for (auto keyVal : keys) {
|
|
key = keyVal;
|
|
// Get the process name.
|
|
nsAutoJSString keyName;
|
|
if (!keyName.init(aCx, key)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Get the scalar value as a JS value.
|
|
JS::RootedValue scalarValue(aCx);
|
|
if (!JS_GetPropertyById(aCx, keyedScalarDataObj, key, &scalarValue)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
if (scalarValue.isNullOrUndefined()) {
|
|
// We can't set scalars to null or undefined values, skip this
|
|
// and try to load other scalars.
|
|
continue;
|
|
}
|
|
|
|
// Unpack the aVal to nsIVariant.
|
|
nsCOMPtr<nsIVariant> unpackedVal;
|
|
nsresult rv =
|
|
nsContentUtils::XPConnect()->JSToVariant(aCx, scalarValue, getter_AddRefs(unpackedVal));
|
|
if (NS_FAILED(rv)) {
|
|
JS_ClearPendingException(aCx);
|
|
continue;
|
|
}
|
|
|
|
// Add the scalar to the map.
|
|
PersistedKeyedScalarArray& processScalars =
|
|
scalarsToUpdate.GetOrInsert(static_cast<uint32_t>(processID));
|
|
processScalars.AppendElement(
|
|
mozilla::MakeTuple(nsCString(NS_ConvertUTF16toUTF8(scalarName)),
|
|
nsString(keyName), unpackedVal));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that all the JS specific operations are finished, update the scalars.
|
|
{
|
|
StaticMutexAutoLock lock(gTelemetryScalarsMutex);
|
|
|
|
for (auto iter = scalarsToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
|
|
PersistedKeyedScalarArray& processScalars = iter.Data();
|
|
for (PersistedKeyedScalarArray::size_type i = 0; i < processScalars.Length(); i++) {
|
|
mozilla::Unused << internal_UpdateKeyedScalar(lock,
|
|
mozilla::Get<0>(processScalars[i]),
|
|
mozilla::Get<1>(processScalars[i]),
|
|
ScalarActionType::eSet,
|
|
mozilla::Get<2>(processScalars[i]),
|
|
ProcessID(iter.Key()),
|
|
true /* aForce */);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|