1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/metrics/dataprovider.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,727 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +#ifndef MERGED_COMPARTMENT 1.11 + 1.12 +this.EXPORTED_SYMBOLS = [ 1.13 + "Measurement", 1.14 + "Provider", 1.15 +]; 1.16 + 1.17 +const {utils: Cu} = Components; 1.18 + 1.19 +const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; 1.20 + 1.21 +#endif 1.22 + 1.23 +Cu.import("resource://gre/modules/Promise.jsm"); 1.24 +Cu.import("resource://gre/modules/Preferences.jsm"); 1.25 +Cu.import("resource://gre/modules/Task.jsm"); 1.26 +Cu.import("resource://gre/modules/Log.jsm"); 1.27 +Cu.import("resource://services-common/utils.js"); 1.28 + 1.29 + 1.30 + 1.31 +/** 1.32 + * Represents a collection of related pieces/fields of data. 1.33 + * 1.34 + * This is an abstract base type. 1.35 + * 1.36 + * This type provides the primary interface for storing, retrieving, and 1.37 + * serializing data. 1.38 + * 1.39 + * Each measurement consists of a set of named fields. Each field is primarily 1.40 + * identified by a string name, which must be unique within the measurement. 1.41 + * 1.42 + * Each derived type must define the following properties: 1.43 + * 1.44 + * name -- String name of this measurement. This is the primary way 1.45 + * measurements are distinguished within a provider. 1.46 + * 1.47 + * version -- Integer version of this measurement. This is a secondary 1.48 + * identifier for a measurement within a provider. The version denotes 1.49 + * the behavior of this measurement and the composition of its fields over 1.50 + * time. When a new field is added or the behavior of an existing field 1.51 + * changes, the version should be incremented. The initial version of a 1.52 + * measurement is typically 1. 1.53 + * 1.54 + * fields -- Object defining the fields this measurement holds. Keys in the 1.55 + * object are string field names. Values are objects describing how the 1.56 + * field works. The following properties are recognized: 1.57 + * 1.58 + * type -- The string type of this field. This is typically one of the 1.59 + * FIELD_* constants from the Metrics.Storage type. 1.60 + * 1.61 + * 1.62 + * FUTURE: provide hook points for measurements to supplement with custom 1.63 + * storage needs. 1.64 + */ 1.65 +this.Measurement = function () { 1.66 + if (!this.name) { 1.67 + throw new Error("Measurement must have a name."); 1.68 + } 1.69 + 1.70 + if (!this.version) { 1.71 + throw new Error("Measurement must have a version."); 1.72 + } 1.73 + 1.74 + if (!Number.isInteger(this.version)) { 1.75 + throw new Error("Measurement's version must be an integer: " + this.version); 1.76 + } 1.77 + 1.78 + if (!this.fields) { 1.79 + throw new Error("Measurement must define fields."); 1.80 + } 1.81 + 1.82 + for (let [name, info] in Iterator(this.fields)) { 1.83 + if (!info) { 1.84 + throw new Error("Field does not contain metadata: " + name); 1.85 + } 1.86 + 1.87 + if (!info.type) { 1.88 + throw new Error("Field is missing required type property: " + name); 1.89 + } 1.90 + } 1.91 + 1.92 + this._log = Log.repository.getLogger("Services.Metrics.Measurement." + this.name); 1.93 + 1.94 + this.id = null; 1.95 + this.storage = null; 1.96 + this._fields = {}; 1.97 + 1.98 + this._serializers = {}; 1.99 + this._serializers[this.SERIALIZE_JSON] = { 1.100 + singular: this._serializeJSONSingular.bind(this), 1.101 + daily: this._serializeJSONDay.bind(this), 1.102 + }; 1.103 +} 1.104 + 1.105 +Measurement.prototype = Object.freeze({ 1.106 + SERIALIZE_JSON: "json", 1.107 + 1.108 + /** 1.109 + * Obtain a serializer for this measurement. 1.110 + * 1.111 + * Implementations should return an object with the following keys: 1.112 + * 1.113 + * singular -- Serializer for singular data. 1.114 + * daily -- Serializer for daily data. 1.115 + * 1.116 + * Each item is a function that takes a single argument: the data to 1.117 + * serialize. The passed data is a subset of that returned from 1.118 + * this.getValues(). For "singular," data.singular is passed. For "daily", 1.119 + * data.days.get(<day>) is passed. 1.120 + * 1.121 + * This function receives a single argument: the serialization format we 1.122 + * are requesting. This is one of the SERIALIZE_* constants on this base type. 1.123 + * 1.124 + * For SERIALIZE_JSON, the function should return an object that 1.125 + * JSON.stringify() knows how to handle. This could be an anonymous object or 1.126 + * array or any object with a property named `toJSON` whose value is a 1.127 + * function. The returned object will be added to a larger document 1.128 + * containing the results of all `serialize` calls. 1.129 + * 1.130 + * The default implementation knows how to serialize built-in types using 1.131 + * very simple logic. If small encoding size is a goal, the default 1.132 + * implementation may not be suitable. If an unknown field type is 1.133 + * encountered, the default implementation will error. 1.134 + * 1.135 + * @param format 1.136 + * (string) A SERIALIZE_* constant defining what serialization format 1.137 + * to use. 1.138 + */ 1.139 + serializer: function (format) { 1.140 + if (!(format in this._serializers)) { 1.141 + throw new Error("Don't know how to serialize format: " + format); 1.142 + } 1.143 + 1.144 + return this._serializers[format]; 1.145 + }, 1.146 + 1.147 + /** 1.148 + * Whether this measurement contains the named field. 1.149 + * 1.150 + * @param name 1.151 + * (string) Name of field. 1.152 + * 1.153 + * @return bool 1.154 + */ 1.155 + hasField: function (name) { 1.156 + return name in this.fields; 1.157 + }, 1.158 + 1.159 + /** 1.160 + * The unique identifier for a named field. 1.161 + * 1.162 + * This will throw if the field is not known. 1.163 + * 1.164 + * @param name 1.165 + * (string) Name of field. 1.166 + */ 1.167 + fieldID: function (name) { 1.168 + let entry = this._fields[name]; 1.169 + 1.170 + if (!entry) { 1.171 + throw new Error("Unknown field: " + name); 1.172 + } 1.173 + 1.174 + return entry[0]; 1.175 + }, 1.176 + 1.177 + fieldType: function (name) { 1.178 + let entry = this._fields[name]; 1.179 + 1.180 + if (!entry) { 1.181 + throw new Error("Unknown field: " + name); 1.182 + } 1.183 + 1.184 + return entry[1]; 1.185 + }, 1.186 + 1.187 + _configureStorage: function () { 1.188 + let missing = []; 1.189 + for (let [name, info] in Iterator(this.fields)) { 1.190 + if (this.storage.hasFieldFromMeasurement(this.id, name)) { 1.191 + this._fields[name] = 1.192 + [this.storage.fieldIDFromMeasurement(this.id, name), info.type]; 1.193 + continue; 1.194 + } 1.195 + 1.196 + missing.push([name, info.type]); 1.197 + } 1.198 + 1.199 + if (!missing.length) { 1.200 + return CommonUtils.laterTickResolvingPromise(); 1.201 + } 1.202 + 1.203 + // We only perform a transaction if we have work to do (to avoid 1.204 + // extra SQLite overhead). 1.205 + return this.storage.enqueueTransaction(function registerFields() { 1.206 + for (let [name, type] of missing) { 1.207 + this._log.debug("Registering field: " + name + " " + type); 1.208 + let id = yield this.storage.registerField(this.id, name, type); 1.209 + this._fields[name] = [id, type]; 1.210 + } 1.211 + }.bind(this)); 1.212 + }, 1.213 + 1.214 + //--------------------------------------------------------------------------- 1.215 + // Data Recording Functions 1.216 + // 1.217 + // Functions in this section are used to record new values against this 1.218 + // measurement instance. 1.219 + // 1.220 + // Generally speaking, these functions will throw if the specified field does 1.221 + // not exist or if the storage function requested is not appropriate for the 1.222 + // type of that field. These functions will also return a promise that will 1.223 + // be resolved when the underlying storage operation has completed. 1.224 + //--------------------------------------------------------------------------- 1.225 + 1.226 + /** 1.227 + * Increment a daily counter field in this measurement by 1. 1.228 + * 1.229 + * By default, the counter for the current day will be incremented. 1.230 + * 1.231 + * If the field is not known or is not a daily counter, this will throw. 1.232 + * 1.233 + * 1.234 + * 1.235 + * @param field 1.236 + * (string) The name of the field whose value to increment. 1.237 + * @param date 1.238 + * (Date) Day on which to increment the counter. 1.239 + * @param by 1.240 + * (integer) How much to increment by. 1.241 + * @return Promise<> 1.242 + */ 1.243 + incrementDailyCounter: function (field, date=new Date(), by=1) { 1.244 + return this.storage.incrementDailyCounterFromFieldID(this.fieldID(field), 1.245 + date, by); 1.246 + }, 1.247 + 1.248 + /** 1.249 + * Record a new numeric value for a daily discrete numeric field. 1.250 + * 1.251 + * @param field 1.252 + * (string) The name of the field to append a value to. 1.253 + * @param value 1.254 + * (Number) Number to append. 1.255 + * @param date 1.256 + * (Date) Day on which to append the value. 1.257 + * 1.258 + * @return Promise<> 1.259 + */ 1.260 + addDailyDiscreteNumeric: function (field, value, date=new Date()) { 1.261 + return this.storage.addDailyDiscreteNumericFromFieldID( 1.262 + this.fieldID(field), value, date); 1.263 + }, 1.264 + 1.265 + /** 1.266 + * Record a new text value for a daily discrete text field. 1.267 + * 1.268 + * This is like `addDailyDiscreteNumeric` but for daily discrete text fields. 1.269 + */ 1.270 + addDailyDiscreteText: function (field, value, date=new Date()) { 1.271 + return this.storage.addDailyDiscreteTextFromFieldID( 1.272 + this.fieldID(field), value, date); 1.273 + }, 1.274 + 1.275 + /** 1.276 + * Record the last seen value for a last numeric field. 1.277 + * 1.278 + * @param field 1.279 + * (string) The name of the field to set the value of. 1.280 + * @param value 1.281 + * (Number) The value to set. 1.282 + * @param date 1.283 + * (Date) When this value was recorded. 1.284 + * 1.285 + * @return Promise<> 1.286 + */ 1.287 + setLastNumeric: function (field, value, date=new Date()) { 1.288 + return this.storage.setLastNumericFromFieldID(this.fieldID(field), value, 1.289 + date); 1.290 + }, 1.291 + 1.292 + /** 1.293 + * Record the last seen value for a last text field. 1.294 + * 1.295 + * This is like `setLastNumeric` except for last text fields. 1.296 + */ 1.297 + setLastText: function (field, value, date=new Date()) { 1.298 + return this.storage.setLastTextFromFieldID(this.fieldID(field), value, 1.299 + date); 1.300 + }, 1.301 + 1.302 + /** 1.303 + * Record the most recent value for a daily last numeric field. 1.304 + * 1.305 + * @param field 1.306 + * (string) The name of a daily last numeric field. 1.307 + * @param value 1.308 + * (Number) The value to set. 1.309 + * @param date 1.310 + * (Date) Day on which to record the last value. 1.311 + * 1.312 + * @return Promise<> 1.313 + */ 1.314 + setDailyLastNumeric: function (field, value, date=new Date()) { 1.315 + return this.storage.setDailyLastNumericFromFieldID(this.fieldID(field), 1.316 + value, date); 1.317 + }, 1.318 + 1.319 + /** 1.320 + * Record the most recent value for a daily last text field. 1.321 + * 1.322 + * This is like `setDailyLastNumeric` except for a daily last text field. 1.323 + */ 1.324 + setDailyLastText: function (field, value, date=new Date()) { 1.325 + return this.storage.setDailyLastTextFromFieldID(this.fieldID(field), 1.326 + value, date); 1.327 + }, 1.328 + 1.329 + //--------------------------------------------------------------------------- 1.330 + // End of data recording APIs. 1.331 + //--------------------------------------------------------------------------- 1.332 + 1.333 + /** 1.334 + * Obtain all values stored for this measurement. 1.335 + * 1.336 + * The default implementation obtains all known types from storage. If the 1.337 + * measurement provides custom types or stores values somewhere other than 1.338 + * storage, it should define its own implementation. 1.339 + * 1.340 + * This returns a promise that resolves to a data structure which is 1.341 + * understood by the measurement's serialize() function. 1.342 + */ 1.343 + getValues: function () { 1.344 + return this.storage.getMeasurementValues(this.id); 1.345 + }, 1.346 + 1.347 + deleteLastNumeric: function (field) { 1.348 + return this.storage.deleteLastNumericFromFieldID(this.fieldID(field)); 1.349 + }, 1.350 + 1.351 + deleteLastText: function (field) { 1.352 + return this.storage.deleteLastTextFromFieldID(this.fieldID(field)); 1.353 + }, 1.354 + 1.355 + /** 1.356 + * This method is used by the default serializers to control whether a field 1.357 + * is included in the output. 1.358 + * 1.359 + * There could be legacy fields in storage we no longer care about. 1.360 + * 1.361 + * This method is a hook to allow measurements to change this behavior, e.g., 1.362 + * to implement a dynamic fieldset. 1.363 + * 1.364 + * You will also need to override `fieldType`. 1.365 + * 1.366 + * @return (boolean) true if the specified field should be included in 1.367 + * payload output. 1.368 + */ 1.369 + shouldIncludeField: function (field) { 1.370 + return field in this._fields; 1.371 + }, 1.372 + 1.373 + _serializeJSONSingular: function (data) { 1.374 + let result = {"_v": this.version}; 1.375 + 1.376 + for (let [field, data] of data) { 1.377 + // There could be legacy fields in storage we no longer care about. 1.378 + if (!this.shouldIncludeField(field)) { 1.379 + continue; 1.380 + } 1.381 + 1.382 + let type = this.fieldType(field); 1.383 + 1.384 + switch (type) { 1.385 + case this.storage.FIELD_LAST_NUMERIC: 1.386 + case this.storage.FIELD_LAST_TEXT: 1.387 + result[field] = data[1]; 1.388 + break; 1.389 + 1.390 + case this.storage.FIELD_DAILY_COUNTER: 1.391 + case this.storage.FIELD_DAILY_DISCRETE_NUMERIC: 1.392 + case this.storage.FIELD_DAILY_DISCRETE_TEXT: 1.393 + case this.storage.FIELD_DAILY_LAST_NUMERIC: 1.394 + case this.storage.FIELD_DAILY_LAST_TEXT: 1.395 + continue; 1.396 + 1.397 + default: 1.398 + throw new Error("Unknown field type: " + type); 1.399 + } 1.400 + } 1.401 + 1.402 + return result; 1.403 + }, 1.404 + 1.405 + _serializeJSONDay: function (data) { 1.406 + let result = {"_v": this.version}; 1.407 + 1.408 + for (let [field, data] of data) { 1.409 + if (!this.shouldIncludeField(field)) { 1.410 + continue; 1.411 + } 1.412 + 1.413 + let type = this.fieldType(field); 1.414 + 1.415 + switch (type) { 1.416 + case this.storage.FIELD_DAILY_COUNTER: 1.417 + case this.storage.FIELD_DAILY_DISCRETE_NUMERIC: 1.418 + case this.storage.FIELD_DAILY_DISCRETE_TEXT: 1.419 + case this.storage.FIELD_DAILY_LAST_NUMERIC: 1.420 + case this.storage.FIELD_DAILY_LAST_TEXT: 1.421 + result[field] = data; 1.422 + break; 1.423 + 1.424 + case this.storage.FIELD_LAST_NUMERIC: 1.425 + case this.storage.FIELD_LAST_TEXT: 1.426 + continue; 1.427 + 1.428 + default: 1.429 + throw new Error("Unknown field type: " + type); 1.430 + } 1.431 + } 1.432 + 1.433 + return result; 1.434 + }, 1.435 +}); 1.436 + 1.437 + 1.438 +/** 1.439 + * An entity that emits data. 1.440 + * 1.441 + * A `Provider` consists of a string name (must be globally unique among all 1.442 + * known providers) and a set of `Measurement` instances. 1.443 + * 1.444 + * The main role of a `Provider` is to produce metrics data and to store said 1.445 + * data in the storage backend. 1.446 + * 1.447 + * Metrics data collection is initiated either by a manager calling a 1.448 + * `collect*` function on `Provider` instances or by the `Provider` registering 1.449 + * to some external event and then reacting whenever they occur. 1.450 + * 1.451 + * `Provider` implementations interface directly with a storage backend. For 1.452 + * common stored values (daily counters, daily discrete values, etc), 1.453 + * implementations should interface with storage via the various helper 1.454 + * functions on the `Measurement` instances. For custom stored value types, 1.455 + * implementations will interact directly with the low-level storage APIs. 1.456 + * 1.457 + * Because multiple providers exist and could be responding to separate 1.458 + * external events simultaneously and because not all operations performed by 1.459 + * storage can safely be performed in parallel, writing directly to storage at 1.460 + * event time is dangerous. Therefore, interactions with storage must be 1.461 + * deferred until it is safe to perform them. 1.462 + * 1.463 + * This typically looks something like: 1.464 + * 1.465 + * // This gets called when an external event worthy of recording metrics 1.466 + * // occurs. The function receives a numeric value associated with the event. 1.467 + * function onExternalEvent (value) { 1.468 + * let now = new Date(); 1.469 + * let m = this.getMeasurement("foo", 1); 1.470 + * 1.471 + * this.enqueueStorageOperation(function storeExternalEvent() { 1.472 + * 1.473 + * // We interface with storage via the `Measurement` helper functions. 1.474 + * // These each return a promise that will be resolved when the 1.475 + * // operation finishes. We rely on behavior of storage where operations 1.476 + * // are executed single threaded and sequentially. Therefore, we only 1.477 + * // need to return the final promise. 1.478 + * m.incrementDailyCounter("foo", now); 1.479 + * return m.addDailyDiscreteNumericValue("my_value", value, now); 1.480 + * }.bind(this)); 1.481 + * 1.482 + * } 1.483 + * 1.484 + * 1.485 + * `Provider` is an abstract base class. Implementations must define a few 1.486 + * properties: 1.487 + * 1.488 + * name 1.489 + * The `name` property should be a string defining the provider's name. The 1.490 + * name must be globally unique for the application. The name is used as an 1.491 + * identifier to distinguish providers from each other. 1.492 + * 1.493 + * measurementTypes 1.494 + * This must be an array of `Measurement`-derived types. Note that elements 1.495 + * in the array are the type functions, not instances. Instances of the 1.496 + * `Measurement` are created at run-time by the `Provider` and are bound 1.497 + * to the provider and to a specific storage backend. 1.498 + */ 1.499 +this.Provider = function () { 1.500 + if (!this.name) { 1.501 + throw new Error("Provider must define a name."); 1.502 + } 1.503 + 1.504 + if (!Array.isArray(this.measurementTypes)) { 1.505 + throw new Error("Provider must define measurement types."); 1.506 + } 1.507 + 1.508 + this._log = Log.repository.getLogger("Services.Metrics.Provider." + this.name); 1.509 + 1.510 + this.measurements = null; 1.511 + this.storage = null; 1.512 +} 1.513 + 1.514 +Provider.prototype = Object.freeze({ 1.515 + /** 1.516 + * Whether the provider only pulls data from other sources. 1.517 + * 1.518 + * If this is true, the provider pulls data from other sources. By contrast, 1.519 + * "push-based" providers subscribe to foreign sources and record/react to 1.520 + * external events as they happen. 1.521 + * 1.522 + * Pull-only providers likely aren't instantiated until a data collection 1.523 + * is performed. Thus, implementations cannot rely on a provider instance 1.524 + * always being alive. This is an optimization so provider instances aren't 1.525 + * dead weight while the application is running. 1.526 + * 1.527 + * This must be set on the prototype to have an effect. 1.528 + */ 1.529 + pullOnly: false, 1.530 + 1.531 + /** 1.532 + * Obtain a `Measurement` from its name and version. 1.533 + * 1.534 + * If the measurement is not found, an Error is thrown. 1.535 + */ 1.536 + getMeasurement: function (name, version) { 1.537 + if (!Number.isInteger(version)) { 1.538 + throw new Error("getMeasurement expects an integer version. Got: " + version); 1.539 + } 1.540 + 1.541 + let m = this.measurements.get([name, version].join(":")); 1.542 + 1.543 + if (!m) { 1.544 + throw new Error("Unknown measurement: " + name + " v" + version); 1.545 + } 1.546 + 1.547 + return m; 1.548 + }, 1.549 + 1.550 + init: function (storage) { 1.551 + if (this.storage !== null) { 1.552 + throw new Error("Provider() not called. Did the sub-type forget to call it?"); 1.553 + } 1.554 + 1.555 + if (this.storage) { 1.556 + throw new Error("Provider has already been initialized."); 1.557 + } 1.558 + 1.559 + this.measurements = new Map(); 1.560 + this.storage = storage; 1.561 + 1.562 + let self = this; 1.563 + return Task.spawn(function init() { 1.564 + let pre = self.preInit(); 1.565 + if (!pre || typeof(pre.then) != "function") { 1.566 + throw new Error("preInit() does not return a promise."); 1.567 + } 1.568 + yield pre; 1.569 + 1.570 + for (let measurementType of self.measurementTypes) { 1.571 + let measurement = new measurementType(); 1.572 + 1.573 + measurement.provider = self; 1.574 + measurement.storage = self.storage; 1.575 + 1.576 + let id = yield storage.registerMeasurement(self.name, measurement.name, 1.577 + measurement.version); 1.578 + 1.579 + measurement.id = id; 1.580 + 1.581 + yield measurement._configureStorage(); 1.582 + 1.583 + self.measurements.set([measurement.name, measurement.version].join(":"), 1.584 + measurement); 1.585 + } 1.586 + 1.587 + let post = self.postInit(); 1.588 + if (!post || typeof(post.then) != "function") { 1.589 + throw new Error("postInit() does not return a promise."); 1.590 + } 1.591 + yield post; 1.592 + }); 1.593 + }, 1.594 + 1.595 + shutdown: function () { 1.596 + let promise = this.onShutdown(); 1.597 + 1.598 + if (!promise || typeof(promise.then) != "function") { 1.599 + throw new Error("onShutdown implementation does not return a promise."); 1.600 + } 1.601 + 1.602 + return promise; 1.603 + }, 1.604 + 1.605 + /** 1.606 + * Hook point for implementations to perform pre-initialization activity. 1.607 + * 1.608 + * This method will be called before measurement registration. 1.609 + * 1.610 + * Implementations should return a promise which is resolved when 1.611 + * initialization activities have completed. 1.612 + */ 1.613 + preInit: function () { 1.614 + return CommonUtils.laterTickResolvingPromise(); 1.615 + }, 1.616 + 1.617 + /** 1.618 + * Hook point for implementations to perform post-initialization activity. 1.619 + * 1.620 + * This method will be called after `preInit` and measurement registration, 1.621 + * but before initialization is finished. 1.622 + * 1.623 + * If a `Provider` instance needs to register observers, etc, it should 1.624 + * implement this function. 1.625 + * 1.626 + * Implementations should return a promise which is resolved when 1.627 + * initialization activities have completed. 1.628 + */ 1.629 + postInit: function () { 1.630 + return CommonUtils.laterTickResolvingPromise(); 1.631 + }, 1.632 + 1.633 + /** 1.634 + * Hook point for shutdown of instances. 1.635 + * 1.636 + * This is the opposite of `onInit`. If a `Provider` needs to unregister 1.637 + * observers, etc, this is where it should do it. 1.638 + * 1.639 + * Implementations should return a promise which is resolved when 1.640 + * shutdown activities have completed. 1.641 + */ 1.642 + onShutdown: function () { 1.643 + return CommonUtils.laterTickResolvingPromise(); 1.644 + }, 1.645 + 1.646 + /** 1.647 + * Collects data that doesn't change during the application's lifetime. 1.648 + * 1.649 + * Implementations should return a promise that resolves when all data has 1.650 + * been collected and storage operations have been finished. 1.651 + * 1.652 + * @return Promise<> 1.653 + */ 1.654 + collectConstantData: function () { 1.655 + return CommonUtils.laterTickResolvingPromise(); 1.656 + }, 1.657 + 1.658 + /** 1.659 + * Collects data approximately every day. 1.660 + * 1.661 + * For long-running applications, this is called approximately every day. 1.662 + * It may or may not be called every time the application is run. It also may 1.663 + * be called more than once per day. 1.664 + * 1.665 + * Implementations should return a promise that resolves when all data has 1.666 + * been collected and storage operations have completed. 1.667 + * 1.668 + * @return Promise<> 1.669 + */ 1.670 + collectDailyData: function () { 1.671 + return CommonUtils.laterTickResolvingPromise(); 1.672 + }, 1.673 + 1.674 + /** 1.675 + * Queue a deferred storage operation. 1.676 + * 1.677 + * Deferred storage operations are the preferred method for providers to 1.678 + * interact with storage. When collected data is to be added to storage, 1.679 + * the provider creates a function that performs the necessary storage 1.680 + * interactions and then passes that function to this function. Pending 1.681 + * storage operations will be executed sequentially by a coordinator. 1.682 + * 1.683 + * The passed function should return a promise which will be resolved upon 1.684 + * completion of storage interaction. 1.685 + */ 1.686 + enqueueStorageOperation: function (func) { 1.687 + return this.storage.enqueueOperation(func); 1.688 + }, 1.689 + 1.690 + /** 1.691 + * Obtain persisted provider state. 1.692 + * 1.693 + * Provider state consists of key-value pairs of string names and values. 1.694 + * Providers can stuff whatever they want into state. They are encouraged to 1.695 + * store as little as possible for performance reasons. 1.696 + * 1.697 + * State is backed by storage and is robust. 1.698 + * 1.699 + * These functions do not enqueue on storage automatically, so they should 1.700 + * be guarded by `enqueueStorageOperation` or some other mutex. 1.701 + * 1.702 + * @param key 1.703 + * (string) The property to retrieve. 1.704 + * 1.705 + * @return Promise<string|null> String value on success. null if no state 1.706 + * is available under this key. 1.707 + */ 1.708 + getState: function (key) { 1.709 + return this.storage.getProviderState(this.name, key); 1.710 + }, 1.711 + 1.712 + /** 1.713 + * Set state for this provider. 1.714 + * 1.715 + * This is the complementary API for `getState` and obeys the same 1.716 + * storage restrictions. 1.717 + */ 1.718 + setState: function (key, value) { 1.719 + return this.storage.setProviderState(this.name, key, value); 1.720 + }, 1.721 + 1.722 + _dateToDays: function (date) { 1.723 + return Math.floor(date.getTime() / MILLISECONDS_PER_DAY); 1.724 + }, 1.725 + 1.726 + _daysToDate: function (days) { 1.727 + return new Date(days * MILLISECONDS_PER_DAY); 1.728 + }, 1.729 +}); 1.730 +