Bug 1425909 - Enable adding scalars in artifact builds without rebuilding Firefox. r=chutten,froydnj

This patch enables generating a JSON file that mirrors the scalar definitions
in Scalars.yaml. On local developer builds, this file is loaded when Firefox
starts to register all the scalars. If some change was introduced in the
definition files, the new scalar will be dynamically added.
The JSON definition file will be regenerated every time an artifact build
is performed or the build faster command is invoked.

MozReview-Commit-ID: Do3WjE38aIK

--HG--
extra : rebase_source : 2d9701d77abeb6ce8de096674349b6d071c4b102
This commit is contained in:
Alessio Placitelli 2018-01-18 18:25:01 +01:00
parent 65b1fa2979
commit e39fc7c8b3
11 changed files with 436 additions and 81 deletions

View File

@ -23,14 +23,16 @@ struct BaseScalarInfo {
uint32_t dataset;
mozilla::Telemetry::Common::RecordedProcessType record_in_processes;
bool keyed;
bool builtin;
BaseScalarInfo(uint32_t aKind, uint32_t aDataset,
mozilla::Telemetry::Common::RecordedProcessType aRecordInProcess,
bool aKeyed)
bool aKeyed, bool aBuiltin = true)
: kind(aKind)
, dataset(aDataset)
, record_in_processes(aRecordInProcess)
, keyed(aKeyed)
, builtin(aBuiltin)
{}
virtual ~BaseScalarInfo() {}

View File

@ -1727,7 +1727,15 @@ TelemetryImpl::RegisterScalars(const nsACString& aCategoryName,
JS::Handle<JS::Value> aScalarData,
JSContext* cx)
{
return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, cx);
return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, false, cx);
}
NS_IMETHODIMP
TelemetryImpl::RegisterBuiltinScalars(const nsACString& aCategoryName,
JS::Handle<JS::Value> aScalarData,
JSContext* cx)
{
return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, true, cx);
}
NS_IMETHODIMP

View File

@ -67,6 +67,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TelemetryModules: "resource://gre/modules/TelemetryModules.jsm",
UpdatePing: "resource://gre/modules/UpdatePing.jsm",
TelemetryHealthPing: "resource://gre/modules/TelemetryHealthPing.jsm",
OS: "resource://gre/modules/osfile.jsm",
});
/**
@ -156,6 +157,13 @@ this.TelemetryController = Object.freeze({
return Impl.setupContentTelemetry(true);
},
/**
* Used only for testing purposes.
*/
testPromiseJsProbeRegistration() {
return Promise.resolve(Impl._probeRegistrationPromise);
},
/**
* Send a notification.
*/
@ -322,6 +330,8 @@ var Impl = {
_testMode: false,
// The task performing the delayed sending of the "new-profile" ping.
_delayedNewPingTask: null,
// The promise used to wait for the JS probe registration (dynamic builtin).
_probeRegistrationPromise: null,
get _log() {
if (!this._logger) {
@ -679,6 +689,11 @@ var Impl = {
return Promise.resolve();
}
// Enable adding scalars in artifact builds and build faster modes.
// The function is async: we intentionally don't wait for it to complete
// as we don't want to delay startup.
this._probeRegistrationPromise = this.registerJsProbes();
// This will trigger displaying the datachoices infobar.
TelemetryReportingPolicy.setup();
@ -1038,4 +1053,64 @@ var Impl = {
.then(() => TelemetrySession.markNewProfilePingSent(),
e => this._log.error("sendNewProfilePing - failed to submit new-profile ping", e));
},
/**
* Register 'dynamic builtin' probes from the JSON definition files.
* This is needed to support adding new probes in developer builds
* without rebuilding the whole codebase.
*
* This is not meant to be used outside of local developer builds.
*/
async registerJsProbes() {
// We don't support this outside of developer builds.
if (AppConstants.MOZILLA_OFFICIAL && !this._testMode) {
return;
}
this._log.trace("registerJsProbes - registering builtin JS probes");
// Load the scalar probes JSON file.
const scalarProbeFilename = "ScalarArtifactDefinitions.json";
let scalarProbeFile = Services.dirsvc.get("GreBinD", Ci.nsIFile);
scalarProbeFile.append(scalarProbeFilename);
if (!scalarProbeFile.exists()) {
this._log.trace("registerJsProbes - no scalar builtin JS probes");
return;
}
// Load the file off the disk.
let scalarJSProbes = {};
try {
let fileContent = await OS.File.read(scalarProbeFile.path, { encoding: "utf-8" });
scalarJSProbes = JSON.parse(fileContent, (property, value) => {
// Fixup the "kind" property: it's a string, and we need the constant
// coming from nsITelemetry.
if (property !== "kind" || typeof value != "string") {
return value;
}
let newValue;
switch (value) {
case "nsITelemetry::SCALAR_TYPE_COUNT":
newValue = Telemetry.SCALAR_TYPE_COUNT;
break;
case "nsITelemetry::SCALAR_TYPE_BOOLEAN":
newValue = Telemetry.SCALAR_TYPE_BOOLEAN;
break;
case "nsITelemetry::SCALAR_TYPE_STRING":
newValue = Telemetry.SCALAR_TYPE_STRING;
break;
}
return newValue;
});
} catch (ex) {
this._log.error(`registerJsProbes - there was an error loading {$scalarProbeFilename}`,
ex);
}
// Register the builtin probes.
for (let category in scalarJSProbes) {
Telemetry.registerBuiltinScalars(category, scalarJSProbes[category]);
}
},
};

View File

@ -137,13 +137,14 @@ struct DynamicScalarInfo : BaseScalarInfo {
DynamicScalarInfo(uint32_t aKind, bool aRecordOnRelease,
bool aExpired, const nsACString& aName,
bool aKeyed)
bool aKeyed, bool aBuiltin)
: BaseScalarInfo(aKind,
aRecordOnRelease ?
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT :
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
RecordedProcessType::All,
aKeyed)
aKeyed,
aBuiltin)
, mDynamicName(aName)
, mDynamicExpiration(aExpired)
{}
@ -854,6 +855,10 @@ ScalarMapType gScalarNameIDMap(kScalarCount);
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;
} // namespace
@ -1100,8 +1105,11 @@ internal_GetScalarByEnum(const StaticMutexAutoLock& lock,
const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
// Dynamic scalars fixup: they are always stored in the "dynamic" process.
if (aId.dynamic) {
// 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;
}
@ -1111,11 +1119,16 @@ internal_GetScalarByEnum(const StaticMutexAutoLock& lock,
// 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 (!gScalarStorageMap.Get(storageId, &scalarStorage)) {
if (!processStorage.Get(storageId, &scalarStorage)) {
scalarStorage = new ScalarStorageMapType();
gScalarStorageMap.Put(storageId, scalarStorage);
processStorage.Put(storageId, scalarStorage);
}
// Check if the scalar is already allocated in the parent or in the child storage.
@ -1257,8 +1270,11 @@ internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock,
const BaseScalarInfo &info = internal_GetScalarInfo(lock, aId);
// Dynamic scalars fixup: they are always stored in the "dynamic" process.
if (aId.dynamic) {
// 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;
}
@ -1268,11 +1284,16 @@ internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock,
// 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 (!gKeyedScalarStorageMap.Get(storageId, &scalarStorage)) {
if (!processStorage.Get(storageId, &scalarStorage)) {
scalarStorage = new KeyedScalarStorageMapType();
gKeyedScalarStorageMap.Put(storageId, scalarStorage);
processStorage.Put(storageId, scalarStorage);
}
if (scalarStorage->Get(aId.id, &scalar)) {
@ -1425,7 +1446,7 @@ internal_RegisterScalars(const StaticMutexAutoLock& lock,
CharPtrEntryType *existingKey = gScalarNameIDMap.GetEntry(scalarInfo.name());
if (existingKey) {
// Change the scalar to expired if needed.
if (scalarInfo.mDynamicExpiration) {
if (scalarInfo.mDynamicExpiration && !scalarInfo.builtin) {
DynamicScalarInfo& scalarData = (*gDynamicScalarInfo)[existingKey->mData.id];
scalarData.mDynamicExpiration = true;
}
@ -1488,6 +1509,8 @@ TelemetryScalar::DeInitializeGlobalState()
gScalarNameIDMap.Clear();
gScalarStorageMap.Clear();
gKeyedScalarStorageMap.Clear();
gDynamicBuiltinScalarStorageMap.Clear();
gDynamicBuiltinKeyedScalarStorageMap.Clear();
gDynamicScalarInfo = nullptr;
gInitDone = false;
}
@ -2133,41 +2156,63 @@ TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSCo
nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
{
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
// Iterate the scalars in gScalarStorageMap. The storage may contain empty or yet to be
// initialized scalars from all the supported processes.
for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
// Are we in the "Dynamic" process?
bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
// The snapshotting function is the same for both static and dynamic builtin scalars.
// We can use the same function and store the scalars in the same output storage.
auto snapshotter = [aDataset, &locker, &scalarsToReflect]
(ProcessesScalarsMapType& aProcessStorage, bool aIsBuiltinDynamic)
-> nsresult
{
// 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());
ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
// Iterate each available child storage.
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
// Are we in the "Dynamic" process?
bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
// Get the informations for this scalar.
const BaseScalarInfo& info =
internal_GetScalarInfo(locker, ScalarKey{childIter.Key(),
isDynamicProcess});
// Iterate each available child storage.
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
// 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;
// Get the informations for this scalar.
const BaseScalarInfo& info =
internal_GetScalarInfo(locker, 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::MakePair(info.name(), scalarValue));
}
// Append it to our list.
processScalars.AppendElement(mozilla::MakePair(info.name(), scalarValue));
}
}
return NS_OK;
};
// Take a snapshot of the scalars.
nsresult rv = snapshotter(gScalarStorageMap, false);
if (NS_FAILED(rv)) {
return rv;
}
// And a snapshot of the dynamic builtin ones.
rv = snapshotter(gDynamicBuiltinScalarStorageMap, true);
if (NS_FAILED(rv)) {
return rv;
}
if (aClearScalars) {
// The map already takes care of freeing the allocated memory.
gScalarStorageMap.Clear();
gDynamicBuiltinScalarStorageMap.Clear();
}
}
@ -2241,41 +2286,61 @@ TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars,
nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
{
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
// Iterate the scalars in gKeyedScalarStorageMap. The storage may contain empty or yet
// to be initialized scalars from all the supported processes.
for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
KeyedScalarStorageMapType* scalarStorage =
static_cast<KeyedScalarStorageMapType*>(iter.Data());
ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
// Are we in the "Dynamic" process?
bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
auto snapshotter = [aDataset, &locker, &scalarsToReflect]
(ProcessesKeyedScalarsMapType& aProcessStorage,
bool aIsBuiltinDynamic) -> nsresult
{
// 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());
ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
// Are we in the "Dynamic" process?
bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
// Get the informations for this scalar.
const BaseScalarInfo& info =
internal_GetScalarInfo(locker, ScalarKey{childIter.Key(),
isDynamicProcess});
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
// 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;
// Get the informations for this scalar.
const BaseScalarInfo& info =
internal_GetScalarInfo(locker, 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::MakePair(info.name(), scalarKeyedData));
}
// Append it to our list.
processScalars.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData));
}
}
return NS_OK;
};
// Take a snapshot of the scalars.
nsresult rv = snapshotter(gKeyedScalarStorageMap, false);
if (NS_FAILED(rv)) {
return rv;
}
// And a snapshot of the dynamic builtin ones.
rv = snapshotter(gDynamicBuiltinKeyedScalarStorageMap, true);
if (NS_FAILED(rv)) {
return rv;
}
if (aClearScalars) {
// The map already takes care of freeing the allocated memory.
gKeyedScalarStorageMap.Clear();
gDynamicBuiltinKeyedScalarStorageMap.Clear();
}
}
@ -2333,6 +2398,7 @@ TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars,
nsresult
TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
JS::Handle<JS::Value> aScalarData,
bool aBuiltin,
JSContext* cx)
{
MOZ_ASSERT(XRE_IsParentProcess(),
@ -2431,7 +2497,7 @@ TelemetryScalar::RegisterScalars(const nsACString& aCategoryName,
// 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
kind, recordOnRelease, expired, fullName, keyed, aBuiltin
});
}
@ -2461,6 +2527,8 @@ TelemetryScalar::ClearScalars()
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
gScalarStorageMap.Clear();
gKeyedScalarStorageMap.Clear();
gDynamicBuiltinScalarStorageMap.Clear();
gDynamicBuiltinKeyedScalarStorageMap.Clear();
}
size_t
@ -2475,23 +2543,26 @@ TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSize
{
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
size_t n = 0;
// Account for scalar data coming from parent and child processes.
for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
n += scalar->SizeOfIncludingThis(aMallocSizeOf);
auto getSizeOf = [aMallocSizeOf](auto &storageMap)
{
size_t partial = 0;
for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
auto scalarStorage = iter.Data();
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
auto scalar = childIter.Data();
partial += scalar->SizeOfIncludingThis(aMallocSizeOf);
}
}
}
// Also account for keyed scalar data coming from parent and child processes.
for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
KeyedScalarStorageMapType* scalarStorage =
static_cast<KeyedScalarStorageMapType*>(iter.Data());
for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
n += 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;
}
@ -2766,7 +2837,8 @@ TelemetryScalar::AddDynamicScalarDefinitions(
recordOnRelease,
def.expired,
def.name,
def.keyed});
def.keyed,
false /* builtin */});
}
{

View File

@ -56,7 +56,7 @@ void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aValue);
void SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
nsresult RegisterScalars(const nsACString& aCategoryName, JS::Handle<JS::Value> aScalarData,
JSContext* cx);
bool aBuiltin, JSContext* cx);
// Only to be used for testing.
void ClearScalars();

View File

@ -6,8 +6,10 @@
# in a file provided as a command-line argument.
from __future__ import print_function
from collections import OrderedDict
from shared_telemetry_utils import StringTable, static_assert, ParserError
import json
import parse_scalars
import sys
@ -78,17 +80,49 @@ def write_scalar_tables(scalars, output):
"index overflow")
def main(output, *filenames):
# Load the scalars first.
def parse_scalar_definitions(filenames):
if len(filenames) > 1:
raise Exception('We don\'t support loading from more than one file.')
try:
scalars = parse_scalars.load_scalars(filenames[0])
return parse_scalars.load_scalars(filenames[0])
except ParserError as ex:
print("\nError processing scalars:\n" + str(ex) + "\n")
sys.exit(1)
def generate_JSON_definitions(output, *filenames):
""" Write the scalar definitions to a JSON file.
:param output: the file to write the content to.
:param filenames: a list of filenames provided by the build system.
We only support a single file.
"""
scalars = parse_scalar_definitions(filenames)
scalar_definitions = OrderedDict()
for scalar in scalars:
category = scalar.category
if category not in scalar_definitions:
scalar_definitions[category] = OrderedDict()
scalar_definitions[category][scalar.name] = OrderedDict({
'kind': scalar.nsITelemetry_kind,
'keyed': scalar.keyed,
'record_on_release': True if scalar.dataset == 'opt-out' else False,
# We don't expire dynamic-builtin scalars: they're only meant for
# use in local developer builds anyway. They will expire when rebuilding.
'expired': False,
})
json.dump(scalar_definitions, output)
def main(output, *filenames):
# Load the scalars first.
scalars = parse_scalar_definitions(filenames)
# Write the scalar data file.
print(banner, file=output)
print(file_header, file=output)

View File

@ -110,6 +110,7 @@ PYTHON_UNITTEST_MANIFESTS += [
]
GENERATED_FILES = [
'ScalarArtifactDefinitions.json',
'TelemetryEventData.h',
'TelemetryEventEnums.h',
'TelemetryHistogramData.inc',
@ -148,6 +149,15 @@ scalar_enums = GENERATED_FILES['TelemetryScalarEnums.h']
scalar_enums.script = 'gen_scalar_enum.py'
scalar_enums.inputs = scalar_files
# Generate the JSON scalar definitions. They will only be
# used in artifact or "build faster" builds.
scalar_json_data = GENERATED_FILES['ScalarArtifactDefinitions.json']
scalar_json_data.script = 'gen_scalar_data.py:generate_JSON_definitions'
scalar_json_data.inputs = scalar_files
# Move the scalars JSON file to the directory where the Firefox binary is.
FINAL_TARGET_FILES += ['!ScalarArtifactDefinitions.json']
# Generate event files.
event_files = [
'Events.yaml',

View File

@ -541,6 +541,17 @@ interface nsITelemetry : nsISupports
[implicit_jscontext]
void registerScalars(in ACString aCategoryName, in jsval aScalarData);
/**
* Parent process only. Register dynamic builtin scalars. The parameters
* have the same meaning as the usual |registerScalars| function.
*
* This function is only meant to be used to support the "artifact build"/
* "built faster" developers by allowing to add new scalars without rebuilding
* the C++ components including the headers files.
*/
[implicit_jscontext]
void registerBuiltinScalars(in ACString aCategoryName, in jsval aScalarData);
/**
* Resets all the stored events. This is intended to be only used in tests.
*/

View File

@ -198,6 +198,11 @@ class ScalarType:
raise ParserError('{} - invalid expires: {}.\nSee: {}#required-fields'
.format(self._name, expires, BASE_DOC_URL))
@property
def category(self):
"""Get the category name"""
return self._category_name
@property
def name(self):
"""Get the scalar name"""

View File

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
*/
const UINT_SCALAR = "telemetry.test.unsigned_int_kind";
const STRING_SCALAR = "telemetry.test.string_kind";
const BOOLEAN_SCALAR = "telemetry.test.boolean_kind";
const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int";
ChromeUtils.import("resource://services-common/utils.js");
/**
* Return the path to the definitions file for the scalars.
*/
function getDefinitionsPath() {
// Write the scalar definition to the spec file in the binary directory.
let definitionFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
definitionFile = Services.dirsvc.get("GreBinD", Ci.nsIFile);
definitionFile.append("ScalarArtifactDefinitions.json");
return definitionFile.path;
}
add_task(async function test_setup() {
do_get_profile();
});
add_task({
// The test needs to write a file, and that fails in tests on Android.
// We don't really need the Android coverage, so skip on Android.
skip_if: () => AppConstants.platform == "android"
}, async function test_invalidJSON() {
const INVALID_JSON = "{ invalid,JSON { {1}";
const FILE_PATH = getDefinitionsPath();
// Write a corrupted JSON file.
await OS.File.writeAtomic(FILE_PATH, INVALID_JSON, { encoding: "utf-8", noOverwrite: false });
// Simulate Firefox startup. This should not throw!
await TelemetryController.testSetup();
await TelemetryController.testPromiseJsProbeRegistration();
// Cleanup.
await TelemetryController.testShutdown();
await OS.File.remove(FILE_PATH);
});
add_task({
// The test needs to write a file, and that fails in tests on Android.
// We don't really need the Android coverage, so skip on Android.
skip_if: () => AppConstants.platform == "android"
}, async function test_dynamicBuiltin() {
const DYNAMIC_SCALAR_SPEC = {
"telemetry.test": {
"builtin_dynamic": {
"kind": "nsITelemetry::SCALAR_TYPE_COUNT",
"expired": false,
"record_on_release": false,
"keyed": false
},
"builtin_dynamic_other": {
"kind": "nsITelemetry::SCALAR_TYPE_BOOLEAN",
"expired": false,
"record_on_release": false,
"keyed": false
}
}
};
Telemetry.clearScalars();
// Let's write to the definition file to also cover the file
// loading part.
const FILE_PATH = getDefinitionsPath();
await CommonUtils.writeJSON(DYNAMIC_SCALAR_SPEC, FILE_PATH);
// Start TelemetryController to trigger loading the specs.
await TelemetryController.testReset();
await TelemetryController.testPromiseJsProbeRegistration();
// Store to that scalar.
const TEST_SCALAR1 = "telemetry.test.builtin_dynamic";
const TEST_SCALAR2 = "telemetry.test.builtin_dynamic_other";
Telemetry.scalarSet(TEST_SCALAR1, 3785);
Telemetry.scalarSet(TEST_SCALAR2, true);
// Check the values we tried to store.
const scalars =
Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent;
// Check that they are serialized to the correct format.
Assert.equal(typeof(scalars[TEST_SCALAR1]), "number",
TEST_SCALAR1 + " must be serialized to the correct format.");
Assert.ok(Number.isInteger(scalars[TEST_SCALAR1]),
TEST_SCALAR1 + " must be a finite integer.");
Assert.equal(scalars[TEST_SCALAR1], 3785,
TEST_SCALAR1 + " must have the correct value.");
Assert.equal(typeof(scalars[TEST_SCALAR2]), "boolean",
TEST_SCALAR2 + " must be serialized to the correct format.");
Assert.equal(scalars[TEST_SCALAR2], true,
TEST_SCALAR2 + " must have the correct value.");
// Clean up.
await TelemetryController.testShutdown();
await OS.File.remove(FILE_PATH);
});
add_task(async function test_keyedDynamicBuiltin() {
Telemetry.clearScalars();
// Register the built-in scalars (let's not take the I/O hit).
Telemetry.registerBuiltinScalars("telemetry.test", {
"builtin_dynamic_keyed": {
"kind": Ci.nsITelemetry.SCALAR_TYPE_COUNT,
"expired": false,
"record_on_release": false,
"keyed": true
}
});
// Store to that scalar.
const TEST_SCALAR1 = "telemetry.test.builtin_dynamic_keyed";
Telemetry.keyedScalarSet(TEST_SCALAR1, "test-key", 3785);
// Check the values we tried to store.
const scalars =
Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent;
// Check that they are serialized to the correct format.
Assert.equal(typeof(scalars[TEST_SCALAR1]), "object",
TEST_SCALAR1 + " must be a keyed scalar.");
Assert.equal(typeof(scalars[TEST_SCALAR1]["test-key"]), "number",
TEST_SCALAR1 + " must be serialized to the correct format.");
Assert.ok(Number.isInteger(scalars[TEST_SCALAR1]["test-key"]),
TEST_SCALAR1 + " must be a finite integer.");
Assert.equal(scalars[TEST_SCALAR1]["test-key"], 3785,
TEST_SCALAR1 + " must have the correct value.");
});

View File

@ -64,6 +64,7 @@ skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
skip-if = os == "android" # Disabled due to crashes (see bug 1367762)
tags = addons
[test_TelemetryScalars.js]
[test_TelemetryScalars_buildFaster.js]
[test_TelemetryTimestamps.js]
skip-if = toolkit == 'android'
[test_TelemetryCaptureStack.js]