From 2aa95282646bfe80193da09fdf063ea23a3834f4 Mon Sep 17 00:00:00 2001 From: Sebastian Streich Date: Wed, 4 Dec 2024 16:28:24 +0100 Subject: [PATCH] Add extensionTelemetry (#10057) * Add extension metrics to extension session ping - Added extension_metrics.yaml file to the telemetry directory. - Defined new metrics for extension usage, including onboarding completion, feature usage, and website exclusions. - Added extension_metrics.yaml to the list of metrics sent in the extensionsession ping. - Updated pings.yaml to include extensionsession as a new ping type. - Included client ID in extensionsession ping. - Updated bugs, data reviews, and notification emails for the new metrics. - Added reasons for sending extensionsession ping on session start and end. - Updated comments to reflect changes. * Add support for WebExtension Telemetry - Added a new `WebExtensionTelemetry` class to handle WebExtension telemetry events. - Implemented `fromJson` function to parse JSON telemetry data into `TelemetryInfo` objects. - Added `recordTelemetry` function to record telemetry events to Glean. - Implemented `startSession` and `stopSession` functions to track session start and end events. - Added new handlers for various telemetry events and flags in `handlers.cpp`. - Updated `webextensionadapter.cpp` to handle telemetry requests. - Updated `pings.yaml` to include new telemetry pings. - Updated `webextensiontelemetry.h` to define new telemetry functions and structures. * Allow filtering on a per-ping basis * Use glean lifetimes instead of homebrewed stuff * Address feedback in metrics_yaml * Address feedback in pings.yaml * fix typos in c++ * Update upload enablement in MZGlean - Update MZGlean::upadateUploadEnabled() to MZGlean::updateUploadEnabled() for consistency in naming. - Ensure that the static function updateUploadEnabled() is called correctly in the MZGlean class. - Update the documentation comments for clarity and consistency. * Update flag handling in webextensiontelemetry.cpp - Changed the function declaration of `flag` from `consteval` to `constexpr` to ensure that it is a compile-time constant expression. - This change ensures that the function is not evaluated at runtime, improving performance and avoiding potential issues with runtime evaluation. --- qtglean/glean_parser_ext/run_glean_parser.py | 1 + qtglean/src/lib.rs | 19 +- qtglean/src/uploader.rs | 21 +- src/cmake/sources.cmake | 2 + src/glean/mzglean.cpp | 70 +++++- src/glean/mzglean.h | 6 +- src/settingslist.h | 10 + src/telemetry/extension_metrics.yaml | 237 ++++++++++++++++++ src/telemetry/pings.yaml | 21 ++ .../developerMenu/ViewTelemetryDebugging.qml | 15 ++ src/webextensionadapter.cpp | 22 +- src/webextensiontelemetry.cpp | 102 ++++++++ src/webextensiontelemetry.h | 35 +++ 13 files changed, 542 insertions(+), 19 deletions(-) create mode 100644 src/telemetry/extension_metrics.yaml create mode 100644 src/webextensiontelemetry.cpp create mode 100644 src/webextensiontelemetry.h diff --git a/qtglean/glean_parser_ext/run_glean_parser.py b/qtglean/glean_parser_ext/run_glean_parser.py index 6b55642a13..e1104d7874 100644 --- a/qtglean/glean_parser_ext/run_glean_parser.py +++ b/qtglean/glean_parser_ext/run_glean_parser.py @@ -114,6 +114,7 @@ def create_dir(path): ] metrics_files = [ os.path.join(telemetry_path, "metrics.yaml"), + os.path.join(telemetry_path, "extension_metrics.yaml"), os.path.join(telemetry_path, "interaction_metrics.yaml"), os.path.join(telemetry_path, "impression_metrics.yaml"), os.path.join(telemetry_path, "outcome_metrics.yaml"), diff --git a/qtglean/src/lib.rs b/qtglean/src/lib.rs index 05d3fb0fc2..ab4674d25f 100644 --- a/qtglean/src/lib.rs +++ b/qtglean/src/lib.rs @@ -10,6 +10,9 @@ use glean::{ClientInfoMetrics, Configuration}; use ffi::helpers::FallibleToString; use metrics::__generated_pings::register_pings; +use once_cell::sync::Lazy; +use std::sync::Mutex; +use std::sync::Arc; use uploader::VPNPingUploader; use logger::Logger; @@ -23,6 +26,9 @@ mod logger; const GLEAN_APPLICATION_ID: &str = "mozillavpn"; +static GLOBAL_PING_FILTER_LIST : Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); + + #[no_mangle] pub extern "C" fn glean_register_log_handler(message_handler: extern fn(i32, *mut c_char)) { Logger::init(message_handler); @@ -44,7 +50,7 @@ pub extern "C" fn glean_initialize(is_telemetry_enabled: bool, data_path: FfiStr // Default is "https://incoming.telemetry.mozilla.org" server_endpoint: None, // Use the Glean provided one once https://bugzilla.mozilla.org/show_bug.cgi?id=1675468 is resolved - uploader: Some(Box::new(VPNPingUploader::new())), + uploader: Some(Box::new(VPNPingUploader::new(GLOBAL_PING_FILTER_LIST.clone()))), // Whether Glean should schedule “metrics” pings for you use_core_mps: true, trim_data_to_registered_pings: false, @@ -130,3 +136,14 @@ pub extern "C" fn glean_test_reset_glean(is_telemetry_enabled: bool, data_path: glean::test_reset_glean(cfg, client_info, true); } + +#[no_mangle] +pub extern "C" fn glean_push_ping_filter(ping_name: FfiStr) { + let mut list = GLOBAL_PING_FILTER_LIST.lock().unwrap(); + list.push(ping_name.into_string()); +} +#[no_mangle] +pub extern "C" fn glean_clear_ping_filter() { + let mut list = GLOBAL_PING_FILTER_LIST.lock().unwrap(); + list.clear(); +} diff --git a/qtglean/src/uploader.rs b/qtglean/src/uploader.rs index 420b4c15e3..9b6e789d6e 100644 --- a/qtglean/src/uploader.rs +++ b/qtglean/src/uploader.rs @@ -5,22 +5,41 @@ use glean::net::{PingUploader, UploadResult, PingUploadRequest}; use reqwest::blocking::Client; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use std::sync::Mutex; +use std::sync::Arc; #[derive(Debug)] pub struct VPNPingUploader { client: Client, + forbidden_ping_list: Arc>>, } impl VPNPingUploader { - pub fn new() -> VPNPingUploader { + pub fn new(forbidden_pings :Arc>> ) -> VPNPingUploader { VPNPingUploader { client: Client::new(), + forbidden_ping_list: forbidden_pings } } + + fn allowed_to_send(&self, req: &PingUploadRequest)->bool{ + let data = self.forbidden_ping_list.lock().unwrap(); + // Emtpy list -> allow everything + if data.is_empty(){ + return true; + } + // Otherwise the ping_name MUST NOT be in the list. + return data.iter().all(|e| e != &req.ping_name); + } } impl PingUploader for VPNPingUploader { fn upload(&self, upload_request: PingUploadRequest) -> UploadResult { + if !self.allowed_to_send(&upload_request){ + // If we're not allowed to send, "fake" a 200 response, + // so the data is dropped and not retried later. + return UploadResult::http_status(200); + } let mut parsed_headers = HeaderMap::new(); for (name, value) in upload_request.headers { if let (Ok(parsed_name), Ok(parsed_value)) = ( diff --git a/src/cmake/sources.cmake b/src/cmake/sources.cmake index 40370f78b0..eb74a40c93 100644 --- a/src/cmake/sources.cmake +++ b/src/cmake/sources.cmake @@ -241,6 +241,8 @@ if(NOT CMAKE_CROSSCOMPILING) target_sources(mozillavpn-sources INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/webextensionadapter.h ${CMAKE_CURRENT_SOURCE_DIR}/webextensionadapter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/webextensiontelemetry.h + ${CMAKE_CURRENT_SOURCE_DIR}/webextensiontelemetry.cpp ) target_compile_definitions(mozillavpn-sources INTERFACE MVPN_WEBEXTENSION) endif() diff --git a/src/glean/mzglean.cpp b/src/glean/mzglean.cpp index 1a0f687c6d..bb7d4ecd33 100644 --- a/src/glean/mzglean.cpp +++ b/src/glean/mzglean.cpp @@ -52,7 +52,7 @@ MZGlean::MZGlean(QObject* parent) : QObject(parent) { connect(AndroidVPNActivity::instance(), &AndroidVPNActivity::eventRequestGleanUploadEnabledState, this, [&]() { - broadcastUploadEnabledChange( + broadcastClientUploadEnabledChange( SettingsHolder::instance()->gleanEnabled()); }); #endif @@ -79,8 +79,14 @@ void MZGlean::initialize(const QString& channel) { connect(SettingsHolder::instance(), &SettingsHolder::gleanEnabledChanged, s_instance, []() { - s_instance->setUploadEnabled( - SettingsHolder::instance()->gleanEnabled()); + updatePingFilter(); + updateUploadEnabled(); + }); + connect(SettingsHolder::instance(), + &SettingsHolder::extensionTelemetryEnabledChanged, s_instance, + []() { + updatePingFilter(); + updateUploadEnabled(); }); } @@ -115,10 +121,16 @@ void MZGlean::initialize(const QString& channel) { # else SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); + auto const clientTelemetryEnabled = settingsHolder->gleanEnabled(); + auto const extensionTelemetryEnabled = + settingsHolder->extensionTelemetryEnabled(); + + auto const shouldUpload = + extensionTelemetryEnabled | clientTelemetryEnabled; - glean_initialize(SettingsHolder::instance()->gleanEnabled(), - gleanDirectory.absolutePath().toUtf8(), channel.toUtf8(), - QLocale::system().name().toUtf8()); + updatePingFilter(); + glean_initialize(shouldUpload, gleanDirectory.absolutePath().toUtf8(), + channel.toUtf8(), QLocale::system().name().toUtf8()); setLogPings(settingsHolder->gleanLogPings()); if (settingsHolder->gleanDebugTagActive()) { @@ -130,16 +142,21 @@ void MZGlean::initialize(const QString& channel) { } // static -void MZGlean::setUploadEnabled(bool isTelemetryEnabled) { - logger.debug() << "Changing MZGlean upload status to" << isTelemetryEnabled; +void MZGlean::updateUploadEnabled() { + auto const settings = SettingsHolder::instance(); + auto const clientTelemetryEnabled = settings->gleanEnabled(); + auto const extensionTelemetryEnabled = settings->extensionTelemetryEnabled(); + + auto const shouldUpload = extensionTelemetryEnabled | clientTelemetryEnabled; + logger.debug() << "Changing MZGlean upload status to" << shouldUpload; #if not(defined(MZ_WASM)) - glean_set_upload_enabled(isTelemetryEnabled); + glean_set_upload_enabled(shouldUpload); #endif - broadcastUploadEnabledChange(isTelemetryEnabled); + broadcastClientUploadEnabledChange(clientTelemetryEnabled); - if (isTelemetryEnabled) { + if (clientTelemetryEnabled) { #if defined(MZ_ANDROID) || defined(MZ_IOS) // need to reset installation ID, as it would have been cleared QString uuid = mozilla::glean::session::installation_id.generateAndSet(); @@ -162,7 +179,7 @@ void MZGlean::setUploadEnabled(bool isTelemetryEnabled) { } // static -void MZGlean::broadcastUploadEnabledChange(bool isTelemetryEnabled) { +void MZGlean::broadcastClientUploadEnabledChange(bool isTelemetryEnabled) { #if defined(MZ_ANDROID) logger.debug() << "Broadcasting MZGlean upload status to Android Daemon."; @@ -221,3 +238,32 @@ void MZGlean::setLogPings(bool flag) { glean_set_log_pings(flag); #endif } + +void MZGlean::updatePingFilter() { +#if defined(MZ_WASM) + return; +#else + glean_clear_ping_filter(); + auto settings = SettingsHolder::instance(); + auto clientTelemetryEnabled = settings->gleanEnabled(); + auto extensionTelemetryEnabled = settings->extensionTelemetryEnabled(); + + // Everything is allowed, only required to clear the filter. + if (clientTelemetryEnabled && extensionTelemetryEnabled) { + return; + } + // All extension telemetry is inside the extension session ping + // so filter that ping out. + if (clientTelemetryEnabled) { + glean_push_ping_filter("extensionsession"); + } + // Otherwise, filter the vpn pings out. + else if (extensionTelemetryEnabled) { + glean_push_ping_filter("daemonsession"); + glean_push_ping_filter("main"); + glean_push_ping_filter("vpnsession"); + } + // If neither are enabled, we won't send things anyway. + +#endif +} diff --git a/src/glean/mzglean.h b/src/glean/mzglean.h index 7658d816c4..72bde38cf9 100644 --- a/src/glean/mzglean.h +++ b/src/glean/mzglean.h @@ -16,7 +16,7 @@ class MZGlean final : public QObject { private: explicit MZGlean(QObject* parent); - static void setUploadEnabled(bool isTelemetryEnabled); + static void updateUploadEnabled(); /** * @brief Broadcast to the mobile VPN daemon instances of Glean, @@ -24,7 +24,9 @@ class MZGlean final : public QObject { * * @param isTelemetryEnabled The new upload enabled state. */ - static void broadcastUploadEnabledChange(bool isTelemetryEnabled); + static void broadcastClientUploadEnabledChange(bool isTelemetryEnabled); + + static void updatePingFilter(); public: ~MZGlean(); diff --git a/src/settingslist.h b/src/settingslist.h index 442b5b4d25..dfe47f2e28 100644 --- a/src/settingslist.h +++ b/src/settingslist.h @@ -783,3 +783,13 @@ SETTING_BOOL(addonApiSetting, // getter false // sensitive (do not log) ) #endif + +SETTING_BOOL(extensionTelemetryEnabled, // getter + setExtensionTelemetryEnabled, // setter + removeExtensionTelemetryEnabled, // remover + hasExtensionTelemetryEnabled, // has + "extensionTelemetryEnabled", // key + false, // default value + true, // remove when reset + false // sensitive (do not log) +) diff --git a/src/telemetry/extension_metrics.yaml b/src/telemetry/extension_metrics.yaml new file mode 100644 index 0000000000..95a0b215cf --- /dev/null +++ b/src/telemetry/extension_metrics.yaml @@ -0,0 +1,237 @@ +# 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/. + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 + +extension: + main_screen: + type: event + lifetime: ping + send_in_pings: + - extensionsession + description: | + The user has just moved on to the main screen + i.e. the screen with the VPN toggle button. + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + expires: 2025-06-30 + + error_screen: + type: event + lifetime: ping + send_in_pings: + - extensionsession + description: | + The user has just moved on to the main screen + i.e. the screen with the VPN toggle button. + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + extra_keys: + error: + description: | + The Type of Error: + client_too_old, client_not_opened, unsupported_platform, install_needed + subscription_needed, signin_needed, split_tunneled + type: string + expires: 2025-06-30 + + fx_protection_enabled: + type: event + lifetime: ping + send_in_pings: + - extensionsession + description: | + The user has toggled the Fx Protection On + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + expires: 2025-06-30 + + fx_protection_disabled: + type: event + lifetime: ping + send_in_pings: + - extensionsession + description: | + The user has toggled the Fx Protection Off + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + expires: 2025-06-30 + + fx_protection_mode_changed: + type: event + lifetime: ping + send_in_pings: + - extensionsession + description: | + The Protection Mode Changed + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + expires: 2025-06-30 + extra_keys: + message_state: + description: | + Full,PartialOn,PartialOff,Off, + type: string + + used_feature_disable_firefox_protection: + type: boolean + lifetime: user + description: > + True if the user has disabled the VPN for firefox + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: never + + used_feature_settings_page: + lifetime: user + type: boolean + description: > + True if the user has opened the internal settings page. + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: never + + used_feature_page_action_revoke_exclude: + type: boolean + lifetime: user + description: > + True if the user has removed a Website Exclusion from the Page action + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: never + + used_feature_page_action_revoke_geopref: + type: boolean + lifetime: user + description: > + True if the user has removed a Website GeoPrefrence from the Page action + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: never + + has_completed_onboarding: + type: boolean + lifetime: user + description: > + True if the user has completed onboarding. + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: never + + count_excluded: + type: quantity + unit: websites + description: > + The amount of websites excluded from the VPN + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: 2025-06-30 + + count_geoprefed: + type: quantity + unit: websites + description: > + The amount of Websites with a GeoPrefrence. + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - TBD + data_sensitivity: + - interaction + notification_emails: + - sstreich@mozilla.com + - vpn-telemetry@mozilla.com + send_in_pings: + - extensionsession + expires: 2025-06-30 diff --git a/src/telemetry/pings.yaml b/src/telemetry/pings.yaml index 7cd138b8a6..b0b7baabcc 100644 --- a/src/telemetry/pings.yaml +++ b/src/telemetry/pings.yaml @@ -73,3 +73,24 @@ daemonsession: (Android only) Sent every three hours during an active VPN session. daemon_end: > Sent after as session ending metric is recorded. + +extensionsession: + description: | + Only on desktop. + Data for the VPN extension + This ping will record start/end datestamp, + session-based metrics, etc. + include_client_id: true + send_if_empty: true + bugs: + - https://mozilla-hub.atlassian.net/browse/VPN-6714 + data_reviews: + - N/A, will be done for metrics within + notification_emails: + - vpn-telemetry@mozilla.com + - sstreich@mozilla.com + reasons: + extension_start: > + Sent right after a session start metric is recorded. + extension_stop: > + Sent after as session ending metric is recorded. diff --git a/src/ui/screens/getHelp/developerMenu/ViewTelemetryDebugging.qml b/src/ui/screens/getHelp/developerMenu/ViewTelemetryDebugging.qml index 68af0cd1fb..e31d0a2a22 100644 --- a/src/ui/screens/getHelp/developerMenu/ViewTelemetryDebugging.qml +++ b/src/ui/screens/getHelp/developerMenu/ViewTelemetryDebugging.qml @@ -31,6 +31,17 @@ MZViewBase { VPN.gleanSetLogPings(!MZSettings.gleanLogPings) } } + MZCheckBoxRow { + Layout.fillWidth: true + Layout.rightMargin: MZTheme.theme.windowMargin + labelText: "Glean Enable Extension Telemetry" + subLabelText: "Enables / Disables Glean Telemetry for the extension" + isChecked: MZSettings.extensionTelemetryEnabled + showDivider: false + onClicked: { + MZSettings.extensionTelemetryEnabled = !MZSettings.extensionTelemetryEnabled + } + } MZCheckBoxRow { id: checkBoxRowGleanDebugTag @@ -107,5 +118,9 @@ MZViewBase { text: "Submit the 'vpnsession' ping" onClicked: GleanPings.Vpnsession.submit() } + MZButton { + text: "Submit the 'extensionsession' ping" + onClicked: GleanPings.Extensionsession.submit() + } } } diff --git a/src/webextensionadapter.cpp b/src/webextensionadapter.cpp index 3ee47a78c8..44135586b6 100644 --- a/src/webextensionadapter.cpp +++ b/src/webextensionadapter.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include "connectionhealth.h" #include "controller.h" @@ -28,14 +27,13 @@ #include "settingsholder.h" #include "tasks/controlleraction/taskcontrolleraction.h" #include "taskscheduler.h" -#include "webextensionadapter.h" +#include "webextensiontelemetry.h" #if defined(MZ_WINDOWS) # include "platforms/windows/windowsutils.h" #endif namespace { - // See https://en.cppreference.com/w/cpp/utility/variant/visit template struct match : Ts... { @@ -137,6 +135,24 @@ WebExtensionAdapter::WebExtensionAdapter(QObject* parent) obj["status"] = serializeStatus(); return obj; }}, + RequestType{"telemetry", + [](const QJsonObject& data) { + auto info = WebextensionTelemetry::fromJson(data); + if (info.has_value()) { + WebextensionTelemetry::recordTelemetry(info.value()); + } + return QJsonObject{}; + }}, + RequestType{"session_start", + [](const QJsonObject& data) { + WebextensionTelemetry::startSession(); + return QJsonObject{}; + }}, + RequestType{"session_stop", + [](const QJsonObject& data) { + WebextensionTelemetry::stopSession(); + return QJsonObject{}; + }}, }); } diff --git a/src/webextensiontelemetry.cpp b/src/webextensiontelemetry.cpp new file mode 100644 index 0000000000..7e2b547e66 --- /dev/null +++ b/src/webextensiontelemetry.cpp @@ -0,0 +1,102 @@ +/* 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/. */ + +#include "webextensiontelemetry.h" + +#include +#include +#include +#include + +#include "glean/boolean.h" +#include "glean/event.h" +#include "glean/generated/metrics.h" +#include "glean/generated/pings.h" +#include "glean/quantity.h" +#include "logger.h" +#include "settingsholder.h" + +namespace { +Logger logger("WebExtensionTelemetry"); +} // namespace + +namespace WebextensionTelemetry { + +std::optional fromJson(const QJsonObject& obj) { + // 1: Needs to have type:"telemetry" + if (obj["t"].toString("") != "telemetry") { + return {}; + } + // 2: MUST have name + TelemetryInfo out{}; + if (!obj["name"].isString()) { + return {}; + } + out.name = obj["name"].toString(); + // 3: Can have Variant args + if (obj["args"].toVariant().isValid()) { + out.args = obj["args"].toVariant(); + } + return out; +} +// Const functions to create handlers for glean data types. +namespace handlers { +// For Events we want to just record it to glean. +// It will be send out with the next ping +auto constexpr event(EventMetric* metric) { + return [metric](QVariant) { metric->record(); }; +}; +auto constexpr count(QuantityMetric* metric) { + return [metric](QVariant args) { + bool ok; + const auto count = args.toUInt(&ok); + if (!ok) { + return; + } + metric->set(count); + }; +}; +// For flags, we will flip the flag-bit in the settings to true. +// We will also record this to glean. +// Flags will always be sent with the next ping +auto constexpr flag(BooleanMetric* metric) { + return [metric](QVariant) { metric->set(true); }; +} +using namespace mozilla::glean; +const auto map = QMap>{ + {"fx_protection_disabled", event(&extension::fx_protection_disabled)}, + {"fx_protection_enabled", event(&extension::fx_protection_enabled)}, + {"fx_protection_mode_changed", + event(&extension::fx_protection_mode_changed)}, + {"main_screen", event(&extension::main_screen)}, + {"error_screen", event(&mozilla::glean::extension::error_screen)}, + {"has_completed_onboarding", flag(&extension::has_completed_onboarding)}, + {"used_feature_disable_firefox_protection", + flag(&extension::used_feature_disable_firefox_protection)}, + {"used_feature_settings_page", + flag(&extension::used_feature_settings_page)}, + {"used_feature_page_action_revoke_exclude", + flag(&extension::used_feature_page_action_revoke_exclude)}, + {"used_feature_page_action_revoke_geopref", + flag(&extension::used_feature_page_action_revoke_geopref)}, + {"count_excluded", count(&extension::count_excluded)}, + {"count_geoprefed", count(&extension::count_geoprefed)}, +}; +} // namespace handlers + +bool recordTelemetry(const TelemetryInfo& info) { + if (!handlers::map.contains(info.name)) { + return false; + } + auto handler = handlers::map[info.name]; + handler(info.args); + return true; +} +void startSession() { + mozilla::glean_pings::Extensionsession.submit("extension_start"); +} +void stopSession() { + mozilla::glean_pings::Extensionsession.submit("extension_stop"); +} +} // namespace WebextensionTelemetry diff --git a/src/webextensiontelemetry.h b/src/webextensiontelemetry.h new file mode 100644 index 0000000000..1bcb219a0c --- /dev/null +++ b/src/webextensiontelemetry.h @@ -0,0 +1,35 @@ +/* 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/. */ + +#pragma once + +#include +#include +#include +#include + +namespace WebextensionTelemetry { +struct TelemetryInfo { + QString name; + QVariant args; +}; + +/** + * @brief Tries to parse a JSON Blob into a telemetry info obj, + * @return std::optional - Nullopt on parse-error. + */ +std::optional fromJson(const QJsonObject& obj); +/** + * @brief Records telemetry to glean + * + * @param info - The Info to record, create with fromJson(); + * @return true - info was accepted + * @return false - info was rejected + */ +bool recordTelemetry(const TelemetryInfo& info); + +void startSession(); +void stopSession(); + +}; // namespace WebextensionTelemetry