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/. */ 4 5/* 6 * This module is responsible for uploading pings to the server and persisting 7 * pings that can't be send now. 8 * Those pending pings are persisted on disk and sent at the next opportunity, 9 * newest first. 10 */ 11 12"use strict"; 13 14var EXPORTED_SYMBOLS = [ 15 "TelemetrySend", 16]; 17 18ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this); 19ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this); 20ChromeUtils.import("resource://gre/modules/ClientID.jsm"); 21ChromeUtils.import("resource://gre/modules/Log.jsm", this); 22ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm"); 23ChromeUtils.import("resource://gre/modules/ServiceRequest.jsm", this); 24ChromeUtils.import("resource://gre/modules/Services.jsm", this); 25ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this); 26ChromeUtils.import("resource://gre/modules/Timer.jsm", this); 27 28ChromeUtils.defineModuleGetter(this, "AsyncShutdown", 29 "resource://gre/modules/AsyncShutdown.jsm"); 30ChromeUtils.defineModuleGetter(this, "TelemetryStorage", 31 "resource://gre/modules/TelemetryStorage.jsm"); 32ChromeUtils.defineModuleGetter(this, "TelemetryReportingPolicy", 33 "resource://gre/modules/TelemetryReportingPolicy.jsm"); 34ChromeUtils.defineModuleGetter(this, "OS", 35 "resource://gre/modules/osfile.jsm"); 36XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", 37 "@mozilla.org/base/telemetry;1", 38 "nsITelemetry"); 39ChromeUtils.defineModuleGetter(this, "TelemetryHealthPing", 40 "resource://gre/modules/TelemetryHealthPing.jsm"); 41 42 43const Utils = TelemetryUtils; 44 45const LOGGER_NAME = "Toolkit.Telemetry"; 46const LOGGER_PREFIX = "TelemetrySend::"; 47 48const TOPIC_IDLE_DAILY = "idle-daily"; 49// The following topics are notified when Firefox is closing 50// because the OS is shutting down. 51const TOPIC_QUIT_APPLICATION_GRANTED = "quit-application-granted"; 52const TOPIC_QUIT_APPLICATION_FORCED = "quit-application-forced"; 53const PREF_CHANGED_TOPIC = "nsPref:changed"; 54const TOPIC_PROFILE_CHANGE_NET_TEARDOWN = "profile-change-net-teardown"; 55 56// Whether the FHR/Telemetry unification features are enabled. 57// Changing this pref requires a restart. 58const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(TelemetryUtils.Preferences.Unified, false); 59 60const PING_FORMAT_VERSION = 4; 61 62const MS_IN_A_MINUTE = 60 * 1000; 63 64const PING_TYPE_DELETION = "deletion"; 65 66// We try to spread "midnight" pings out over this interval. 67const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * MS_IN_A_MINUTE; 68// We delay sending "midnight" pings on this client by this interval. 69const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS; 70 71// Timeout after which we consider a ping submission failed. 72const PING_SUBMIT_TIMEOUT_MS = 1.5 * MS_IN_A_MINUTE; 73 74// To keep resource usage in check, we limit ping sending to a maximum number 75// of pings per minute. 76const MAX_PING_SENDS_PER_MINUTE = 10; 77 78// If we have more pending pings then we can send right now, we schedule the next 79// send for after SEND_TICK_DELAY. 80const SEND_TICK_DELAY = 1 * MS_IN_A_MINUTE; 81// If we had any ping send failures since the last ping, we use a backoff timeout 82// for the next ping sends. We increase the delay exponentially up to a limit of 83// SEND_MAXIMUM_BACKOFF_DELAY_MS. 84// This exponential backoff will be reset by external ping submissions & idle-daily. 85const SEND_MAXIMUM_BACKOFF_DELAY_MS = 120 * MS_IN_A_MINUTE; 86 87// The age of a pending ping to be considered overdue (in milliseconds). 88const OVERDUE_PING_FILE_AGE = 7 * 24 * 60 * MS_IN_A_MINUTE; // 1 week 89 90// Strings to map from XHR.errorCode to TELEMETRY_SEND_FAILURE_TYPE. 91// Echoes XMLHttpRequestMainThread's ErrorType enum. 92const XHR_ERROR_TYPE = [ 93 "eOK", 94 "eRequest", 95 "eUnreachable", 96 "eChannelOpen", 97 "eRedirect", 98]; 99 100/** 101 * This is a policy object used to override behavior within this module. 102 * Tests override properties on this object to allow for control of behavior 103 * that would otherwise be very hard to cover. 104 */ 105var Policy = { 106 now: () => new Date(), 107 midnightPingFuzzingDelay: () => MIDNIGHT_FUZZING_DELAY_MS, 108 pingSubmissionTimeout: () => PING_SUBMIT_TIMEOUT_MS, 109 setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs), 110 clearSchedulerTickTimeout: (id) => clearTimeout(id), 111}; 112 113/** 114 * Determine if the ping has the new v4 ping format or the legacy v2 one or earlier. 115 */ 116function isV4PingFormat(aPing) { 117 return ("id" in aPing) && ("application" in aPing) && 118 ("version" in aPing) && (aPing.version >= 2); 119} 120 121/** 122 * Check if the provided ping is a deletion ping. 123 * @param {Object} aPing The ping to check. 124 * @return {Boolean} True if the ping is a deletion ping, false otherwise. 125 */ 126function isDeletionPing(aPing) { 127 return isV4PingFormat(aPing) && (aPing.type == PING_TYPE_DELETION); 128} 129 130/** 131 * Save the provided ping as a pending ping. If it's a deletion ping, save it 132 * to a special location. 133 * @param {Object} aPing The ping to save. 134 * @return {Promise} A promise resolved when the ping is saved. 135 */ 136function savePing(aPing) { 137 if (isDeletionPing(aPing)) { 138 return TelemetryStorage.saveDeletionPing(aPing); 139 } 140 return TelemetryStorage.savePendingPing(aPing); 141} 142 143/** 144 * @return {String} This returns a string with the gzip compressed data. 145 */ 146function gzipCompressString(string) { 147 let observer = { 148 buffer: "", 149 onStreamComplete(loader, context, status, length, result) { 150 // String.fromCharCode can only deal with 500,000 characters at 151 // a time, so chunk the result into parts of that size. 152 const chunkSize = 500000; 153 for (let offset = 0; offset < result.length; offset += chunkSize) { 154 this.buffer += String.fromCharCode.apply(String, result.slice(offset, offset + chunkSize)); 155 } 156 } 157 }; 158 159 let scs = Cc["@mozilla.org/streamConverters;1"] 160 .getService(Ci.nsIStreamConverterService); 161 let listener = Cc["@mozilla.org/network/stream-loader;1"] 162 .createInstance(Ci.nsIStreamLoader); 163 listener.init(observer); 164 let converter = scs.asyncConvertData("uncompressed", "gzip", 165 listener, null); 166 let stringStream = Cc["@mozilla.org/io/string-input-stream;1"] 167 .createInstance(Ci.nsIStringInputStream); 168 stringStream.data = string; 169 converter.onStartRequest(null, null); 170 converter.onDataAvailable(null, null, stringStream, 0, string.length); 171 converter.onStopRequest(null, null, null); 172 return observer.buffer; 173} 174 175var TelemetrySend = { 176 177 /** 178 * Age in ms of a pending ping to be considered overdue. 179 */ 180 get OVERDUE_PING_FILE_AGE() { 181 return OVERDUE_PING_FILE_AGE; 182 }, 183 184 get pendingPingCount() { 185 return TelemetrySendImpl.pendingPingCount; 186 }, 187 188 /** 189 * Partial setup that runs immediately at startup. This currently triggers 190 * the crash report annotations. 191 */ 192 earlyInit() { 193 TelemetrySendImpl.earlyInit(); 194 }, 195 196 /** 197 * Initializes this module. 198 * 199 * @param {Boolean} testing Whether this is run in a test. This changes some behavior 200 * to enable proper testing. 201 * @return {Promise} Resolved when setup is finished. 202 */ 203 setup(testing = false) { 204 return TelemetrySendImpl.setup(testing); 205 }, 206 207 /** 208 * Shutdown this module - this will cancel any pending ping tasks and wait for 209 * outstanding async activity like network and disk I/O. 210 * 211 * @return {Promise} Promise that is resolved when shutdown is finished. 212 */ 213 shutdown() { 214 return TelemetrySendImpl.shutdown(); 215 }, 216 217 /** 218 * Submit a ping for sending. This will: 219 * - send the ping right away if possible or 220 * - save the ping to disk and send it at the next opportunity 221 * 222 * @param {Object} ping The ping data to send, must be serializable to JSON. 223 * @param {Object} [aOptions] Options object. 224 * @param {Boolean} [options.usePingSender=false] if true, send the ping using the PingSender. 225 * @return {Promise} Test-only - a promise that is resolved when the ping is sent or saved. 226 */ 227 submitPing(ping, options = {}) { 228 options.usePingSender = options.usePingSender || false; 229 return TelemetrySendImpl.submitPing(ping, options); 230 }, 231 232 /** 233 * Count of pending pings that were found to be overdue at startup. 234 */ 235 get overduePingsCount() { 236 return TelemetrySendImpl.overduePingsCount; 237 }, 238 239 /** 240 * Notify that we can start submitting data to the servers. 241 */ 242 notifyCanUpload() { 243 return TelemetrySendImpl.notifyCanUpload(); 244 }, 245 246 /** 247 * Only used in tests. Used to reset the module data to emulate a restart. 248 */ 249 reset() { 250 return TelemetrySendImpl.reset(); 251 }, 252 253 /** 254 * Only used in tests. 255 */ 256 setServer(server) { 257 return TelemetrySendImpl.setServer(server); 258 }, 259 260 /** 261 * Clear out unpersisted, yet to be sent, pings and cancel outgoing ping requests. 262 */ 263 clearCurrentPings() { 264 return TelemetrySendImpl.clearCurrentPings(); 265 }, 266 267 /** 268 * Only used in tests to wait on outgoing pending pings. 269 */ 270 testWaitOnOutgoingPings() { 271 return TelemetrySendImpl.promisePendingPingActivity(); 272 }, 273 274 /** 275 * Only used in tests to set whether it is too late in shutdown to send pings. 276 */ 277 testTooLateToSend(tooLate) { 278 TelemetrySendImpl._tooLateToSend = tooLate; 279 }, 280 281 /** 282 * Test-only - this allows overriding behavior to enable ping sending in debug builds. 283 */ 284 setTestModeEnabled(testing) { 285 TelemetrySendImpl.setTestModeEnabled(testing); 286 }, 287 288 /** 289 * This returns state info for this module for AsyncShutdown timeout diagnostics. 290 */ 291 getShutdownState() { 292 return TelemetrySendImpl.getShutdownState(); 293 }, 294 295 /** 296 * Send a ping using the ping sender. 297 * This method will not wait for the ping to be sent, instead it will return 298 * as soon as the pingsender program has been launched. 299 * 300 * This method is currently exposed here only for testing purposes as it's 301 * only used internally. 302 * 303 * @param {String} aUrl The telemetry server URL 304 * @param {String} aPingFilePath The path to the file holding the ping 305 * contents, if if sent successfully the pingsender will delete it. 306 * 307 * @throws NS_ERROR_FAILURE if we couldn't find or run the pingsender 308 * executable. 309 * @throws NS_ERROR_NOT_IMPLEMENTED on Android as the pingsender is not 310 * available. 311 */ 312 testRunPingSender(url, pingPath) { 313 TelemetrySendImpl.runPingSender(url, pingPath); 314 }, 315}; 316 317var CancellableTimeout = { 318 _deferred: null, 319 _timer: null, 320 321 /** 322 * This waits until either the given timeout passed or the timeout was cancelled. 323 * 324 * @param {Number} timeoutMs The timeout in ms. 325 * @return {Promise<bool>} Promise that is resolved with false if the timeout was cancelled, 326 * false otherwise. 327 */ 328 promiseWaitOnTimeout(timeoutMs) { 329 if (!this._deferred) { 330 this._deferred = PromiseUtils.defer(); 331 this._timer = Policy.setSchedulerTickTimeout(() => this._onTimeout(), timeoutMs); 332 } 333 334 return this._deferred.promise; 335 }, 336 337 _onTimeout() { 338 if (this._deferred) { 339 this._deferred.resolve(false); 340 this._timer = null; 341 this._deferred = null; 342 } 343 }, 344 345 cancelTimeout() { 346 if (this._deferred) { 347 Policy.clearSchedulerTickTimeout(this._timer); 348 this._deferred.resolve(true); 349 this._timer = null; 350 this._deferred = null; 351 } 352 }, 353}; 354 355/** 356 * SendScheduler implements the timer & scheduling behavior for ping sends. 357 */ 358var SendScheduler = { 359 // Whether any ping sends failed since the last tick. If yes, we start with our exponential 360 // backoff timeout. 361 _sendsFailed: false, 362 // The current retry delay after ping send failures. We use this for the exponential backoff, 363 // increasing this value everytime we had send failures since the last tick. 364 _backoffDelay: SEND_TICK_DELAY, 365 _shutdown: false, 366 _sendTask: null, 367 // A string that tracks the last seen send task state, null if it never ran. 368 _sendTaskState: null, 369 370 _logger: null, 371 372 get _log() { 373 if (!this._logger) { 374 this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX + "Scheduler::"); 375 } 376 377 return this._logger; 378 }, 379 380 shutdown() { 381 this._log.trace("shutdown"); 382 this._shutdown = true; 383 CancellableTimeout.cancelTimeout(); 384 return Promise.resolve(this._sendTask); 385 }, 386 387 start() { 388 this._log.trace("start"); 389 this._sendsFailed = false; 390 this._backoffDelay = SEND_TICK_DELAY; 391 this._shutdown = false; 392 }, 393 394 /** 395 * Only used for testing, resets the state to emulate a restart. 396 */ 397 reset() { 398 this._log.trace("reset"); 399 return this.shutdown().then(() => this.start()); 400 }, 401 402 /** 403 * Notify the scheduler of a failure in sending out pings that warrants retrying. 404 * This will trigger the exponential backoff timer behavior on the next tick. 405 */ 406 notifySendsFailed() { 407 this._log.trace("notifySendsFailed"); 408 if (this._sendsFailed) { 409 return; 410 } 411 412 this._sendsFailed = true; 413 this._log.trace("notifySendsFailed - had send failures"); 414 }, 415 416 /** 417 * Returns whether ping submissions are currently throttled. 418 */ 419 isThrottled() { 420 const now = Policy.now(); 421 const nextPingSendTime = this._getNextPingSendTime(now); 422 return (nextPingSendTime > now.getTime()); 423 }, 424 425 waitOnSendTask() { 426 return Promise.resolve(this._sendTask); 427 }, 428 429 triggerSendingPings(immediately) { 430 this._log.trace("triggerSendingPings - active send task: " + !!this._sendTask + ", immediately: " + immediately); 431 432 if (!this._sendTask) { 433 this._sendTask = this._doSendTask(); 434 let clear = () => this._sendTask = null; 435 this._sendTask.then(clear, clear); 436 } else if (immediately) { 437 CancellableTimeout.cancelTimeout(); 438 } 439 440 return this._sendTask; 441 }, 442 443 async _doSendTask() { 444 this._sendTaskState = "send task started"; 445 this._backoffDelay = SEND_TICK_DELAY; 446 this._sendsFailed = false; 447 448 const resetBackoffTimer = () => { 449 this._backoffDelay = SEND_TICK_DELAY; 450 }; 451 452 for (;;) { 453 this._log.trace("_doSendTask iteration"); 454 this._sendTaskState = "start iteration"; 455 456 if (this._shutdown) { 457 this._log.trace("_doSendTask - shutting down, bailing out"); 458 this._sendTaskState = "bail out - shutdown check"; 459 return; 460 } 461 462 // Get a list of pending pings, sorted by last modified, descending. 463 // Filter out all the pings we can't send now. This addresses scenarios like "deletion" pings 464 // which can be send even when upload is disabled. 465 let pending = TelemetryStorage.getPendingPingList(); 466 let current = TelemetrySendImpl.getUnpersistedPings(); 467 this._log.trace("_doSendTask - pending: " + pending.length + ", current: " + current.length); 468 // Note that the two lists contain different kind of data. |pending| only holds ping 469 // info, while |current| holds actual ping data. 470 if (!TelemetrySendImpl.sendingEnabled()) { 471 pending = pending.filter(pingInfo => TelemetryStorage.isDeletionPing(pingInfo.id)); 472 current = current.filter(p => isDeletionPing(p)); 473 } 474 this._log.trace("_doSendTask - can send - pending: " + pending.length + ", current: " + current.length); 475 476 // Bail out if there is nothing to send. 477 if ((pending.length == 0) && (current.length == 0)) { 478 this._log.trace("_doSendTask - no pending pings, bailing out"); 479 this._sendTaskState = "bail out - no pings to send"; 480 return; 481 } 482 483 // If we are currently throttled (e.g. fuzzing to avoid midnight spikes), wait for the next send window. 484 const now = Policy.now(); 485 if (this.isThrottled()) { 486 const nextPingSendTime = this._getNextPingSendTime(now); 487 this._log.trace("_doSendTask - throttled, delaying ping send to " + new Date(nextPingSendTime)); 488 this._sendTaskState = "wait for throttling to pass"; 489 490 const delay = nextPingSendTime - now.getTime(); 491 const cancelled = await CancellableTimeout.promiseWaitOnTimeout(delay); 492 if (cancelled) { 493 this._log.trace("_doSendTask - throttling wait was cancelled, resetting backoff timer"); 494 resetBackoffTimer(); 495 } 496 497 continue; 498 } 499 500 let sending = pending.slice(0, MAX_PING_SENDS_PER_MINUTE); 501 pending = pending.slice(MAX_PING_SENDS_PER_MINUTE); 502 this._log.trace("_doSendTask - triggering sending of " + sending.length + " pings now" + 503 ", " + pending.length + " pings waiting"); 504 505 this._sendsFailed = false; 506 const sendStartTime = Policy.now(); 507 this._sendTaskState = "wait on ping sends"; 508 await TelemetrySendImpl.sendPings(current, sending.map(p => p.id)); 509 if (this._shutdown || (TelemetrySend.pendingPingCount == 0)) { 510 this._log.trace("_doSendTask - bailing out after sending, shutdown: " + this._shutdown + 511 ", pendingPingCount: " + TelemetrySend.pendingPingCount); 512 this._sendTaskState = "bail out - shutdown & pending check after send"; 513 return; 514 } 515 516 // Calculate the delay before sending the next batch of pings. 517 // We start with a delay that makes us send max. 1 batch per minute. 518 // If we had send failures in the last batch, we will override this with 519 // a backoff delay. 520 const timeSinceLastSend = Policy.now() - sendStartTime; 521 let nextSendDelay = Math.max(0, SEND_TICK_DELAY - timeSinceLastSend); 522 523 if (!this._sendsFailed) { 524 this._log.trace("_doSendTask - had no send failures, resetting backoff timer"); 525 resetBackoffTimer(); 526 } else { 527 const newDelay = Math.min(SEND_MAXIMUM_BACKOFF_DELAY_MS, 528 this._backoffDelay * 2); 529 this._log.trace("_doSendTask - had send failures, backing off -" + 530 " old timeout: " + this._backoffDelay + 531 ", new timeout: " + newDelay); 532 this._backoffDelay = newDelay; 533 nextSendDelay = this._backoffDelay; 534 } 535 536 this._log.trace("_doSendTask - waiting for next send opportunity, timeout is " + nextSendDelay); 537 this._sendTaskState = "wait on next send opportunity"; 538 const cancelled = await CancellableTimeout.promiseWaitOnTimeout(nextSendDelay); 539 if (cancelled) { 540 this._log.trace("_doSendTask - batch send wait was cancelled, resetting backoff timer"); 541 resetBackoffTimer(); 542 } 543 } 544 }, 545 546 /** 547 * This helper calculates the next time that we can send pings at. 548 * Currently this mostly redistributes ping sends from midnight until one hour after 549 * to avoid submission spikes around local midnight for daily pings. 550 * 551 * @param now Date The current time. 552 * @return Number The next time (ms from UNIX epoch) when we can send pings. 553 */ 554 _getNextPingSendTime(now) { 555 // 1. First we check if the time is between 0am and 1am. If it's not, we send 556 // immediately. 557 // 2. If we confirmed the time is indeed between 0am and 1am in step 1, we disallow 558 // sending before (midnight + fuzzing delay), which is a random time between 0am-1am 559 // (decided at startup). 560 561 const midnight = Utils.truncateToDays(now); 562 // Don't delay pings if we are not within the fuzzing interval. 563 if ((now.getTime() - midnight.getTime()) > MIDNIGHT_FUZZING_INTERVAL_MS) { 564 return now.getTime(); 565 } 566 567 // Delay ping send if we are within the midnight fuzzing range. 568 // We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|. 569 return midnight.getTime() + Policy.midnightPingFuzzingDelay(); 570 }, 571 572 getShutdownState() { 573 return { 574 shutdown: this._shutdown, 575 hasSendTask: !!this._sendTask, 576 sendsFailed: this._sendsFailed, 577 sendTaskState: this._sendTaskState, 578 backoffDelay: this._backoffDelay, 579 }; 580 }, 581 }; 582 583var TelemetrySendImpl = { 584 _sendingEnabled: false, 585 // Tracks the shutdown state. 586 _shutdown: false, 587 _logger: null, 588 // This tracks all pending ping requests to the server. 589 _pendingPingRequests: new Map(), 590 // This tracks all the pending async ping activity. 591 _pendingPingActivity: new Set(), 592 // This is true when running in the test infrastructure. 593 _testMode: false, 594 // This holds pings that we currently try and haven't persisted yet. 595 _currentPings: new Map(), 596 // Used to skip spawning the pingsender if OS is shutting down. 597 _isOSShutdown: false, 598 // Count of pending pings that were overdue. 599 _overduePingCount: 0, 600 // Has the network shut down, making it too late to send pings? 601 _tooLateToSend: false, 602 603 OBSERVER_TOPICS: [ 604 TOPIC_IDLE_DAILY, 605 TOPIC_QUIT_APPLICATION_GRANTED, 606 TOPIC_QUIT_APPLICATION_FORCED, 607 TOPIC_PROFILE_CHANGE_NET_TEARDOWN, 608 ], 609 610 OBSERVED_PREFERENCES: [ 611 TelemetryUtils.Preferences.TelemetryEnabled, 612 TelemetryUtils.Preferences.FhrUploadEnabled, 613 ], 614 615 // Whether sending pings has been overridden. 616 get _overrideOfficialCheck() { 617 return Services.prefs.getBoolPref(TelemetryUtils.Preferences.OverrideOfficialCheck, false); 618 }, 619 620 get _log() { 621 if (!this._logger) { 622 this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX); 623 } 624 625 return this._logger; 626 }, 627 628 get overduePingsCount() { 629 return this._overduePingCount; 630 }, 631 632 get pendingPingRequests() { 633 return this._pendingPingRequests; 634 }, 635 636 get pendingPingCount() { 637 return TelemetryStorage.getPendingPingList().length + this._currentPings.size; 638 }, 639 640 setTestModeEnabled(testing) { 641 this._testMode = testing; 642 }, 643 644 earlyInit() { 645 this._annotateCrashReport(); 646 647 // Install the observer to detect OS shutdown early enough, so 648 // that we catch this before the delayed setup happens. 649 Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_FORCED); 650 Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_GRANTED); 651 }, 652 653 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]), 654 655 async setup(testing) { 656 this._log.trace("setup"); 657 658 this._testMode = testing; 659 660 Services.obs.addObserver(this, TOPIC_IDLE_DAILY); 661 Services.obs.addObserver(this, TOPIC_PROFILE_CHANGE_NET_TEARDOWN); 662 663 this._server = Services.prefs.getStringPref(TelemetryUtils.Preferences.Server, undefined); 664 this._sendingEnabled = true; 665 666 // Annotate crash reports so that crash pings are sent correctly and listen 667 // to pref changes to adjust the annotations accordingly. 668 for (let pref of this.OBSERVED_PREFERENCES) { 669 Services.prefs.addObserver(pref, this, true); 670 } 671 this._annotateCrashReport(); 672 673 // Check the pending pings on disk now. 674 try { 675 await this._checkPendingPings(); 676 } catch (ex) { 677 this._log.error("setup - _checkPendingPings rejected", ex); 678 } 679 680 // Enforce the pending pings storage quota. It could take a while so don't 681 // block on it. 682 TelemetryStorage.runEnforcePendingPingsQuotaTask(); 683 684 // Start sending pings, but don't block on this. 685 SendScheduler.triggerSendingPings(true); 686 }, 687 688 /** 689 * Triggers the crash report annotations depending on the current 690 * configuration. This communicates to the crash reporter if it can send a 691 * crash ping or not. This method can be called safely before setup() has 692 * been called. 693 */ 694 _annotateCrashReport() { 695 try { 696 const cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]; 697 if (cr) { 698 const crs = cr.getService(Ci.nsICrashReporter); 699 700 let clientId = ClientID.getCachedClientID(); 701 let server = this._server || Services.prefs.getStringPref(TelemetryUtils.Preferences.Server, undefined); 702 703 if (!this.sendingEnabled() || !TelemetryReportingPolicy.canUpload()) { 704 // If we cannot send pings then clear the crash annotations 705 crs.annotateCrashReport("TelemetryClientId", ""); 706 crs.annotateCrashReport("TelemetryServerURL", ""); 707 } else { 708 crs.annotateCrashReport("TelemetryClientId", clientId); 709 crs.annotateCrashReport("TelemetryServerURL", server); 710 } 711 } 712 } catch (e) { 713 // Ignore errors when crash reporting is disabled 714 } 715 }, 716 717 /** 718 * Discard old pings from the pending pings and detect overdue ones. 719 * @return {Boolean} True if we have overdue pings, false otherwise. 720 */ 721 async _checkPendingPings() { 722 // Scan the pending pings - that gives us a list sorted by last modified, descending. 723 let infos = await TelemetryStorage.loadPendingPingList(); 724 this._log.info("_checkPendingPings - pending ping count: " + infos.length); 725 if (infos.length == 0) { 726 this._log.trace("_checkPendingPings - no pending pings"); 727 return; 728 } 729 730 const now = Policy.now(); 731 732 // Check for overdue pings. 733 const overduePings = infos.filter((info) => 734 (now.getTime() - info.lastModificationDate) > OVERDUE_PING_FILE_AGE); 735 this._overduePingCount = overduePings.length; 736 737 // Submit the age of the pending pings. 738 for (let pingInfo of infos) { 739 const ageInDays = 740 Utils.millisecondsToDays(Math.abs(now.getTime() - pingInfo.lastModificationDate)); 741 Telemetry.getHistogramById("TELEMETRY_PENDING_PINGS_AGE").add(ageInDays); 742 } 743 }, 744 745 async shutdown() { 746 this._shutdown = true; 747 748 for (let pref of this.OBSERVED_PREFERENCES) { 749 // FIXME: When running tests this causes errors to be printed out if 750 // TelemetrySend.shutdown() is called twice in a row without calling 751 // TelemetrySend.setup() in-between. 752 Services.prefs.removeObserver(pref, this); 753 } 754 755 for (let topic of this.OBSERVER_TOPICS) { 756 try { 757 Services.obs.removeObserver(this, topic); 758 } catch (ex) { 759 this._log.error("shutdown - failed to remove observer for " + topic, ex); 760 } 761 } 762 763 // We can't send anymore now. 764 this._sendingEnabled = false; 765 766 // Cancel any outgoing requests. 767 await this._cancelOutgoingRequests(); 768 769 // Stop any active send tasks. 770 await SendScheduler.shutdown(); 771 772 // Wait for any outstanding async ping activity. 773 await this.promisePendingPingActivity(); 774 775 // Save any outstanding pending pings to disk. 776 await this._persistCurrentPings(); 777 }, 778 779 reset() { 780 this._log.trace("reset"); 781 782 this._shutdown = false; 783 this._currentPings = new Map(); 784 this._overduePingCount = 0; 785 this._tooLateToSend = false; 786 this._isOSShutdown = false; 787 this._sendingEnabled = true; 788 789 const histograms = [ 790 "TELEMETRY_SUCCESS", 791 "TELEMETRY_SEND_SUCCESS", 792 "TELEMETRY_SEND_FAILURE", 793 "TELEMETRY_SEND_FAILURE_TYPE", 794 ]; 795 796 histograms.forEach(h => Telemetry.getHistogramById(h).clear()); 797 798 return SendScheduler.reset(); 799 }, 800 801 /** 802 * Notify that we can start submitting data to the servers. 803 */ 804 notifyCanUpload() { 805 if (!this._sendingEnabled) { 806 this._log.trace("notifyCanUpload - notifying before sending is enabled. Ignoring."); 807 return Promise.resolve(); 808 } 809 // Let the scheduler trigger sending pings if possible, also inform the 810 // crash reporter that it can send crash pings if appropriate. 811 SendScheduler.triggerSendingPings(true); 812 this._annotateCrashReport(); 813 814 return this.promisePendingPingActivity(); 815 }, 816 817 observe(subject, topic, data) { 818 let setOSShutdown = () => { 819 this._log.trace("setOSShutdown - in OS shutdown"); 820 this._isOSShutdown = true; 821 }; 822 823 switch (topic) { 824 case TOPIC_IDLE_DAILY: 825 SendScheduler.triggerSendingPings(true); 826 break; 827 case TOPIC_QUIT_APPLICATION_FORCED: 828 setOSShutdown(); 829 break; 830 case TOPIC_QUIT_APPLICATION_GRANTED: 831 if (data == "syncShutdown") { 832 setOSShutdown(); 833 } 834 break; 835 case PREF_CHANGED_TOPIC: 836 if (this.OBSERVED_PREFERENCES.includes(data)) { 837 this._annotateCrashReport(); 838 } 839 break; 840 case TOPIC_PROFILE_CHANGE_NET_TEARDOWN: 841 this._tooLateToSend = true; 842 break; 843 } 844 }, 845 846 /** 847 * Spawn the PingSender process that sends a ping. This function does 848 * not return an error or throw, it only logs an error. 849 * 850 * Even if the function doesn't fail, it doesn't mean that the ping was 851 * successfully sent, as we have no control over the spawned process. If it, 852 * succeeds, the ping is eventually removed from the disk to prevent duplicated 853 * submissions. 854 * 855 * @param {String} pingId The id of the ping to send. 856 * @param {String} submissionURL The complete Telemetry-compliant URL for the ping. 857 */ 858 _sendWithPingSender(pingId, submissionURL) { 859 this._log.trace("_sendWithPingSender - sending " + pingId + " to " + submissionURL); 860 try { 861 const pingPath = OS.Path.join(TelemetryStorage.pingDirectoryPath, pingId); 862 this.runPingSender(submissionURL, pingPath); 863 } catch (e) { 864 this._log.error("_sendWithPingSender - failed to submit ping", e); 865 } 866 }, 867 868 submitPing(ping, options) { 869 this._log.trace("submitPing - ping id: " + ping.id + ", options: " + JSON.stringify(options)); 870 871 if (!this.sendingEnabled(ping)) { 872 this._log.trace("submitPing - Telemetry is not allowed to send pings."); 873 return Promise.resolve(); 874 } 875 876 // Send the ping using the PingSender, if requested and the user was 877 // notified of our policy. We don't support the pingsender on Android, 878 // so ignore this option on that platform (see bug 1335917). 879 // Moreover, if the OS is shutting down, we don't want to spawn the 880 // pingsender as it could unnecessarily slow down OS shutdown. 881 // Additionally, it could be be killed before it can complete its tasks, 882 // for example after successfully sending the ping but before removing 883 // the copy from the disk, resulting in receiving duplicate pings when 884 // Firefox restarts. 885 if (options.usePingSender && 886 !this._isOSShutdown && 887 TelemetryReportingPolicy.canUpload() && 888 AppConstants.platform != "android") { 889 const url = this._buildSubmissionURL(ping); 890 // Serialize the ping to the disk and then spawn the PingSender. 891 return savePing(ping).then(() => this._sendWithPingSender(ping.id, url)); 892 } 893 894 if (!this.canSendNow) { 895 // Sending is disabled or throttled, add this to the persisted pending pings. 896 this._log.trace("submitPing - can't send ping now, persisting to disk - " + 897 "canSendNow: " + this.canSendNow); 898 return savePing(ping); 899 } 900 901 // Let the scheduler trigger sending pings if possible. 902 // As a safety mechanism, this resets any currently active throttling. 903 this._log.trace("submitPing - can send pings, trying to send now"); 904 this._currentPings.set(ping.id, ping); 905 SendScheduler.triggerSendingPings(true); 906 return Promise.resolve(); 907 }, 908 909 /** 910 * Only used in tests. 911 */ 912 setServer(server) { 913 this._log.trace("setServer", server); 914 this._server = server; 915 }, 916 917 /** 918 * Clear out unpersisted, yet to be sent, pings and cancel outgoing ping requests. 919 */ 920 async clearCurrentPings() { 921 if (this._shutdown) { 922 this._log.trace("clearCurrentPings - in shutdown, bailing out"); 923 return; 924 } 925 926 // Temporarily disable the scheduler. It must not try to reschedule ping sending 927 // while we're deleting them. 928 await SendScheduler.shutdown(); 929 930 // Now that the ping activity has settled, abort outstanding ping requests. 931 this._cancelOutgoingRequests(); 932 933 // Also, purge current pings. 934 this._currentPings.clear(); 935 936 // We might have been interrupted and shutdown could have been started. 937 // We need to bail out in that case to avoid triggering send activity etc. 938 // at unexpected times. 939 if (this._shutdown) { 940 this._log.trace("clearCurrentPings - in shutdown, not spinning SendScheduler up again"); 941 return; 942 } 943 944 // Enable the scheduler again and spin the send task. 945 SendScheduler.start(); 946 SendScheduler.triggerSendingPings(true); 947 }, 948 949 _cancelOutgoingRequests() { 950 // Abort any pending ping XHRs. 951 for (let [id, request] of this._pendingPingRequests) { 952 this._log.trace("_cancelOutgoingRequests - aborting ping request for id " + id); 953 try { 954 request.abort(); 955 } catch (e) { 956 this._log.error("_cancelOutgoingRequests - failed to abort request for id " + id, e); 957 } 958 } 959 this._pendingPingRequests.clear(); 960 }, 961 962 sendPings(currentPings, persistedPingIds) { 963 let pingSends = []; 964 965 // Prioritize health pings to enable low-latency monitoring. 966 currentPings = [ 967 ...currentPings.filter(ping => ping.type === "health"), 968 ...currentPings.filter(ping => ping.type !== "health"), 969 ]; 970 971 for (let current of currentPings) { 972 let ping = current; 973 let p = (async () => { 974 try { 975 await this._doPing(ping, ping.id, false); 976 } catch (ex) { 977 this._log.info("sendPings - ping " + ping.id + " not sent, saving to disk", ex); 978 // Deletion pings must be saved to a special location. 979 await savePing(ping); 980 } finally { 981 this._currentPings.delete(ping.id); 982 } 983 })(); 984 985 this._trackPendingPingTask(p); 986 pingSends.push(p); 987 } 988 989 if (persistedPingIds.length > 0) { 990 pingSends.push(this._sendPersistedPings(persistedPingIds).catch((ex) => { 991 this._log.info("sendPings - persisted pings not sent", ex); 992 })); 993 } 994 995 return Promise.all(pingSends); 996 }, 997 998 /** 999 * Send the persisted pings to the server. 1000 * 1001 * @param {Array<string>} List of ping ids that should be sent. 1002 * 1003 * @return Promise A promise that is resolved when all pings finished sending or failed. 1004 */ 1005 async _sendPersistedPings(pingIds) { 1006 this._log.trace("sendPersistedPings"); 1007 1008 if (TelemetryStorage.pendingPingCount < 1) { 1009 this._log.trace("_sendPersistedPings - no pings to send"); 1010 return; 1011 } 1012 1013 if (pingIds.length < 1) { 1014 this._log.trace("sendPersistedPings - no pings to send"); 1015 return; 1016 } 1017 1018 // We can send now. 1019 // If there are any send failures, _doPing() sets up handlers that e.g. trigger backoff timer behavior. 1020 this._log.trace("sendPersistedPings - sending " + pingIds.length + " pings"); 1021 let pingSendPromises = []; 1022 for (let pingId of pingIds) { 1023 const id = pingId; 1024 pingSendPromises.push( 1025 TelemetryStorage.loadPendingPing(id) 1026 .then((data) => this._doPing(data, id, true)) 1027 .catch(e => this._log.error("sendPersistedPings - failed to send ping " + id, e))); 1028 } 1029 1030 let promise = Promise.all(pingSendPromises); 1031 this._trackPendingPingTask(promise); 1032 await promise; 1033 }, 1034 1035 _onPingRequestFinished(success, startTime, id, isPersisted) { 1036 this._log.trace("_onPingRequestFinished - success: " + success + ", persisted: " + isPersisted); 1037 1038 let sendId = success ? "TELEMETRY_SEND_SUCCESS" : "TELEMETRY_SEND_FAILURE"; 1039 let hsend = Telemetry.getHistogramById(sendId); 1040 let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS"); 1041 1042 hsend.add(Utils.monotonicNow() - startTime); 1043 hsuccess.add(success); 1044 1045 if (!success) { 1046 // Let the scheduler know about send failures for triggering backoff timeouts. 1047 SendScheduler.notifySendsFailed(); 1048 } 1049 1050 if (success && isPersisted) { 1051 if (TelemetryStorage.isDeletionPing(id)) { 1052 return TelemetryStorage.removeDeletionPing(); 1053 } 1054 return TelemetryStorage.removePendingPing(id); 1055 } 1056 return Promise.resolve(); 1057 }, 1058 1059 _buildSubmissionURL(ping) { 1060 const version = isV4PingFormat(ping) ? PING_FORMAT_VERSION : 1; 1061 return this._server + this._getSubmissionPath(ping) + "?v=" + version; 1062 }, 1063 1064 _getSubmissionPath(ping) { 1065 // The new ping format contains an "application" section, the old one doesn't. 1066 let pathComponents; 1067 if (isV4PingFormat(ping)) { 1068 // We insert the Ping id in the URL to simplify server handling of duplicated 1069 // pings. 1070 let app = ping.application; 1071 pathComponents = [ 1072 ping.id, ping.type, app.name, app.version, app.channel, app.buildId 1073 ]; 1074 } else { 1075 // This is a ping in the old format. 1076 if (!("slug" in ping)) { 1077 // That's odd, we don't have a slug. Generate one so that TelemetryStorage.jsm works. 1078 ping.slug = Utils.generateUUID(); 1079 } 1080 1081 // Do we have enough info to build a submission URL? 1082 let payload = ("payload" in ping) ? ping.payload : null; 1083 if (payload && ("info" in payload)) { 1084 let info = ping.payload.info; 1085 pathComponents = [ ping.slug, info.reason, info.appName, info.appVersion, 1086 info.appUpdateChannel, info.appBuildID ]; 1087 } else { 1088 // Only use the UUID as the slug. 1089 pathComponents = [ ping.slug ]; 1090 } 1091 } 1092 1093 let slug = pathComponents.join("/"); 1094 return "/submit/telemetry/" + slug; 1095 }, 1096 1097 _doPing(ping, id, isPersisted) { 1098 if (!this.sendingEnabled(ping)) { 1099 // We can't send the pings to the server, so don't try to. 1100 this._log.trace("_doPing - Can't send ping " + ping.id); 1101 return Promise.resolve(); 1102 } 1103 1104 if (this._tooLateToSend) { 1105 // Too late to send now. Reject so we pend the ping to send it next time. 1106 this._log.trace("_doPing - Too late to send ping " + ping.id); 1107 Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE_TYPE").add("eTooLate"); 1108 return Promise.reject(); 1109 } 1110 1111 this._log.trace("_doPing - server: " + this._server + ", persisted: " + isPersisted + 1112 ", id: " + id); 1113 1114 const url = this._buildSubmissionURL(ping); 1115 1116 // Don't send cookies with these requests. 1117 let request = new ServiceRequest({mozAnon: true}); 1118 request.mozBackgroundRequest = true; 1119 request.timeout = Policy.pingSubmissionTimeout(); 1120 1121 request.open("POST", url, true); 1122 request.overrideMimeType("text/plain"); 1123 request.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); 1124 request.setRequestHeader("Date", Policy.now().toUTCString()); 1125 1126 this._pendingPingRequests.set(id, request); 1127 1128 // Prevent the request channel from running though URLClassifier (bug 1296802) 1129 request.channel.loadFlags &= ~Ci.nsIChannel.LOAD_CLASSIFY_URI; 1130 1131 const monotonicStartTime = Utils.monotonicNow(); 1132 let deferred = PromiseUtils.defer(); 1133 1134 let onRequestFinished = (success, event) => { 1135 let onCompletion = () => { 1136 if (success) { 1137 let histogram = Telemetry.getHistogramById("TELEMETRY_SUCCESSFUL_SEND_PINGS_SIZE_KB"); 1138 histogram.add(compressedPingSizeKB); 1139 deferred.resolve(); 1140 } else { 1141 let histogram = Telemetry.getHistogramById("TELEMETRY_FAILED_SEND_PINGS_SIZE_KB"); 1142 histogram.add(compressedPingSizeKB); 1143 deferred.reject(event); 1144 } 1145 }; 1146 1147 this._pendingPingRequests.delete(id); 1148 this._onPingRequestFinished(success, monotonicStartTime, id, isPersisted) 1149 .then(() => onCompletion(), 1150 (error) => { 1151 this._log.error("_doPing - request success: " + success + ", error: " + error); 1152 onCompletion(); 1153 }); 1154 }; 1155 1156 let errorhandler = (event) => { 1157 let failure = event.type; 1158 if (failure === "error") { 1159 failure = XHR_ERROR_TYPE[request.errorCode]; 1160 } 1161 1162 TelemetryHealthPing.recordSendFailure(failure); 1163 Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE_TYPE").add(failure); 1164 1165 this._log.error("_doPing - error making request to " + url + ": " + failure); 1166 onRequestFinished(false, event); 1167 }; 1168 request.onerror = errorhandler; 1169 request.ontimeout = errorhandler; 1170 request.onabort = errorhandler; 1171 1172 request.onload = (event) => { 1173 let status = request.status; 1174 let statusClass = status - (status % 100); 1175 let success = false; 1176 1177 if (statusClass === 200) { 1178 // We can treat all 2XX as success. 1179 this._log.info("_doPing - successfully loaded, status: " + status); 1180 success = true; 1181 } else if (statusClass === 400) { 1182 // 4XX means that something with the request was broken. 1183 this._log.error("_doPing - error submitting to " + url + ", status: " + status 1184 + " - ping request broken?"); 1185 Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").add(); 1186 // TODO: we should handle this better, but for now we should avoid resubmitting 1187 // broken requests by pretending success. 1188 success = true; 1189 } else if (statusClass === 500) { 1190 // 5XX means there was a server-side error and we should try again later. 1191 this._log.error("_doPing - error submitting to " + url + ", status: " + status 1192 + " - server error, should retry later"); 1193 } else { 1194 // We received an unexpected status code. 1195 this._log.error("_doPing - error submitting to " + url + ", status: " + status 1196 + ", type: " + event.type); 1197 } 1198 1199 onRequestFinished(success, event); 1200 }; 1201 1202 // If that's a legacy ping format, just send its payload. 1203 let networkPayload = isV4PingFormat(ping) ? ping : ping.payload; 1204 request.setRequestHeader("Content-Encoding", "gzip"); 1205 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1206 .createInstance(Ci.nsIScriptableUnicodeConverter); 1207 converter.charset = "UTF-8"; 1208 let startTime = Utils.monotonicNow(); 1209 let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(networkPayload)); 1210 utf8Payload += converter.Finish(); 1211 Telemetry.getHistogramById("TELEMETRY_STRINGIFY").add(Utils.monotonicNow() - startTime); 1212 1213 // Check the size and drop pings which are too big. 1214 const pingSizeBytes = utf8Payload.length; 1215 if (pingSizeBytes > TelemetryStorage.MAXIMUM_PING_SIZE) { 1216 this._log.error("_doPing - submitted ping exceeds the size limit, size: " + pingSizeBytes); 1217 Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").add(); 1218 Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB") 1219 .add(Math.floor(pingSizeBytes / 1024 / 1024)); 1220 // We don't need to call |request.abort()| as it was not sent yet. 1221 this._pendingPingRequests.delete(id); 1222 1223 TelemetryHealthPing.recordDiscardedPing(ping.type); 1224 return TelemetryStorage.removePendingPing(id); 1225 } 1226 1227 let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"] 1228 .createInstance(Ci.nsIStringInputStream); 1229 startTime = Utils.monotonicNow(); 1230 payloadStream.data = gzipCompressString(utf8Payload); 1231 1232 const compressedPingSizeKB = Math.floor(payloadStream.data.length / 1024); 1233 Telemetry.getHistogramById("TELEMETRY_COMPRESS").add(Utils.monotonicNow() - startTime); 1234 request.sendInputStream(payloadStream); 1235 1236 return deferred.promise; 1237 }, 1238 1239 /** 1240 * Check if sending is temporarily disabled. 1241 * @return {Boolean} True if we can send pings to the server right now, false if 1242 * sending is temporarily disabled. 1243 */ 1244 get canSendNow() { 1245 // If the reporting policy was not accepted yet, don't send pings. 1246 if (!TelemetryReportingPolicy.canUpload()) { 1247 return false; 1248 } 1249 1250 return this._sendingEnabled; 1251 }, 1252 1253 /** 1254 * Check if sending is disabled. If FHR is not allowed to upload, 1255 * pings are not sent to the server (Telemetry is a sub-feature of FHR). If trying 1256 * to send a deletion ping, don't block it. 1257 * If unified telemetry is off, don't send pings if Telemetry is disabled. 1258 * 1259 * @param {Object} [ping=null] A ping to be checked. 1260 * @return {Boolean} True if pings can be send to the servers, false otherwise. 1261 */ 1262 sendingEnabled(ping = null) { 1263 // We only send pings from official builds, but allow overriding this for tests. 1264 if (!Telemetry.isOfficialTelemetry && 1265 !this._testMode && 1266 !this._overrideOfficialCheck) { 1267 return false; 1268 } 1269 1270 // With unified Telemetry, the FHR upload setting controls whether we can send pings. 1271 // The Telemetry pref enables sending extended data sets instead. 1272 if (IS_UNIFIED_TELEMETRY) { 1273 // Deletion pings are sent even if the upload is disabled. 1274 if (ping && isDeletionPing(ping)) { 1275 return true; 1276 } 1277 return Services.prefs.getBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, false); 1278 } 1279 1280 // Without unified Telemetry, the Telemetry enabled pref controls ping sending. 1281 return Utils.isTelemetryEnabled; 1282 }, 1283 1284 /** 1285 * Track any pending ping send and save tasks through the promise passed here. 1286 * This is needed to block shutdown on any outstanding ping activity. 1287 */ 1288 _trackPendingPingTask(promise) { 1289 let clear = () => this._pendingPingActivity.delete(promise); 1290 promise.then(clear, clear); 1291 this._pendingPingActivity.add(promise); 1292 }, 1293 1294 /** 1295 * Return a promise that allows to wait on pending pings. 1296 * @return {Object<Promise>} A promise resolved when all the pending pings promises 1297 * are resolved. 1298 */ 1299 promisePendingPingActivity() { 1300 this._log.trace("promisePendingPingActivity - Waiting for ping task"); 1301 let p = Array.from(this._pendingPingActivity, p => p.catch(ex => { 1302 this._log.error("promisePendingPingActivity - ping activity had an error", ex); 1303 })); 1304 p.push(SendScheduler.waitOnSendTask()); 1305 return Promise.all(p); 1306 }, 1307 1308 async _persistCurrentPings() { 1309 for (let [id, ping] of this._currentPings) { 1310 try { 1311 await savePing(ping); 1312 this._log.trace("_persistCurrentPings - saved ping " + id); 1313 } catch (ex) { 1314 this._log.error("_persistCurrentPings - failed to save ping " + id, ex); 1315 } finally { 1316 this._currentPings.delete(id); 1317 } 1318 } 1319 }, 1320 1321 /** 1322 * Returns the current pending, not yet persisted, pings, newest first. 1323 */ 1324 getUnpersistedPings() { 1325 let current = [...this._currentPings.values()]; 1326 current.reverse(); 1327 return current; 1328 }, 1329 1330 getShutdownState() { 1331 return { 1332 sendingEnabled: this._sendingEnabled, 1333 pendingPingRequestCount: this._pendingPingRequests.size, 1334 pendingPingActivityCount: this._pendingPingActivity.size, 1335 unpersistedPingCount: this._currentPings.size, 1336 persistedPingCount: TelemetryStorage.getPendingPingList().length, 1337 schedulerState: SendScheduler.getShutdownState(), 1338 }; 1339 }, 1340 1341 runPingSender(url, pingPath) { 1342 if (AppConstants.platform === "android") { 1343 throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1344 } 1345 1346 const exeName = AppConstants.platform === "win" ? "pingsender.exe" 1347 : "pingsender"; 1348 1349 let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile); 1350 exe.append(exeName); 1351 1352 let process = Cc["@mozilla.org/process/util;1"] 1353 .createInstance(Ci.nsIProcess); 1354 process.init(exe); 1355 process.startHidden = true; 1356 process.noShell = true; 1357 process.run(/* blocking */ false, [url, pingPath], 2); 1358 }, 1359}; 1360