Bug 1235345 - Remove services/metrics. r=gfritzsche

This commit is contained in:
Alessio Placitelli 2016-01-07 04:23:00 +01:00
parent c921f00246
commit f1a95bc7c1
20 changed files with 0 additions and 5390 deletions

View File

@ -19,7 +19,6 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_METRICS=1
MOZ_WEBSMS_BACKEND=1
MOZ_NO_SMART_CARDS=1

View File

@ -26,7 +26,6 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_METRICS=1
MOZ_CAPTIVEDETECT=1
MOZ_WEBSMS_BACKEND=1

View File

@ -31,7 +31,6 @@ MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_CRYPTO=1
MOZ_SERVICES_HEALTHREPORT=1
MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_SERVICES_CLOUDSYNC=1
MOZ_APP_VERSION=$FIREFOX_VERSION

View File

@ -12,7 +12,6 @@ MOZ_PLACES=1
MOZ_EXTENSIONS_DEFAULT=" gio"
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_CRYPTO=1
MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_MEDIA_NAVIGATOR=1
MOZ_SERVICES_HEALTHREPORT=1

View File

@ -1,16 +0,0 @@
=======================
Firefox Services Module
=======================
The ``/services`` directory contains code for a variety of application
features that communicate with external services - hence its name.
It was originally created to hold code for Firefox Sync. Later, it
became the location for code written by the Mozilla Services Client team
and thus includes :ref:`healthreport`. This team no longer exists, but
the directory remains.
.. toctree::
:maxdepth: 1
metrics

View File

@ -1,130 +0,0 @@
.. _services_metrics:
============================
Metrics Collection Framework
============================
The ``services/metrics`` directory contains a generic data metrics
collecting and persisting framework for Gecko applications.
Overview
========
The Metrics framework by itself doesn't do much: it simply provides a
generic mechanism for collecting and persisting data. It is up to users
of this framework to drive collection and do something with the obtained
data. A consumer of this framework is :ref:`healthreport`.
Relationship to Telemetry
-------------------------
Telemetry provides similar features to code in this directory. The two
may be unified in the future.
Usage
=====
To use the code in this directory, import Metrics.jsm. e.g.
Components.utils.import("resource://gre/modules/Metrics.jsm");
This exports a *Metrics* object which holds references to the main JS
types and functions provided by this feature. Read below for what those
types are.
Metrics Types
=============
``Metrics.jsm`` exports a number of types. They are documented in the
sections below.
Metrics.Provider
----------------
``Metrics.Provider`` is an entity that collects and manages data. Providers
are typically domain-specific: if you need to collect a new type of data,
you create a ``Metrics.Provider`` type that does this.
Metrics.Measurement
-------------------
A ``Metrics.Measurement`` represents a collection of related pieces/fields
of data.
All data recorded by the metrics framework is modeled as
``Metrics.Measurement`` instances. Instances of ``Metrics.Measurement``
are essentially data structure descriptors.
Each ``Metrics.Measurement`` consists of a name and version to identify
itself (and its data) as well as a list of *fields* that this measurement
holds. A *field* is effectively an entry in a data structure. It consists
of a name and strongly enumerated type.
Metrics.Storage
---------------
This entity is responsible for persisting collected data and state.
It currently uses SQLite to store data, but this detail is abstracted away
in order to facilitate swapping of storage backends.
Metrics.ProviderManager
-----------------------
High-level entity coordinating activity among several ``Metrics.Provider``
instances.
Providers and Measurements
==========================
The most important types in this framework are ``Metrics.Provider`` and
``Metrics.Measurement``, henceforth known as ``Provider`` and
``Measurement``, respectively. As you will see, these two types go
hand in hand.
A ``Provider`` is an entity that *provides* data about a specific subsystem
or feature. They do this by recording data to specific ``Measurement``
types. Both ``Provider`` and ``Measurement`` are abstract base types.
A ``Measurement`` implementation defines a name and version. More
importantly, it also defines its storage requirements and how
previously-stored values are serialized.
Storage allocation is performed by communicating with the SQLite
backend. There is a startup function that tells SQLite what fields the
measurement is recording. The storage backend then registers these in
the database. Internally, this is creating a new primary key for
individual fields so later storage operations can directly reference
these primary keys in order to retrieve data without having to perform
complicated joins.
A ``Provider`` can be thought of as a collection of ``Measurement``
implementations. e.g. an Addons provider may consist of a measurement
for all *current* add-ons as well as a separate measurement for
historical counts of add-ons. A provider's primary role is to take
metrics data and write it to various measurements. This effectively
persists the data to SQLite.
Data is emitted from providers in either a push or pull based mechanism.
In push-based scenarios, the provider likely subscribes to external
events (e.g. observer notifications). An event of interest can occur at
any time. When it does, the provider immediately writes the event of
interest to storage or buffers it for eventual writing. In pull-based
scenarios, the provider is periodically queried and asked to populate
data.
SQLite Storage
==============
``Metrics.Storage`` provides an interface for persisting metrics data to a
SQLite database.
The storage API organizes values by fields. A field is a named member of
a ``Measurement`` that has specific type and retention characteristics.
Some example field types include:
* Last text value
* Last numeric value for a given day
* Discrete text values for a given day
See ``storage.jsm`` for more.

View File

@ -1,38 +0,0 @@
/* 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/. */
#ifndef MERGED_COMPARTMENT
"use strict";
this.EXPORTED_SYMBOLS = ["Metrics"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
#endif
// We concatenate the JSMs together to eliminate compartment overhead.
// This is a giant hack until compartment overhead is no longer an
// issue.
#define MERGED_COMPARTMENT
#include providermanager.jsm
;
#include dataprovider.jsm
;
#include storage.jsm
;
this.Metrics = {
ProviderManager: ProviderManager,
DailyValues: DailyValues,
Measurement: Measurement,
Provider: Provider,
Storage: MetricsStorageBackend,
dateToDays: dateToDays,
daysToDate: daysToDate,
};

View File

@ -1,727 +0,0 @@
/* 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/. */
#ifndef MERGED_COMPARTMENT
"use strict";
this.EXPORTED_SYMBOLS = [
"Measurement",
"Provider",
];
const {utils: Cu} = Components;
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
#endif
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");
/**
* Represents a collection of related pieces/fields of data.
*
* This is an abstract base type.
*
* This type provides the primary interface for storing, retrieving, and
* serializing data.
*
* Each measurement consists of a set of named fields. Each field is primarily
* identified by a string name, which must be unique within the measurement.
*
* Each derived type must define the following properties:
*
* name -- String name of this measurement. This is the primary way
* measurements are distinguished within a provider.
*
* version -- Integer version of this measurement. This is a secondary
* identifier for a measurement within a provider. The version denotes
* the behavior of this measurement and the composition of its fields over
* time. When a new field is added or the behavior of an existing field
* changes, the version should be incremented. The initial version of a
* measurement is typically 1.
*
* fields -- Object defining the fields this measurement holds. Keys in the
* object are string field names. Values are objects describing how the
* field works. The following properties are recognized:
*
* type -- The string type of this field. This is typically one of the
* FIELD_* constants from the Metrics.Storage type.
*
*
* FUTURE: provide hook points for measurements to supplement with custom
* storage needs.
*/
this.Measurement = function () {
if (!this.name) {
throw new Error("Measurement must have a name.");
}
if (!this.version) {
throw new Error("Measurement must have a version.");
}
if (!Number.isInteger(this.version)) {
throw new Error("Measurement's version must be an integer: " + this.version);
}
if (!this.fields) {
throw new Error("Measurement must define fields.");
}
for (let [name, info] in Iterator(this.fields)) {
if (!info) {
throw new Error("Field does not contain metadata: " + name);
}
if (!info.type) {
throw new Error("Field is missing required type property: " + name);
}
}
this._log = Log.repository.getLogger("Services.Metrics.Measurement." + this.name);
this.id = null;
this.storage = null;
this._fields = {};
this._serializers = {};
this._serializers[this.SERIALIZE_JSON] = {
singular: this._serializeJSONSingular.bind(this),
daily: this._serializeJSONDay.bind(this),
};
}
Measurement.prototype = Object.freeze({
SERIALIZE_JSON: "json",
/**
* Obtain a serializer for this measurement.
*
* Implementations should return an object with the following keys:
*
* singular -- Serializer for singular data.
* daily -- Serializer for daily data.
*
* Each item is a function that takes a single argument: the data to
* serialize. The passed data is a subset of that returned from
* this.getValues(). For "singular," data.singular is passed. For "daily",
* data.days.get(<day>) is passed.
*
* This function receives a single argument: the serialization format we
* are requesting. This is one of the SERIALIZE_* constants on this base type.
*
* For SERIALIZE_JSON, the function should return an object that
* JSON.stringify() knows how to handle. This could be an anonymous object or
* array or any object with a property named `toJSON` whose value is a
* function. The returned object will be added to a larger document
* containing the results of all `serialize` calls.
*
* The default implementation knows how to serialize built-in types using
* very simple logic. If small encoding size is a goal, the default
* implementation may not be suitable. If an unknown field type is
* encountered, the default implementation will error.
*
* @param format
* (string) A SERIALIZE_* constant defining what serialization format
* to use.
*/
serializer: function (format) {
if (!(format in this._serializers)) {
throw new Error("Don't know how to serialize format: " + format);
}
return this._serializers[format];
},
/**
* Whether this measurement contains the named field.
*
* @param name
* (string) Name of field.
*
* @return bool
*/
hasField: function (name) {
return name in this.fields;
},
/**
* The unique identifier for a named field.
*
* This will throw if the field is not known.
*
* @param name
* (string) Name of field.
*/
fieldID: function (name) {
let entry = this._fields[name];
if (!entry) {
throw new Error("Unknown field: " + name);
}
return entry[0];
},
fieldType: function (name) {
let entry = this._fields[name];
if (!entry) {
throw new Error("Unknown field: " + name);
}
return entry[1];
},
_configureStorage: function () {
let missing = [];
for (let [name, info] in Iterator(this.fields)) {
if (this.storage.hasFieldFromMeasurement(this.id, name)) {
this._fields[name] =
[this.storage.fieldIDFromMeasurement(this.id, name), info.type];
continue;
}
missing.push([name, info.type]);
}
if (!missing.length) {
return CommonUtils.laterTickResolvingPromise();
}
// We only perform a transaction if we have work to do (to avoid
// extra SQLite overhead).
return this.storage.enqueueTransaction(function registerFields() {
for (let [name, type] of missing) {
this._log.debug("Registering field: " + name + " " + type);
let id = yield this.storage.registerField(this.id, name, type);
this._fields[name] = [id, type];
}
}.bind(this));
},
//---------------------------------------------------------------------------
// Data Recording Functions
//
// Functions in this section are used to record new values against this
// measurement instance.
//
// Generally speaking, these functions will throw if the specified field does
// not exist or if the storage function requested is not appropriate for the
// type of that field. These functions will also return a promise that will
// be resolved when the underlying storage operation has completed.
//---------------------------------------------------------------------------
/**
* Increment a daily counter field in this measurement by 1.
*
* By default, the counter for the current day will be incremented.
*
* If the field is not known or is not a daily counter, this will throw.
*
*
*
* @param field
* (string) The name of the field whose value to increment.
* @param date
* (Date) Day on which to increment the counter.
* @param by
* (integer) How much to increment by.
* @return Promise<>
*/
incrementDailyCounter: function (field, date=new Date(), by=1) {
return this.storage.incrementDailyCounterFromFieldID(this.fieldID(field),
date, by);
},
/**
* Record a new numeric value for a daily discrete numeric field.
*
* @param field
* (string) The name of the field to append a value to.
* @param value
* (Number) Number to append.
* @param date
* (Date) Day on which to append the value.
*
* @return Promise<>
*/
addDailyDiscreteNumeric: function (field, value, date=new Date()) {
return this.storage.addDailyDiscreteNumericFromFieldID(
this.fieldID(field), value, date);
},
/**
* Record a new text value for a daily discrete text field.
*
* This is like `addDailyDiscreteNumeric` but for daily discrete text fields.
*/
addDailyDiscreteText: function (field, value, date=new Date()) {
return this.storage.addDailyDiscreteTextFromFieldID(
this.fieldID(field), value, date);
},
/**
* Record the last seen value for a last numeric field.
*
* @param field
* (string) The name of the field to set the value of.
* @param value
* (Number) The value to set.
* @param date
* (Date) When this value was recorded.
*
* @return Promise<>
*/
setLastNumeric: function (field, value, date=new Date()) {
return this.storage.setLastNumericFromFieldID(this.fieldID(field), value,
date);
},
/**
* Record the last seen value for a last text field.
*
* This is like `setLastNumeric` except for last text fields.
*/
setLastText: function (field, value, date=new Date()) {
return this.storage.setLastTextFromFieldID(this.fieldID(field), value,
date);
},
/**
* Record the most recent value for a daily last numeric field.
*
* @param field
* (string) The name of a daily last numeric field.
* @param value
* (Number) The value to set.
* @param date
* (Date) Day on which to record the last value.
*
* @return Promise<>
*/
setDailyLastNumeric: function (field, value, date=new Date()) {
return this.storage.setDailyLastNumericFromFieldID(this.fieldID(field),
value, date);
},
/**
* Record the most recent value for a daily last text field.
*
* This is like `setDailyLastNumeric` except for a daily last text field.
*/
setDailyLastText: function (field, value, date=new Date()) {
return this.storage.setDailyLastTextFromFieldID(this.fieldID(field),
value, date);
},
//---------------------------------------------------------------------------
// End of data recording APIs.
//---------------------------------------------------------------------------
/**
* Obtain all values stored for this measurement.
*
* The default implementation obtains all known types from storage. If the
* measurement provides custom types or stores values somewhere other than
* storage, it should define its own implementation.
*
* This returns a promise that resolves to a data structure which is
* understood by the measurement's serialize() function.
*/
getValues: function () {
return this.storage.getMeasurementValues(this.id);
},
deleteLastNumeric: function (field) {
return this.storage.deleteLastNumericFromFieldID(this.fieldID(field));
},
deleteLastText: function (field) {
return this.storage.deleteLastTextFromFieldID(this.fieldID(field));
},
/**
* This method is used by the default serializers to control whether a field
* is included in the output.
*
* There could be legacy fields in storage we no longer care about.
*
* This method is a hook to allow measurements to change this behavior, e.g.,
* to implement a dynamic fieldset.
*
* You will also need to override `fieldType`.
*
* @return (boolean) true if the specified field should be included in
* payload output.
*/
shouldIncludeField: function (field) {
return field in this._fields;
},
_serializeJSONSingular: function (data) {
let result = {"_v": this.version};
for (let [field, value] of data) {
// There could be legacy fields in storage we no longer care about.
if (!this.shouldIncludeField(field)) {
continue;
}
let type = this.fieldType(field);
switch (type) {
case this.storage.FIELD_LAST_NUMERIC:
case this.storage.FIELD_LAST_TEXT:
result[field] = value[1];
break;
case this.storage.FIELD_DAILY_COUNTER:
case this.storage.FIELD_DAILY_DISCRETE_NUMERIC:
case this.storage.FIELD_DAILY_DISCRETE_TEXT:
case this.storage.FIELD_DAILY_LAST_NUMERIC:
case this.storage.FIELD_DAILY_LAST_TEXT:
continue;
default:
throw new Error("Unknown field type: " + type);
}
}
return result;
},
_serializeJSONDay: function (data) {
let result = {"_v": this.version};
for (let [field, value] of data) {
if (!this.shouldIncludeField(field)) {
continue;
}
let type = this.fieldType(field);
switch (type) {
case this.storage.FIELD_DAILY_COUNTER:
case this.storage.FIELD_DAILY_DISCRETE_NUMERIC:
case this.storage.FIELD_DAILY_DISCRETE_TEXT:
case this.storage.FIELD_DAILY_LAST_NUMERIC:
case this.storage.FIELD_DAILY_LAST_TEXT:
result[field] = value;
break;
case this.storage.FIELD_LAST_NUMERIC:
case this.storage.FIELD_LAST_TEXT:
continue;
default:
throw new Error("Unknown field type: " + type);
}
}
return result;
},
});
/**
* An entity that emits data.
*
* A `Provider` consists of a string name (must be globally unique among all
* known providers) and a set of `Measurement` instances.
*
* The main role of a `Provider` is to produce metrics data and to store said
* data in the storage backend.
*
* Metrics data collection is initiated either by a manager calling a
* `collect*` function on `Provider` instances or by the `Provider` registering
* to some external event and then reacting whenever they occur.
*
* `Provider` implementations interface directly with a storage backend. For
* common stored values (daily counters, daily discrete values, etc),
* implementations should interface with storage via the various helper
* functions on the `Measurement` instances. For custom stored value types,
* implementations will interact directly with the low-level storage APIs.
*
* Because multiple providers exist and could be responding to separate
* external events simultaneously and because not all operations performed by
* storage can safely be performed in parallel, writing directly to storage at
* event time is dangerous. Therefore, interactions with storage must be
* deferred until it is safe to perform them.
*
* This typically looks something like:
*
* // This gets called when an external event worthy of recording metrics
* // occurs. The function receives a numeric value associated with the event.
* function onExternalEvent (value) {
* let now = new Date();
* let m = this.getMeasurement("foo", 1);
*
* this.enqueueStorageOperation(function storeExternalEvent() {
*
* // We interface with storage via the `Measurement` helper functions.
* // These each return a promise that will be resolved when the
* // operation finishes. We rely on behavior of storage where operations
* // are executed single threaded and sequentially. Therefore, we only
* // need to return the final promise.
* m.incrementDailyCounter("foo", now);
* return m.addDailyDiscreteNumericValue("my_value", value, now);
* }.bind(this));
*
* }
*
*
* `Provider` is an abstract base class. Implementations must define a few
* properties:
*
* name
* The `name` property should be a string defining the provider's name. The
* name must be globally unique for the application. The name is used as an
* identifier to distinguish providers from each other.
*
* measurementTypes
* This must be an array of `Measurement`-derived types. Note that elements
* in the array are the type functions, not instances. Instances of the
* `Measurement` are created at run-time by the `Provider` and are bound
* to the provider and to a specific storage backend.
*/
this.Provider = function () {
if (!this.name) {
throw new Error("Provider must define a name.");
}
if (!Array.isArray(this.measurementTypes)) {
throw new Error("Provider must define measurement types.");
}
this._log = Log.repository.getLogger("Services.Metrics.Provider." + this.name);
this.measurements = null;
this.storage = null;
}
Provider.prototype = Object.freeze({
/**
* Whether the provider only pulls data from other sources.
*
* If this is true, the provider pulls data from other sources. By contrast,
* "push-based" providers subscribe to foreign sources and record/react to
* external events as they happen.
*
* Pull-only providers likely aren't instantiated until a data collection
* is performed. Thus, implementations cannot rely on a provider instance
* always being alive. This is an optimization so provider instances aren't
* dead weight while the application is running.
*
* This must be set on the prototype to have an effect.
*/
pullOnly: false,
/**
* Obtain a `Measurement` from its name and version.
*
* If the measurement is not found, an Error is thrown.
*/
getMeasurement: function (name, version) {
if (!Number.isInteger(version)) {
throw new Error("getMeasurement expects an integer version. Got: " + version);
}
let m = this.measurements.get([name, version].join(":"));
if (!m) {
throw new Error("Unknown measurement: " + name + " v" + version);
}
return m;
},
init: function (storage) {
if (this.storage !== null) {
throw new Error("Provider() not called. Did the sub-type forget to call it?");
}
if (this.storage) {
throw new Error("Provider has already been initialized.");
}
this.measurements = new Map();
this.storage = storage;
let self = this;
return Task.spawn(function init() {
let pre = self.preInit();
if (!pre || typeof(pre.then) != "function") {
throw new Error("preInit() does not return a promise.");
}
yield pre;
for (let measurementType of self.measurementTypes) {
let measurement = new measurementType();
measurement.provider = self;
measurement.storage = self.storage;
let id = yield storage.registerMeasurement(self.name, measurement.name,
measurement.version);
measurement.id = id;
yield measurement._configureStorage();
self.measurements.set([measurement.name, measurement.version].join(":"),
measurement);
}
let post = self.postInit();
if (!post || typeof(post.then) != "function") {
throw new Error("postInit() does not return a promise.");
}
yield post;
});
},
shutdown: function () {
let promise = this.onShutdown();
if (!promise || typeof(promise.then) != "function") {
throw new Error("onShutdown implementation does not return a promise.");
}
return promise;
},
/**
* Hook point for implementations to perform pre-initialization activity.
*
* This method will be called before measurement registration.
*
* Implementations should return a promise which is resolved when
* initialization activities have completed.
*/
preInit: function () {
return CommonUtils.laterTickResolvingPromise();
},
/**
* Hook point for implementations to perform post-initialization activity.
*
* This method will be called after `preInit` and measurement registration,
* but before initialization is finished.
*
* If a `Provider` instance needs to register observers, etc, it should
* implement this function.
*
* Implementations should return a promise which is resolved when
* initialization activities have completed.
*/
postInit: function () {
return CommonUtils.laterTickResolvingPromise();
},
/**
* Hook point for shutdown of instances.
*
* This is the opposite of `onInit`. If a `Provider` needs to unregister
* observers, etc, this is where it should do it.
*
* Implementations should return a promise which is resolved when
* shutdown activities have completed.
*/
onShutdown: function () {
return CommonUtils.laterTickResolvingPromise();
},
/**
* Collects data that doesn't change during the application's lifetime.
*
* Implementations should return a promise that resolves when all data has
* been collected and storage operations have been finished.
*
* @return Promise<>
*/
collectConstantData: function () {
return CommonUtils.laterTickResolvingPromise();
},
/**
* Collects data approximately every day.
*
* For long-running applications, this is called approximately every day.
* It may or may not be called every time the application is run. It also may
* be called more than once per day.
*
* Implementations should return a promise that resolves when all data has
* been collected and storage operations have completed.
*
* @return Promise<>
*/
collectDailyData: function () {
return CommonUtils.laterTickResolvingPromise();
},
/**
* Queue a deferred storage operation.
*
* Deferred storage operations are the preferred method for providers to
* interact with storage. When collected data is to be added to storage,
* the provider creates a function that performs the necessary storage
* interactions and then passes that function to this function. Pending
* storage operations will be executed sequentially by a coordinator.
*
* The passed function should return a promise which will be resolved upon
* completion of storage interaction.
*/
enqueueStorageOperation: function (func) {
return this.storage.enqueueOperation(func);
},
/**
* Obtain persisted provider state.
*
* Provider state consists of key-value pairs of string names and values.
* Providers can stuff whatever they want into state. They are encouraged to
* store as little as possible for performance reasons.
*
* State is backed by storage and is robust.
*
* These functions do not enqueue on storage automatically, so they should
* be guarded by `enqueueStorageOperation` or some other mutex.
*
* @param key
* (string) The property to retrieve.
*
* @return Promise<string|null> String value on success. null if no state
* is available under this key.
*/
getState: function (key) {
return this.storage.getProviderState(this.name, key);
},
/**
* Set state for this provider.
*
* This is the complementary API for `getState` and obeys the same
* storage restrictions.
*/
setState: function (key, value) {
return this.storage.setProviderState(this.name, key, value);
},
_dateToDays: function (date) {
return Math.floor(date.getTime() / MILLISECONDS_PER_DAY);
},
_daysToDate: function (days) {
return new Date(days * MILLISECONDS_PER_DAY);
},
});

View File

@ -1,154 +0,0 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = [
"DummyMeasurement",
"DummyProvider",
"DummyConstantProvider",
"DummyPullOnlyThrowsOnInitProvider",
"DummyThrowOnInitProvider",
"DummyThrowOnShutdownProvider",
];
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Metrics.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.DummyMeasurement = function DummyMeasurement(name="DummyMeasurement") {
this.name = name;
Metrics.Measurement.call(this);
}
DummyMeasurement.prototype = {
__proto__: Metrics.Measurement.prototype,
version: 1,
fields: {
"daily-counter": {type: Metrics.Storage.FIELD_DAILY_COUNTER},
"daily-discrete-numeric": {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC},
"daily-discrete-text": {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
"daily-last-numeric": {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
"daily-last-text": {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT},
"last-numeric": {type: Metrics.Storage.FIELD_LAST_NUMERIC},
"last-text": {type: Metrics.Storage.FIELD_LAST_TEXT},
},
};
this.DummyProvider = function DummyProvider(name="DummyProvider") {
Object.defineProperty(this, "name", {
value: name,
});
this.measurementTypes = [DummyMeasurement];
Metrics.Provider.call(this);
this.constantMeasurementName = "DummyMeasurement";
this.collectConstantCount = 0;
this.throwDuringCollectConstantData = null;
this.throwDuringConstantPopulate = null;
this.collectDailyCount = 0;
this.havePushedMeasurements = true;
}
DummyProvider.prototype = {
__proto__: Metrics.Provider.prototype,
name: "DummyProvider",
collectConstantData: function () {
this.collectConstantCount++;
if (this.throwDuringCollectConstantData) {
throw new Error(this.throwDuringCollectConstantData);
}
return this.enqueueStorageOperation(function doStorage() {
if (this.throwDuringConstantPopulate) {
throw new Error(this.throwDuringConstantPopulate);
}
let m = this.getMeasurement("DummyMeasurement", 1);
let now = new Date();
m.incrementDailyCounter("daily-counter", now);
m.addDailyDiscreteNumeric("daily-discrete-numeric", 1, now);
m.addDailyDiscreteNumeric("daily-discrete-numeric", 2, now);
m.addDailyDiscreteText("daily-discrete-text", "foo", now);
m.addDailyDiscreteText("daily-discrete-text", "bar", now);
m.setDailyLastNumeric("daily-last-numeric", 3, now);
m.setDailyLastText("daily-last-text", "biz", now);
m.setLastNumeric("last-numeric", 4, now);
return m.setLastText("last-text", "bazfoo", now);
}.bind(this));
},
collectDailyData: function () {
this.collectDailyCount++;
return Promise.resolve();
},
};
this.DummyConstantProvider = function () {
DummyProvider.call(this, this.name);
}
DummyConstantProvider.prototype = {
__proto__: DummyProvider.prototype,
name: "DummyConstantProvider",
pullOnly: true,
};
this.DummyThrowOnInitProvider = function () {
DummyProvider.call(this, "DummyThrowOnInitProvider");
throw new Error("Dummy Error");
};
this.DummyThrowOnInitProvider.prototype = {
__proto__: DummyProvider.prototype,
name: "DummyThrowOnInitProvider",
};
this.DummyPullOnlyThrowsOnInitProvider = function () {
DummyConstantProvider.call(this);
throw new Error("Dummy Error");
};
this.DummyPullOnlyThrowsOnInitProvider.prototype = {
__proto__: DummyConstantProvider.prototype,
name: "DummyPullOnlyThrowsOnInitProvider",
};
this.DummyThrowOnShutdownProvider = function () {
DummyProvider.call(this, "DummyThrowOnShutdownProvider");
};
this.DummyThrowOnShutdownProvider.prototype = {
__proto__: DummyProvider.prototype,
name: "DummyThrowOnShutdownProvider",
pullOnly: true,
onShutdown: function () {
throw new Error("Dummy shutdown error");
},
};

View File

@ -1,23 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
# We install Metrics.jsm into the "main" JSM repository and the rest in
# services. External consumers should only go through Metrics.jsm.
EXTRA_PP_JS_MODULES += [
'Metrics.jsm',
]
EXTRA_PP_JS_MODULES.services.metrics += [
'dataprovider.jsm',
'providermanager.jsm',
'storage.jsm',
]
TESTING_JS_MODULES.services.metrics += [
'modules-testing/mocks.jsm',
]

View File

@ -1,558 +0,0 @@
/* 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/. */
#ifndef MERGED_COMPARTMENT
"use strict";
this.EXPORTED_SYMBOLS = ["ProviderManager"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
#endif
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");
/**
* Handles and coordinates the collection of metrics data from providers.
*
* This provides an interface for managing `Metrics.Provider` instances. It
* provides APIs for bulk collection of data.
*/
this.ProviderManager = function (storage) {
this._log = Log.repository.getLogger("Services.Metrics.ProviderManager");
this._providers = new Map();
this._storage = storage;
this._providerInitQueue = [];
this._providerInitializing = false;
this._pullOnlyProviders = {};
this._pullOnlyProvidersRegisterCount = 0;
this._pullOnlyProvidersState = this.PULL_ONLY_NOT_REGISTERED;
this._pullOnlyProvidersCurrentPromise = null;
// Callback to allow customization of providers after they are constructed
// but before they call out into their initialization code.
this.onProviderInit = null;
}
this.ProviderManager.prototype = Object.freeze({
PULL_ONLY_NOT_REGISTERED: "none",
PULL_ONLY_REGISTERING: "registering",
PULL_ONLY_UNREGISTERING: "unregistering",
PULL_ONLY_REGISTERED: "registered",
get providers() {
let providers = [];
for (let [name, entry] of this._providers) {
providers.push(entry.provider);
}
return providers;
},
/**
* Obtain a provider from its name.
*/
getProvider: function (name) {
let provider = this._providers.get(name);
if (!provider) {
return null;
}
return provider.provider;
},
/**
* Registers providers from a category manager category.
*
* This examines the specified category entries and registers found
* providers.
*
* Category entries are essentially JS modules and the name of the symbol
* within that module that is a `Metrics.Provider` instance.
*
* The category entry name is the name of the JS type for the provider. The
* value is the resource:// URI to import which makes this type available.
*
* Example entry:
*
* FooProvider resource://gre/modules/foo.jsm
*
* One can register entries in the application's .manifest file. e.g.
*
* category healthreport-js-provider-default FooProvider resource://gre/modules/foo.jsm
* category healthreport-js-provider-nightly EyeballProvider resource://gre/modules/eyeball.jsm
*
* Then to load them:
*
* let reporter = getHealthReporter("healthreport.");
* reporter.registerProvidersFromCategoryManager("healthreport-js-provider-default");
*
* If the category has no defined members, this call has no effect, and no error is raised.
*
* @param category
* (string) Name of category from which to query and load.
* @param providerDiagnostic
* (function) Optional, called with the name of the provider currently being initialized.
* @return a newly spawned Task.
*/
registerProvidersFromCategoryManager: function (category, providerDiagnostic) {
this._log.info("Registering providers from category: " + category);
let cm = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
let promiseList = [];
let enumerator = cm.enumerateCategory(category);
while (enumerator.hasMoreElements()) {
let entry = enumerator.getNext()
.QueryInterface(Ci.nsISupportsCString)
.toString();
let uri = cm.getCategoryEntry(category, entry);
this._log.info("Attempting to load provider from category manager: " +
entry + " from " + uri);
try {
let ns = {};
Cu.import(uri, ns);
let promise = this.registerProviderFromType(ns[entry]);
if (promise) {
promiseList.push({name: entry, promise: promise});
}
} catch (ex) {
this._recordProviderError(entry,
"Error registering provider from category manager",
ex);
continue;
}
}
return Task.spawn(function* wait() {
for (let entry of promiseList) {
if (providerDiagnostic) {
providerDiagnostic(entry.name);
}
yield entry.promise;
}
});
},
/**
* Registers a `MetricsProvider` with this manager.
*
* Once a `MetricsProvider` is registered, data will be collected from it
* whenever we collect data.
*
* The returned value is a promise that will be resolved once registration
* is complete.
*
* Providers are initialized as part of registration by calling
* provider.init().
*
* @param provider
* (Metrics.Provider) The provider instance to register.
*
* @return Promise<null>
*/
registerProvider: function (provider) {
// We should perform an instanceof check here. However, due to merged
// compartments, the Provider type may belong to one of two JSMs
// isinstance gets confused depending on which module Provider comes
// from. Some code references Provider from dataprovider.jsm; others from
// Metrics.jsm.
if (!provider.name) {
throw new Error("Provider is not valid: does not have a name.");
}
if (this._providers.has(provider.name)) {
return CommonUtils.laterTickResolvingPromise();
}
let deferred = Promise.defer();
this._providerInitQueue.push([provider, deferred]);
if (this._providerInitQueue.length == 1) {
this._popAndInitProvider();
}
return deferred.promise;
},
/**
* Registers a provider from its constructor function.
*
* If the provider is pull-only, it will be stashed away and
* initialized later. Null will be returned.
*
* If it is not pull-only, it will be initialized immediately and a
* promise will be returned. The promise will be resolved when the
* provider has finished initializing.
*/
registerProviderFromType: function (type) {
let proto = type.prototype;
if (proto.pullOnly) {
this._log.info("Provider is pull-only. Deferring initialization: " +
proto.name);
this._pullOnlyProviders[proto.name] = type;
return null;
}
let provider = this._initProviderFromType(type);
return this.registerProvider(provider);
},
/**
* Initializes a provider from its type.
*
* This is how a constructor function should be turned into a provider
* instance.
*
* A side-effect is the provider is registered with the manager.
*/
_initProviderFromType: function (type) {
let provider = new type();
if (this.onProviderInit) {
this.onProviderInit(provider);
}
return provider;
},
/**
* Remove a named provider from the manager.
*
* It is the caller's responsibility to shut down the provider
* instance.
*/
unregisterProvider: function (name) {
this._providers.delete(name);
},
/**
* Ensure that pull-only providers are registered.
*/
ensurePullOnlyProvidersRegistered: function () {
let state = this._pullOnlyProvidersState;
this._pullOnlyProvidersRegisterCount++;
if (state == this.PULL_ONLY_REGISTERED) {
this._log.debug("Requested pull-only provider registration and " +
"providers are already registered.");
return CommonUtils.laterTickResolvingPromise();
}
// If we're in the process of registering, chain off that request.
if (state == this.PULL_ONLY_REGISTERING) {
this._log.debug("Requested pull-only provider registration and " +
"registration is already in progress.");
return this._pullOnlyProvidersCurrentPromise;
}
this._log.debug("Pull-only provider registration requested.");
// A side-effect of setting this is that an active unregistration will
// effectively short circuit and finish as soon as the in-flight
// unregistration (if any) finishes.
this._pullOnlyProvidersState = this.PULL_ONLY_REGISTERING;
let inFlightPromise = this._pullOnlyProvidersCurrentPromise;
this._pullOnlyProvidersCurrentPromise =
Task.spawn(function registerPullProviders() {
if (inFlightPromise) {
this._log.debug("Waiting for in-flight pull-only provider activity " +
"to finish before registering.");
try {
yield inFlightPromise;
} catch (ex) {
this._log.warn("Error when waiting for existing pull-only promise", ex);
}
}
for (let name in this._pullOnlyProviders) {
let providerType = this._pullOnlyProviders[name];
// Short-circuit if we're no longer registering.
if (this._pullOnlyProvidersState != this.PULL_ONLY_REGISTERING) {
this._log.debug("Aborting pull-only provider registration.");
break;
}
try {
let provider = this._initProviderFromType(providerType);
// This is a no-op if the provider is already registered. So, the
// only overhead is constructing an instance. This should be cheap
// and isn't worth optimizing.
yield this.registerProvider(provider);
} catch (ex) {
this._recordProviderError(providerType.prototype.name,
"Error registering pull-only provider",
ex);
}
}
// It's possible we changed state while registering. Only mark as
// registered if we didn't change state.
if (this._pullOnlyProvidersState == this.PULL_ONLY_REGISTERING) {
this._pullOnlyProvidersState = this.PULL_ONLY_REGISTERED;
this._pullOnlyProvidersCurrentPromise = null;
}
}.bind(this));
return this._pullOnlyProvidersCurrentPromise;
},
ensurePullOnlyProvidersUnregistered: function () {
let state = this._pullOnlyProvidersState;
// If we're not registered, this is a no-op.
if (state == this.PULL_ONLY_NOT_REGISTERED) {
this._log.debug("Requested pull-only provider unregistration but none " +
"are registered.");
return CommonUtils.laterTickResolvingPromise();
}
// If we're currently unregistering, recycle the promise from last time.
if (state == this.PULL_ONLY_UNREGISTERING) {
this._log.debug("Requested pull-only provider unregistration and " +
"unregistration is in progress.");
this._pullOnlyProvidersRegisterCount =
Math.max(0, this._pullOnlyProvidersRegisterCount - 1);
return this._pullOnlyProvidersCurrentPromise;
}
// We ignore this request while multiple entities have requested
// registration because we don't want a request from an "inner,"
// short-lived request to overwrite the desire of the "parent,"
// longer-lived request.
if (this._pullOnlyProvidersRegisterCount > 1) {
this._log.debug("Requested pull-only provider unregistration while " +
"other callers still want them registered. Ignoring.");
this._pullOnlyProvidersRegisterCount--;
return CommonUtils.laterTickResolvingPromise();
}
// We are either fully registered or registering with a single consumer.
// In both cases we are authoritative and can commence unregistration.
this._log.debug("Pull-only providers being unregistered.");
this._pullOnlyProvidersRegisterCount =
Math.max(0, this._pullOnlyProvidersRegisterCount - 1);
this._pullOnlyProvidersState = this.PULL_ONLY_UNREGISTERING;
let inFlightPromise = this._pullOnlyProvidersCurrentPromise;
this._pullOnlyProvidersCurrentPromise =
Task.spawn(function unregisterPullProviders() {
if (inFlightPromise) {
this._log.debug("Waiting for in-flight pull-only provider activity " +
"to complete before unregistering.");
try {
yield inFlightPromise;
} catch (ex) {
this._log.warn("Error when waiting for existing pull-only promise", ex);
}
}
for (let provider of this.providers) {
if (this._pullOnlyProvidersState != this.PULL_ONLY_UNREGISTERING) {
return;
}
if (!provider.pullOnly) {
continue;
}
this._log.info("Shutting down pull-only provider: " +
provider.name);
try {
yield provider.shutdown();
} catch (ex) {
this._recordProviderError(provider.name,
"Error when shutting down provider",
ex);
} finally {
this.unregisterProvider(provider.name);
}
}
if (this._pullOnlyProvidersState == this.PULL_ONLY_UNREGISTERING) {
this._pullOnlyProvidersState = this.PULL_ONLY_NOT_REGISTERED;
this._pullOnlyProvidersCurrentPromise = null;
}
}.bind(this));
return this._pullOnlyProvidersCurrentPromise;
},
_popAndInitProvider: function () {
if (!this._providerInitQueue.length || this._providerInitializing) {
return;
}
let [provider, deferred] = this._providerInitQueue.shift();
this._providerInitializing = true;
this._log.info("Initializing provider with storage: " + provider.name);
Task.spawn(function initProvider() {
try {
let result = yield provider.init(this._storage);
this._log.info("Provider successfully initialized: " + provider.name);
this._providers.set(provider.name, {
provider: provider,
constantsCollected: false,
});
deferred.resolve(result);
} catch (ex) {
this._recordProviderError(provider.name, "Failed to initialize", ex);
deferred.reject(ex);
} finally {
this._providerInitializing = false;
this._popAndInitProvider();
}
}.bind(this));
},
/**
* Collects all constant measurements from all providers.
*
* Returns a Promise that will be fulfilled once all data providers have
* provided their constant data. A side-effect of this promise fulfillment
* is that the manager is populated with the obtained collection results.
* The resolved value to the promise is this `ProviderManager` instance.
*
* @param providerDiagnostic
* (function) Optional, called with the name of the provider currently being initialized.
*/
collectConstantData: function (providerDiagnostic=null) {
let entries = [];
for (let [name, entry] of this._providers) {
if (entry.constantsCollected) {
this._log.trace("Provider has already provided constant data: " +
name);
continue;
}
entries.push(entry);
}
let onCollect = function (entry, result) {
entry.constantsCollected = true;
};
return this._callCollectOnProviders(entries, "collectConstantData",
onCollect, providerDiagnostic);
},
/**
* Calls collectDailyData on all providers.
*/
collectDailyData: function (providerDiagnostic=null) {
return this._callCollectOnProviders(this._providers.values(),
"collectDailyData",
null,
providerDiagnostic);
},
_callCollectOnProviders: function (entries, fnProperty, onCollect=null, providerDiagnostic=null) {
let promises = [];
for (let entry of entries) {
let provider = entry.provider;
let collectPromise;
try {
collectPromise = provider[fnProperty].call(provider);
} catch (ex) {
this._recordProviderError(provider.name, "Exception when calling " +
"collect function: " + fnProperty, ex);
continue;
}
if (!collectPromise) {
this._recordProviderError(provider.name, "Does not return a promise " +
"from " + fnProperty + "()");
continue;
}
let promise = collectPromise.then(function onCollected(result) {
if (onCollect) {
try {
onCollect(entry, result);
} catch (ex) {
this._log.warn("onCollect callback threw", ex);
}
}
return CommonUtils.laterTickResolvingPromise(result);
});
promises.push([provider.name, promise]);
}
return this._handleCollectionPromises(promises, providerDiagnostic);
},
/**
* Handles promises returned by the collect* functions.
*
* This consumes the data resolved by the promises and returns a new promise
* that will be resolved once all promises have been resolved.
*
* The promise is resolved even if one of the underlying collection
* promises is rejected.
*/
_handleCollectionPromises: function (promises, providerDiagnostic=null) {
return Task.spawn(function waitForPromises() {
for (let [name, promise] of promises) {
if (providerDiagnostic) {
providerDiagnostic(name);
}
try {
yield promise;
this._log.debug("Provider collected successfully: " + name);
} catch (ex) {
this._recordProviderError(name, "Failed to collect", ex);
}
}
throw new Task.Result(this);
}.bind(this));
},
/**
* Record an error that occurred operating on a provider.
*/
_recordProviderError: function (name, msg, ex) {
msg = "Provider error: " + name + ": " + msg;
if (ex) {
msg += ": " + Log.exceptionStr(ex);
}
this._log.warn(msg);
if (this.onProviderError) {
try {
this.onProviderError(msg);
} catch (callError) {
this._log.warn("Exception when calling onProviderError callback", callError);
}
}
},
});

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
(function initMetricsTestingInfrastructure() {
do_get_profile();
let ns = {};
Components.utils.import("resource://testing-common/services/common/logging.js",
ns);
ns.initTestLogging("Trace");
}).call(this);

View File

@ -1,31 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const modules = [
"dataprovider.jsm",
"providermanager.jsm",
"storage.jsm",
];
const test_modules = [
"mocks.jsm",
];
function run_test() {
for (let m of modules) {
let resource = "resource://gre/modules/services/metrics/" + m;
Components.utils.import(resource, {});
}
Components.utils.import("resource://gre/modules/Metrics.jsm", {});
for (let m of test_modules) {
let resource = "resource://testing-common/services/metrics/" + m;
Components.utils.import(resource, {});
}
Components.utils.import("resource://gre/modules/Metrics.jsm", {});
}

View File

@ -1,297 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var {utils: Cu} = Components;
Cu.import("resource://gre/modules/Metrics.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
function getProvider(storageName) {
return Task.spawn(function () {
let provider = new DummyProvider();
let storage = yield Metrics.Storage(storageName);
yield provider.init(storage);
throw new Task.Result(provider);
});
}
function run_test() {
run_next_test();
};
add_test(function test_constructor() {
let failed = false;
try {
new Metrics.Provider();
} catch(ex) {
do_check_true(ex.message.startsWith("Provider must define a name"));
failed = true;
}
finally {
do_check_true(failed);
}
run_next_test();
});
add_task(function test_init() {
let provider = new DummyProvider();
let storage = yield Metrics.Storage("init");
yield provider.init(storage);
let m = provider.getMeasurement("DummyMeasurement", 1);
do_check_true(m instanceof Metrics.Measurement);
do_check_eq(m.id, 1);
do_check_eq(Object.keys(m._fields).length, 7);
do_check_true(m.hasField("daily-counter"));
do_check_false(m.hasField("does-not-exist"));
yield storage.close();
});
add_task(function test_default_collectors() {
let provider = new DummyProvider();
let storage = yield Metrics.Storage("default_collectors");
yield provider.init(storage);
for (let property in Metrics.Provider.prototype) {
if (!property.startsWith("collect")) {
continue;
}
let result = provider[property]();
do_check_neq(result, null);
do_check_eq(typeof(result.then), "function");
}
yield storage.close();
});
add_task(function test_measurement_storage_basic() {
let provider = yield getProvider("measurement_storage_basic");
let m = provider.getMeasurement("DummyMeasurement", 1);
let now = new Date();
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
// Daily counter.
let counterID = m.fieldID("daily-counter");
yield m.incrementDailyCounter("daily-counter", now);
yield m.incrementDailyCounter("daily-counter", now);
yield m.incrementDailyCounter("daily-counter", yesterday);
let count = yield provider.storage.getDailyCounterCountFromFieldID(counterID, now);
do_check_eq(count, 2);
count = yield provider.storage.getDailyCounterCountFromFieldID(counterID, yesterday);
do_check_eq(count, 1);
yield m.incrementDailyCounter("daily-counter", now, 4);
count = yield provider.storage.getDailyCounterCountFromFieldID(counterID, now);
do_check_eq(count, 6);
// Daily discrete numeric.
let dailyDiscreteNumericID = m.fieldID("daily-discrete-numeric");
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", 5, now);
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", 6, now);
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", 7, yesterday);
let values = yield provider.storage.getDailyDiscreteNumericFromFieldID(
dailyDiscreteNumericID, now);
do_check_eq(values.size, 1);
do_check_true(values.hasDay(now));
let actual = values.getDay(now);
do_check_eq(actual.length, 2);
do_check_eq(actual[0], 5);
do_check_eq(actual[1], 6);
values = yield provider.storage.getDailyDiscreteNumericFromFieldID(
dailyDiscreteNumericID, yesterday);
do_check_eq(values.size, 1);
do_check_true(values.hasDay(yesterday));
do_check_eq(values.getDay(yesterday)[0], 7);
// Daily discrete text.
let dailyDiscreteTextID = m.fieldID("daily-discrete-text");
yield m.addDailyDiscreteText("daily-discrete-text", "foo", now);
yield m.addDailyDiscreteText("daily-discrete-text", "bar", now);
yield m.addDailyDiscreteText("daily-discrete-text", "biz", yesterday);
values = yield provider.storage.getDailyDiscreteTextFromFieldID(
dailyDiscreteTextID, now);
do_check_eq(values.size, 1);
do_check_true(values.hasDay(now));
actual = values.getDay(now);
do_check_eq(actual.length, 2);
do_check_eq(actual[0], "foo");
do_check_eq(actual[1], "bar");
values = yield provider.storage.getDailyDiscreteTextFromFieldID(
dailyDiscreteTextID, yesterday);
do_check_true(values.hasDay(yesterday));
do_check_eq(values.getDay(yesterday)[0], "biz");
// Daily last numeric.
let lastDailyNumericID = m.fieldID("daily-last-numeric");
yield m.setDailyLastNumeric("daily-last-numeric", 5, now);
yield m.setDailyLastNumeric("daily-last-numeric", 6, yesterday);
let result = yield provider.storage.getDailyLastNumericFromFieldID(
lastDailyNumericID, now);
do_check_eq(result.size, 1);
do_check_true(result.hasDay(now));
do_check_eq(result.getDay(now), 5);
result = yield provider.storage.getDailyLastNumericFromFieldID(
lastDailyNumericID, yesterday);
do_check_true(result.hasDay(yesterday));
do_check_eq(result.getDay(yesterday), 6);
yield m.setDailyLastNumeric("daily-last-numeric", 7, now);
result = yield provider.storage.getDailyLastNumericFromFieldID(
lastDailyNumericID, now);
do_check_eq(result.getDay(now), 7);
// Daily last text.
let lastDailyTextID = m.fieldID("daily-last-text");
yield m.setDailyLastText("daily-last-text", "foo", now);
yield m.setDailyLastText("daily-last-text", "bar", yesterday);
result = yield provider.storage.getDailyLastTextFromFieldID(
lastDailyTextID, now);
do_check_eq(result.size, 1);
do_check_true(result.hasDay(now));
do_check_eq(result.getDay(now), "foo");
result = yield provider.storage.getDailyLastTextFromFieldID(
lastDailyTextID, yesterday);
do_check_true(result.hasDay(yesterday));
do_check_eq(result.getDay(yesterday), "bar");
yield m.setDailyLastText("daily-last-text", "biz", now);
result = yield provider.storage.getDailyLastTextFromFieldID(
lastDailyTextID, now);
do_check_eq(result.getDay(now), "biz");
// Last numeric.
let lastNumericID = m.fieldID("last-numeric");
yield m.setLastNumeric("last-numeric", 1, now);
result = yield provider.storage.getLastNumericFromFieldID(lastNumericID);
do_check_eq(result[1], 1);
do_check_true(result[0].getTime() < now.getTime());
do_check_true(result[0].getTime() > yesterday.getTime());
yield m.setLastNumeric("last-numeric", 2, now);
result = yield provider.storage.getLastNumericFromFieldID(lastNumericID);
do_check_eq(result[1], 2);
// Last text.
let lastTextID = m.fieldID("last-text");
yield m.setLastText("last-text", "foo", now);
result = yield provider.storage.getLastTextFromFieldID(lastTextID);
do_check_eq(result[1], "foo");
do_check_true(result[0].getTime() < now.getTime());
do_check_true(result[0].getTime() > yesterday.getTime());
yield m.setLastText("last-text", "bar", now);
result = yield provider.storage.getLastTextFromFieldID(lastTextID);
do_check_eq(result[1], "bar");
yield provider.storage.close();
});
add_task(function test_serialize_json_default() {
let provider = yield getProvider("serialize_json_default");
let now = new Date();
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
let m = provider.getMeasurement("DummyMeasurement", 1);
m.incrementDailyCounter("daily-counter", now);
m.incrementDailyCounter("daily-counter", now);
m.incrementDailyCounter("daily-counter", yesterday);
m.addDailyDiscreteNumeric("daily-discrete-numeric", 1, now);
m.addDailyDiscreteNumeric("daily-discrete-numeric", 2, now);
m.addDailyDiscreteNumeric("daily-discrete-numeric", 3, yesterday);
m.addDailyDiscreteText("daily-discrete-text", "foo", now);
m.addDailyDiscreteText("daily-discrete-text", "bar", now);
m.addDailyDiscreteText("daily-discrete-text", "baz", yesterday);
m.setDailyLastNumeric("daily-last-numeric", 4, now);
m.setDailyLastNumeric("daily-last-numeric", 5, yesterday);
m.setDailyLastText("daily-last-text", "apple", now);
m.setDailyLastText("daily-last-text", "orange", yesterday);
m.setLastNumeric("last-numeric", 6, now);
yield m.setLastText("last-text", "hello", now);
let data = yield provider.storage.getMeasurementValues(m.id);
let serializer = m.serializer(m.SERIALIZE_JSON);
let formatted = serializer.singular(data.singular);
do_check_eq(Object.keys(formatted).length, 3); // Our keys + _v.
do_check_true("last-numeric" in formatted);
do_check_true("last-text" in formatted);
do_check_eq(formatted["last-numeric"], 6);
do_check_eq(formatted["last-text"], "hello");
do_check_eq(formatted["_v"], 1);
formatted = serializer.daily(data.days.getDay(now));
do_check_eq(Object.keys(formatted).length, 6); // Our keys + _v.
do_check_eq(formatted["daily-counter"], 2);
do_check_eq(formatted["_v"], 1);
do_check_true(Array.isArray(formatted["daily-discrete-numeric"]));
do_check_eq(formatted["daily-discrete-numeric"].length, 2);
do_check_eq(formatted["daily-discrete-numeric"][0], 1);
do_check_eq(formatted["daily-discrete-numeric"][1], 2);
do_check_true(Array.isArray(formatted["daily-discrete-text"]));
do_check_eq(formatted["daily-discrete-text"].length, 2);
do_check_eq(formatted["daily-discrete-text"][0], "foo");
do_check_eq(formatted["daily-discrete-text"][1], "bar");
do_check_eq(formatted["daily-last-numeric"], 4);
do_check_eq(formatted["daily-last-text"], "apple");
formatted = serializer.daily(data.days.getDay(yesterday));
do_check_eq(formatted["daily-last-numeric"], 5);
do_check_eq(formatted["daily-last-text"], "orange");
// Now let's turn off a field so that it's present in the DB
// but not present in the output.
let called = false;
let excluded = "daily-last-numeric";
Object.defineProperty(m, "shouldIncludeField", {
value: function fakeShouldIncludeField(field) {
called = true;
return field != excluded;
},
});
let limited = serializer.daily(data.days.getDay(yesterday));
do_check_true(called);
do_check_false(excluded in limited);
do_check_eq(formatted["daily-last-text"], "orange");
yield provider.storage.close();
});

View File

@ -1,357 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Metrics.jsm");
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
const PULL_ONLY_TESTING_CATEGORY = "testing-only-pull-only-providers";
function run_test() {
let cm = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
cm.addCategoryEntry(PULL_ONLY_TESTING_CATEGORY, "DummyProvider",
"resource://testing-common/services/metrics/mocks.jsm",
false, true);
cm.addCategoryEntry(PULL_ONLY_TESTING_CATEGORY, "DummyConstantProvider",
"resource://testing-common/services/metrics/mocks.jsm",
false, true);
run_next_test();
};
add_task(function test_constructor() {
let storage = yield Metrics.Storage("constructor");
let manager = new Metrics.ProviderManager(storage);
yield storage.close();
});
add_task(function test_register_provider() {
let storage = yield Metrics.Storage("register_provider");
let manager = new Metrics.ProviderManager(storage);
let dummy = new DummyProvider();
yield manager.registerProvider(dummy);
do_check_eq(manager._providers.size, 1);
yield manager.registerProvider(dummy);
do_check_eq(manager._providers.size, 1);
do_check_eq(manager.getProvider(dummy.name), dummy);
let failed = false;
try {
manager.registerProvider({});
} catch (ex) {
do_check_true(ex.message.startsWith("Provider is not valid"));
failed = true;
} finally {
do_check_true(failed);
failed = false;
}
manager.unregisterProvider(dummy.name);
do_check_eq(manager._providers.size, 0);
do_check_null(manager.getProvider(dummy.name));
yield storage.close();
});
add_task(function test_register_providers_from_category_manager() {
const category = "metrics-providers-js-modules";
let cm = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
cm.addCategoryEntry(category, "DummyProvider",
"resource://testing-common/services/metrics/mocks.jsm",
false, true);
let storage = yield Metrics.Storage("register_providers_from_category_manager");
let manager = new Metrics.ProviderManager(storage);
try {
do_check_eq(manager._providers.size, 0);
yield manager.registerProvidersFromCategoryManager(category);
do_check_eq(manager._providers.size, 1);
} finally {
yield storage.close();
}
});
add_task(function test_collect_constant_data() {
let storage = yield Metrics.Storage("collect_constant_data");
let errorCount = 0;
let manager= new Metrics.ProviderManager(storage);
manager.onProviderError = function () { errorCount++; }
let provider = new DummyProvider();
yield manager.registerProvider(provider);
do_check_eq(provider.collectConstantCount, 0);
yield manager.collectConstantData();
do_check_eq(provider.collectConstantCount, 1);
do_check_true(manager._providers.get("DummyProvider").constantsCollected);
yield storage.close();
do_check_eq(errorCount, 0);
});
add_task(function test_collect_constant_throws() {
let storage = yield Metrics.Storage("collect_constant_throws");
let manager = new Metrics.ProviderManager(storage);
let errors = [];
manager.onProviderError = function (error) { errors.push(error); };
let provider = new DummyProvider();
provider.throwDuringCollectConstantData = "Fake error during collect";
yield manager.registerProvider(provider);
yield manager.collectConstantData();
do_check_eq(errors.length, 1);
do_check_true(errors[0].includes(provider.throwDuringCollectConstantData));
yield storage.close();
});
add_task(function test_collect_constant_populate_throws() {
let storage = yield Metrics.Storage("collect_constant_populate_throws");
let manager = new Metrics.ProviderManager(storage);
let errors = [];
manager.onProviderError = function (error) { errors.push(error); };
let provider = new DummyProvider();
provider.throwDuringConstantPopulate = "Fake error during constant populate";
yield manager.registerProvider(provider);
yield manager.collectConstantData();
do_check_eq(errors.length, 1);
do_check_true(errors[0].includes(provider.throwDuringConstantPopulate));
do_check_false(manager._providers.get(provider.name).constantsCollected);
yield storage.close();
});
add_task(function test_collect_constant_onetime() {
let storage = yield Metrics.Storage("collect_constant_onetime");
let manager = new Metrics.ProviderManager(storage);
let provider = new DummyProvider();
yield manager.registerProvider(provider);
yield manager.collectConstantData();
do_check_eq(provider.collectConstantCount, 1);
yield manager.collectConstantData();
do_check_eq(provider.collectConstantCount, 1);
yield storage.close();
});
add_task(function test_collect_multiple() {
let storage = yield Metrics.Storage("collect_multiple");
let manager = new Metrics.ProviderManager(storage);
for (let i = 0; i < 10; i++) {
yield manager.registerProvider(new DummyProvider("provider" + i));
}
do_check_eq(manager._providers.size, 10);
yield manager.collectConstantData();
yield storage.close();
});
add_task(function test_collect_daily() {
let storage = yield Metrics.Storage("collect_daily");
let manager = new Metrics.ProviderManager(storage);
let provider1 = new DummyProvider("DP1");
let provider2 = new DummyProvider("DP2");
yield manager.registerProvider(provider1);
yield manager.registerProvider(provider2);
yield manager.collectDailyData();
do_check_eq(provider1.collectDailyCount, 1);
do_check_eq(provider2.collectDailyCount, 1);
yield manager.collectDailyData();
do_check_eq(provider1.collectDailyCount, 2);
do_check_eq(provider2.collectDailyCount, 2);
yield storage.close();
});
add_task(function test_pull_only_not_initialized() {
let storage = yield Metrics.Storage("pull_only_not_initialized");
let manager = new Metrics.ProviderManager(storage);
yield manager.registerProvidersFromCategoryManager(PULL_ONLY_TESTING_CATEGORY);
do_check_eq(manager.providers.length, 1);
do_check_eq(manager.providers[0].name, "DummyProvider");
yield storage.close();
});
add_task(function test_pull_only_registration() {
let storage = yield Metrics.Storage("pull_only_registration");
let manager = new Metrics.ProviderManager(storage);
yield manager.registerProvidersFromCategoryManager(PULL_ONLY_TESTING_CATEGORY);
do_check_eq(manager.providers.length, 1);
// Simple registration and unregistration.
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(manager.providers.length, 2);
do_check_neq(manager.getProvider("DummyConstantProvider"), null);
yield manager.ensurePullOnlyProvidersUnregistered();
do_check_eq(manager.providers.length, 1);
do_check_null(manager.getProvider("DummyConstantProvider"));
// Multiple calls to register work.
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(manager.providers.length, 2);
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(manager.providers.length, 2);
// Unregister with 2 requests for registration should not unregister.
yield manager.ensurePullOnlyProvidersUnregistered();
do_check_eq(manager.providers.length, 2);
// But the 2nd one will.
yield manager.ensurePullOnlyProvidersUnregistered();
do_check_eq(manager.providers.length, 1);
yield storage.close();
});
add_task(function test_pull_only_register_while_registering() {
let storage = yield Metrics.Storage("pull_only_register_will_registering");
let manager = new Metrics.ProviderManager(storage);
yield manager.registerProvidersFromCategoryManager(PULL_ONLY_TESTING_CATEGORY);
manager.ensurePullOnlyProvidersRegistered();
manager.ensurePullOnlyProvidersRegistered();
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(manager.providers.length, 2);
manager.ensurePullOnlyProvidersUnregistered();
manager.ensurePullOnlyProvidersUnregistered();
yield manager.ensurePullOnlyProvidersUnregistered();
do_check_eq(manager.providers.length, 1);
yield storage.close();
});
add_task(function test_pull_only_unregister_while_registering() {
let storage = yield Metrics.Storage("pull_only_unregister_while_registering");
let manager = new Metrics.ProviderManager(storage);
yield manager.registerProvidersFromCategoryManager(PULL_ONLY_TESTING_CATEGORY);
manager.ensurePullOnlyProvidersRegistered();
yield manager.ensurePullOnlyProvidersUnregistered();
do_check_eq(manager.providers.length, 1);
yield storage.close();
});
add_task(function test_pull_only_register_while_unregistering() {
let storage = yield Metrics.Storage("pull_only_register_while_unregistering");
let manager = new Metrics.ProviderManager(storage);
yield manager.registerProvidersFromCategoryManager(PULL_ONLY_TESTING_CATEGORY);
yield manager.ensurePullOnlyProvidersRegistered();
manager.ensurePullOnlyProvidersUnregistered();
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(manager.providers.length, 2);
yield storage.close();
});
// Re-use database for perf reasons.
const REGISTRATION_ERRORS_DB = "registration_errors";
add_task(function test_category_manager_registration_error() {
let storage = yield Metrics.Storage(REGISTRATION_ERRORS_DB);
let manager = new Metrics.ProviderManager(storage);
let cm = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
cm.addCategoryEntry("registration-errors", "DummyThrowOnInitProvider",
"resource://testing-common/services/metrics/mocks.jsm",
false, true);
let deferred = Promise.defer();
let errorCount = 0;
manager.onProviderError = function (msg) {
errorCount++;
deferred.resolve(msg);
};
yield manager.registerProvidersFromCategoryManager("registration-errors");
do_check_eq(manager.providers.length, 0);
do_check_eq(errorCount, 1);
let msg = yield deferred.promise;
do_check_true(msg.includes("Provider error: DummyThrowOnInitProvider: "
+ "Error registering provider from category manager: "
+ "Error: Dummy Error"));
yield storage.close();
});
add_task(function test_pull_only_registration_error() {
let storage = yield Metrics.Storage(REGISTRATION_ERRORS_DB);
let manager = new Metrics.ProviderManager(storage);
let deferred = Promise.defer();
let errorCount = 0;
manager.onProviderError = function (msg) {
errorCount++;
deferred.resolve(msg);
};
yield manager.registerProviderFromType(DummyPullOnlyThrowsOnInitProvider);
do_check_eq(errorCount, 0);
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(errorCount, 1);
let msg = yield deferred.promise;
do_check_true(msg.includes("Provider error: DummyPullOnlyThrowsOnInitProvider: " +
"Error registering pull-only provider: Error: Dummy Error"));
yield storage.close();
});
add_task(function test_error_during_shutdown() {
let storage = yield Metrics.Storage(REGISTRATION_ERRORS_DB);
let manager = new Metrics.ProviderManager(storage);
let deferred = Promise.defer();
let errorCount = 0;
manager.onProviderError = function (msg) {
errorCount++;
deferred.resolve(msg);
};
yield manager.registerProviderFromType(DummyThrowOnShutdownProvider);
yield manager.registerProviderFromType(DummyProvider);
do_check_eq(errorCount, 0);
do_check_eq(manager.providers.length, 1);
yield manager.ensurePullOnlyProvidersRegistered();
do_check_eq(errorCount, 0);
yield manager.ensurePullOnlyProvidersUnregistered();
do_check_eq(errorCount, 1);
let msg = yield deferred.promise;
do_check_true(msg.includes("Provider error: DummyThrowOnShutdownProvider: " +
"Error when shutting down provider: Error: Dummy shutdown error"));
yield storage.close();
});

View File

@ -1,839 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var {utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Metrics.jsm");
Cu.import("resource://services-common/utils.js");
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
function run_test() {
run_next_test();
}
add_test(function test_days_date_conversion() {
let toDays = Metrics.dateToDays;
let toDate = Metrics.daysToDate;
let d = new Date(0);
do_check_eq(toDays(d), 0);
d = new Date(MILLISECONDS_PER_DAY);
do_check_eq(toDays(d), 1);
d = new Date(MILLISECONDS_PER_DAY - 1);
do_check_eq(toDays(d), 0);
d = new Date("1970-12-31T23:59:59.999Z");
do_check_eq(toDays(d), 364);
d = new Date("1971-01-01T00:00:00Z");
do_check_eq(toDays(d), 365);
d = toDate(0);
do_check_eq(d.getTime(), 0);
d = toDate(1);
do_check_eq(d.getTime(), MILLISECONDS_PER_DAY);
d = toDate(365);
do_check_eq(d.getUTCFullYear(), 1971);
do_check_eq(d.getUTCMonth(), 0);
do_check_eq(d.getUTCDate(), 1);
do_check_eq(d.getUTCHours(), 0);
do_check_eq(d.getUTCMinutes(), 0);
do_check_eq(d.getUTCSeconds(), 0);
do_check_eq(d.getUTCMilliseconds(), 0);
run_next_test();
});
add_task(function test_get_sqlite_backend() {
let backend = yield Metrics.Storage("get_sqlite_backend.sqlite");
do_check_neq(backend._connection, null);
// Ensure WAL and auto checkpoint are enabled.
do_check_neq(backend._enabledWALCheckpointPages, null);
let rows = yield backend._connection.execute("PRAGMA journal_mode");
do_check_eq(rows[0].getResultByIndex(0), "wal");
rows = yield backend._connection.execute("PRAGMA wal_autocheckpoint");
do_check_eq(rows[0].getResultByIndex(0), backend._enabledWALCheckpointPages);
yield backend.close();
do_check_null(backend._connection);
});
add_task(function test_reconnect() {
let backend = yield Metrics.Storage("reconnect");
yield backend.close();
let backend2 = yield Metrics.Storage("reconnect");
yield backend2.close();
});
add_task(function test_future_schema_errors() {
let backend = yield Metrics.Storage("future_schema_errors");
yield backend._connection.setSchemaVersion(2);
yield backend.close();
let backend2;
let failed = false;
try {
backend2 = yield Metrics.Storage("future_schema_errors");
} catch (ex) {
failed = true;
do_check_true(ex.message.startsWith("Unknown database schema"));
}
do_check_null(backend2);
do_check_true(failed);
});
add_task(function test_checkpoint_apis() {
let backend = yield Metrics.Storage("checkpoint_apis");
let c = backend._connection;
let count = c._connectionData._statementCounter;
yield backend.setAutoCheckpoint(0);
do_check_eq(c._connectionData._statementCounter, count + 1);
let rows = yield c.execute("PRAGMA wal_autocheckpoint");
do_check_eq(rows[0].getResultByIndex(0), 0);
count = c._connectionData._statementCounter;
yield backend.setAutoCheckpoint(1);
do_check_eq(c._connectionData._statementCounter, count + 1);
rows = yield c.execute("PRAGMA wal_autocheckpoint");
do_check_eq(rows[0].getResultByIndex(0), backend._enabledWALCheckpointPages);
count = c._connectionData._statementCounter;
yield backend.checkpoint();
do_check_eq(c._connectionData._statementCounter, count + 1);
yield backend.checkpoint();
do_check_eq(c._connectionData._statementCounter, count + 2);
yield backend.close();
});
add_task(function test_measurement_registration() {
let backend = yield Metrics.Storage("measurement_registration");
do_check_false(backend.hasProvider("foo"));
do_check_false(backend.hasMeasurement("foo", "bar", 1));
let id = yield backend.registerMeasurement("foo", "bar", 1);
do_check_eq(id, 1);
do_check_true(backend.hasProvider("foo"));
do_check_true(backend.hasMeasurement("foo", "bar", 1));
do_check_eq(backend.measurementID("foo", "bar", 1), id);
do_check_false(backend.hasMeasurement("foo", "bar", 2));
let id2 = yield backend.registerMeasurement("foo", "bar", 2);
do_check_eq(id2, 2);
do_check_true(backend.hasMeasurement("foo", "bar", 2));
do_check_eq(backend.measurementID("foo", "bar", 2), id2);
yield backend.close();
});
add_task(function test_field_registration_basic() {
let backend = yield Metrics.Storage("field_registration_basic");
do_check_false(backend.hasField("foo", "bar", 1, "baz"));
let mID = yield backend.registerMeasurement("foo", "bar", 1);
do_check_false(backend.hasField("foo", "bar", 1, "baz"));
do_check_false(backend.hasFieldFromMeasurement(mID, "baz"));
let bazID = yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_COUNTER);
do_check_true(backend.hasField("foo", "bar", 1, "baz"));
do_check_true(backend.hasFieldFromMeasurement(mID, "baz"));
let bar2ID = yield backend.registerMeasurement("foo", "bar2", 1);
yield backend.registerField(bar2ID, "baz",
backend.FIELD_DAILY_DISCRETE_NUMERIC);
do_check_true(backend.hasField("foo", "bar2", 1, "baz"));
yield backend.close();
});
// Ensure changes types of fields results in fatal error.
add_task(function test_field_registration_changed_type() {
let backend = yield Metrics.Storage("field_registration_changed_type");
let mID = yield backend.registerMeasurement("bar", "bar", 1);
let id = yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_COUNTER);
let caught = false;
try {
yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_DISCRETE_NUMERIC);
} catch (ex) {
caught = true;
do_check_true(ex.message.startsWith("Field already defined with different type"));
}
do_check_true(caught);
yield backend.close();
});
add_task(function test_field_registration_repopulation() {
let backend = yield Metrics.Storage("field_registration_repopulation");
let mID1 = yield backend.registerMeasurement("foo", "bar", 1);
let mID2 = yield backend.registerMeasurement("foo", "bar", 2);
let mID3 = yield backend.registerMeasurement("foo", "biz", 1);
let mID4 = yield backend.registerMeasurement("baz", "foo", 1);
let fID1 = yield backend.registerField(mID1, "foo", backend.FIELD_DAILY_COUNTER);
let fID2 = yield backend.registerField(mID1, "bar", backend.FIELD_DAILY_DISCRETE_NUMERIC);
let fID3 = yield backend.registerField(mID4, "foo", backend.FIELD_LAST_TEXT);
yield backend.close();
backend = yield Metrics.Storage("field_registration_repopulation");
do_check_true(backend.hasProvider("foo"));
do_check_true(backend.hasProvider("baz"));
do_check_true(backend.hasMeasurement("foo", "bar", 1));
do_check_eq(backend.measurementID("foo", "bar", 1), mID1);
do_check_true(backend.hasMeasurement("foo", "bar", 2));
do_check_eq(backend.measurementID("foo", "bar", 2), mID2);
do_check_true(backend.hasMeasurement("foo", "biz", 1));
do_check_eq(backend.measurementID("foo", "biz", 1), mID3);
do_check_true(backend.hasMeasurement("baz", "foo", 1));
do_check_eq(backend.measurementID("baz", "foo", 1), mID4);
do_check_true(backend.hasField("foo", "bar", 1, "foo"));
do_check_eq(backend.fieldID("foo", "bar", 1, "foo"), fID1);
do_check_true(backend.hasField("foo", "bar", 1, "bar"));
do_check_eq(backend.fieldID("foo", "bar", 1, "bar"), fID2);
do_check_true(backend.hasField("baz", "foo", 1, "foo"));
do_check_eq(backend.fieldID("baz", "foo", 1, "foo"), fID3);
yield backend.close();
});
add_task(function test_enqueue_operation_execution_order() {
let backend = yield Metrics.Storage("enqueue_operation_execution_order");
let executionCount = 0;
let fns = {
op1: function () {
do_check_eq(executionCount, 1);
},
op2: function () {
do_check_eq(executionCount, 2);
},
op3: function () {
do_check_eq(executionCount, 3);
},
};
function enqueuedOperation(fn) {
let deferred = Promise.defer();
CommonUtils.nextTick(function onNextTick() {
executionCount++;
fn();
deferred.resolve();
});
return deferred.promise;
}
let promises = [];
for (let i = 1; i <= 3; i++) {
let fn = fns["op" + i];
promises.push(backend.enqueueOperation(enqueuedOperation.bind(this, fn)));
}
for (let promise of promises) {
yield promise;
}
yield backend.close();
});
add_task(function test_enqueue_operation_many() {
let backend = yield Metrics.Storage("enqueue_operation_many");
let promises = [];
for (let i = 0; i < 100; i++) {
promises.push(backend.registerMeasurement("foo", "bar" + i, 1));
}
for (let promise of promises) {
yield promise;
}
yield backend.close();
});
// If the operation did not return a promise, everything should still execute.
add_task(function test_enqueue_operation_no_return_promise() {
let backend = yield Metrics.Storage("enqueue_operation_no_return_promise");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
let now = new Date();
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(backend.enqueueOperation(function op() {
backend.incrementDailyCounterFromFieldID(fID, now);
}));
}
let deferred = Promise.defer();
let finished = 0;
for (let promise of promises) {
promise.then(
do_throw.bind(this, "Unexpected resolve."),
function onError() {
finished++;
if (finished == promises.length) {
backend.getDailyCounterCountFromFieldID(fID, now).then(function onCount(count) {
// There should not be a race condition here because storage
// serializes all statements. So, for the getDailyCounterCount
// query to finish means that all counter update statements must
// have completed.
do_check_eq(count, promises.length);
deferred.resolve();
});
}
}
);
}
yield deferred.promise;
yield backend.close();
});
// If an operation throws, subsequent operations should still execute.
add_task(function test_enqueue_operation_throw_exception() {
let backend = yield Metrics.Storage("enqueue_operation_rejected_promise");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
let now = new Date();
let deferred = Promise.defer();
backend.enqueueOperation(function bad() {
throw new Error("I failed.");
}).then(do_throw, function onError(error) {
do_check_true(error.message.includes("I failed."));
deferred.resolve();
});
let promise = backend.enqueueOperation(function () {
return backend.incrementDailyCounterFromFieldID(fID, now);
});
yield deferred.promise;
yield promise;
let count = yield backend.getDailyCounterCountFromFieldID(fID, now);
do_check_eq(count, 1);
yield backend.close();
});
// If an operation rejects, subsequent operations should still execute.
add_task(function test_enqueue_operation_reject_promise() {
let backend = yield Metrics.Storage("enqueue_operation_reject_promise");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
let now = new Date();
let deferred = Promise.defer();
backend.enqueueOperation(function reject() {
let d = Promise.defer();
CommonUtils.nextTick(function nextTick() {
d.reject("I failed.");
});
return d.promise;
}).then(do_throw, function onError(error) {
deferred.resolve();
});
let promise = backend.enqueueOperation(function () {
return backend.incrementDailyCounterFromFieldID(fID, now);
});
yield deferred.promise;
yield promise;
let count = yield backend.getDailyCounterCountFromFieldID(fID, now);
do_check_eq(count, 1);
yield backend.close();
});
add_task(function test_enqueue_transaction() {
let backend = yield Metrics.Storage("enqueue_transaction");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
let now = new Date();
yield backend.incrementDailyCounterFromFieldID(fID, now);
yield backend.enqueueTransaction(function transaction() {
yield backend.incrementDailyCounterFromFieldID(fID, now);
});
let count = yield backend.getDailyCounterCountFromFieldID(fID, now);
do_check_eq(count, 2);
let errored = false;
try {
yield backend.enqueueTransaction(function aborted() {
yield backend.incrementDailyCounterFromFieldID(fID, now);
throw new Error("Some error.");
});
} catch (ex) {
errored = true;
} finally {
do_check_true(errored);
}
count = yield backend.getDailyCounterCountFromFieldID(fID, now);
do_check_eq(count, 2);
yield backend.close();
});
add_task(function test_increment_daily_counter_basic() {
let backend = yield Metrics.Storage("increment_daily_counter_basic");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let fieldID = yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_COUNTER);
let now = new Date();
yield backend.incrementDailyCounterFromFieldID(fieldID, now);
let count = yield backend.getDailyCounterCountFromFieldID(fieldID, now);
do_check_eq(count, 1);
yield backend.incrementDailyCounterFromFieldID(fieldID, now);
count = yield backend.getDailyCounterCountFromFieldID(fieldID, now);
do_check_eq(count, 2);
yield backend.incrementDailyCounterFromFieldID(fieldID, now, 10);
count = yield backend.getDailyCounterCountFromFieldID(fieldID, now);
do_check_eq(count, 12);
yield backend.close();
});
add_task(function test_increment_daily_counter_multiple_days() {
let backend = yield Metrics.Storage("increment_daily_counter_multiple_days");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let fieldID = yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_COUNTER);
let days = [];
let now = Date.now();
for (let i = 0; i < 100; i++) {
days.push(new Date(now - i * MILLISECONDS_PER_DAY));
}
for (let day of days) {
yield backend.incrementDailyCounterFromFieldID(fieldID, day);
}
let result = yield backend.getDailyCounterCountsFromFieldID(fieldID);
do_check_eq(result.size, 100);
for (let day of days) {
do_check_true(result.hasDay(day));
do_check_eq(result.getDay(day), 1);
}
let fields = yield backend.getMeasurementDailyCountersFromMeasurementID(mID);
do_check_eq(fields.size, 1);
do_check_true(fields.has("baz"));
do_check_eq(fields.get("baz").size, 100);
for (let day of days) {
do_check_true(fields.get("baz").hasDay(day));
do_check_eq(fields.get("baz").getDay(day), 1);
}
yield backend.close();
});
add_task(function test_last_values() {
let backend = yield Metrics.Storage("set_last");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let numberID = yield backend.registerField(mID, "number",
backend.FIELD_LAST_NUMERIC);
let textID = yield backend.registerField(mID, "text",
backend.FIELD_LAST_TEXT);
let now = new Date();
let nowDay = new Date(Math.floor(now.getTime() / MILLISECONDS_PER_DAY) * MILLISECONDS_PER_DAY);
yield backend.setLastNumericFromFieldID(numberID, 42, now);
yield backend.setLastTextFromFieldID(textID, "hello world", now);
let result = yield backend.getLastNumericFromFieldID(numberID);
do_check_true(Array.isArray(result));
do_check_eq(result[0].getTime(), nowDay.getTime());
do_check_eq(typeof(result[1]), "number");
do_check_eq(result[1], 42);
result = yield backend.getLastTextFromFieldID(textID);
do_check_true(Array.isArray(result));
do_check_eq(result[0].getTime(), nowDay.getTime());
do_check_eq(typeof(result[1]), "string");
do_check_eq(result[1], "hello world");
let missingID = yield backend.registerField(mID, "missing",
backend.FIELD_LAST_NUMERIC);
do_check_null(yield backend.getLastNumericFromFieldID(missingID));
let fields = yield backend.getMeasurementLastValuesFromMeasurementID(mID);
do_check_eq(fields.size, 2);
do_check_true(fields.has("number"));
do_check_true(fields.has("text"));
do_check_eq(fields.get("number")[1], 42);
do_check_eq(fields.get("text")[1], "hello world");
yield backend.close();
});
add_task(function test_discrete_values_basic() {
let backend = yield Metrics.Storage("discrete_values_basic");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let numericID = yield backend.registerField(mID, "numeric",
backend.FIELD_DAILY_DISCRETE_NUMERIC);
let textID = yield backend.registerField(mID, "text",
backend.FIELD_DAILY_DISCRETE_TEXT);
let now = new Date();
let expectedNumeric = [];
let expectedText = [];
for (let i = 0; i < 100; i++) {
expectedNumeric.push(i);
expectedText.push("value" + i);
yield backend.addDailyDiscreteNumericFromFieldID(numericID, i, now);
yield backend.addDailyDiscreteTextFromFieldID(textID, "value" + i, now);
}
let values = yield backend.getDailyDiscreteNumericFromFieldID(numericID);
do_check_eq(values.size, 1);
do_check_true(values.hasDay(now));
do_check_true(Array.isArray(values.getDay(now)));
do_check_eq(values.getDay(now).length, expectedNumeric.length);
for (let i = 0; i < expectedNumeric.length; i++) {
do_check_eq(values.getDay(now)[i], expectedNumeric[i]);
}
values = yield backend.getDailyDiscreteTextFromFieldID(textID);
do_check_eq(values.size, 1);
do_check_true(values.hasDay(now));
do_check_true(Array.isArray(values.getDay(now)));
do_check_eq(values.getDay(now).length, expectedText.length);
for (let i = 0; i < expectedText.length; i++) {
do_check_eq(values.getDay(now)[i], expectedText[i]);
}
let fields = yield backend.getMeasurementDailyDiscreteValuesFromMeasurementID(mID);
do_check_eq(fields.size, 2);
do_check_true(fields.has("numeric"));
do_check_true(fields.has("text"));
let numeric = fields.get("numeric");
let text = fields.get("text");
do_check_true(numeric.hasDay(now));
do_check_true(text.hasDay(now));
do_check_eq(numeric.getDay(now).length, expectedNumeric.length);
do_check_eq(text.getDay(now).length, expectedText.length);
for (let i = 0; i < expectedNumeric.length; i++) {
do_check_eq(numeric.getDay(now)[i], expectedNumeric[i]);
}
for (let i = 0; i < expectedText.length; i++) {
do_check_eq(text.getDay(now)[i], expectedText[i]);
}
yield backend.close();
});
add_task(function test_discrete_values_multiple_days() {
let backend = yield Metrics.Storage("discrete_values_multiple_days");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let id = yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_DISCRETE_NUMERIC);
let now = new Date();
let dates = [];
for (let i = 0; i < 50; i++) {
let date = new Date(now.getTime() + i * MILLISECONDS_PER_DAY);
dates.push(date);
yield backend.addDailyDiscreteNumericFromFieldID(id, i, date);
}
let values = yield backend.getDailyDiscreteNumericFromFieldID(id);
do_check_eq(values.size, 50);
let i = 0;
for (let date of dates) {
do_check_true(values.hasDay(date));
do_check_eq(values.getDay(date)[0], i);
i++;
}
let fields = yield backend.getMeasurementDailyDiscreteValuesFromMeasurementID(mID);
do_check_eq(fields.size, 1);
do_check_true(fields.has("baz"));
let baz = fields.get("baz");
do_check_eq(baz.size, 50);
i = 0;
for (let date of dates) {
do_check_true(baz.hasDay(date));
do_check_eq(baz.getDay(date).length, 1);
do_check_eq(baz.getDay(date)[0], i);
i++;
}
yield backend.close();
});
add_task(function test_daily_last_values() {
let backend = yield Metrics.Storage("daily_last_values");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let numericID = yield backend.registerField(mID, "numeric",
backend.FIELD_DAILY_LAST_NUMERIC);
let textID = yield backend.registerField(mID, "text",
backend.FIELD_DAILY_LAST_TEXT);
let now = new Date();
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
let dayBefore = new Date(yesterday.getTime() - MILLISECONDS_PER_DAY);
yield backend.setDailyLastNumericFromFieldID(numericID, 1, yesterday);
yield backend.setDailyLastNumericFromFieldID(numericID, 2, now);
yield backend.setDailyLastNumericFromFieldID(numericID, 3, dayBefore);
yield backend.setDailyLastTextFromFieldID(textID, "foo", now);
yield backend.setDailyLastTextFromFieldID(textID, "bar", yesterday);
yield backend.setDailyLastTextFromFieldID(textID, "baz", dayBefore);
let days = yield backend.getDailyLastNumericFromFieldID(numericID);
do_check_eq(days.size, 3);
do_check_eq(days.getDay(yesterday), 1);
do_check_eq(days.getDay(now), 2);
do_check_eq(days.getDay(dayBefore), 3);
days = yield backend.getDailyLastTextFromFieldID(textID);
do_check_eq(days.size, 3);
do_check_eq(days.getDay(now), "foo");
do_check_eq(days.getDay(yesterday), "bar");
do_check_eq(days.getDay(dayBefore), "baz");
yield backend.setDailyLastNumericFromFieldID(numericID, 4, yesterday);
days = yield backend.getDailyLastNumericFromFieldID(numericID);
do_check_eq(days.getDay(yesterday), 4);
yield backend.setDailyLastTextFromFieldID(textID, "biz", yesterday);
days = yield backend.getDailyLastTextFromFieldID(textID);
do_check_eq(days.getDay(yesterday), "biz");
days = yield backend.getDailyLastNumericFromFieldID(numericID, yesterday);
do_check_eq(days.size, 1);
do_check_eq(days.getDay(yesterday), 4);
days = yield backend.getDailyLastTextFromFieldID(textID, yesterday);
do_check_eq(days.size, 1);
do_check_eq(days.getDay(yesterday), "biz");
let fields = yield backend.getMeasurementDailyLastValuesFromMeasurementID(mID);
do_check_eq(fields.size, 2);
do_check_true(fields.has("numeric"));
do_check_true(fields.has("text"));
let numeric = fields.get("numeric");
let text = fields.get("text");
do_check_true(numeric.hasDay(yesterday));
do_check_true(numeric.hasDay(dayBefore));
do_check_true(numeric.hasDay(now));
do_check_true(text.hasDay(yesterday));
do_check_true(text.hasDay(dayBefore));
do_check_true(text.hasDay(now));
do_check_eq(numeric.getDay(yesterday), 4);
do_check_eq(text.getDay(yesterday), "biz");
yield backend.close();
});
add_task(function test_prune_data_before() {
let backend = yield Metrics.Storage("prune_data_before");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let counterID = yield backend.registerField(mID, "baz",
backend.FIELD_DAILY_COUNTER);
let text1ID = yield backend.registerField(mID, "one_text_1",
backend.FIELD_LAST_TEXT);
let text2ID = yield backend.registerField(mID, "one_text_2",
backend.FIELD_LAST_TEXT);
let numeric1ID = yield backend.registerField(mID, "one_numeric_1",
backend.FIELD_LAST_NUMERIC);
let numeric2ID = yield backend.registerField(mID, "one_numeric_2",
backend.FIELD_LAST_NUMERIC);
let text3ID = yield backend.registerField(mID, "daily_last_text_1",
backend.FIELD_DAILY_LAST_TEXT);
let text4ID = yield backend.registerField(mID, "daily_last_text_2",
backend.FIELD_DAILY_LAST_TEXT);
let numeric3ID = yield backend.registerField(mID, "daily_last_numeric_1",
backend.FIELD_DAILY_LAST_NUMERIC);
let numeric4ID = yield backend.registerField(mID, "daily_last_numeric_2",
backend.FIELD_DAILY_LAST_NUMERIC);
let now = new Date();
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
let dayBefore = new Date(yesterday.getTime() - MILLISECONDS_PER_DAY);
yield backend.incrementDailyCounterFromFieldID(counterID, now);
yield backend.incrementDailyCounterFromFieldID(counterID, yesterday);
yield backend.incrementDailyCounterFromFieldID(counterID, dayBefore);
yield backend.setLastTextFromFieldID(text1ID, "hello", dayBefore);
yield backend.setLastTextFromFieldID(text2ID, "world", yesterday);
yield backend.setLastNumericFromFieldID(numeric1ID, 42, dayBefore);
yield backend.setLastNumericFromFieldID(numeric2ID, 43, yesterday);
yield backend.setDailyLastTextFromFieldID(text3ID, "foo", dayBefore);
yield backend.setDailyLastTextFromFieldID(text3ID, "bar", yesterday);
yield backend.setDailyLastTextFromFieldID(text4ID, "hello", dayBefore);
yield backend.setDailyLastTextFromFieldID(text4ID, "world", yesterday);
yield backend.setDailyLastNumericFromFieldID(numeric3ID, 40, dayBefore);
yield backend.setDailyLastNumericFromFieldID(numeric3ID, 41, yesterday);
yield backend.setDailyLastNumericFromFieldID(numeric4ID, 42, dayBefore);
yield backend.setDailyLastNumericFromFieldID(numeric4ID, 43, yesterday);
let days = yield backend.getDailyCounterCountsFromFieldID(counterID);
do_check_eq(days.size, 3);
yield backend.pruneDataBefore(yesterday);
days = yield backend.getDailyCounterCountsFromFieldID(counterID);
do_check_eq(days.size, 2);
do_check_false(days.hasDay(dayBefore));
do_check_null(yield backend.getLastTextFromFieldID(text1ID));
do_check_null(yield backend.getLastNumericFromFieldID(numeric1ID));
let result = yield backend.getLastTextFromFieldID(text2ID);
do_check_true(Array.isArray(result));
do_check_eq(result[1], "world");
result = yield backend.getLastNumericFromFieldID(numeric2ID);
do_check_true(Array.isArray(result));
do_check_eq(result[1], 43);
result = yield backend.getDailyLastNumericFromFieldID(numeric3ID);
do_check_eq(result.size, 1);
do_check_true(result.hasDay(yesterday));
result = yield backend.getDailyLastTextFromFieldID(text3ID);
do_check_eq(result.size, 1);
do_check_true(result.hasDay(yesterday));
yield backend.close();
});
add_task(function test_provider_state() {
let backend = yield Metrics.Storage("provider_state");
yield backend.registerMeasurement("foo", "bar", 1);
yield backend.setProviderState("foo", "apple", "orange");
let value = yield backend.getProviderState("foo", "apple");
do_check_eq(value, "orange");
yield backend.setProviderState("foo", "apple", "pear");
value = yield backend.getProviderState("foo", "apple");
do_check_eq(value, "pear");
yield backend.close();
});
add_task(function test_get_measurement_values() {
let backend = yield Metrics.Storage("get_measurement_values");
let mID = yield backend.registerMeasurement("foo", "bar", 1);
let id1 = yield backend.registerField(mID, "id1", backend.FIELD_DAILY_COUNTER);
let id2 = yield backend.registerField(mID, "id2", backend.FIELD_DAILY_DISCRETE_NUMERIC);
let id3 = yield backend.registerField(mID, "id3", backend.FIELD_DAILY_DISCRETE_TEXT);
let id4 = yield backend.registerField(mID, "id4", backend.FIELD_DAILY_LAST_NUMERIC);
let id5 = yield backend.registerField(mID, "id5", backend.FIELD_DAILY_LAST_TEXT);
let id6 = yield backend.registerField(mID, "id6", backend.FIELD_LAST_NUMERIC);
let id7 = yield backend.registerField(mID, "id7", backend.FIELD_LAST_TEXT);
let now = new Date();
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
yield backend.incrementDailyCounterFromFieldID(id1, now);
yield backend.addDailyDiscreteNumericFromFieldID(id2, 3, now);
yield backend.addDailyDiscreteNumericFromFieldID(id2, 4, now);
yield backend.addDailyDiscreteNumericFromFieldID(id2, 5, yesterday);
yield backend.addDailyDiscreteNumericFromFieldID(id2, 6, yesterday);
yield backend.addDailyDiscreteTextFromFieldID(id3, "1", now);
yield backend.addDailyDiscreteTextFromFieldID(id3, "2", now);
yield backend.addDailyDiscreteTextFromFieldID(id3, "3", yesterday);
yield backend.addDailyDiscreteTextFromFieldID(id3, "4", yesterday);
yield backend.setDailyLastNumericFromFieldID(id4, 1, now);
yield backend.setDailyLastNumericFromFieldID(id4, 2, yesterday);
yield backend.setDailyLastTextFromFieldID(id5, "foo", now);
yield backend.setDailyLastTextFromFieldID(id5, "bar", yesterday);
yield backend.setLastNumericFromFieldID(id6, 42, now);
yield backend.setLastTextFromFieldID(id7, "foo", now);
let fields = yield backend.getMeasurementValues(mID);
do_check_eq(Object.keys(fields).length, 2);
do_check_true("days" in fields);
do_check_true("singular" in fields);
do_check_eq(fields.days.size, 2);
do_check_true(fields.days.hasDay(now));
do_check_true(fields.days.hasDay(yesterday));
do_check_eq(fields.days.getDay(now).size, 5);
do_check_eq(fields.days.getDay(yesterday).size, 4);
do_check_eq(fields.days.getDay(now).get("id3")[0], 1);
do_check_eq(fields.days.getDay(yesterday).get("id4"), 2);
do_check_eq(fields.singular.size, 2);
do_check_eq(fields.singular.get("id6")[1], 42);
do_check_eq(fields.singular.get("id7")[1], "foo");
yield backend.close();
});

View File

@ -1,9 +0,0 @@
[DEFAULT]
head = head.js
tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_load_modules.js]
[test_metrics_provider.js]
[test_metrics_provider_manager.js]
[test_metrics_storage.js]

View File

@ -12,9 +12,6 @@ DIRS += [
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' or CONFIG['MOZ_B2GDROID']:
DIRS += ['fxaccounts']
if CONFIG['MOZ_SERVICES_METRICS']:
DIRS += ['metrics']
if CONFIG['MOZ_SERVICES_SYNC']:
DIRS += ['sync']
@ -23,5 +20,3 @@ if CONFIG['MOZ_B2G'] or CONFIG['MOZ_B2GDROID']:
if CONFIG['MOZ_SERVICES_CLOUDSYNC']:
DIRS += ['cloudsync']
SPHINX_TREES['services'] = 'docs'

View File

@ -14,7 +14,6 @@ MOZ_EXTENSIONS_DEFAULT=" gio"
MOZ_URL_CLASSIFIER=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_CRYPTO=1
MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_MEDIA_NAVIGATOR=1
MOZ_SERVICES_HEALTHREPORT=1