Bug 1517469 - Enable Events Telemetry for Uptake monitory r=janerik

Enable Events Telemetry for Uptake monitoring

Differential Revision: https://phabricator.services.mozilla.com/D19496

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mathieu Leplatre 2019-02-15 09:38:18 +00:00
Родитель 26b2971330
Коммит c8bbaf4887
9 изменённых файлов: 126 добавлений и 40 удалений

Просмотреть файл

@ -154,23 +154,47 @@ function uninstallFakePAC() {
MockRegistrar.unregister(fakePACCID);
}
function _eventsTelemetrySnapshot(component, source) {
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const TELEMETRY_CATEGORY_ID = "uptake.remotecontent.result";
const snapshot = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, true);
const parentEvents = snapshot.parent || [];
return parentEvents
// Transform raw event data to objects.
.map(([i, category, method, object, value, extras]) => { return { category, method, object, value, extras }; })
// Keep only for the specified component and source.
.filter((e) => e.category == TELEMETRY_CATEGORY_ID && e.object == component && e.extras.source == source)
// Return total number of events received by status, to mimic histograms snapshots.
.reduce((acc, e) => {
acc[e.value] = (acc[e.value] || 0) + 1;
return acc;
}, {});
}
function getUptakeTelemetrySnapshot(key) {
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const TELEMETRY_HISTOGRAM_ID = "UPTAKE_REMOTE_CONTENT_RESULT_1";
return Services.telemetry
.getKeyedHistogramById(TELEMETRY_HISTOGRAM_ID)
.snapshot()[key];
const TELEMETRY_COMPONENT = "remotesettings";
const histogram = Services.telemetry.getKeyedHistogramById(TELEMETRY_HISTOGRAM_ID).snapshot()[key];
const events = _eventsTelemetrySnapshot(TELEMETRY_COMPONENT, key);
return { histogram, events };
}
function checkUptakeTelemetry(snapshot1, snapshot2, expectedIncrements) {
const LABELS = ["up_to_date", "success", "backoff", "pref_disabled", "parse_error", "content_error", "sign_error", "sign_retry_error", "conflict_error", "sync_error", "apply_error", "server_error", "certificate_error", "download_error", "timeout_error", "network_error", "offline_error", "cleanup_error", "unknown_error", "custom_1_error", "custom_2_error", "custom_3_error", "custom_4_error", "custom_5_error"];
for (const label of LABELS) {
const key = LABELS.indexOf(label);
const expected = expectedIncrements[label] || 0;
let value1 = (snapshot1 && snapshot1.values[key]) || 0;
let value2 = (snapshot2 && snapshot2.values[key]) || 0;
const actual = value2 - value1;
equal(expected, actual, `check histogram values for ${label}`);
const STATUSES = ["up_to_date", "success", "backoff", "pref_disabled", "parse_error", "content_error", "sign_error", "sign_retry_error", "conflict_error", "sync_error", "apply_error", "server_error", "certificate_error", "download_error", "timeout_error", "network_error", "offline_error", "cleanup_error", "unknown_error", "custom_1_error", "custom_2_error", "custom_3_error", "custom_4_error", "custom_5_error"];
for (const status of STATUSES) {
const key = STATUSES.indexOf(status);
const expected = expectedIncrements[status] || 0;
// Check histogram increments.
let value1 = (snapshot1 && snapshot1.histogram && snapshot1.histogram.values[key]) || 0;
let value2 = (snapshot2 && snapshot2.histogram && snapshot2.histogram.values[key]) || 0;
let actual = value2 - value1;
equal(expected, actual, `check histogram values for ${status}`);
// Check events increments.
value1 = (snapshot1 && snapshot1.histogram && snapshot1.histogram.values[key]) || 0;
value2 = (snapshot2 && snapshot2.histogram && snapshot2.histogram.values[key]) || 0;
actual = value2 - value1;
equal(expected, actual, `check events for ${status}`);
}
}

Просмотреть файл

@ -1,10 +1,14 @@
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { UptakeTelemetry } = ChromeUtils.import("resource://services-common/uptake-telemetry.js");
const COMPONENT = "remotesettings";
add_task(async function test_unknown_status_is_not_reported() {
const source = "update-source";
const startHistogram = getUptakeTelemetrySnapshot(source);
UptakeTelemetry.report(source, "unknown-status");
UptakeTelemetry.report(COMPONENT, "unknown-status", { source });
const endHistogram = getUptakeTelemetrySnapshot(source);
const expectedIncrements = {};
@ -18,7 +22,7 @@ add_task(async function test_each_status_can_be_caught_in_snapshot() {
const expectedIncrements = {};
for (const label of Object.keys(UptakeTelemetry.STATUS)) {
const status = UptakeTelemetry.STATUS[label];
UptakeTelemetry.report(source, status);
UptakeTelemetry.report(COMPONENT, status, { source });
expectedIncrements[status] = 1;
}

Просмотреть файл

@ -10,9 +10,13 @@ var EXPORTED_SYMBOLS = ["UptakeTelemetry"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
// Telemetry report results.
// Telemetry histogram id (see Histograms.json).
const TELEMETRY_HISTOGRAM_ID = "UPTAKE_REMOTE_CONTENT_RESULT_1";
// Telemetry events id (see Events.yaml).
const TELEMETRY_EVENTS_ID = "uptake.remotecontent.result";
/**
* A Telemetry helper to report uptake of remote content.
*/
@ -79,10 +83,30 @@ class UptakeTelemetry {
/**
* Reports the uptake status for the specified source.
*
* @param {string} source the identifier of the update source.
* @param {string} status the uptake status.
* @param {string} component the component reporting the uptake (eg. "normandy").
* @param {string} status the uptake status (eg. "network_error")
* @param {Object} extra extra values to report
* @param {string} extra.source the update source (eg. "recipe-42").
*/
static report(source, status) {
static report(component, status, extra = {}) {
const { source } = extra;
if (!source) {
throw new Error("`source` value is mandatory.");
}
// Report event for real-time monitoring. See Events.yaml for registration.
// Contrary to histograms, Telemetry Events are not enabled by default.
// Enable them on first call to `report()`.
if (!this._eventsEnabled) {
Services.telemetry.setEventRecordingEnabled(TELEMETRY_EVENTS_ID, true);
this._eventsEnabled = true;
}
Services.telemetry
.recordEvent(TELEMETRY_EVENTS_ID, "uptake", component, status, extra);
// Report via histogram in main ping.
// Note: this is the legacy equivalent of the above event. We keep it for continuity.
Services.telemetry
.getKeyedHistogramById(TELEMETRY_HISTOGRAM_ID)
.add(source, status);

Просмотреть файл

@ -29,6 +29,8 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
// IndexedDB name.
const DB_NAME = "remote-settings";
const TELEMETRY_COMPONENT = "remotesettings";
const INVALID_SIGNATURE = "Invalid content signature";
const MISSING_SIGNATURE = "Missing signature";
@ -383,7 +385,7 @@ class RemoteSettingsClient extends EventEmitter {
reportStatus = UptakeTelemetry.STATUS.SUCCESS;
}
// Report success/error status to Telemetry.
UptakeTelemetry.report(this.identifier, reportStatus);
UptakeTelemetry.report(TELEMETRY_COMPONENT, reportStatus, { source: this.identifier });
}
}

Просмотреть файл

@ -40,8 +40,10 @@ const PREF_SETTINGS_CLOCK_SKEW_SECONDS = "clock_skew_seconds";
const PREF_SETTINGS_LOAD_DUMP = "load_dump";
// Telemetry update source identifier.
const TELEMETRY_HISTOGRAM_KEY = "settings-changes-monitoring";
// Telemetry identifiers.
const TELEMETRY_COMPONENT = "remotesettings";
const TELEMETRY_SOURCE = "settings-changes-monitoring";
// Push broadcast id.
const BROADCAST_ID = "remote-settings/monitor_changes";
@ -160,8 +162,9 @@ function remoteSettingsFunction() {
const remainingMilliseconds = parseInt(backoffReleaseTime, 10) - Date.now();
if (remainingMilliseconds > 0) {
// Backoff time has not elapsed yet.
UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY,
UptakeTelemetry.STATUS.BACKOFF);
UptakeTelemetry.report(TELEMETRY_COMPONENT,
UptakeTelemetry.STATUS.BACKOFF,
{ source: TELEMETRY_SOURCE });
throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
} else {
gPrefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
@ -177,15 +180,15 @@ function remoteSettingsFunction() {
pollResult = await Utils.fetchLatestChanges(remoteSettings.pollingEndpoint, { expectedTimestamp, lastEtag });
} catch (e) {
// Report polling error to Uptake Telemetry.
let report;
let reportStatus;
if (/Server/.test(e.message)) {
report = UptakeTelemetry.STATUS.SERVER_ERROR;
reportStatus = UptakeTelemetry.STATUS.SERVER_ERROR;
} else if (/NetworkError/.test(e.message)) {
report = UptakeTelemetry.STATUS.NETWORK_ERROR;
reportStatus = UptakeTelemetry.STATUS.NETWORK_ERROR;
} else {
report = UptakeTelemetry.STATUS.UNKNOWN_ERROR;
reportStatus = UptakeTelemetry.STATUS.UNKNOWN_ERROR;
}
UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY, report);
UptakeTelemetry.report(TELEMETRY_COMPONENT, reportStatus, { source: TELEMETRY_SOURCE });
// No need to go further.
throw new Error(`Polling for changes failed: ${e.message}.`);
}
@ -193,9 +196,9 @@ function remoteSettingsFunction() {
const {serverTimeMillis, changes, currentEtag, backoffSeconds} = pollResult;
// Report polling success to Uptake Telemetry.
const report = changes.length == 0 ? UptakeTelemetry.STATUS.UP_TO_DATE
: UptakeTelemetry.STATUS.SUCCESS;
UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY, report);
const reportStatus = changes.length === 0 ? UptakeTelemetry.STATUS.UP_TO_DATE
: UptakeTelemetry.STATUS.SUCCESS;
UptakeTelemetry.report(TELEMETRY_COMPONENT, reportStatus, { source: TELEMETRY_SOURCE });
// Check if the server asked the clients to back off (for next poll).
if (backoffSeconds) {

Просмотреть файл

@ -9,7 +9,7 @@ ChromeUtils.defineModuleGetter(
var EXPORTED_SYMBOLS = ["Uptake"];
const SOURCE_PREFIX = "normandy";
const COMPONENT = "normandy";
var Uptake = {
// Action uptake
@ -32,14 +32,14 @@ var Uptake = {
RUNNER_SUCCESS: UptakeTelemetry.STATUS.SUCCESS,
reportRunner(status) {
UptakeTelemetry.report(`${SOURCE_PREFIX}/runner`, status);
UptakeTelemetry.report(COMPONENT, status, { source: `${COMPONENT}/runner` });
},
reportRecipe(recipeId, status) {
UptakeTelemetry.report(`${SOURCE_PREFIX}/recipe/${recipeId}`, status);
UptakeTelemetry.report(COMPONENT, status, { source: `${COMPONENT}/recipe/${recipeId}` });
},
reportAction(actionName, status) {
UptakeTelemetry.report(`${SOURCE_PREFIX}/action/${actionName}`, status);
UptakeTelemetry.report(COMPONENT, status, { source: `${COMPONENT}/action/${actionName}` });
},
};

Просмотреть файл

@ -858,6 +858,31 @@ security.ui.identitypopup:
cr: Whether Cookie Restrictions was active while the user interacted with the UI
products:
- firefox
uptake.remotecontent.result:
uptake:
description: >
Was the remote content successfully pulled?
This uptake telemetry allows to monitor the behaviour of our clients when it comes
to fetching data from remote servers. This helps defect-detection and allow observation of
the proportion of success among clients and sources, the distribution of error causes, and
its evolution over time.
methods:
- uptake
objects:
- remotesettings
- normandy
extra_keys:
source: >
A label to distinguish what is being pulled or updated in the component (eg. recipe id,
settings collection name, ...).
bug_numbers:
- 1517469
record_in_processes: ["main"]
release_channel_collection: opt-out
expiry_version: never
notification_emails:
- mleplatre@mozilla.com
- bens-directs@mozilla.com
intl.ui.browserLanguage:
action:

Просмотреть файл

@ -74,6 +74,8 @@ Only ``value`` and the values of ``extra`` will be truncated if over the specifi
Any other ``String`` going over its limit will be reported as an error and the operation
aborted.
.. _eventdefinition:
The YAML definition file
========================

Просмотреть файл

@ -26,9 +26,10 @@ Usage
const { UptakeTelemetry } = ChromeUtils.import("resource://services-common/uptake-telemetry.js", {});
UptakeTelemetry.report(source, status);
UptakeTelemetry.report(component, status, { source });
- ``source``, a ``string`` that is an identifier for the update source (eg. ``addons-blocklist``)
- ``component``, a ``string`` that identifies the calling component (eg. ``"remotesettings"``, ``"normandy"``). Arbitrary components have to be previously declared in the :ref:`Telemetry Events definition file <eventdefinition>`.
- ``source``, a ``string`` to distinguish what is being pulled or updated in the component (eg. ``"blocklists/addons"``, ``"recipes/33"``)
- ``status``, one of the following status constants:
- ``UptakeTelemetry.STATUS.UP_TO_DATE``: Local content was already up-to-date with remote content.
@ -57,12 +58,13 @@ Usage
- ``UptakeTelemetry.STATUS.CUSTOM_5_ERROR``: Error #5 specific to this update source.
The data is submitted to a single :ref:`keyed histogram <histogram-type-keyed>` whose id is ``UPTAKE_REMOTE_CONTENT_RESULT_1`` and the specified update ``source`` as the key.
The data is submitted as a :ref:`Telemetry Event <eventtelemetry>`, and to a single :ref:`keyed histogram <histogram-type-keyed>` whose id is ``UPTAKE_REMOTE_CONTENT_RESULT_1`` and the specified update ``source`` as the key.
Example:
.. code-block:: js
const COMPONENT = "normandy";
const UPDATE_SOURCE = "update-monitoring";
let status;
@ -74,7 +76,7 @@ Example:
UptakeTelemetry.STATUS.NETWORK_ERROR :
UptakeTelemetry.STATUS.SERVER_ERROR ;
}
UptakeTelemetry.report(UPDATE_SOURCE, status);
UptakeTelemetry.report(COMPONENT, status, { source: UPDATE_SOURCE });
Use-cases
@ -88,7 +90,7 @@ The following remote data sources are already using this unified histogram.
* plugins blocklist
* certificate revocation
* certificate pinning
* :ref:`Shield Recipe client <components/normandy>`
* :ref:`Normandy Recipe client <components/normandy>`
Obviously, the goal is to eventually converge and avoid ad-hoc Telemetry probes for measuring uptake of remote content. Some notable potential use-cases are: