tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c4888cef7e32ab0438960020070050592d4bbaeb
parent baa3cf6e1213dd783eee4fe314551ebfd18d5929
Author: Alex Franchuk <afranchuk@mozilla.com>
Date:   Mon, 15 Dec 2025 18:57:37 +0000

Bug 1989439 p1 - Create the crashping crate. r=gsvelto

This crate handles converting crash annotations to Glean metrics and
sending the crash ping.

Glean metrics are generated from `CrashAnnotations.yaml`.

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

Diffstat:
MCargo.lock | 14++++++++++++++
MCargo.toml | 1+
Mbuild/workspace-hack/Cargo.toml | 1+
Mpython/mach/mach/command_util.py | 3+++
Atoolkit/crashreporter/crashping/Cargo.toml | 19+++++++++++++++++++
Atoolkit/crashreporter/crashping/build.rs | 34++++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/conversions.py | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/generated_metrics.yaml | 1054+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/glean_metrics.py | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/glean_rust.py | 34++++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/moz.build | 32++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/pings.yaml | 28++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/src/annotations.rs | 355+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/crashreporter/crashping/src/lib.rs | 540+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/crashreporter/moz.build | 1+
Mtoolkit/library/rust/moz.build | 1+
16 files changed, 2294 insertions(+), 0 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1203,6 +1203,20 @@ dependencies = [ ] [[package]] +name = "crashping" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "glean", + "log", + "mozbuild", + "mozilla-central-workspace-hack", + "serde_json", + "time", +] + +[[package]] name = "crashreporter" version = "1.0.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "toolkit/crashreporter/client/app", "toolkit/crashreporter/crash_helper_server", "toolkit/crashreporter/crash_helper_client", + "toolkit/crashreporter/crashping", "toolkit/crashreporter/minidump-analyzer/android/export", "toolkit/crashreporter/mozwer-rust", "toolkit/library/gtest/rust", diff --git a/build/workspace-hack/Cargo.toml b/build/workspace-hack/Cargo.toml @@ -268,6 +268,7 @@ optional = true [features] crash_helper_server = ["dep:cc", "dep:num-traits", "dep:log", "dep:once_cell", "dep:uuid", "dep:windows-sys"] crashreporter = ["dep:allocator-api2", "dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:crossbeam-utils", "dep:env_logger", "dep:flate2", "dep:fluent", "dep:fluent-langneg", "dep:fnv", "dep:form_urlencoded", "dep:futures-channel", "dep:futures-core", "dep:futures-executor", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hex", "dep:hyper", "dep:icu_locale_core", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:itertools", "dep:log", "dep:memchr", "dep:mio", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:object", "dep:once_cell", "dep:percent-encoding", "dep:phf", "dep:regex", "dep:rkv", "dep:scroll", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:time", "dep:time-macros", "dep:tinystr", "dep:tokio", "dep:tokio-util", "dep:toml", "dep:tracing", "dep:unic-langid", "dep:unic-langid-impl", "dep:unicode-bidi", "dep:uniffi", "dep:url", "dep:uuid", "dep:windows-sys", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec", "dep:zip"] +crashping = ["dep:serde_json", "dep:time"] geckodriver = ["dep:allocator-api2", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:clap", "dep:crossbeam-utils", "dep:flate2", "dep:fnv", "dep:form_urlencoded", "dep:futures-channel", "dep:futures-core", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hyper", "dep:icu_locale_core", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:log", "dep:memchr", "dep:mio", "dep:num-integer", "dep:num-traits", "dep:once_cell", "dep:percent-encoding", "dep:regex", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:strsim", "dep:time", "dep:time-macros", "dep:tinystr", "dep:tokio", "dep:tokio-util", "dep:tracing", "dep:unicode-bidi", "dep:url", "dep:uuid", "dep:windows-sys", "dep:xml-rs", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec", "dep:zip"] gkrust = ["dep:allocator-api2", "dep:arrayvec", "dep:bindgen", "dep:bitflags", "dep:byteorder", "dep:bytes", "dep:cc", "dep:chrono", "dep:core-foundation-sys", "dep:crossbeam-utils", "dep:env_logger", "dep:flate2", "dep:fluent", "dep:fluent-langneg", "dep:fnv", "dep:form_urlencoded", "dep:futures", "dep:futures-channel", "dep:futures-core", "dep:futures-executor", "dep:futures-sink", "dep:futures-util", "dep:getrandom", "dep:hashbrown", "dep:hex", "dep:icu_locale_core", "dep:icu_properties", "dep:idna", "dep:indexmap", "dep:itertools", "dep:log", "dep:memchr", "dep:nom", "dep:num-integer", "dep:num-traits", "dep:object", "dep:once_cell", "dep:percent-encoding", "dep:phf", "dep:regex", "dep:rkv", "dep:scopeguard", "dep:scroll", "dep:semver", "dep:serde_json", "dep:smallvec", "dep:stable_deref_trait", "dep:strsim", "dep:time", "dep:time-macros", "dep:tinystr", "dep:toml", "dep:unic-langid", "dep:unic-langid-impl", "dep:unicode-bidi", "dep:uniffi", "dep:url", "dep:uuid", "dep:windows", "dep:windows-sys", "dep:xml-rs", "dep:yoke", "dep:zerocopy", "dep:zerofrom", "dep:zerovec"] gkrust-gtest = ["gkrust"] diff --git a/python/mach/mach/command_util.py b/python/mach/mach/command_util.py @@ -87,6 +87,9 @@ MACH_COMMANDS = { ), "configure": MachCommandReference("python/mozbuild/mozbuild/build_commands.py"), "cppunittest": MachCommandReference("testing/mach_commands.py"), + "crash-ping-metrics": MachCommandReference( + "toolkit/crashreporter/crashping/glean_metrics.py" + ), "crashtest": MachCommandReference("layout/tools/reftest/mach_commands.py"), "data-review": MachCommandReference( "toolkit/components/glean/build_scripts/mach_commands.py" diff --git a/toolkit/crashreporter/crashping/Cargo.toml b/toolkit/crashreporter/crashping/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "crashping" +version = "0.1.0" +edition = "2021" +rust-version.workspace = true + +[dependencies] +anyhow = "1" +glean = { workspace = true } +log = "0.4" +mozilla-central-workspace-hack = { version = "0.1", features = ["crashping"], optional = true } +serde_json = "1" +time = { version = "0.3" } + +[dev-dependencies] +env_logger = { version = "0.10", default-features = false } + +[build-dependencies] +mozbuild = "0.1" diff --git a/toolkit/crashreporter/crashping/build.rs b/toolkit/crashreporter/crashping/build.rs @@ -0,0 +1,34 @@ +/* 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/. */ + +use std::path::Path; + +fn main() { + set_generated_files(); +} + +/// Set the GLEAN_METRICS_FILE and CONVERSIONS_FILE environment variables to the locations of the +/// generated files. +/// +/// We do this here to avoid a hardcoded path in the source (just in case this crate's src dir +/// moves, not that it's likely). +fn set_generated_files() { + let full_path = Path::new(env!("CARGO_MANIFEST_DIR")); + let relative_path = full_path + .strip_prefix(mozbuild::TOPSRCDIR) + .expect("CARGO_MANIFEST_DIR not a child of TOPSRCDIR"); + + for (file, env) in [ + // Generated by glean_rust.py + ("glean_metrics.rs", "GLEAN_METRICS_FILE"), + // Generated by conversions.py + ("conversions.rs", "CONVERSIONS_FILE"), + ] { + let objpath = mozbuild::TOPOBJDIR.join(relative_path).join(file); + // We don't really need anything like `rerun-if-env-changed=CARGO_MANIFEST_DIR` because that + // will inevitably mean the entire crate has moved or the srcdir has moved, which would result + // in a rebuild anyway. + println!("cargo:rustc-env={env}={}", objpath.display()); + } +} diff --git a/toolkit/crashreporter/crashping/conversions.py b/toolkit/crashreporter/crashping/conversions.py @@ -0,0 +1,62 @@ +# 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 https://mozilla.org/MPL/2.0/. + +import yaml + + +def main(output, annotations_path): + with open(annotations_path) as annotations_file: + annotations = yaml.safe_load(annotations_file) + + names = [] + + for name, data in annotations: + glean = data.get("glean") + if glean is None: + continue + + annotation_type = data["type"] + glean_type = glean["type"] + + namespace, _, metric = glean["metric"].rpartition(".") + namespace = namespace.replace(".", "_") + + # Treat all numbers the same way + if annotation_type in ["usize", "u64", "u32"]: + annotation_type = "u64" + + conversion = None + unique_conversion = f"convert_to_{namespace}_{metric}" + custom_convert = glean.get("custom_convert") + if custom_convert: + if custom_convert is True: + conversion = unique_conversion + else: + conversion = f"convert_{custom_convert}" + + if conversion is None: + # Only use a standard conversion when there is no ambiguity + is_standard = (annotation_type, glean_type) in [ + ("boolean", "boolean"), + ("string", "string"), + ("u64", "quantity"), + ("string", "string_list"), + ] + if is_standard: + conversion = f"convert_{annotation_type}_to_{glean_type}" + else: + conversion = unique_conversion + + args = "" + if glean_type == "string_list": + args += f", \"{glean['delimiter']}\"" + + output.write( + f'#[allow(non_upper_case_globals)] pub const {name}: Annotation = convert!({namespace}::{metric} = {conversion}("{name}"{args}));\n' + ) + names.append(name) + + names = ",".join(names) + + output.write(f"pub const ANNOTATIONS: &[Annotation] = &[{names}];\n") diff --git a/toolkit/crashreporter/crashping/generated_metrics.yaml b/toolkit/crashreporter/crashping/generated_metrics.yaml @@ -0,0 +1,1054 @@ + +# 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/. + +# THIS FILE IS AUTO-GENERATED BY glean_metrics.py. DO NOT EDIT! + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 +$tags: +- 'Toolkit :: Crash Reporting' +crash: + app_build: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Product application's build ID. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + app_channel: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Application release channel (e.g. default, beta, ...) + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + app_display_version: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Product version. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + async_shutdown_timeout: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: This annotation is present if a shutdown blocker was not released + in time and the browser was crashed instead of waiting for shutdown to finish. + The condition that caused the hang is contained in the annotation. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + structure: + properties: + broken_add_blockers: + items: + type: string + type: array + conditions: + type: string + phase: + type: string + type: object + type: object + background_task_name: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: If the app was invoked in background task mode via `--backgroundtask + <task name>`, the string "task name". + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + build_id: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Application build ID, the format is YYYYMMDDHHMMSS. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + crash_type: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: The type of crash that occurred (Android-only). + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + dom_fission_enabled: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set to 1 when DOM fission is enabled, and subframes are potentially + loaded in a separate process. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean + event_loop_nesting_level: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Present only if higher than 0, indicates that we're running in a + nested event loop and indicates the nesting level. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: levels + font_name: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set before attempting to load a font to help diagnose crashes during + loading. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + gpu_process_launch: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Number of times the GPU process was launched. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: events + hang: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set if the crash was the result of a hang, with a value which describes + the type of hang (e.g. "ui" or "shutdown"). + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + ipc_channel_error: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set before a content process crashes because of an IPC channel error. + Holds a description of the error. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + is_garbage_collecting: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: If true then the JavaScript garbage collector was running when the + crash occurred. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean + java_exception: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: JSON structured Java stack trace, only present on Firefox for Android + if we encounter an uncaught Java exception. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + structure: + properties: + throwables: + items: + properties: + message: + type: string + stack: + items: + properties: + class_name: + type: string + file: + type: string + is_native: + type: boolean + line: + type: number + method_name: + type: string + type: object + type: array + type_name: + type: string + type: object + type: array + type: object + type: object + version: 1 + last_interaction_duration: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: How long the user had been inactive in seconds if the user was inactive + at crash. The value is not set if the user state was active. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + time_unit: second + type: timespan + linux_memory_psi: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: 'Memory PSI (Pressure Stall Information) values from /proc/pressure/memory + as comma-separated list: some_avg10,some_avg60,some_avg300,some_total,full_avg10,full_avg60,full_avg300,full_total' + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + main_thread_runnable_name: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Name of the currently executing nsIRunnable on the main thread. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + minidump_sha_256_hash: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: The sha256 hash of the minidump file, if available. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + moz_crash_reason: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Plaintext description of why Firefox crashed, this is usually set + by assertions and the like. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + process_type: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Type of the process that crashed, the possible values are defined + in GeckoProcessTypes.h. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + product_id: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Application UUID (e.g. ec8030f7-c20a-464f-9b0e-13a3a9e97384). + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + product_name: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Application name (e.g. Firefox). + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + profiler_child_shutdown_phase: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: When a child process shuts down, this describes if the profiler is + running, and the point the profiler shutdown sequence has reached. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + quota_manager_shutdown_timeout: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: 'This annotation is present if the quota manager shutdown (resp. + the shutdown of the quota manager clients) was not finished in time and the + browser was crashed instead of waiting for the shutdown to finish. The status + of objects which were blocking completion of the shutdown when reaching the + timeout is contained in the annotation. + + In the case of IndexedDB, objects are divided into three groups: FactoryOperations, + LiveDatabases and DatabaseMaintenances. + + In the case of LocalStorage, objects are divided into three groups: PrepareDatastoreOperations, + Datastores and LiveDatabases. + + In the case of Cache API, objects are in one group only: Managers. + + Each group is reported separately and contains the number of objects in the + group and the status of individual objects in the group (duplicate entries are + removed): "GroupName: N (objectStatus1, objectStatus2, ...)" where N is the + number of objects in the group. + + The status of individual objects is constructed by taking selected object properties. + Properties which contain origin strings are anonymized. + + In addition, intermediate steps are recorded for change events after shutdown + started. These include the time difference and the type of object.' + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + structure: + items: + type: string + type: array + type: object + remote_type: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Type of the content process, can be set to "web", "file" or "extension". + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + shutdown_progress: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Shutdown step at which the browser crashed, can be set to "quit-application", + "profile-change-teardown", "profile-before-change", "xpcom-will-shutdown" or + "xpcom-shutdown". + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + shutdown_reason: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: One out of "Unknown", "AppClose", "AppRestart", "OSForceClose", "OSSessionEnd" + or "OSShutdown". + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + stack_traces: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Stack traces extracted from the crash minidump, if available. These + are sent in pings, however they are extracted and transformed into a different + format. The field in the extra file is meant only for client use. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + structure: + properties: + crash_address: + type: string + crash_thread: + type: number + crash_type: + type: string + error: + type: string + main_module: + type: number + modules: + items: + properties: + base_address: + type: string + code_id: + type: string + debug_file: + type: string + debug_id: + type: string + end_address: + type: string + filename: + type: string + version: + type: string + type: object + type: array + threads: + items: + properties: + frames: + items: + properties: + ip: + type: string + module_index: + type: number + trust: + type: string + type: object + type: array + type: object + type: array + type: object + type: object + startup: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: If set to 1 then this crash occurred during startup. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean + time: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Crash time in seconds since the Epoch. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + time_unit: second + type: datetime + time_since_last_crash: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Time in seconds since the last crash occurred. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + time_unit: second + type: timespan + utility_actors_name: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Comma-separated list of IPC actors name running on this Utility process + instance + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string_list +crash.windows: + error_reporting: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set to 1 if this crash was intercepted via the Windows Error Reporting + runtime exception module. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean + file_dialog_error_code: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: The HRESULT returned from a Win32 system call leading to termination + of the file-dialog utility process. MozCrashReason is expected to provide context + for the value. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string +dll_blocklist: + init_failed: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set to 1 if the DLL blocklist could not be initialized. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean + list: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Semicolon-separated list of blocked DLLS, Windows-only + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string_list + user32_loaded_before: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Set to 1 if user32.dll was loaded before we could install the DLL + blocklist. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean +environment: + headless_mode: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: True if the app was invoked in headless mode via `--headless ...` + or `--backgroundtask ...`, false otherwise. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: boolean + nimbus_enrollments: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: A comma-separated string that specifies the active Nimbus experiments + and rollouts, as well as their branches. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string_list + uptime: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Uptime in seconds. This annotation uses a string instead of an integer + because it has a fractional component. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + time_unit: millisecond + type: timespan +memory: + available_commit: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: "Available commit-space in bytes. - Under Windows, computed from\ + \ the PERFORMANCE_INFORMATION structure by substracting\n the CommitTotal field\ + \ from the CommitLimit field.\n- Under Linux, computed from /proc/meminfo's\ + \ CommitLimit - Committed_AS. Note that\n the kernel is not guaranteed to enforce\ + \ that CommittedLimit >= Committed_AS. If\n Committed_AS > CommittedLimit,\ + \ this value is set to 0.\n- Not available on other platforms." + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + available_physical: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Amount of free physical memory in bytes. - Under Windows, populated + with the contents of the MEMORYSTATUSEX's structure ullAvailPhys field. - Under + macOS, populated with vm_statistics64_data_t::free_count. - Under Linux, populated + with /proc/meminfo's MemFree. - Not available on other platforms. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + available_swap: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: "Amount of free swap space in bytes. - Under macOS, populated with\ + \ the contents of\n sysctl \"vm.swapusage\" :: xsu_avail.\n- Under Linux, populated\ + \ with /proc/meminfo's SwapFree. - Not available on other platforms." + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + available_virtual: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Amount of free virtual memory in bytes - Under Windows, populated + with the contents of the MEMORYSTATUSEX's structure ullAvailVirtual field. - + Under Linux, populated with /proc/meminfo's MemAvailable. - Not available on + other platforms. - For macOS, see AvailableSwapMemory, AvailablePhysicalMemory + and PurgeablePhysicalMemory. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + js_large_allocation_failure: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: A large allocation couldn't be satisfied, check the JSOutOfMemory + description for the possible values of this annotation. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + js_out_of_memory: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: A small allocation couldn't be satisfied, the annotation may contain + the "Reporting", "Reported" or "Recovered" value. The first one means that we + crashed while responding to the OOM condition (possibly while running a memory-pressure + observers), the second that we crashed after having tried to free some memory, + and the last that the GC had managed to free enough memory to satisfy the allocation. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string + low_physical: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Number of times the available memory tracker has detected that free + physical memory is running low. This is a Windows-specific annotation. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: events + oom_allocation_size: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Size of the allocation that caused an out-of-memory condition. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + purgeable_physical: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: macOS only. Amount of physical memory currently allocated but which + may be deallocated by the system in case of memory pressure. Populated from + vm_statistics64_data_t::purgeable_count * vm_page_size. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + system_use_percentage: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Windows-only, percentage of physical memory in use. This annotation + is populated with the contents of the MEMORYSTATUSEX's structure dwMemoryLoad + field. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: percent + texture: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Amount of memory in bytes consumed by textures. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + total_page_file: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: "Maximum amount of memory that can be committed without extending\ + \ the swap/page file. - Under Windows, populated with the contents of the PERFORMANCE_INFORMATION's\n\ + \ structure CommitLimit field.\n- Under Linux, populated with /proc/meminfo\ + \ MemTotal + SwapTotal. The swap file\n typically cannot be extended, so that's\ + \ a hard limit.\n- Not available on other systems." + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + total_physical: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: Amount of physical memory in bytes. - Under Windows, populated with + the contents of the MEMORYSTATUSEX's structure ullTotalPhys field. - Under macOS, + populated with sysctl "hw.memsize". - Under Linux, populated with /proc/meminfo's + "MemTotal". - Not available on other systems. + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes + total_virtual: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: "Size of the virtual address space. - Under Windows, populated with\ + \ the contents of the MEMORYSTATUSEX's structure\n ullTotalVirtual field.\n\ + - Not available on other platforms." + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: quantity + unit: bytes +windows: + package_family_name: + bugs: + - https://bugzilla.mozilla.org/1950749 + data_reviews: + - https://bugzilla.mozilla.org/1950749 + data_sensitivity: + - technical + description: 'If running in a Windows package context, the package family name, + per https://docs.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getcurrentpackagefamilyname. + + The package family name is only included when it is likely to have been produced + by Mozilla: it starts "Mozilla." or "MozillaCorporation.".' + expires: never + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + send_in_pings: + - crash + type: string diff --git a/toolkit/crashreporter/crashping/glean_metrics.py b/toolkit/crashreporter/crashping/glean_metrics.py @@ -0,0 +1,115 @@ +# 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 sys +from os import path + +import yaml + +header = """ +# 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/. + +# THIS FILE IS AUTO-GENERATED BY glean_metrics.py. DO NOT EDIT! + +""" +source_path = path.join(path.dirname(__file__), "generated_metrics.yaml") + + +def make_bugzilla_url(bug): + return f"https://bugzilla.mozilla.org/{bug}" + + +def remove_non_glean_fields(glean): + """Remove all known fields in the glean descriptor which aren't directly + forwarded to the glean metrics definition file. + """ + if glean["type"] == "string_list": + glean.pop("delimiter") + glean.pop("custom_convert", None) + + +def generate_string(annotations): + output = { + "$schema": "moz://mozilla.org/schemas/glean/metrics/2-0-0", + "$tags": ["Toolkit :: Crash Reporting"], + } + + for name, definition in annotations: + glean = definition.get("glean") + if glean is None: + continue + + metric_category, _, metric_name = glean.pop("metric").rpartition(".") + + remove_non_glean_fields(glean) + + bug_urls = [ + make_bugzilla_url(bug) for bug in definition.get("bugs", ["1950749"]) + ] + + glean.update( + { + "description": definition["description"].strip(), + "notification_emails": [ + "crash-reporting-wg@mozilla.org", + "stability@mozilla.org", + ], + "bugs": bug_urls, + "data_reviews": bug_urls.copy(), # Copy so the produced yaml doesn't use anchors + "data_sensitivity": ["technical"], + "expires": "never", + "send_in_pings": ["crash"], + } + ) + + output.setdefault(metric_category, {})[metric_name] = glean + + return header + yaml.safe_dump(output, explicit_start=True) + + +def import_path(mod, path): + import importlib.util + + spec = importlib.util.spec_from_file_location(mod, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def main(output, annotations_path): + with open(annotations_path) as annotations_file: + annotations = yaml.safe_load(annotations_file) + out = generate_string(annotations) + + # Check whether the source path contents match what we would generate. + source_contents = "" + if path.exists(source_path): + with open(source_path) as file: + source_contents = file.read() + if source_contents != out: + sys.exit(f"{source_path} contents outdated; run ./mach crash-ping-metrics") + + output.write(out) + + return {source_path} + + +if __name__.startswith("mach.commands"): + from mach.decorators import Command + + @Command( + "crash-ping-metrics", + category="misc", + description="Generate the crash ping annotations Glean metrics files.", + ) + def crash_ping_metrics(command_context): + load = import_path( + "annotations.load", + path.join(path.dirname(__file__), "..", "annotations", "load.py"), + ) + annotations = load.read_annotations() + with open(source_path, "w") as file: + file.write(generate_string(annotations)) diff --git a/toolkit/crashreporter/crashping/glean_rust.py b/toolkit/crashreporter/crashping/glean_rust.py @@ -0,0 +1,34 @@ +# 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 https://mozilla.org/MPL/2.0/. + +import shutil +import sys +from contextlib import redirect_stderr, redirect_stdout +from io import StringIO +from pathlib import Path +from tempfile import TemporaryDirectory + + +def main(output, *paths): + # There's no way to just get the output as a string nor to write to our + # `output`, so we have to make a temporary directory for glean_parser to + # write to (which is ironic as glean_parser makes a temporary directory + # itself). + with TemporaryDirectory() as outdir: + outdir_path = Path(outdir) + # Capture translate output to only display on error + translate_output = StringIO() + with redirect_stdout(translate_output), redirect_stderr(translate_output): + # This is a bit tricky: sys.stderr is bound as a default argument + # in some functions of glean_parser, so we must redirect stderr + # _before_ importing the module. + from glean_parser.translate import translate + + result = translate([Path(p) for p in paths], "rust", outdir_path) + if result != 0: + print(translate_output.getvalue()) + sys.exit(result) + glean_metrics_file = outdir_path / "glean_metrics.rs" + with glean_metrics_file.open() as glean_metrics: + shutil.copyfileobj(glean_metrics, output) diff --git a/toolkit/crashreporter/crashping/moz.build b/toolkit/crashreporter/crashping/moz.build @@ -0,0 +1,32 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# Generate the metrics file and ensure the srcdir metrics file is up-to-date. +# The srcdir metrics file is necessary for the Glean probe-scraper to read and +# create database tables. +GeneratedFile( + "generated_metrics.yaml", + script="glean_metrics.py", + inputs=["!../annotations/validated.yaml"], +) + +# Generate the Rust language bindings for the crash ping metrics. +GeneratedFile( + "glean_metrics.rs", + script="glean_rust.py", + inputs=[ + "/toolkit/components/glean/tags.yaml", + "pings.yaml", + "!generated_metrics.yaml", + ], +) + +# Generate the conversion code for each annotation to Glean metrics. +GeneratedFile( + "conversions.rs", + script="conversions.py", + inputs=["!../annotations/validated.yaml"], +) diff --git a/toolkit/crashreporter/crashping/pings.yaml b/toolkit/crashreporter/crashping/pings.yaml @@ -0,0 +1,28 @@ +# 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/pings/2-0-0 + +crash: + description: > + A ping to report crash information. This information is sent as soon as + possible after a crash occurs (whether the crash is a background/content + process or the main process). It is expected to be used for crash report + analysis and to reduce blind spots in crash reporting. + include_client_id: true + send_if_empty: false + notification_emails: + - crash-reporting-wg@mozilla.org + - stability@mozilla.org + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1790569 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1790569#c12 + reasons: + crash: > + A process crashed and a ping was immediately sent. + event_found: > + A process crashed and produced a crash event, which was later found and + sent in a ping. diff --git a/toolkit/crashreporter/crashping/src/annotations.rs b/toolkit/crashreporter/crashping/src/annotations.rs @@ -0,0 +1,355 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use super::glean_metrics; +use anyhow::Context; +use glean::private::{ + BooleanMetric, DatetimeMetric, ObjectMetric, QuantityMetric, StringListMetric, StringMetric, + TimespanMetric, +}; +use glean::TestGetValue; + +pub struct Annotation { + pub key: &'static str, + pub glean_key: &'static str, + #[cfg(test)] + pub convert_fn: &'static str, + pub set_glean_metric: fn(&serde_json::Value) -> anyhow::Result<()>, + pub test_get_glean_value: fn() -> Option<serde_json::Value>, +} + +macro_rules! convert { + ( $category:ident :: $metric:ident = $func:ident ($key:literal $(, $($args:expr),*)? ) ) => { + Annotation { + key: $key, + glean_key: concat!(stringify!($category), ".", stringify!($metric)), + #[cfg(test)] + convert_fn: stringify!($func), + set_glean_metric: |value: &serde_json::Value| -> anyhow::Result<()> { + SourceValue(value).try_into().map_err(|e| anyhow::Error::from(e)) + .and_then(|v| $func(&glean_metrics::$category::$metric, v $(, $($args),*)? )) + .context(concat!("while trying to set ", stringify!($category), "::", stringify!($metric), " from annotation ", $key)) + }, + test_get_glean_value: || -> Option<serde_json::Value> { + glean_metrics::$category::$metric.test_get_value(Some("crash".into())) + .map(|v| TestGetValueToJson(v).into()) + } + } + }; +} + +// Env variable set to the file generated by conversions.py (by build.rs). +include!(env!("CONVERSIONS_FILE")); + +struct SourceValue<'a>(&'a serde_json::Value); + +impl<'a> TryFrom<SourceValue<'a>> for &'a str { + type Error = anyhow::Error; + + fn try_from(value: SourceValue<'a>) -> Result<Self, Self::Error> { + value.0.as_str().context("expected a string") + } +} + +impl<'a> TryFrom<SourceValue<'a>> for &'a serde_json::Value { + type Error = std::convert::Infallible; + + fn try_from(value: SourceValue<'a>) -> Result<Self, Self::Error> { + Ok(value.0) + } +} + +struct TestGetValueToJson<T>(T); + +macro_rules! TestGetValueToJson_passthrough { + ( $($T:ty),* ) => { + $( + impl From<TestGetValueToJson<$T>> for serde_json::Value { + fn from(value: TestGetValueToJson<$T>) -> Self { + value.0.into() + } + })* + } +} + +TestGetValueToJson_passthrough![serde_json::Value, String, bool, i64, Vec<String>]; + +impl From<TestGetValueToJson<glean::Datetime>> for serde_json::Value { + fn from(value: TestGetValueToJson<glean::Datetime>) -> Self { + let glean::Datetime { + year, + month, + day, + hour, + minute, + second, + nanosecond, + offset_seconds, + } = value.0; + + time::OffsetDateTime::new_in_offset( + time::Date::from_calendar_date(year, (month as u8).try_into().unwrap(), day as u8) + .unwrap(), + time::Time::from_hms_nano(hour as u8, minute as u8, second as u8, nanosecond).unwrap(), + time::UtcOffset::from_whole_seconds(offset_seconds).unwrap(), + ) + .to_string() + .into() + } +} + +fn convert_boolean_to_boolean(metric: &BooleanMetric, value: &str) -> anyhow::Result<()> { + metric.set(value == "1"); + Ok(()) +} + +fn convert_string_to_string(metric: &StringMetric, value: &str) -> anyhow::Result<()> { + metric.set(value.to_owned()); + Ok(()) +} + +fn convert_u64_to_quantity(metric: &QuantityMetric, value: &str) -> anyhow::Result<()> { + metric.set(value.parse().context("couldn't parse quantity")?); + Ok(()) +} + +fn convert_string_to_string_list( + metric: &StringListMetric, + value: &str, + delimiter: &str, +) -> anyhow::Result<()> { + metric.set( + value + .split(delimiter) + .filter(|s| !s.is_empty()) + .map(|s| s.to_owned()) + .collect(), + ); + Ok(()) +} + +fn convert_to_crash_time(metric: &DatetimeMetric, value: &str) -> anyhow::Result<()> { + let seconds_since_epoch: i64 = value + .parse() + .context("failed to parse crash time seconds")?; + metric.set(Some(glean_datetime( + time::OffsetDateTime::from_unix_timestamp(seconds_since_epoch) + .context("failed to convert crash time unix timestamp")?, + ))); + Ok(()) +} + +fn convert_duration_seconds(metric: &TimespanMetric, value: &str) -> anyhow::Result<()> { + metric.set_raw(std::time::Duration::from_secs( + value + .parse() + .context("failed to parse seconds since last crash")?, + )); + Ok(()) +} + +fn convert_to_environment_uptime(metric: &TimespanMetric, value: &str) -> anyhow::Result<()> { + metric.set_raw(std::time::Duration::from_secs_f32( + value.parse().context("failed to parse uptime")?, + )); + Ok(()) +} + +fn convert_to_crash_windows_file_dialog_error_code( + metric: &StringMetric, + value: &str, +) -> anyhow::Result<()> { + metric.set(value.to_owned()); // Just assume it is an integer as expected + Ok(()) +} + +fn convert_to_crash_async_shutdown_timeout( + metric: &ObjectMetric<glean_metrics::crash::AsyncShutdownTimeoutObject>, + value: &str, +) -> anyhow::Result<()> { + // The JSON value is stored as a string, so we need to deserialize it. + let mut value: serde_json::Map<String, serde_json::Value> = serde_json::from_str(value)?; + metric.set(glean_metrics::crash::AsyncShutdownTimeoutObject { + broken_add_blockers: { + if let Some(broken_add_blockers) = value.remove("brokenAddBlockers") { + let broken_add_blockers = broken_add_blockers + .as_array() + .context("expected array for brokenAddBlockers")?; + let mut ret = Vec::with_capacity(broken_add_blockers.len()); + for entry in broken_add_blockers { + ret.push( + entry + .as_str() + .context("expected brokenAddBlockers entry to be a string")? + .to_owned(), + ); + } + ret + } else { + Default::default() + } + }, + conditions: { + if let Some(conditions) = value.remove("conditions") { + Some(if let Some(s) = conditions.as_str() { + s.to_owned() + } else { + serde_json::to_string(&conditions).context("failed to serialize conditions")? + }) + } else { + None + } + }, + phase: { + if let Some(phase) = value.remove("phase") { + Some( + phase + .as_str() + .context("expected phase to be a string")? + .to_owned(), + ) + } else { + None + } + }, + }); + Ok(()) +} + +fn convert_to_crash_quota_manager_shutdown_timeout( + metric: &ObjectMetric<glean_metrics::crash::QuotaManagerShutdownTimeoutObject>, + value: &str, +) -> anyhow::Result<()> { + // The Glean metric is an array of the lines. + metric.set(value.lines().map(|s| s.to_owned()).collect::<Vec<_>>()); + Ok(()) +} + +fn convert_to_crash_stack_traces( + metric: &ObjectMetric<glean_metrics::crash::StackTracesObject>, + value: &serde_json::Value, +) -> anyhow::Result<()> { + let error = value["status"] + .as_str() + .and_then(|v| (v != "OK").then_some(v.to_owned())); + let crash_type = value["crash_info"]["type"].as_str().map(|s| s.to_owned()); + let crash_address = value["crash_info"]["address"] + .as_str() + .map(|s| s.to_owned()); + let crash_thread = value["crash_info"]["crashing_thread"].as_i64(); + let main_module = value["main_module"].as_i64(); + let modules = value["modules"] + .as_array() + .map(|modules| { + modules + .iter() + .map(|m| glean_metrics::crash::StackTracesObjectModulesItem { + base_address: m["base_addr"].as_str().map(|s| s.to_owned()), + end_address: m["end_addr"].as_str().map(|s| s.to_owned()), + code_id: m["code_id"].as_str().map(|s| s.to_owned()), + debug_file: m["debug_file"].as_str().map(|s| s.to_owned()), + debug_id: m["debug_id"].as_str().map(|s| s.to_owned()), + filename: m["filename"].as_str().map(|s| s.to_owned()), + version: m["version"].as_str().map(|s| s.to_owned()), + }) + .collect::<Vec<_>>() + }) + .unwrap_or_default(); + let threads = value["threads"] + .as_array() + .map(|threads| { + threads + .iter() + .map(|t| glean_metrics::crash::StackTracesObjectThreadsItem { + frames: t["frames"] + .as_array() + .map(|frames| { + frames + .iter() + .map(|f| { + glean_metrics::crash::StackTracesObjectThreadsItemFramesItem { + module_index: f["module_index"].as_i64(), + ip: f["ip"].as_str().map(|s| s.to_owned()), + trust: f["trust"].as_str().map(|s| s.to_owned()), + } + }) + .collect::<Vec<_>>() + }) + .unwrap_or_default(), + }) + .collect::<Vec<_>>() + }) + .unwrap_or_default(); + metric.set(glean_metrics::crash::StackTracesObject { + error, + crash_type, + crash_address, + crash_thread, + main_module, + modules, + threads, + }); + Ok(()) +} + +// The format of the JSON value is defined in getStacktraceAsJsonString +// (mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/ext/Throwable.kt). +fn convert_to_crash_java_exception( + metric: &ObjectMetric<glean_metrics::crash::JavaExceptionObject>, + value: &str, +) -> anyhow::Result<()> { + // The JSON value is stored as a string, so we need to deserialize it. + let value: serde_json::Map<String, serde_json::Value> = serde_json::from_str(value)?; + let throwables = value["exception"]["values"] + .as_array() + .context("expected throwables array")? + .iter() + // The format stores throwables in reverse order (deepest cause first), but we define the + // glean format to be by top cause first. + .rev() + .map(|throwable| &throwable["stacktrace"]) + .map( + |throwable| glean_metrics::crash::JavaExceptionObjectThrowablesItem { + message: throwable["value"].as_str().map(ToOwned::to_owned), + type_name: throwable["module"] + .as_str() + .zip(throwable["type"].as_str()) + .map(|(m, t)| format!("{m}.{t}")), + stack: throwable["frames"] + .as_array() + .map(|frames| { + frames + .iter() + .map(|frame| { + glean_metrics::crash::JavaExceptionObjectThrowablesItemStackItem { + class_name: frame["module"].as_str().map(ToOwned::to_owned), + method_name: frame["function"].as_str().map(ToOwned::to_owned), + is_native: frame["in_app"].as_bool().map(|b| !b), + line: frame["lineno"].as_i64(), + file: frame["filename"].as_str().map(ToOwned::to_owned), + } + }) + .collect() + }) + .unwrap_or_default(), + }, + ) + .collect(); + + metric.set(glean_metrics::crash::JavaExceptionObject { throwables }); + Ok(()) +} + +fn glean_datetime(datetime: time::OffsetDateTime) -> glean::Datetime { + glean::Datetime { + year: datetime.year(), + month: datetime.month() as _, + day: datetime.day() as _, + hour: datetime.hour() as _, + minute: datetime.minute() as _, + second: datetime.second() as _, + nanosecond: datetime.nanosecond(), + offset_seconds: datetime.offset().whole_seconds(), + } +} diff --git a/toolkit/crashreporter/crashping/src/lib.rs b/toolkit/crashreporter/crashping/src/lib.rs @@ -0,0 +1,540 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! The crashping crate allows populating and sending the crash ping, which contains all +//! ping-scoped crash annotations. + +pub use glean::net; +pub use glean::ClientInfoMetrics; +use glean::{Configuration, ConfigurationBuilder}; +use std::path::PathBuf; + +mod glean_metrics { + // Env variable set to the file generated by glean_rust.py (by build.rs). + include!(env!("GLEAN_METRICS_FILE")); +} +mod annotations; +use annotations::ANNOTATIONS; + +const TELEMETRY_SERVER: &str = "https://incoming.telemetry.mozilla.org"; + +/// Initialize the Glean ping. This must be called _before_ Glean initialization. +/// +/// Since Glean v63.0.0, custom pings are required to be instantiated prior to Glean init +/// in order to ensure they are enabled and able to collect data. This is due to the data +/// collection state being determined at the ping level now instead of just by the global +/// Glean collection enabled flag. See Bug 1934931 for more information. +pub fn init() { + _ = &*glean_metrics::crash; +} + +/// Initialize Glean and the Glean ping. +/// +/// If using this type, you do not need to call `init()`. This is intended for use in runtimes that +/// are only using Glean to send the crash ping. +/// +/// You should be sure to set an `uploader` on the `configuration` before initializing. It is +/// recommended to set the `ClientInfoMetrics` fields to more useful values as well. +pub struct InitGlean { + pub configuration: Configuration, + pub client_info_metrics: ClientInfoMetrics, + pub clear_uploader_for_tests: bool, +} + +impl InitGlean { + /// The data_dir should be a dedicated directory for use by Glean. + pub fn new(data_dir: PathBuf, app_id: &str, client_info_metrics: ClientInfoMetrics) -> Self { + InitGlean { + configuration: ConfigurationBuilder::new(true, data_dir, app_id) + .with_server_endpoint(TELEMETRY_SERVER) + .with_use_core_mps(false) + .with_internal_pings(false) + .build(), + client_info_metrics, + clear_uploader_for_tests: true, + } + } + + pub fn initialize(self) { + self.init_with(glean::initialize) + } + + pub fn test_reset_glean(self, clear_stores: bool) { + self.init_with(move |c, m| glean::test_reset_glean(c, m, clear_stores)) + } + + pub fn init_with<F: FnOnce(Configuration, ClientInfoMetrics)>(self, f: F) { + // Clear the uploader for tests, if configured. + // No need to check `cfg!(test)`, since we don't set an uploader in unit tests (and if we + // did, it would be test-specific). + let is_test = std::env::var_os("XPCSHELL_TEST_PROFILE_DIR").is_some() + || std::env::var_os("MOZ_AUTOMATION").is_some(); + if self.clear_uploader_for_tests && is_test { + self.configuration.uploader = None; + self.configuration.server_endpoint = None; + } + + init(); + f(self.configuration, self.client_info_metrics) + } +} + +/// Send the Glean crash ping. +pub fn send(annotations: &serde_json::Value, reason: Option<&str>) -> anyhow::Result<()> { + // The crash.time metric may be overwritten if a CrashTime annotation is present. + glean_metrics::crash::time.set(None); + set_metrics_from_annotations(annotations)?; + log::debug!("submitting Glean crash ping"); + glean_metrics::crash.submit(reason); + Ok(()) +} + +/// **Test-only API** +/// +/// Register a callback that will be called before the next ping is sent. +pub fn test_before_next_send<F: FnOnce(Option<&str>) + Send + 'static>(cb: F) { + glean_metrics::crash.test_before_next_submit(cb); +} + +/// **Test-only API** +/// +/// Get all metric values as a JSON object. +pub fn test_get_metric_values() -> serde_json::Value { + let mut ret: serde_json::Map<String, serde_json::Value> = Default::default(); + for annotation in ANNOTATIONS { + if let Some(value) = (annotation.test_get_glean_value)() { + ret.insert(annotation.glean_key.into(), value); + } + } + ret.into() +} + +/// Set Glean metrics from the given annotations. +fn set_metrics_from_annotations(annotations: &serde_json::Value) -> anyhow::Result<()> { + for annotation in ANNOTATIONS { + if let Some(value) = annotations.get(annotation.key) { + (annotation.set_glean_metric)(value)?; + } + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::{send, test_before_next_send, ANNOTATIONS}; + use std::sync::{ + atomic::{AtomicBool, Ordering::Relaxed}, + Arc, Mutex, + }; + + static TEST_LOCK: Mutex<()> = Mutex::new(()); + + #[must_use] + fn test_init_glean() -> std::sync::MutexGuard<'static, ()> { + let guard = TEST_LOCK.lock().unwrap(); + let glean_path = std::env::temp_dir().join("crashping_glean"); + super::InitGlean::new( + glean_path, + "crashping.test", + super::ClientInfoMetrics::unknown(), + ) + .test_reset_glean(true); + return guard; + } + + /// Run a test that uses Glean. + /// + /// This function ensures that Glean tests run sequentially. + fn glean_test<F: FnOnce() + std::panic::UnwindSafe>(f: F) { + let _ = env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .is_test(true) + .try_init(); + let res = { + let _guard = test_init_glean(); + // Catch panics so that we don't poison the mutex (so other tests can run). + std::panic::catch_unwind(f) + }; + if let Err(e) = res { + std::panic::resume_unwind(e); + } + } + + #[derive(Clone)] + struct SoftAssert { + failed: Arc<AtomicBool>, + message: &'static str, + } + + impl SoftAssert { + fn new(failed: bool, message: &'static str) -> Self { + SoftAssert { + failed: Arc::new(AtomicBool::new(failed)), + message, + } + } + + fn assert<T: std::fmt::Display>(&self, value: bool, msg: T) { + if !value { + eprintln!("{}", msg); + self.failed.store(true, Relaxed); + } + } + + fn clear(&self) { + self.failed.store(false, Relaxed); + } + } + + impl Drop for SoftAssert { + fn drop(&mut self) { + assert!(!self.failed.load(Relaxed), "{}", self.message); + } + } + + #[test] + fn all_annotations() { + glean_test(|| { + // Set test values here if the default values aren't sufficient nor applicable. + let mut annotations = serde_json::json!({ + "AsyncShutdownTimeout": "{\"phase\":\"abcd\",\"conditions\":[{\"foo\":\"bar\"}],\"brokenAddBlockers\":[\"foo\"]}", + "BlockedDllList": "Foo.dll;bar.dll;rawr.dll", + "CrashTime": "1234", + "LastInteractionDuration": "100", + "NimbusEnrollments": "a,b,c,d,e", + "QuotaManagerShutdownTimeout": "line1\nline2\nline3", + "JavaException": r#"{ + "exception": { + "values": [{ + "stacktrace": { + "value": "something went wrong", + "module": "foo.bar", + "type": "FooBarType", + "frames": [{ + "module": "org.mozilla", + "function": "FooBar", + "in_app": true, + "lineno": 42, + "file": "FooBar.java" + }] + } + }] + } + }"#, + "SecondsSinceLastCrash": "50000", + "StackTraces": { + "status": "OK", + // Add extraneous field to ensure it doesn't affect setting the metric + "foobar": "baz", + "crash_info": { + "type": "bad crash", + "address": "0xcafe", + "crashing_thread": 1 + }, + "main_module": 0, + "modules": [{ + "base_addr": "0xcafe", + "end_addr": "0xf000", + "code_id": "CODEID", + "debug_file": "debug_file.so", + "debug_id": "DEBUGID", + "filename": "file.so", + "version": "1.0.0" + }], + "threads": [ + {"frames": [ + { + "ip": "0xf00", + "trust": "crash", + "module_index": 0 + } + ]}, + {"frames": [ + { + "ip": "0x0", + "trust": "crash", + "module_index": 0 + }, + { + "ip": "0xbadf00d", + "trust": "cfi", + "module_index": 0 + } + ]} + ] + }, + "UptimeTS": "400.5", + "UtilityActorsName": "abc,def", + "WindowsFileDialogErrorCode": "40", + }); + + // For convenience, automatically populate example values for the simple cases. + for annotation in ANNOTATIONS { + if !annotations + .as_object() + .unwrap() + .contains_key(annotation.key) + { + let default_val: Option<serde_json::Value> = match annotation.convert_fn { + "convert_boolean_to_boolean" => Some("1".into()), + "convert_string_to_string" => Some("some_string".into()), + "convert_u64_to_quantity" => Some("42".into()), + _ => None, + }; + if let Some(val) = default_val { + annotations + .as_object_mut() + .unwrap() + .insert(annotation.key.to_owned(), val); + } + } + } + + let success = SoftAssert::new(false, "one or more failures occurred"); + + // Ensure all annotations have a test value. + for annotation in ANNOTATIONS { + success.assert( + annotations + .as_object() + .unwrap() + .contains_key(annotation.key), + format!("{} test value is not set", annotation.key), + ); + } + + // Ensure all metrics are set. + let metrics_tested = SoftAssert::new(true, "test_before_next_send did not run"); + { + let success = success.clone(); + let metrics_tested = metrics_tested.clone(); + test_before_next_send(move |_| { + for annotation in ANNOTATIONS { + success.assert( + (annotation.test_get_glean_value)().is_some(), + format!("{} not set", annotation.glean_key), + ); + } + metrics_tested.clear(); + }); + } + + send(&annotations, Some("crash")).expect("failed to set metrics"); + }); + } + + fn test_annotation( + annotation: &'static crate::annotations::Annotation, + value: serde_json::Value, + expected: serde_json::Value, + ) { + glean_test(|| { + let annotations = serde_json::json!({annotation.key: value}); + + let sent = SoftAssert::new(true, "test_before_next_send did not run"); + let check = SoftAssert::new(false, "annotation check failed"); + let sent_inner = sent.clone(); + let check_inner = check.clone(); + let input_str = annotations.to_string(); + test_before_next_send(move |_| { + sent_inner.clear(); + // Use a SoftAssert rather than `assert_eq!` so that we don't panic in the callback + // (which will poison the mutex that Glean uses, making other tests fail + // unnecessarily). + let actual = (annotation.test_get_glean_value)(); + if let Some(actual) = actual { + check_inner.assert(actual == expected, "value mismatch"); + } else { + check_inner.assert( + false, + format!( + "missing value for {} with input {}", + annotation.glean_key, input_str, + ), + ); + } + }); + + send(&annotations, Some("crash")).expect("failed to set metrics"); + }); + } + + macro_rules! test_annotations { + ( ) => {}; + ( $test_name:ident ($annotation:ident) { $($value:tt => $expected:tt),+ } $($rest:tt)* ) => { + #[test] + fn $test_name() { + $(test_annotation(&crate::annotations::$annotation, serde_json::json!($value), serde_json::json!($expected));)* + } + + test_annotations! { $($rest)* } + }; + } + + test_annotations! { + test_async_shutdown_timeout(AsyncShutdownTimeout) { + r#"{"phase":"AddonManager: Waiting to start provider shutdown.","conditions":[{"name":"AddonRepository Background Updater","state":"(none)","filename":"resource://gre/modules/addons/AddonRepository.sys.mjs","lineNumber":576,"stack":["resource://gre/modules/addons/AddonRepository.sys.mjs:backgroundUpdateCheck:576","resource://gre/modules/AddonManager.sys.mjs:backgroundUpdateCheck/buPromise<:1269"]}],"brokenAddBlockers":["JSON store: writing data for 'creditcards' - IOUtils: waiting for profileBeforeChange IO to complete finished","StorageSyncService: shutdown - profile-change-teardown finished"]}"# + => { + "phase": "AddonManager: Waiting to start provider shutdown.", + "conditions": serde_json::json!([ + { + "name": "AddonRepository Background Updater", + "state": "(none)", + "filename": "resource://gre/modules/addons/AddonRepository.sys.mjs", + "lineNumber": 576, + "stack": [ + "resource://gre/modules/addons/AddonRepository.sys.mjs:backgroundUpdateCheck:576", + "resource://gre/modules/AddonManager.sys.mjs:backgroundUpdateCheck/buPromise<:1269", + ] + } + ]).to_string(), + "broken_add_blockers": [ + "JSON store: writing data for 'creditcards' - IOUtils: waiting for profileBeforeChange IO to complete finished", + "StorageSyncService: shutdown - profile-change-teardown finished" + ] + } + } + + test_blocked_dll_list(BlockedDllList) { + "Foo.dll;bar.dll;rawr.dll" => ["Foo.dll", "bar.dll", "rawr.dll"] + } + + test_java_exception(JavaException) { + r#"{ + "exception": { + "values": [{ + "stacktrace": { + "value": "something went wrong", + "module": "foo.bar", + "type": "FooBarType", + "frames": [{ + "module": "org.mozilla", + "function": "FooBar", + "in_app": true, + "lineno": 42, + "filename": "FooBar.java" + }] + } + }] + } + }"# + => { + "throwables": [ + { + "message": "something went wrong", + "type_name": "foo.bar.FooBarType", + "stack": [ + { + "class_name": "org.mozilla", + "method_name": "FooBar", + "file": "FooBar.java", + "is_native": false, + "line": 42 + } + ] + } + ] + } + } + + test_nimbus_enrollments(NimbusEnrollments) { + "foo:control,bar:treatment-a" => ["foo:control", "bar:treatment-a"] + } + + test_quota_manager_shutdown_timeout(QuotaManagerShutdownTimeout) { + "foo\nbar\nbaz" => ["foo", "bar", "baz"] + } + + test_stack_traces(StackTraces) { + { + "status": "OK", + "crash_info": { + "type": "main", + "address": "0xf001ba11", + "crashing_thread": 1 + }, + "main_module": 0, + "modules": [ + { + "base_addr": "0x00000000", + "end_addr": "0x00004000", + "code_id": "8675309", + "debug_file": "", + "debug_id": "18675309", + "filename": "foo.exe", + "version": "1.0.0" + }, + { + "base_addr": "0x00004000", + "end_addr": "0x00008000", + "code_id": "42", + "debug_file": "foo.pdb", + "debug_id": "43", + "filename": "foo.dll", + "version": "1.1.0" + } + ], + "threads": [ + { + "frames": [ + { "module_index": 0, "ip": "0x10", "trust": "context" }, + { "module_index": 0, "ip": "0x20", "trust": "cfi" } + ] + }, + { + "frames": [ + { "module_index": 1, "ip": "0x4010", "trust": "context" }, + { "module_index": 0, "ip": "0x30", "trust": "cfi" } + ] + } + ] + } + => { + "crash_type": "main", + "crash_address": "0xf001ba11", + "crash_thread": 1, + "main_module": 0, + "modules": [ + { + "base_address": "0x00000000", + "end_address": "0x00004000", + "code_id": "8675309", + "debug_file": "", + "debug_id": "18675309", + "filename": "foo.exe", + "version": "1.0.0", + }, + { + "base_address": "0x00004000", + "end_address": "0x00008000", + "code_id": "42", + "debug_file": "foo.pdb", + "debug_id": "43", + "filename": "foo.dll", + "version": "1.1.0", + }, + ], + "threads": [ + { + "frames": [ + { "module_index": 0, "ip": "0x10", "trust": "context" }, + { "module_index": 0, "ip": "0x20", "trust": "cfi" }, + ], + }, + { + "frames": [ + { "module_index": 1, "ip": "0x4010", "trust": "context" }, + { "module_index": 0, "ip": "0x30", "trust": "cfi" }, + ], + }, + ], + } + } + + test_utility_actors_name(UtilityActorsName) { + "audio-decoder-generic,js-oracle" => ["audio-decoder-generic","js-oracle"] + } + } +} diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build @@ -84,6 +84,7 @@ if CONFIG["MOZ_CRASHREPORTER"]: "crash_helper", "crash_helper_client", "crash_helper_server", + "crashping", "mozannotation_client", ] diff --git a/toolkit/library/rust/moz.build b/toolkit/library/rust/moz.build @@ -21,6 +21,7 @@ for feature in gkrust_features: # Target directory doesn't matter a lot here, since we can't share panic=abort # compilation artifacts with gkrust. RUST_TESTS = [ + "crashping", "crashreporter", "firefox-on-glean", "l10nregistry",