diff options
Diffstat (limited to 'toolkit/mozapps/update/UpdateTelemetry.sys.mjs')
-rw-r--r-- | toolkit/mozapps/update/UpdateTelemetry.sys.mjs | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/UpdateTelemetry.sys.mjs b/toolkit/mozapps/update/UpdateTelemetry.sys.mjs new file mode 100644 index 0000000000..20fb0ab4a4 --- /dev/null +++ b/toolkit/mozapps/update/UpdateTelemetry.sys.mjs @@ -0,0 +1,652 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +import { + BitsError, + BitsUnknownError, +} from "resource://gre/modules/Bits.sys.mjs"; + +export var AUSTLMY = { + // Telemetry for the application update background update check occurs when + // the background update timer fires after the update interval which is + // determined by the app.update.interval preference and its telemetry + // histogram IDs have the suffix '_NOTIFY'. + // Telemetry for the externally initiated background update check occurs when + // a call is made to |checkForBackgroundUpdates| which is typically initiated + // by an application when it has determined that the application should have + // received an update. This has separate telemetry so it is possible to + // analyze using the telemetry data systems that have not been updating when + // they should have. + + // The update check was performed by the call to checkForBackgroundUpdates in + // nsUpdateService.js. + EXTERNAL: "EXTERNAL", + // The update check was performed by the call to notify in nsUpdateService.js. + NOTIFY: "NOTIFY", + // The update check was performed after an update is already ready. There is + // currently no way for a user to initiate an update check when there is a + // ready update (the UI just prompts you to install the ready update). So + // subsequent update checks are necessarily "notify" update checks, not + // "external" ones. + SUBSEQUENT: "SUBSEQUENT", + + /** + * Values for the UPDATE_CHECK_CODE_NOTIFY and UPDATE_CHECK_CODE_EXTERNAL + * Telemetry histograms. + */ + // No update found (no notification) + CHK_NO_UPDATE_FOUND: 0, + // Update will be downloaded in the background (background download) + CHK_DOWNLOAD_UPDATE: 1, + // Showing prompt due to preference (update notification) + CHK_SHOWPROMPT_PREF: 3, + // Already has an active update in progress (no notification) + CHK_HAS_ACTIVEUPDATE: 8, + // A background download is already in progress (no notification) + CHK_IS_DOWNLOADING: 9, + // An update is already staged (no notification) + CHK_IS_STAGED: 10, + // An update is already downloaded (no notification) + CHK_IS_DOWNLOADED: 11, + // Note: codes 12-13 were removed along with the |app.update.enabled| pref. + // Unable to check for updates per hasUpdateMutex() (no notification) + CHK_NO_MUTEX: 14, + // Unable to check for updates per gCanCheckForUpdates (no notification). This + // should be covered by other codes and is recorded just in case. + CHK_UNABLE_TO_CHECK: 15, + // Note: code 16 was removed when the feature for disabling updates for the + // session was removed. + // Unable to perform a background check while offline (no notification) + CHK_OFFLINE: 17, + // Note: codes 18 - 21 were removed along with the certificate checking code. + // General update check failure and threshold reached + // (check failure notification) + CHK_GENERAL_ERROR_PROMPT: 22, + // General update check failure and threshold not reached (no notification) + CHK_GENERAL_ERROR_SILENT: 23, + // No compatible update found though there were updates (no notification) + CHK_NO_COMPAT_UPDATE_FOUND: 24, + // Update found for a previous version (no notification) + CHK_UPDATE_PREVIOUS_VERSION: 25, + // Update found without a type attribute (no notification) + CHK_UPDATE_INVALID_TYPE: 27, + // The system is no longer supported (system unsupported notification) + CHK_UNSUPPORTED: 28, + // Unable to apply updates (manual install to update notification) + CHK_UNABLE_TO_APPLY: 29, + // Unable to check for updates due to no OS version (no notification) + CHK_NO_OS_VERSION: 30, + // Unable to check for updates due to no OS ABI (no notification) + CHK_NO_OS_ABI: 31, + // Invalid update url (no notification) + CHK_INVALID_DEFAULT_URL: 32, + // Update elevation failures or cancelations threshold reached for this + // version, OSX only (no notification) + CHK_ELEVATION_DISABLED_FOR_VERSION: 35, + // User opted out of elevated updates for the available update version, OSX + // only (no notification) + CHK_ELEVATION_OPTOUT_FOR_VERSION: 36, + // Update checks disabled by enterprise policy + CHK_DISABLED_BY_POLICY: 37, + // Update check failed due to write error + CHK_ERR_WRITE_FAILURE: 38, + // Update check was delayed because another instance of the application is + // currently running + CHK_OTHER_INSTANCE: 39, + // Cannot yet download update because no partial patch is available and an + // update has already been downloaded. + CHK_NO_PARTIAL_PATCH: 40, + + /** + * Submit a telemetry ping for the update check result code or a telemetry + * ping for a count type histogram count when no update was found. The no + * update found ping is separate since it is the typical result, is less + * interesting than the other result codes, and it is easier to analyze the + * other codes without including it. + * + * @param aSuffix + * The histogram id suffix for histogram IDs: + * UPDATE_CHECK_CODE_EXTERNAL + * UPDATE_CHECK_CODE_NOTIFY + * UPDATE_CHECK_CODE_SUBSEQUENT + * UPDATE_CHECK_NO_UPDATE_EXTERNAL + * UPDATE_CHECK_NO_UPDATE_NOTIFY + * UPDATE_CHECK_NO_UPDATE_SUBSEQUENT + * @param aCode + * An integer value as defined by the values that start with CHK_ in + * the above section. + */ + pingCheckCode: function UT_pingCheckCode(aSuffix, aCode) { + try { + if (aCode == this.CHK_NO_UPDATE_FOUND) { + let id = "UPDATE_CHECK_NO_UPDATE_" + aSuffix; + // count type histogram + Services.telemetry.getHistogramById(id).add(); + } else { + let id = "UPDATE_CHECK_CODE_" + aSuffix; + // enumerated type histogram + Services.telemetry.getHistogramById(id).add(aCode); + } + } catch (e) { + console.error(e); + } + }, + + /** + * Submit a telemetry ping for a failed update check's unhandled error code + * when the pingCheckCode is CHK_GENERAL_ERROR_SILENT. The histogram is a + * keyed count type with key names that are prefixed with 'AUS_CHECK_EX_ERR_'. + * + * @param aSuffix + * The histogram id suffix for histogram IDs: + * UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL + * UPDATE_CHECK_EXTENDED_ERROR_NOTIFY + * UPDATE_CHECK_EXTENDED_ERROR_SUBSEQUENT + * @param aCode + * The extended error value return by a failed update check. + */ + pingCheckExError: function UT_pingCheckExError(aSuffix, aCode) { + try { + let id = "UPDATE_CHECK_EXTENDED_ERROR_" + aSuffix; + let val = "AUS_CHECK_EX_ERR_" + aCode; + // keyed count type histogram + Services.telemetry.getKeyedHistogramById(id).add(val); + } catch (e) { + console.error(e); + } + }, + + // The state code and if present the status error code were read on startup. + STARTUP: "STARTUP", + // The state code and status error code if present were read after staging. + STAGE: "STAGE", + + // Patch type Complete + PATCH_COMPLETE: "COMPLETE", + // Patch type partial + PATCH_PARTIAL: "PARTIAL", + // Patch type unknown + PATCH_UNKNOWN: "UNKNOWN", + + /** + * Values for the UPDATE_DOWNLOAD_CODE_COMPLETE, UPDATE_DOWNLOAD_CODE_PARTIAL, + * and UPDATE_DOWNLOAD_CODE_UNKNOWN Telemetry histograms. + */ + DWNLD_SUCCESS: 0, + DWNLD_RETRY_OFFLINE: 1, + DWNLD_RETRY_NET_TIMEOUT: 2, + DWNLD_RETRY_CONNECTION_REFUSED: 3, + DWNLD_RETRY_NET_RESET: 4, + DWNLD_ERR_NO_UPDATE: 5, + DWNLD_ERR_NO_UPDATE_PATCH: 6, + DWNLD_ERR_PATCH_SIZE_LARGER: 8, + DWNLD_ERR_PATCH_SIZE_NOT_EQUAL: 9, + DWNLD_ERR_BINDING_ABORTED: 10, + DWNLD_ERR_ABORT: 11, + DWNLD_ERR_DOCUMENT_NOT_CACHED: 12, + DWNLD_ERR_VERIFY_NO_REQUEST: 13, + DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14, + DWNLD_ERR_WRITE_FAILURE: 15, + // Temporary failure code to see if there are failures without an update phase + DWNLD_UNKNOWN_PHASE_ERR_WRITE_FAILURE: 40, + + /** + * Submit a telemetry ping for the update download result code. + * + * @param aIsComplete + * If true the histogram is for a patch type complete, if false the + * histogram is for a patch type partial, and when undefined the + * histogram is for an unknown patch type. This is used to determine + * the histogram ID out of the following histogram IDs: + * UPDATE_DOWNLOAD_CODE_COMPLETE + * UPDATE_DOWNLOAD_CODE_PARTIAL + * UPDATE_DOWNLOAD_CODE_UNKNOWN + * @param aCode + * An integer value as defined by the values that start with DWNLD_ in + * the above section. + */ + pingDownloadCode: function UT_pingDownloadCode(aIsComplete, aCode) { + let patchType = this.PATCH_UNKNOWN; + if (aIsComplete === true) { + patchType = this.PATCH_COMPLETE; + } else if (aIsComplete === false) { + patchType = this.PATCH_PARTIAL; + } + try { + let id = "UPDATE_DOWNLOAD_CODE_" + patchType; + // enumerated type histogram + Services.telemetry.getHistogramById(id).add(aCode); + } catch (e) { + console.error(e); + } + }, + + // Previous state codes are defined in pingStateAndStatusCodes() in + // nsUpdateService.js + STATE_WRITE_FAILURE: 14, + + /** + * Submit a telemetry ping for the update status state code. + * + * @param aSuffix + * The histogram id suffix for histogram IDs: + * UPDATE_STATE_CODE_COMPLETE_STARTUP + * UPDATE_STATE_CODE_PARTIAL_STARTUP + * UPDATE_STATE_CODE_UNKNOWN_STARTUP + * UPDATE_STATE_CODE_COMPLETE_STAGE + * UPDATE_STATE_CODE_PARTIAL_STAGE + * UPDATE_STATE_CODE_UNKNOWN_STAGE + * @param aCode + * An integer value as defined by the values that start with STATE_ in + * the above section for the update state from the update.status file. + */ + pingStateCode: function UT_pingStateCode(aSuffix, aCode) { + try { + let id = "UPDATE_STATE_CODE_" + aSuffix; + // enumerated type histogram + Services.telemetry.getHistogramById(id).add(aCode); + } catch (e) { + console.error(e); + } + }, + + /** + * Submit a telemetry ping for the update status error code. This does not + * submit a success value which can be determined from the state code. + * + * @param aSuffix + * The histogram id suffix for histogram IDs: + * UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP + * UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP + * UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP + * UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE + * UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE + * UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE + * @param aCode + * An integer value for the error code from the update.status file. + */ + pingStatusErrorCode: function UT_pingStatusErrorCode(aSuffix, aCode) { + try { + let id = "UPDATE_STATUS_ERROR_CODE_" + aSuffix; + // enumerated type histogram + Services.telemetry.getHistogramById(id).add(aCode); + } catch (e) { + console.error(e); + } + }, + + /** + * Submit a telemetry ping for a failing binary transparency result. + * + * @param aSuffix + * Key to use on the update.binarytransparencyresult collection. + * Must be one of "COMPLETE_STARTUP", "PARTIAL_STARTUP", + * "UNKNOWN_STARTUP", "COMPLETE_STAGE", "PARTIAL_STAGE", + * "UNKNOWN_STAGE". + * @param aCode + * An integer value for the error code from the update.bt file. + */ + pingBinaryTransparencyResult: function UT_pingBinaryTransparencyResult( + aSuffix, + aCode + ) { + try { + let id = "update.binarytransparencyresult"; + let key = aSuffix.toLowerCase().replace("_", "-"); + Services.telemetry.keyedScalarSet(id, key, aCode); + } catch (e) { + console.error(e); + } + }, + + /** + * Records a failed BITS update download using Telemetry. + * In addition to the BITS Result histogram, this also sends an + * update.bitshresult scalar value. + * + * @param aIsComplete + * If true the histogram is for a patch type complete, if false the + * histogram is for a patch type partial. This will determine the + * histogram id out of the following histogram ids: + * UPDATE_BITS_RESULT_COMPLETE + * UPDATE_BITS_RESULT_PARTIAL + * This value is also used to determine the key for the keyed scalar + * update.bitshresult (key is either "COMPLETE" or "PARTIAL") + * @param aError + * The BitsError that occurred. See Bits.jsm for details on BitsError. + */ + pingBitsError: function UT_pingBitsError(aIsComplete, aError) { + if (AppConstants.platform != "win") { + console.error( + "Warning: Attempted to submit BITS telemetry on a " + + "non-Windows platform" + ); + return; + } + if (!(aError instanceof BitsError)) { + console.error("Error sending BITS Error ping: Error is not a BitsError"); + aError = new BitsUnknownError(); + } + // Coerce the error to integer + let type = +aError.type; + if (isNaN(type)) { + console.error( + "Error sending BITS Error ping: Either error is not a " + + "BitsError, or error type is not an integer." + ); + type = Ci.nsIBits.ERROR_TYPE_UNKNOWN; + } else if (type == Ci.nsIBits.ERROR_TYPE_SUCCESS) { + console.error( + "Error sending BITS Error ping: The error type must not " + + "be the success type." + ); + type = Ci.nsIBits.ERROR_TYPE_UNKNOWN; + } + this._pingBitsResult(aIsComplete, type); + + if (aError.codeType == Ci.nsIBits.ERROR_CODE_TYPE_HRESULT) { + let scalarKey; + if (aIsComplete) { + scalarKey = this.PATCH_COMPLETE; + } else { + scalarKey = this.PATCH_PARTIAL; + } + try { + Services.telemetry.keyedScalarSet( + "update.bitshresult", + scalarKey, + aError.code + ); + } catch (e) { + console.error(e); + } + } + }, + + /** + * Records a successful BITS update download using Telemetry. + * + * @param aIsComplete + * If true the histogram is for a patch type complete, if false the + * histogram is for a patch type partial. This will determine the + * histogram id out of the following histogram ids: + * UPDATE_BITS_RESULT_COMPLETE + * UPDATE_BITS_RESULT_PARTIAL + */ + pingBitsSuccess: function UT_pingBitsSuccess(aIsComplete) { + if (AppConstants.platform != "win") { + console.error( + "Warning: Attempted to submit BITS telemetry on a " + + "non-Windows platform" + ); + return; + } + this._pingBitsResult(aIsComplete, Ci.nsIBits.ERROR_TYPE_SUCCESS); + }, + + /** + * This is the helper function that does all the work for pingBitsError and + * pingBitsSuccess. It submits a telemetry ping indicating the result of the + * BITS update download. + * + * @param aIsComplete + * If true the histogram is for a patch type complete, if false the + * histogram is for a patch type partial. This will determine the + * histogram id out of the following histogram ids: + * UPDATE_BITS_RESULT_COMPLETE + * UPDATE_BITS_RESULT_PARTIAL + * @param aResultType + * The result code. This will be one of the ERROR_TYPE_* values defined + * in the nsIBits interface. + */ + _pingBitsResult: function UT_pingBitsResult(aIsComplete, aResultType) { + let patchType; + if (aIsComplete) { + patchType = this.PATCH_COMPLETE; + } else { + patchType = this.PATCH_PARTIAL; + } + try { + let id = "UPDATE_BITS_RESULT_" + patchType; + Services.telemetry.getHistogramById(id).add(aResultType); + } catch (e) { + console.error(e); + } + }, + + /** + * Submit the interval in days since the last notification for this background + * update check or a boolean if the last notification is in the future. + * + * @param aSuffix + * The histogram id suffix for histogram IDs: + * UPDATE_INVALID_LASTUPDATETIME_EXTERNAL + * UPDATE_INVALID_LASTUPDATETIME_NOTIFY + * UPDATE_INVALID_LASTUPDATETIME_SUBSEQUENT + * UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL + * UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY + * UPDATE_LAST_NOTIFY_INTERVAL_DAYS_SUBSEQUENT + */ + pingLastUpdateTime: function UT_pingLastUpdateTime(aSuffix) { + const PREF_APP_UPDATE_LASTUPDATETIME = + "app.update.lastUpdateTime.background-update-timer"; + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) { + let lastUpdateTimeSeconds = Services.prefs.getIntPref( + PREF_APP_UPDATE_LASTUPDATETIME + ); + if (lastUpdateTimeSeconds) { + let currentTimeSeconds = Math.round(Date.now() / 1000); + if (lastUpdateTimeSeconds > currentTimeSeconds) { + try { + let id = "UPDATE_INVALID_LASTUPDATETIME_" + aSuffix; + // count type histogram + Services.telemetry.getHistogramById(id).add(); + } catch (e) { + console.error(e); + } + } else { + let intervalDays = + (currentTimeSeconds - lastUpdateTimeSeconds) / (60 * 60 * 24); + try { + let id = "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_" + aSuffix; + // exponential type histogram + Services.telemetry.getHistogramById(id).add(intervalDays); + } catch (e) { + console.error(e); + } + } + } + } + }, + + /** + * Submit a telemetry ping for a boolean type histogram that indicates if the + * service is installed and a telemetry ping for a boolean type histogram that + * indicates if the service was at some point installed and is now + * uninstalled. + * + * @param aSuffix + * The histogram id suffix for histogram IDs: + * UPDATE_SERVICE_INSTALLED_EXTERNAL + * UPDATE_SERVICE_INSTALLED_NOTIFY + * UPDATE_SERVICE_INSTALLED_SUBSEQUENT + * UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL + * UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY + * UPDATE_SERVICE_MANUALLY_UNINSTALLED_SUBSEQUENT + * @param aInstalled + * Whether the service is installed. + */ + pingServiceInstallStatus: function UT_PSIS(aSuffix, aInstalled) { + // Report the error but don't throw since it is more important to + // successfully update than to throw. + if (!("@mozilla.org/windows-registry-key;1" in Cc)) { + console.error(Cr.NS_ERROR_NOT_AVAILABLE); + return; + } + + try { + let id = "UPDATE_SERVICE_INSTALLED_" + aSuffix; + // boolean type histogram + Services.telemetry.getHistogramById(id).add(aInstalled); + } catch (e) { + console.error(e); + } + + let attempted = 0; + try { + let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + wrk.open( + wrk.ROOT_KEY_LOCAL_MACHINE, + "SOFTWARE\\Mozilla\\MaintenanceService", + wrk.ACCESS_READ | wrk.WOW64_64 + ); + // Was the service at some point installed, but is now uninstalled? + attempted = wrk.readIntValue("Attempted"); + wrk.close(); + } catch (e) { + // Since this will throw if the registry key doesn't exist (e.g. the + // service has never been installed) don't report an error. + } + + try { + let id = "UPDATE_SERVICE_MANUALLY_UNINSTALLED_" + aSuffix; + if (!aInstalled && attempted) { + // count type histogram + Services.telemetry.getHistogramById(id).add(); + } + } catch (e) { + console.error(e); + } + }, + + /** + * Submit a telemetry ping for a count type histogram when the expected value + * does not equal the boolean value of a pref or if the pref isn't present + * when the expected value does not equal default value. This lessens the + * amount of data submitted to telemetry. + * + * @param aID + * The histogram ID to report to. + * @param aPref + * The preference to check. + * @param aDefault + * The default value when the preference isn't present. + * @param aExpected (optional) + * If specified and the value is the same as the value that will be + * added the value won't be added to telemetry. + */ + pingBoolPref: function UT_pingBoolPref(aID, aPref, aDefault, aExpected) { + try { + let val = aDefault; + if (Services.prefs.getPrefType(aPref) != Ci.nsIPrefBranch.PREF_INVALID) { + val = Services.prefs.getBoolPref(aPref); + } + if (val != aExpected) { + // count type histogram + Services.telemetry.getHistogramById(aID).add(); + } + } catch (e) { + console.error(e); + } + }, + + /** + * Submit a telemetry ping for a histogram with the integer value of a + * preference when it is not the expected value or the default value when it + * is not the expected value. This lessens the amount of data submitted to + * telemetry. + * + * @param aID + * The histogram ID to report to. + * @param aPref + * The preference to check. + * @param aDefault + * The default value when the pref is not set. + * @param aExpected (optional) + * If specified and the value is the same as the value that will be + * added the value won't be added to telemetry. + */ + pingIntPref: function UT_pingIntPref(aID, aPref, aDefault, aExpected) { + try { + let val = aDefault; + if (Services.prefs.getPrefType(aPref) != Ci.nsIPrefBranch.PREF_INVALID) { + val = Services.prefs.getIntPref(aPref); + } + if (aExpected === undefined || val != aExpected) { + // enumerated or exponential type histogram + Services.telemetry.getHistogramById(aID).add(val); + } + } catch (e) { + console.error(e); + } + }, + + /** + * Submit a telemetry ping for all histogram types that take a single + * parameter to the telemetry add function and the count type histogram when + * the aExpected parameter is specified. If the aExpected parameter is + * specified and it equals the value specified by the aValue + * parameter the telemetry submission will be skipped. + * + * @param aID + * The histogram ID to report to. + * @param aValue + * The value to add when aExpected is not defined or the value to + * check if it is equal to when aExpected is defined. + * @param aExpected (optional) + * If specified and the value is the same as the value specified by + * aValue parameter the submission will be skipped. + */ + pingGeneric: function UT_pingGeneric(aID, aValue, aExpected) { + try { + if (aExpected === undefined) { + Services.telemetry.getHistogramById(aID).add(aValue); + } else if (aValue != aExpected) { + // count type histogram + Services.telemetry.getHistogramById(aID).add(); + } + } catch (e) { + console.error(e); + } + }, + + /** + * Valid keys for the update.moveresult scalar. + */ + MOVE_RESULT_SUCCESS: "SUCCESS", + MOVE_RESULT_UNKNOWN_FAILURE: "UNKNOWN_FAILURE", + + /** + * Reports the passed result of attempting to move the downloading update + * into the ready update directory. + */ + pingMoveResult: function UT_pingMoveResult(aResult) { + Services.telemetry.keyedScalarAdd("update.move_result", aResult, 1); + }, + + pingSuppressPrompts: function UT_pingSuppressPrompts() { + try { + let val = Services.prefs.getBoolPref("app.update.suppressPrompts", false); + if (val === true) { + Services.telemetry.scalarSet("update.suppress_prompts", true); + } + } catch (e) { + console.error(e); + } + }, + + pingPinPolicy: function UT_pingPinPolicy(updatePin) { + try { + Services.telemetry.scalarSet("update.version_pin", updatePin); + } catch (e) { + console.error(e); + } + }, +}; + +Object.freeze(AUSTLMY); |