Bumping gaia.json for 10 gaia revision(s) a=gaia-bump
[gecko.git] / services / metrics / dataprovider.jsm
blob9b8a8466f144900d5de7f90c3ec2711f7dcf2f7b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #ifndef MERGED_COMPARTMENT
7 "use strict";
9 this.EXPORTED_SYMBOLS = [
10   "Measurement",
11   "Provider",
14 const {utils: Cu} = Components;
16 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
18 #endif
20 Cu.import("resource://gre/modules/Promise.jsm");
21 Cu.import("resource://gre/modules/Preferences.jsm");
22 Cu.import("resource://gre/modules/Task.jsm");
23 Cu.import("resource://gre/modules/Log.jsm");
24 Cu.import("resource://services-common/utils.js");
28 /**
29  * Represents a collection of related pieces/fields of data.
30  *
31  * This is an abstract base type.
32  *
33  * This type provides the primary interface for storing, retrieving, and
34  * serializing data.
35  *
36  * Each measurement consists of a set of named fields. Each field is primarily
37  * identified by a string name, which must be unique within the measurement.
38  *
39  * Each derived type must define the following properties:
40  *
41  *   name -- String name of this measurement. This is the primary way
42  *     measurements are distinguished within a provider.
43  *
44  *   version -- Integer version of this measurement. This is a secondary
45  *     identifier for a measurement within a provider. The version denotes
46  *     the behavior of this measurement and the composition of its fields over
47  *     time. When a new field is added or the behavior of an existing field
48  *     changes, the version should be incremented. The initial version of a
49  *     measurement is typically 1.
50  *
51  *   fields -- Object defining the fields this measurement holds. Keys in the
52  *     object are string field names. Values are objects describing how the
53  *     field works. The following properties are recognized:
54  *
55  *       type -- The string type of this field. This is typically one of the
56  *         FIELD_* constants from the Metrics.Storage type.
57  *
58  *
59  * FUTURE: provide hook points for measurements to supplement with custom
60  * storage needs.
61  */
62 this.Measurement = function () {
63   if (!this.name) {
64     throw new Error("Measurement must have a name.");
65   }
67   if (!this.version) {
68     throw new Error("Measurement must have a version.");
69   }
71   if (!Number.isInteger(this.version)) {
72     throw new Error("Measurement's version must be an integer: " + this.version);
73   }
75   if (!this.fields) {
76     throw new Error("Measurement must define fields.");
77   }
79   for (let [name, info] in Iterator(this.fields)) {
80     if (!info) {
81       throw new Error("Field does not contain metadata: " + name);
82     }
84     if (!info.type) {
85       throw new Error("Field is missing required type property: " + name);
86     }
87   }
89   this._log = Log.repository.getLogger("Services.Metrics.Measurement." + this.name);
91   this.id = null;
92   this.storage = null;
93   this._fields = {};
95   this._serializers = {};
96   this._serializers[this.SERIALIZE_JSON] = {
97     singular: this._serializeJSONSingular.bind(this),
98     daily: this._serializeJSONDay.bind(this),
99   };
102 Measurement.prototype = Object.freeze({
103   SERIALIZE_JSON: "json",
105   /**
106    * Obtain a serializer for this measurement.
107    *
108    * Implementations should return an object with the following keys:
109    *
110    *   singular -- Serializer for singular data.
111    *   daily -- Serializer for daily data.
112    *
113    * Each item is a function that takes a single argument: the data to
114    * serialize. The passed data is a subset of that returned from
115    * this.getValues(). For "singular," data.singular is passed. For "daily",
116    * data.days.get(<day>) is passed.
117    *
118    * This function receives a single argument: the serialization format we
119    * are requesting. This is one of the SERIALIZE_* constants on this base type.
120    *
121    * For SERIALIZE_JSON, the function should return an object that
122    * JSON.stringify() knows how to handle. This could be an anonymous object or
123    * array or any object with a property named `toJSON` whose value is a
124    * function. The returned object will be added to a larger document
125    * containing the results of all `serialize` calls.
126    *
127    * The default implementation knows how to serialize built-in types using
128    * very simple logic. If small encoding size is a goal, the default
129    * implementation may not be suitable. If an unknown field type is
130    * encountered, the default implementation will error.
131    *
132    * @param format
133    *        (string) A SERIALIZE_* constant defining what serialization format
134    *        to use.
135    */
136   serializer: function (format) {
137     if (!(format in this._serializers)) {
138       throw new Error("Don't know how to serialize format: " + format);
139     }
141     return this._serializers[format];
142   },
144   /**
145    * Whether this measurement contains the named field.
146    *
147    * @param name
148    *        (string) Name of field.
149    *
150    * @return bool
151    */
152   hasField: function (name) {
153     return name in this.fields;
154   },
156   /**
157    * The unique identifier for a named field.
158    *
159    * This will throw if the field is not known.
160    *
161    * @param name
162    *        (string) Name of field.
163    */
164   fieldID: function (name) {
165     let entry = this._fields[name];
167     if (!entry) {
168       throw new Error("Unknown field: " + name);
169     }
171     return entry[0];
172   },
174   fieldType: function (name) {
175     let entry = this._fields[name];
177     if (!entry) {
178       throw new Error("Unknown field: " + name);
179     }
181     return entry[1];
182   },
184   _configureStorage: function () {
185     let missing = [];
186     for (let [name, info] in Iterator(this.fields)) {
187       if (this.storage.hasFieldFromMeasurement(this.id, name)) {
188         this._fields[name] =
189           [this.storage.fieldIDFromMeasurement(this.id, name), info.type];
190         continue;
191       }
193       missing.push([name, info.type]);
194     }
196     if (!missing.length) {
197       return CommonUtils.laterTickResolvingPromise();
198     }
200     // We only perform a transaction if we have work to do (to avoid
201     // extra SQLite overhead).
202     return this.storage.enqueueTransaction(function registerFields() {
203       for (let [name, type] of missing) {
204         this._log.debug("Registering field: " + name + " " + type);
205         let id = yield this.storage.registerField(this.id, name, type);
206         this._fields[name] = [id, type];
207       }
208     }.bind(this));
209   },
211   //---------------------------------------------------------------------------
212   // Data Recording Functions
213   //
214   // Functions in this section are used to record new values against this
215   // measurement instance.
216   //
217   // Generally speaking, these functions will throw if the specified field does
218   // not exist or if the storage function requested is not appropriate for the
219   // type of that field. These functions will also return a promise that will
220   // be resolved when the underlying storage operation has completed.
221   //---------------------------------------------------------------------------
223   /**
224    * Increment a daily counter field in this measurement by 1.
225    *
226    * By default, the counter for the current day will be incremented.
227    *
228    * If the field is not known or is not a daily counter, this will throw.
229    *
230    *
231    *
232    * @param field
233    *        (string) The name of the field whose value to increment.
234    * @param date
235    *        (Date) Day on which to increment the counter.
236    * @param by
237    *        (integer) How much to increment by.
238    * @return Promise<>
239    */
240   incrementDailyCounter: function (field, date=new Date(), by=1) {
241     return this.storage.incrementDailyCounterFromFieldID(this.fieldID(field),
242                                                          date, by);
243   },
245   /**
246    * Record a new numeric value for a daily discrete numeric field.
247    *
248    * @param field
249    *        (string) The name of the field to append a value to.
250    * @param value
251    *        (Number) Number to append.
252    * @param date
253    *        (Date) Day on which to append the value.
254    *
255    * @return Promise<>
256    */
257   addDailyDiscreteNumeric: function (field, value, date=new Date()) {
258     return this.storage.addDailyDiscreteNumericFromFieldID(
259                           this.fieldID(field), value, date);
260   },
262   /**
263    * Record a new text value for a daily discrete text field.
264    *
265    * This is like `addDailyDiscreteNumeric` but for daily discrete text fields.
266    */
267   addDailyDiscreteText: function (field, value, date=new Date()) {
268     return this.storage.addDailyDiscreteTextFromFieldID(
269                           this.fieldID(field), value, date);
270   },
272   /**
273    * Record the last seen value for a last numeric field.
274    *
275    * @param field
276    *        (string) The name of the field to set the value of.
277    * @param value
278    *        (Number) The value to set.
279    * @param date
280    *        (Date) When this value was recorded.
281    *
282    * @return Promise<>
283    */
284   setLastNumeric: function (field, value, date=new Date()) {
285     return this.storage.setLastNumericFromFieldID(this.fieldID(field), value,
286                                                   date);
287   },
289   /**
290    * Record the last seen value for a last text field.
291    *
292    * This is like `setLastNumeric` except for last text fields.
293    */
294   setLastText: function (field, value, date=new Date()) {
295     return this.storage.setLastTextFromFieldID(this.fieldID(field), value,
296                                                date);
297   },
299   /**
300    * Record the most recent value for a daily last numeric field.
301    *
302    * @param field
303    *        (string) The name of a daily last numeric field.
304    * @param value
305    *        (Number) The value to set.
306    * @param date
307    *        (Date) Day on which to record the last value.
308    *
309    * @return Promise<>
310    */
311   setDailyLastNumeric: function (field, value, date=new Date()) {
312     return this.storage.setDailyLastNumericFromFieldID(this.fieldID(field),
313                                                        value, date);
314   },
316   /**
317    * Record the most recent value for a daily last text field.
318    *
319    * This is like `setDailyLastNumeric` except for a daily last text field.
320    */
321   setDailyLastText: function (field, value, date=new Date()) {
322     return this.storage.setDailyLastTextFromFieldID(this.fieldID(field),
323                                                     value, date);
324   },
326   //---------------------------------------------------------------------------
327   // End of data recording APIs.
328   //---------------------------------------------------------------------------
330   /**
331    * Obtain all values stored for this measurement.
332    *
333    * The default implementation obtains all known types from storage. If the
334    * measurement provides custom types or stores values somewhere other than
335    * storage, it should define its own implementation.
336    *
337    * This returns a promise that resolves to a data structure which is
338    * understood by the measurement's serialize() function.
339    */
340   getValues: function () {
341     return this.storage.getMeasurementValues(this.id);
342   },
344   deleteLastNumeric: function (field) {
345     return this.storage.deleteLastNumericFromFieldID(this.fieldID(field));
346   },
348   deleteLastText: function (field) {
349     return this.storage.deleteLastTextFromFieldID(this.fieldID(field));
350   },
352   /**
353    * This method is used by the default serializers to control whether a field
354    * is included in the output.
355    *
356    * There could be legacy fields in storage we no longer care about.
357    *
358    * This method is a hook to allow measurements to change this behavior, e.g.,
359    * to implement a dynamic fieldset.
360    *
361    * You will also need to override `fieldType`.
362    *
363    * @return (boolean) true if the specified field should be included in
364    *                   payload output.
365    */
366   shouldIncludeField: function (field) {
367     return field in this._fields;
368   },
370   _serializeJSONSingular: function (data) {
371     let result = {"_v": this.version};
373     for (let [field, data] of data) {
374       // There could be legacy fields in storage we no longer care about.
375       if (!this.shouldIncludeField(field)) {
376         continue;
377       }
379       let type = this.fieldType(field);
381       switch (type) {
382         case this.storage.FIELD_LAST_NUMERIC:
383         case this.storage.FIELD_LAST_TEXT:
384           result[field] = data[1];
385           break;
387         case this.storage.FIELD_DAILY_COUNTER:
388         case this.storage.FIELD_DAILY_DISCRETE_NUMERIC:
389         case this.storage.FIELD_DAILY_DISCRETE_TEXT:
390         case this.storage.FIELD_DAILY_LAST_NUMERIC:
391         case this.storage.FIELD_DAILY_LAST_TEXT:
392           continue;
394         default:
395           throw new Error("Unknown field type: " + type);
396       }
397     }
399     return result;
400   },
402   _serializeJSONDay: function (data) {
403     let result = {"_v": this.version};
405     for (let [field, data] of data) {
406       if (!this.shouldIncludeField(field)) {
407         continue;
408       }
410       let type = this.fieldType(field);
412       switch (type) {
413         case this.storage.FIELD_DAILY_COUNTER:
414         case this.storage.FIELD_DAILY_DISCRETE_NUMERIC:
415         case this.storage.FIELD_DAILY_DISCRETE_TEXT:
416         case this.storage.FIELD_DAILY_LAST_NUMERIC:
417         case this.storage.FIELD_DAILY_LAST_TEXT:
418           result[field] = data;
419           break;
421         case this.storage.FIELD_LAST_NUMERIC:
422         case this.storage.FIELD_LAST_TEXT:
423           continue;
425         default:
426           throw new Error("Unknown field type: " + type);
427       }
428     }
430     return result;
431   },
436  * An entity that emits data.
438  * A `Provider` consists of a string name (must be globally unique among all
439  * known providers) and a set of `Measurement` instances.
441  * The main role of a `Provider` is to produce metrics data and to store said
442  * data in the storage backend.
444  * Metrics data collection is initiated either by a manager calling a
445  * `collect*` function on `Provider` instances or by the `Provider` registering
446  * to some external event and then reacting whenever they occur.
448  * `Provider` implementations interface directly with a storage backend. For
449  * common stored values (daily counters, daily discrete values, etc),
450  * implementations should interface with storage via the various helper
451  * functions on the `Measurement` instances. For custom stored value types,
452  * implementations will interact directly with the low-level storage APIs.
454  * Because multiple providers exist and could be responding to separate
455  * external events simultaneously and because not all operations performed by
456  * storage can safely be performed in parallel, writing directly to storage at
457  * event time is dangerous. Therefore, interactions with storage must be
458  * deferred until it is safe to perform them.
460  * This typically looks something like:
462  *   // This gets called when an external event worthy of recording metrics
463  *   // occurs. The function receives a numeric value associated with the event.
464  *   function onExternalEvent (value) {
465  *     let now = new Date();
466  *     let m = this.getMeasurement("foo", 1);
468  *     this.enqueueStorageOperation(function storeExternalEvent() {
470  *       // We interface with storage via the `Measurement` helper functions.
471  *       // These each return a promise that will be resolved when the
472  *       // operation finishes. We rely on behavior of storage where operations
473  *       // are executed single threaded and sequentially. Therefore, we only
474  *       // need to return the final promise.
475  *       m.incrementDailyCounter("foo", now);
476  *       return m.addDailyDiscreteNumericValue("my_value", value, now);
477  *     }.bind(this));
479  *   }
482  * `Provider` is an abstract base class. Implementations must define a few
483  * properties:
485  *   name
486  *     The `name` property should be a string defining the provider's name. The
487  *     name must be globally unique for the application. The name is used as an
488  *     identifier to distinguish providers from each other.
490  *   measurementTypes
491  *     This must be an array of `Measurement`-derived types. Note that elements
492  *     in the array are the type functions, not instances. Instances of the
493  *     `Measurement` are created at run-time by the `Provider` and are bound
494  *     to the provider and to a specific storage backend.
495  */
496 this.Provider = function () {
497   if (!this.name) {
498     throw new Error("Provider must define a name.");
499   }
501   if (!Array.isArray(this.measurementTypes)) {
502     throw new Error("Provider must define measurement types.");
503   }
505   this._log = Log.repository.getLogger("Services.Metrics.Provider." + this.name);
507   this.measurements = null;
508   this.storage = null;
511 Provider.prototype = Object.freeze({
512   /**
513    * Whether the provider only pulls data from other sources.
514    *
515    * If this is true, the provider pulls data from other sources. By contrast,
516    * "push-based" providers subscribe to foreign sources and record/react to
517    * external events as they happen.
518    *
519    * Pull-only providers likely aren't instantiated until a data collection
520    * is performed. Thus, implementations cannot rely on a provider instance
521    * always being alive. This is an optimization so provider instances aren't
522    * dead weight while the application is running.
523    *
524    * This must be set on the prototype to have an effect.
525    */
526   pullOnly: false,
528   /**
529    * Obtain a `Measurement` from its name and version.
530    *
531    * If the measurement is not found, an Error is thrown.
532    */
533   getMeasurement: function (name, version) {
534     if (!Number.isInteger(version)) {
535       throw new Error("getMeasurement expects an integer version. Got: " + version);
536     }
538     let m = this.measurements.get([name, version].join(":"));
540     if (!m) {
541       throw new Error("Unknown measurement: " + name + " v" + version);
542     }
544     return m;
545   },
547   init: function (storage) {
548     if (this.storage !== null) {
549       throw new Error("Provider() not called. Did the sub-type forget to call it?");
550     }
552     if (this.storage) {
553       throw new Error("Provider has already been initialized.");
554     }
556     this.measurements = new Map();
557     this.storage = storage;
559     let self = this;
560     return Task.spawn(function init() {
561       let pre = self.preInit();
562       if (!pre || typeof(pre.then) != "function") {
563         throw new Error("preInit() does not return a promise.");
564       }
565       yield pre;
567       for (let measurementType of self.measurementTypes) {
568         let measurement = new measurementType();
570         measurement.provider = self;
571         measurement.storage = self.storage;
573         let id = yield storage.registerMeasurement(self.name, measurement.name,
574                                                    measurement.version);
576         measurement.id = id;
578         yield measurement._configureStorage();
580         self.measurements.set([measurement.name, measurement.version].join(":"),
581                               measurement);
582       }
584       let post = self.postInit();
585       if (!post || typeof(post.then) != "function") {
586         throw new Error("postInit() does not return a promise.");
587       }
588       yield post;
589     });
590   },
592   shutdown: function () {
593     let promise = this.onShutdown();
595     if (!promise || typeof(promise.then) != "function") {
596       throw new Error("onShutdown implementation does not return a promise.");
597     }
599     return promise;
600   },
602   /**
603    * Hook point for implementations to perform pre-initialization activity.
604    *
605    * This method will be called before measurement registration.
606    *
607    * Implementations should return a promise which is resolved when
608    * initialization activities have completed.
609    */
610   preInit: function () {
611     return CommonUtils.laterTickResolvingPromise();
612   },
614   /**
615    * Hook point for implementations to perform post-initialization activity.
616    *
617    * This method will be called after `preInit` and measurement registration,
618    * but before initialization is finished.
619    *
620    * If a `Provider` instance needs to register observers, etc, it should
621    * implement this function.
622    *
623    * Implementations should return a promise which is resolved when
624    * initialization activities have completed.
625    */
626   postInit: function () {
627     return CommonUtils.laterTickResolvingPromise();
628   },
630   /**
631    * Hook point for shutdown of instances.
632    *
633    * This is the opposite of `onInit`. If a `Provider` needs to unregister
634    * observers, etc, this is where it should do it.
635    *
636    * Implementations should return a promise which is resolved when
637    * shutdown activities have completed.
638    */
639   onShutdown: function () {
640     return CommonUtils.laterTickResolvingPromise();
641   },
643   /**
644    * Collects data that doesn't change during the application's lifetime.
645    *
646    * Implementations should return a promise that resolves when all data has
647    * been collected and storage operations have been finished.
648    *
649    * @return Promise<>
650    */
651   collectConstantData: function () {
652     return CommonUtils.laterTickResolvingPromise();
653   },
655   /**
656    * Collects data approximately every day.
657    *
658    * For long-running applications, this is called approximately every day.
659    * It may or may not be called every time the application is run. It also may
660    * be called more than once per day.
661    *
662    * Implementations should return a promise that resolves when all data has
663    * been collected and storage operations have completed.
664    *
665    * @return Promise<>
666    */
667   collectDailyData: function () {
668     return CommonUtils.laterTickResolvingPromise();
669   },
671   /**
672    * Queue a deferred storage operation.
673    *
674    * Deferred storage operations are the preferred method for providers to
675    * interact with storage. When collected data is to be added to storage,
676    * the provider creates a function that performs the necessary storage
677    * interactions and then passes that function to this function. Pending
678    * storage operations will be executed sequentially by a coordinator.
679    *
680    * The passed function should return a promise which will be resolved upon
681    * completion of storage interaction.
682    */
683   enqueueStorageOperation: function (func) {
684     return this.storage.enqueueOperation(func);
685   },
687   /**
688    * Obtain persisted provider state.
689    *
690    * Provider state consists of key-value pairs of string names and values.
691    * Providers can stuff whatever they want into state. They are encouraged to
692    * store as little as possible for performance reasons.
693    *
694    * State is backed by storage and is robust.
695    *
696    * These functions do not enqueue on storage automatically, so they should
697    * be guarded by `enqueueStorageOperation` or some other mutex.
698    *
699    * @param key
700    *        (string) The property to retrieve.
701    *
702    * @return Promise<string|null> String value on success. null if no state
703    *         is available under this key.
704    */
705   getState: function (key) {
706     return this.storage.getProviderState(this.name, key);
707   },
709   /**
710    * Set state for this provider.
711    *
712    * This is the complementary API for `getState` and obeys the same
713    * storage restrictions.
714    */
715   setState: function (key, value) {
716     return this.storage.setProviderState(this.name, key, value);
717   },
719   _dateToDays: function (date) {
720     return Math.floor(date.getTime() / MILLISECONDS_PER_DAY);
721   },
723   _daysToDate: function (days) {
724     return new Date(days * MILLISECONDS_PER_DAY);
725   },