fix telemetry init and enforce queue length limit

This commit is contained in:
Christopher Willis-Ford 2019-07-09 13:59:39 -07:00
parent f3346944a6
commit 02b06b29a4

View file

@ -20,6 +20,14 @@ const TelemetryServerURL = Object.freeze({
staging: 'http://scratch-telemetry-s.us-east-1.elasticbeanstalk.com/', staging: 'http://scratch-telemetry-s.us-east-1.elasticbeanstalk.com/',
production: 'https://telemetry.scratch.mit.edu/' production: 'https://telemetry.scratch.mit.edu/'
}); });
const DefaultServerURL = (
process.env.NODE_ENV === 'production' ? TelemetryServerURL.production : TelemetryServerURL.staging
);
/**
* Default name for persistent configuration & queue storage
*/
const DefaultStoreName = 'telemetry';
/** /**
* Default interval, in seconds, between delivery attempts * Default interval, in seconds, between delivery attempts
@ -31,6 +39,17 @@ const DefaultDeliveryInterval = 60;
*/ */
const DefaultNetworkCheckInterval = 300; const DefaultNetworkCheckInterval = 300;
/**
* Default limit on the number of queued events
*/
const DefaultQueueLimit = 100;
/**
* Default limit on the number of delivery attempts for each event
*/
const DeliveryAttemptLimit = 3;
/** /**
* Client interface for the Scratch telemetry service. * Client interface for the Scratch telemetry service.
* *
@ -45,32 +64,39 @@ class TelemetryClient {
* @param {object} [options] - optional configuration settings for this client * @param {object} [options] - optional configuration settings for this client
* @property {string} [storeName] - optional name for persistent config/queue storage (default: 'telemetry') * @property {string} [storeName] - optional name for persistent config/queue storage (default: 'telemetry')
* @property {string} [clientId] - optional UUID for this client (default: automatically determine a UUID) * @property {string} [clientId] - optional UUID for this client (default: automatically determine a UUID)
* @property {string} [url] - optional telemetry service endpoint URL (default: automatically choose a server) * @property {string} [serverURL] - optional telemetry service endpoint URL (default: automatically choose a server)
* @property {boolean} [didOptIn] - optional flag for whether the user opted into telemetry service (default: false) * @property {boolean} [didOptIn] - optional flag for whether the user opted into telemetry service (default: false)
* @property {int} [deliveryInterval] - optional number of seconds between delivery attempts (default: 60) * @property {int} [deliveryInterval] - optional number of seconds between delivery attempts (default: 60)
* @property {int} [networkInterval] - optional number of seconds between connectivity checks (default: 300) * @property {int} [networkCheckInterval] - optional number of seconds between connectivity checks (default: 300)
* @property {int} [queueLimit] - optional limit on the number of queued events (default: 100) * @property {int} [queueLimit] - optional limit on the number of queued events (default: 100)
* @property {int} [attemptLimit] - optional limit on the number of delivery attempts for each event (default: 3) * @property {int} [deliveryAttemptLimit] - optional limit on delivery attempts for each event (default: 3)
*/ */
constructor (options = null) { constructor ({
options = options || {}; storeName = DefaultStoreName,
clientID, // undefined = load or create
serverURL, // undefined = automatic
didOptIn, // undefined = show prompt
deliveryInterval = DefaultDeliveryInterval,
networkCheckInterval = DefaultNetworkCheckInterval,
queueLimit = DefaultQueueLimit,
deliveryAttemptLimit = DeliveryAttemptLimit
} = {}) {
/** /**
* Persistent storage for the client ID, opt in flag, and packet queue. * Persistent storage for the client ID, opt in flag, and packet queue.
*/ */
this._store = new ElectronStore({ this._store = new ElectronStore({
name: options.storeName || 'telemetry' name: storeName
}); });
console.log(`Telemetry configuration storage path: ${this._store.path}`); console.log(`Telemetry configuration storage path: ${this._store.path}`);
if (options.hasOwnProperty('clientID')) { if (clientID) {
this.clientID = options.clientID; this.clientID = clientID;
} else if (!this._store.has('clientID')) { } else if (!this._store.has('clientID')) {
this.clientID = uuidv1(); this.clientID = uuidv1();
} }
if (options.hasOwnProperty('optIn')) { if (typeof didOptIn !== 'undefined') {
this.didOptIn = options.didOptIn; this.didOptIn = didOptIn;
} }
/** /**
@ -81,7 +107,7 @@ class TelemetryClient {
/** /**
* Server URL * Server URL
*/ */
this._serverURL = options.url || TelemetryServerURL.staging; this._serverURL = serverURL || DefaultServerURL;
/** /**
* Can we currently reach the telemetry service? * Can we currently reach the telemetry service?
@ -91,13 +117,22 @@ class TelemetryClient {
/** /**
* Try to deliver telemetry packets at this interval * Try to deliver telemetry packets at this interval
*/ */
this._deliveryInterval = (options.deliveryInterval > 0) ? options.deliveryInterval : DefaultDeliveryInterval; this._deliveryInterval = (deliveryInterval > 0) ? deliveryInterval : DefaultDeliveryInterval;
/** /**
* Check for connectivity at this interval * Check for connectivity at this interval
*/ */
this._networkCheckInterval = this._networkCheckInterval = (networkCheckInterval > 0) ? networkCheckInterval : DefaultNetworkCheckInterval;
(options.networkCheckInterval > 0) ? options.networkCheckInterval : DefaultNetworkCheckInterval;
/**
* Queue at most this many events
*/
this._queueLimit = (queueLimit > 0) ? queueLimit : DefaultQueueLimit;
/**
* Attempt to deliver an event at most this many times
*/
this._deliveryAttemptLimit = (deliveryAttemptLimit > 0) ? deliveryAttemptLimit : DeliveryAttemptLimit;
/** /**
* Bind event handlers * Bind event handlers
@ -121,9 +156,13 @@ class TelemetryClient {
* Stop this client. Do not use this object after disposal. * Stop this client. Do not use this object after disposal.
*/ */
dispose () { dispose () {
if (this._deliveryInterval !== null) { if (this._networkTimer !== null) {
clearInterval(this._networkTimer);
this._networkTimer = null;
}
if (this._deliveryTimer !== null) {
clearInterval(this._deliveryTimer); clearInterval(this._deliveryTimer);
this._deliveryInterval = null; this._deliveryTimer = null;
} }
} }
@ -183,6 +222,7 @@ class TelemetryClient {
packet packet
}; };
this._packetQueue.push(packetInfo); this._packetQueue.push(packetInfo);
this._packetQueue.splice(0, this._packetQueue.length - this._queueLimit); // enforce queue length limit
this.saveQueue(); this.saveQueue();
} }
@ -220,7 +260,7 @@ class TelemetryClient {
// TODO: check if the failure is because there's no Internet connection and if so refund the attempt // TODO: check if the failure is because there's no Internet connection and if so refund the attempt
const packetFailed = err || (res.statusCode !== 200); const packetFailed = err || (res.statusCode !== 200);
if (packetFailed) { if (packetFailed) {
if (packetInfo.attempts < this._attemptsLimit) { if (packetInfo.attempts < this._deliveryAttemptLimit) {
this._packetQueue.push(packetInfo); this._packetQueue.push(packetInfo);
} else { } else {
console.warn('Dropping packet which exceeded retry limit', packet); console.warn('Dropping packet which exceeded retry limit', packet);