author | Gregory Szorc <gps@mozilla.com> |
Thu, 21 Feb 2013 14:11:54 -0800 | |
changeset 122586 | 5054f997ef77367e94a3cc8f920ba390702a81f2 |
parent 122585 | 437c955ff06d87ef8205b04c283aaa939559ab1a |
child 122587 | 6c126d076b0dfec40521a53c6f31579e3911958b |
push id | 24349 |
push user | ryanvm@gmail.com |
push date | Fri, 22 Feb 2013 17:43:12 +0000 |
treeherder | mozilla-central@e36f42046452 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | rnewman |
bugs | 841074 |
milestone | 22.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/services/healthreport/profile.jsm +++ b/services/healthreport/profile.jsm @@ -186,19 +186,19 @@ function ProfileMetadataMeasurement() { Metrics.Measurement.call(this); } ProfileMetadataMeasurement.prototype = { __proto__: Metrics.Measurement.prototype, name: DEFAULT_PROFILE_MEASUREMENT_NAME, version: 1, - configureStorage: function () { + fields: { // Profile creation date. Number of days since Unix epoch. - return this.registerStorageField("profileCreation", this.storage.FIELD_LAST_NUMERIC); + profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, }, }; /** * Turn a millisecond timestamp into a day timestamp. * * @param msec a number of milliseconds since epoch. * @return the number of whole days denoted by the input.
--- a/services/healthreport/providers.jsm +++ b/services/healthreport/providers.jsm @@ -44,76 +44,69 @@ Cu.import("resource://services-common/ut XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", "resource://gre/modules/UpdateChannel.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils", "resource://gre/modules/PlacesDBUtils.jsm"); +const LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_LAST_TEXT}; +const DAILY_DISCRETE_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC}; +const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}; +const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER}; + /** * Represents basic application state. * * This is roughly a union of nsIXULAppInfo, nsIXULRuntime, with a few extra * pieces thrown in. */ function AppInfoMeasurement() { Metrics.Measurement.call(this); } AppInfoMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "appinfo", version: 1, - LAST_TEXT_FIELDS: [ - "vendor", - "name", - "id", - "version", - "appBuildID", - "platformVersion", - "platformBuildID", - "os", - "xpcomabi", - "updateChannel", - "distributionID", - "distributionVersion", - "hotfixVersion", - "locale", - ], - - configureStorage: function () { - let self = this; - return Task.spawn(function configureStorage() { - for (let field of self.LAST_TEXT_FIELDS) { - yield self.registerStorageField(field, self.storage.FIELD_LAST_TEXT); - } - - yield self.registerStorageField("isDefaultBrowser", - self.storage.FIELD_DAILY_LAST_NUMERIC); - }); + fields: { + vendor: LAST_TEXT_FIELD, + name: LAST_TEXT_FIELD, + id: LAST_TEXT_FIELD, + version: LAST_TEXT_FIELD, + appBuildID: LAST_TEXT_FIELD, + platformVersion: LAST_TEXT_FIELD, + platformBuildID: LAST_TEXT_FIELD, + os: LAST_TEXT_FIELD, + xpcomabi: LAST_TEXT_FIELD, + updateChannel: LAST_TEXT_FIELD, + distributionID: LAST_TEXT_FIELD, + distributionVersion: LAST_TEXT_FIELD, + hotfixVersion: LAST_TEXT_FIELD, + locale: LAST_TEXT_FIELD, + isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC}, }, }); function AppVersionMeasurement() { Metrics.Measurement.call(this); } AppVersionMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "versions", version: 1, - configureStorage: function () { - return this.registerStorageField("version", - this.storage.FIELD_DAILY_DISCRETE_TEXT); + fields: { + version: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT}, }, }); this.AppInfoProvider = function AppInfoProvider() { Metrics.Provider.call(this); @@ -272,27 +265,25 @@ function SysInfoMeasurement() { } SysInfoMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "sysinfo", version: 1, - configureStorage: function () { - return Task.spawn(function configureStorage() { - yield this.registerStorageField("cpuCount", this.storage.FIELD_LAST_NUMERIC); - yield this.registerStorageField("memoryMB", this.storage.FIELD_LAST_NUMERIC); - yield this.registerStorageField("manufacturer", this.storage.FIELD_LAST_TEXT); - yield this.registerStorageField("device", this.storage.FIELD_LAST_TEXT); - yield this.registerStorageField("hardware", this.storage.FIELD_LAST_TEXT); - yield this.registerStorageField("name", this.storage.FIELD_LAST_TEXT); - yield this.registerStorageField("version", this.storage.FIELD_LAST_TEXT); - yield this.registerStorageField("architecture", this.storage.FIELD_LAST_TEXT); - }.bind(this)); + fields: { + cpuCount: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, + memoryMB: {type: Metrics.Storage.FIELD_LAST_NUMERIC}, + manufacturer: LAST_TEXT_FIELD, + device: LAST_TEXT_FIELD, + hardware: LAST_TEXT_FIELD, + name: LAST_TEXT_FIELD, + version: LAST_TEXT_FIELD, + architecture: LAST_TEXT_FIELD, }, }); this.SysInfoProvider = function SysInfoProvider() { Metrics.Provider.call(this); }; @@ -377,19 +368,18 @@ function CurrentSessionMeasurement() { } CurrentSessionMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "current", version: 3, - configureStorage: function () { - return Promise.resolve(); - }, + // Storage is in preferences. + fields: {}, /** * All data is stored in prefs, so we have a custom implementation. */ getValues: function () { let sessions = this.provider.healthReporter.sessionRecorder; let fields = new Map(); @@ -426,37 +416,29 @@ function PreviousSessionsMeasurement() { } PreviousSessionsMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "previous", version: 3, - DAILY_DISCRETE_NUMERIC_FIELDS: [ + fields: { // Milliseconds of sessions that were properly shut down. - "cleanActiveTicks", - "cleanTotalTime", + cleanActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD, + cleanTotalTime: DAILY_DISCRETE_NUMERIC_FIELD, // Milliseconds of sessions that were not properly shut down. - "abortedActiveTicks", - "abortedTotalTime", + abortedActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD, + abortedTotalTime: DAILY_DISCRETE_NUMERIC_FIELD, // Startup times in milliseconds. - "main", - "firstPaint", - "sessionRestored", - ], - - configureStorage: function () { - return Task.spawn(function configureStorage() { - for (let field of this.DAILY_DISCRETE_NUMERIC_FIELDS) { - yield this.registerStorageField(field, this.storage.FIELD_DAILY_DISCRETE_NUMERIC); - } - }.bind(this)); + main: DAILY_DISCRETE_NUMERIC_FIELD, + firstPaint: DAILY_DISCRETE_NUMERIC_FIELD, + sessionRestored: DAILY_DISCRETE_NUMERIC_FIELD, }, }); /** * Records information about the current browser session. * * A browser session is defined as an application/process lifetime. We @@ -535,18 +517,18 @@ function ActiveAddonsMeasurement() { } ActiveAddonsMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "active", version: 1, - configureStorage: function () { - return this.registerStorageField("addons", this.storage.FIELD_LAST_TEXT); + fields: { + addons: LAST_TEXT_FIELD, }, _serializeJSONSingular: function (data) { if (!data.has("addons")) { this._log.warn("Don't have active addons info. Weird."); return null; } @@ -563,23 +545,21 @@ function AddonCountsMeasurement() { } AddonCountsMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "counts", version: 1, - configureStorage: function () { - return Task.spawn(function registerFields() { - yield this.registerStorageField("theme", this.storage.FIELD_DAILY_LAST_NUMERIC); - yield this.registerStorageField("lwtheme", this.storage.FIELD_DAILY_LAST_NUMERIC); - yield this.registerStorageField("plugin", this.storage.FIELD_DAILY_LAST_NUMERIC); - yield this.registerStorageField("extension", this.storage.FIELD_DAILY_LAST_NUMERIC); - }.bind(this)); + fields: { + theme: DAILY_LAST_NUMERIC_FIELD, + lwtheme: DAILY_LAST_NUMERIC_FIELD, + plugin: DAILY_LAST_NUMERIC_FIELD, + extension: DAILY_LAST_NUMERIC_FIELD, }, }); this.AddonsProvider = function () { Metrics.Provider.call(this); this._prefs = new Preferences({defaultBranch: null}); @@ -735,19 +715,19 @@ function DailyCrashesMeasurement() { } DailyCrashesMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "crashes", version: 1, - configureStorage: function () { - this.registerStorageField("pending", this.storage.FIELD_DAILY_COUNTER); - this.registerStorageField("submitted", this.storage.FIELD_DAILY_COUNTER); + fields: { + pending: DAILY_COUNTER_FIELD, + submitted: DAILY_COUNTER_FIELD, }, }); this.CrashesProvider = function () { Metrics.Provider.call(this); }; CrashesProvider.prototype = Object.freeze({ @@ -902,21 +882,19 @@ function PlacesMeasurement() { } PlacesMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "places", version: 1, - configureStorage: function () { - return Task.spawn(function registerFields() { - yield this.registerStorageField("pages", this.storage.FIELD_DAILY_LAST_NUMERIC); - yield this.registerStorageField("bookmarks", this.storage.FIELD_DAILY_LAST_NUMERIC); - }.bind(this)); + fields: { + pages: DAILY_LAST_NUMERIC_FIELD, + bookmarks: DAILY_LAST_NUMERIC_FIELD, }, }); /** * Collects information about Places. */ this.PlacesProvider = function () { @@ -964,50 +942,56 @@ function SearchCountMeasurement() { } SearchCountMeasurement.prototype = Object.freeze({ __proto__: Metrics.Measurement.prototype, name: "counts", version: 1, + // We only record searches for search engines that have partner agreements + // with Mozilla. + fields: { + "amazon.com.abouthome": DAILY_COUNTER_FIELD, + "amazon.com.contextmenu": DAILY_COUNTER_FIELD, + "amazon.com.searchbar": DAILY_COUNTER_FIELD, + "amazon.com.urlbar": DAILY_COUNTER_FIELD, + "bing.abouthome": DAILY_COUNTER_FIELD, + "bing.contextmenu": DAILY_COUNTER_FIELD, + "bing.searchbar": DAILY_COUNTER_FIELD, + "bing.urlbar": DAILY_COUNTER_FIELD, + "google.abouthome": DAILY_COUNTER_FIELD, + "google.contextmenu": DAILY_COUNTER_FIELD, + "google.searchbar": DAILY_COUNTER_FIELD, + "google.urlbar": DAILY_COUNTER_FIELD, + "yahoo.abouthome": DAILY_COUNTER_FIELD, + "yahoo.contextmenu": DAILY_COUNTER_FIELD, + "yahoo.searchbar": DAILY_COUNTER_FIELD, + "yahoo.urlbar": DAILY_COUNTER_FIELD, + "other.abouthome": DAILY_COUNTER_FIELD, + "other.contextmenu": DAILY_COUNTER_FIELD, + "other.searchbar": DAILY_COUNTER_FIELD, + "other.urlbar": DAILY_COUNTER_FIELD, + }, + // If an engine is removed from this list, it may not be reported any more. // Verify side-effects are sane before removing an entry. PARTNER_ENGINES: [ "amazon.com", "bing", "google", "yahoo", ], SOURCES: [ "abouthome", "contextmenu", "searchbar", "urlbar", ], - - configureStorage: function () { - // We only record searches for search engines that have partner - // agreements with Mozilla. - let engines = this.PARTNER_ENGINES.concat("other"); - - let promise; - - // While this creates a large number of fields, storage is sparse and there - // will be no overhead for fields that aren't used in a given day. - for (let engine of engines) { - for (let source of this.SOURCES) { - promise = this.registerStorageField(engine + "." + source, - this.storage.FIELD_DAILY_COUNTER); - } - } - - return promise; - }, }); this.SearchesProvider = function () { Metrics.Provider.call(this); }; this.SearchesProvider.prototype = Object.freeze({ __proto__: Metrics.Provider.prototype,
--- a/services/metrics/dataprovider.jsm +++ b/services/metrics/dataprovider.jsm @@ -23,36 +23,43 @@ Cu.import("resource://services-common/lo Cu.import("resource://services-common/preferences.js"); Cu.import("resource://services-common/utils.js"); /** * Represents a collection of related pieces/fields of data. * - * This is an abstract base type. Providers implement child types that - * implement core functions such as `registerStorage`. + * This is an abstract base type. * * This type provides the primary interface for storing, retrieving, and * serializing data. * - * Each derived type must define a `name` and `version` property. These must be - * a string name and integer version, respectively. The `name` is used to - * identify the measurement within a `Provider`. The version is to denote the - * behavior of the `Measurement` and the composition of its fields over time. - * When a new field is added or the behavior of an existing field changes - * (perhaps the method for storing it has changed), the version should be - * incremented. - * * 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. * - * For fields backed by the SQLite metrics storage backend, fields must have a - * strongly defined type. Valid types include daily counters, daily discrete - * text values, etc. See `MetricsStorageSqliteBackend.FIELD_*`. + * 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."); } @@ -60,48 +67,47 @@ this.Measurement = function () { 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 = Log4Moz.repository.getLogger("Services.Metrics.Measurement." + this.name); this.id = null; this.storage = null; - this._fieldsByName = new Map(); + 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", /** - * Configures the storage backend so that it can store this measurement. - * - * Implementations must return a promise which is resolved when storage has - * been configured. - * - * Most implementations will typically call into this.registerStorageField() - * to configure fields in storage. - * - * FUTURE: Provide method for upgrading from older measurement versions. - */ - configureStorage: function () { - throw new Error("configureStorage() must be implemented."); - }, - - /** * 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 @@ -139,78 +145,56 @@ Measurement.prototype = Object.freeze({ * Whether this measurement contains the named field. * * @param name * (string) Name of field. * * @return bool */ hasField: function (name) { - return this._fieldsByName.has(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._fieldsByName.get(name); + let entry = this._fields[name]; if (!entry) { throw new Error("Unknown field: " + name); } return entry[0]; }, fieldType: function (name) { - let entry = this._fieldsByName.get(name); + let entry = this._fields[name]; if (!entry) { throw new Error("Unknown field: " + name); } return entry[1]; }, - /** - * Register a named field with storage that's attached to this measurement. - * - * This is typically called during `configureStorage`. The `Measurement` - * implementation passes the field name and its type (one of the - * storage.FIELD_* constants). The storage backend then allocates space - * for this named field. A side-effect of calling this is that the field's - * storage ID is stored in this._fieldsByName and subsequent calls to the - * storage modifiers below will know how to reference this field in the - * storage backend. - * - * @param name - * (string) The name of the field being registered. - * @param type - * (string) A field type name. This is typically one of the - * storage.FIELD_* constants. It could also be a custom type - * (presumably registered by this measurement or provider). - */ - registerStorageField: function (name, type) { - this._log.debug("Registering field: " + name + " " + type); + _configureStorage: function () { + return Task.spawn(function configureFields() { + for (let [name, info] in Iterator(this.fields)) { + this._log.debug("Registering field: " + name + " " + info.type); - let deferred = Promise.defer(); - - let self = this; - this.storage.registerField(this.id, name, type).then( - function onSuccess(id) { - self._fieldsByName.set(name, [id, type]); - deferred.resolve(); - }, deferred.reject); - - return deferred.promise; + let id = yield this.storage.registerField(this.id, name, info.type); + this._fields[name] = [id, info.type]; + } + }.bind(this)); }, //--------------------------------------------------------------------------- // Data Recording Functions // // Functions in this section are used to record new values against this // measurement instance. // @@ -347,17 +331,17 @@ Measurement.prototype = Object.freeze({ return this.storage.deleteLastTextFromFieldID(this.fieldID(field)); }, _serializeJSONSingular: function (data) { let result = {"_v": this.version}; for (let [field, data] of data) { // There could be legacy fields in storage we no longer care about. - if (!this._fieldsByName.has(field)) { + if (!(field in this._fields)) { continue; } let type = this.fieldType(field); switch (type) { case this.storage.FIELD_LAST_NUMERIC: case this.storage.FIELD_LAST_TEXT: @@ -378,17 +362,17 @@ Measurement.prototype = Object.freeze({ return result; }, _serializeJSONDay: function (data) { let result = {"_v": this.version}; for (let [field, data] of data) { - if (!this._fieldsByName.has(field)) { + if (!(field in this._fields)) { continue; } let type = this.fieldType(field); switch (type) { case this.storage.FIELD_DAILY_COUNTER: case this.storage.FIELD_DAILY_DISCRETE_NUMERIC: @@ -561,17 +545,17 @@ Provider.prototype = Object.freeze({ measurement.provider = self; measurement.storage = self.storage; let id = yield storage.registerMeasurement(self.name, measurement.name, measurement.version); measurement.id = id; - yield measurement.configureStorage(); + yield measurement._configureStorage(); self.measurements.set([measurement.name, measurement.version].join(":"), measurement); } let promise = self.onInit(); if (!promise || typeof(promise.then) != "function") {
--- a/services/metrics/modules-testing/mocks.jsm +++ b/services/metrics/modules-testing/mocks.jsm @@ -22,27 +22,24 @@ this.DummyMeasurement = function DummyMe Metrics.Measurement.call(this); } DummyMeasurement.prototype = { __proto__: Metrics.Measurement.prototype, version: 1, - configureStorage: function () { - let self = this; - return Task.spawn(function configureStorage() { - yield self.registerStorageField("daily-counter", self.storage.FIELD_DAILY_COUNTER); - yield self.registerStorageField("daily-discrete-numeric", self.storage.FIELD_DAILY_DISCRETE_NUMERIC); - yield self.registerStorageField("daily-discrete-text", self.storage.FIELD_DAILY_DISCRETE_TEXT); - yield self.registerStorageField("daily-last-numeric", self.storage.FIELD_DAILY_LAST_NUMERIC); - yield self.registerStorageField("daily-last-text", self.storage.FIELD_DAILY_LAST_TEXT); - yield self.registerStorageField("last-numeric", self.storage.FIELD_LAST_NUMERIC); - yield self.registerStorageField("last-text", self.storage.FIELD_LAST_TEXT); - }); + 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") { this.name = name; this.measurementTypes = [DummyMeasurement];
--- a/services/metrics/storage.jsm +++ b/services/metrics/storage.jsm @@ -1066,36 +1066,34 @@ MetricsStorageSqliteBackend.prototype = if (valueType != existingType) { throw new Error("Field already defined with different type: " + existingType); } return Promise.resolve(this.fieldIDFromMeasurement(measurementID, field)); } let self = this; - return this.enqueueOperation(function addFieldOperation() { - return Task.spawn(function createField() { - let params = { - measurement_id: measurementID, - field: field, - value_type: typeID, - }; + return Task.spawn(function createField() { + let params = { + measurement_id: measurementID, + field: field, + value_type: typeID, + }; - yield self._connection.executeCached(SQL.addField, params); + yield self._connection.executeCached(SQL.addField, params); - let rows = yield self._connection.executeCached(SQL.getFieldID, params); + let rows = yield self._connection.executeCached(SQL.getFieldID, params); - let fieldID = rows[0].getResultByIndex(0); + let fieldID = rows[0].getResultByIndex(0); - self._fieldsByID.set(fieldID, [measurementID, field, valueType]); - self._fieldsByInfo.set([measurementID, field].join(":"), fieldID); - self._fieldsByMeasurement.get(measurementID).add(fieldID); + self._fieldsByID.set(fieldID, [measurementID, field, valueType]); + self._fieldsByInfo.set([measurementID, field].join(":"), fieldID); + self._fieldsByMeasurement.get(measurementID).add(fieldID); - throw new Task.Result(fieldID); - }); + throw new Task.Result(fieldID); }); }, /** * Initializes this instance with the database. * * This performs 2 major roles: * @@ -2054,8 +2052,13 @@ MetricsStorageSqliteBackend.prototype = }, function onError(error) { deferred.reject(error); }); return deferred.promise; }, }); +// Alias built-in field types to public API. +for (let property of MetricsStorageSqliteBackend.prototype._BUILTIN_TYPES) { + this.MetricsStorageBackend[property] = MetricsStorageSqliteBackend.prototype[property]; +} +
--- a/services/metrics/tests/xpcshell/test_metrics_provider.js +++ b/services/metrics/tests/xpcshell/test_metrics_provider.js @@ -49,17 +49,17 @@ 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(m._fieldsByName.size, 7); + 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_test(function test_prefs_integration() { let branch = "testing.prefs_integration.";