diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /toolkit/components/glean | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-adbda400be353e676059e335c3c0aaf99e719475.tar.xz firefox-adbda400be353e676059e335c3c0aaf99e719475.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/glean')
66 files changed, 1470 insertions, 218 deletions
diff --git a/toolkit/components/glean/Cargo.toml b/toolkit/components/glean/Cargo.toml index 45423a183b..98b8d8a95b 100644 --- a/toolkit/components/glean/Cargo.toml +++ b/toolkit/components/glean/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "MPL-2.0" [dependencies] -glean = "57.0.0" +glean = "58.1.0" log = "0.4" nserror = { path = "../../../xpcom/rust/nserror" } nsstring = { path = "../../../xpcom/rust/nsstring" } @@ -18,6 +18,10 @@ cstr = "0.2" viaduct = "0.1" url = "2.1" thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } +ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client"] } +bhttp = "0.3" +thiserror = "1.0" +mozbuild = "0.1" [features] # Leave data collection enabled, but disable upload. diff --git a/toolkit/components/glean/api/Cargo.toml b/toolkit/components/glean/api/Cargo.toml index 403168fb90..dc688dc41e 100644 --- a/toolkit/components/glean/api/Cargo.toml +++ b/toolkit/components/glean/api/Cargo.toml @@ -9,7 +9,7 @@ license = "MPL-2.0" [dependencies] bincode = "1.0" chrono = "0.4.10" -glean = "57.0.0" +glean = "58.1.0" inherent = "1.0.0" log = "0.4" nsstring = { path = "../../../../xpcom/rust/nsstring", optional = true } @@ -19,6 +19,7 @@ uuid = { version = "1.0", features = ["v4"] } xpcom = { path = "../../../../xpcom/rust/xpcom", optional = true } thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } mozbuild = "0.1" +serde_json = "1" [dev-dependencies] tempfile = "3.1.0" diff --git a/toolkit/components/glean/api/src/ffi/custom_distribution.rs b/toolkit/components/glean/api/src/ffi/custom_distribution.rs index 853a6e9845..643ebfbff5 100644 --- a/toolkit/components/glean/api/src/ffi/custom_distribution.rs +++ b/toolkit/components/glean/api/src/ffi/custom_distribution.rs @@ -22,6 +22,7 @@ pub extern "C" fn fog_custom_distribution_test_get_value( id: u32, ping_name: &nsACString, sum: &mut u64, + count: &mut u64, buckets: &mut ThinVec<u64>, counts: &mut ThinVec<u64>, ) { @@ -33,6 +34,7 @@ pub extern "C" fn fog_custom_distribution_test_get_value( ); // FIXME(bug 1771885): Glean should use `u64` where it can. *sum = val.sum as _; + *count = val.count as _; for (&bucket, &count) in val.values.iter() { buckets.push(bucket as _); counts.push(count as _); diff --git a/toolkit/components/glean/api/src/ffi/memory_distribution.rs b/toolkit/components/glean/api/src/ffi/memory_distribution.rs index cf09d3f8de..35c8326c4d 100644 --- a/toolkit/components/glean/api/src/ffi/memory_distribution.rs +++ b/toolkit/components/glean/api/src/ffi/memory_distribution.rs @@ -22,6 +22,7 @@ pub extern "C" fn fog_memory_distribution_test_get_value( id: u32, ping_name: &nsACString, sum: &mut u64, + count: &mut u64, buckets: &mut ThinVec<u64>, counts: &mut ThinVec<u64>, ) { @@ -32,6 +33,7 @@ pub extern "C" fn fog_memory_distribution_test_get_value( test_get!(metric, ping_name) ); *sum = val.sum as _; + *count = val.count as _; for (&bucket, &count) in val.values.iter() { buckets.push(bucket as _); counts.push(count as _); diff --git a/toolkit/components/glean/api/src/ffi/mod.rs b/toolkit/components/glean/api/src/ffi/mod.rs index 23235fc2f1..4eb614aefc 100644 --- a/toolkit/components/glean/api/src/ffi/mod.rs +++ b/toolkit/components/glean/api/src/ffi/mod.rs @@ -16,6 +16,7 @@ mod event; mod labeled; mod memory_distribution; mod numerator; +mod object; mod ping; mod quantity; mod rate; diff --git a/toolkit/components/glean/api/src/ffi/object.rs b/toolkit/components/glean/api/src/ffi/object.rs new file mode 100644 index 0000000000..85f5269da9 --- /dev/null +++ b/toolkit/components/glean/api/src/ffi/object.rs @@ -0,0 +1,68 @@ +// 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/. + +#![cfg(feature = "with_gecko")] + +use nsstring::nsACString; + +use crate::metrics::__glean_metric_maps as metric_maps; + +#[no_mangle] +pub extern "C" fn fog_object_set_string(id: u32, value: &nsACString) { + if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 { + panic!("No dynamic metric for objects"); + } + + let value = value.to_utf8().to_string(); + if metric_maps::set_object_by_id(id, value).is_err() { + panic!("No object for id {}", id); + } +} + +#[no_mangle] +pub unsafe extern "C" fn fog_object_test_has_value(id: u32, ping_name: &nsACString) -> bool { + let storage = if ping_name.is_empty() { + None + } else { + Some(ping_name.to_utf8().into_owned()) + }; + if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 { + panic!("No dynamic metric for objects"); + } else { + metric_maps::object_test_get_value(id, storage).is_some() + } +} + +#[no_mangle] +pub extern "C" fn fog_object_test_get_value( + id: u32, + ping_name: &nsACString, + value: &mut nsACString, +) { + let storage = if ping_name.is_empty() { + None + } else { + Some(ping_name.to_utf8().into_owned()) + }; + + let object = if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 { + panic!("No dynamic metric for objects"); + } else { + match metric_maps::object_test_get_value(id, storage) { + Some(object) => object, + None => return, + } + }; + value.assign(&object); +} + +#[no_mangle] +pub extern "C" fn fog_object_test_get_error(id: u32, error_str: &mut nsACString) -> bool { + let err = if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 { + panic!("No dynamic metric for objects"); + } else { + metric_maps::object_test_get_error(id) + }; + err.map(|err_str| error_str.assign(&err_str)).is_some() +} diff --git a/toolkit/components/glean/api/src/ffi/timing_distribution.rs b/toolkit/components/glean/api/src/ffi/timing_distribution.rs index 4ac5d03986..4391985efa 100644 --- a/toolkit/components/glean/api/src/ffi/timing_distribution.rs +++ b/toolkit/components/glean/api/src/ffi/timing_distribution.rs @@ -58,6 +58,7 @@ pub extern "C" fn fog_timing_distribution_test_get_value( id: u32, ping_name: &nsACString, sum: &mut u64, + count: &mut u64, buckets: &mut ThinVec<u64>, counts: &mut ThinVec<u64>, ) { @@ -68,6 +69,7 @@ pub extern "C" fn fog_timing_distribution_test_get_value( test_get!(metric, ping_name) ); *sum = val.sum as _; + *count = val.count as _; for (&bucket, &count) in val.values.iter() { buckets.push(bucket as _); counts.push(count as _); diff --git a/toolkit/components/glean/api/src/pings.rs b/toolkit/components/glean/api/src/pings.rs index f1d0332695..21eb3855ee 100644 --- a/toolkit/components/glean/api/src/pings.rs +++ b/toolkit/components/glean/api/src/pings.rs @@ -6,7 +6,7 @@ //! //! The contents of this module are generated by //! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from -//! 'toolkit/components/glean/pings.yaml`. +//! ping definitions files identified by `toolkit/components/glean/metrics_index.py`. include!(mozbuild::objdir_path!( "toolkit/components/glean/api/src/pings.rs" diff --git a/toolkit/components/glean/api/src/private/custom_distribution.rs b/toolkit/components/glean/api/src/private/custom_distribution.rs index 2114430898..aeaf9b58c2 100644 --- a/toolkit/components/glean/api/src/private/custom_distribution.rs +++ b/toolkit/components/glean/api/src/private/custom_distribution.rs @@ -92,6 +92,10 @@ impl CustomDistribution for CustomDistributionMetric { } } + pub fn accumulate_single_sample_signed(&self, _sample: i64) { + unimplemented!("bug 1884183: expose this to FOG") + } + pub fn test_get_value<'a, S: Into<Option<&'a str>>>( &self, ping_name: S, diff --git a/toolkit/components/glean/api/src/private/mod.rs b/toolkit/components/glean/api/src/private/mod.rs index b0b1e11393..e86e121d72 100644 --- a/toolkit/components/glean/api/src/private/mod.rs +++ b/toolkit/components/glean/api/src/private/mod.rs @@ -24,6 +24,7 @@ mod labeled; mod labeled_counter; mod memory_distribution; mod numerator; +mod object; mod ping; mod quantity; mod rate; @@ -46,6 +47,7 @@ pub use self::labeled::LabeledMetric; pub use self::labeled_counter::LabeledCounterMetric; pub use self::memory_distribution::MemoryDistributionMetric; pub use self::numerator::NumeratorMetric; +pub use self::object::ObjectMetric; pub use self::ping::Ping; pub use self::quantity::QuantityMetric; pub use self::rate::RateMetric; diff --git a/toolkit/components/glean/api/src/private/object.rs b/toolkit/components/glean/api/src/private/object.rs new file mode 100644 index 0000000000..5199cfad31 --- /dev/null +++ b/toolkit/components/glean/api/src/private/object.rs @@ -0,0 +1,83 @@ +// 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::{CommonMetricData, MetricId}; + +use crate::ipc::need_ipc; + +use glean::traits::ObjectSerialize; + +/// An object metric. +pub enum ObjectMetric<K> { + Parent { + id: MetricId, + inner: glean::private::ObjectMetric<K>, + }, + Child, +} + +impl<K: ObjectSerialize> ObjectMetric<K> { + /// Create a new object metric. + pub fn new(id: MetricId, meta: CommonMetricData) -> Self { + if need_ipc() { + ObjectMetric::Child + } else { + let inner = glean::private::ObjectMetric::new(meta); + ObjectMetric::Parent { id, inner } + } + } + + pub fn set(&self, value: K) { + match self { + ObjectMetric::Parent { inner, .. } => { + inner.set(value); + } + ObjectMetric::Child => { + log::error!("Unable to set object metric in non-main process. This operation will be ignored."); + // TODO: Record an error. + } + }; + } + + pub fn set_string(&self, value: String) { + match self { + ObjectMetric::Parent { inner, .. } => { + inner.set_string(value); + } + ObjectMetric::Child => { + log::error!("Unable to set object metric in non-main process. This operation will be ignored."); + // TODO: Record an error. + } + }; + } + + pub fn test_get_value<'a, S: Into<Option<&'a str>>>( + &self, + ping_name: S, + ) -> Option<serde_json::Value> { + match self { + ObjectMetric::Parent { inner, .. } => inner.test_get_value(ping_name), + ObjectMetric::Child => { + panic!("Cannot get test value for object metric in non-parent process!",) + } + } + } + + pub fn test_get_value_as_str<'a, S: Into<Option<&'a str>>>( + &self, + ping_name: S, + ) -> Option<String> { + self.test_get_value(ping_name) + .map(|val| serde_json::to_string(&val).unwrap()) + } + + pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 { + match self { + ObjectMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error), + ObjectMetric::Child => { + panic!("Cannot get the number of recorded errors in non-parent process!") + } + } + } +} diff --git a/toolkit/components/glean/api/src/private/ping.rs b/toolkit/components/glean/api/src/private/ping.rs index cc9585eea1..7e03c1ff00 100644 --- a/toolkit/components/glean/api/src/private/ping.rs +++ b/toolkit/components/glean/api/src/private/ping.rs @@ -30,6 +30,7 @@ impl Ping { include_client_id: bool, send_if_empty: bool, precise_timestamps: bool, + include_info_sections: bool, reason_codes: Vec<String>, ) -> Self { if need_ipc() { @@ -40,6 +41,7 @@ impl Ping { include_client_id, send_if_empty, precise_timestamps, + include_info_sections, reason_codes, )) } @@ -103,7 +105,7 @@ mod test { // Smoke test for what should be the generated code. static PROTOTYPE_PING: Lazy<Ping> = - Lazy::new(|| Ping::new("prototype", false, true, true, vec![])); + Lazy::new(|| Ping::new("prototype", false, true, true, true, vec![])); #[test] fn smoke_test_custom_ping() { diff --git a/toolkit/components/glean/api/src/private/timing_distribution.rs b/toolkit/components/glean/api/src/private/timing_distribution.rs index 0ab25cc900..6707560e41 100644 --- a/toolkit/components/glean/api/src/private/timing_distribution.rs +++ b/toolkit/components/glean/api/src/private/timing_distribution.rs @@ -374,6 +374,10 @@ impl TimingDistribution for TimingDistributionMetric { } } + pub fn accumulate_single_sample(&self, _sample: i64) { + unimplemented!("bug 1884183: expose this to FOG") + } + /// **Exported for test purposes.** /// /// Gets the currently stored value of the metric. diff --git a/toolkit/components/glean/bindings/GleanMetric.h b/toolkit/components/glean/bindings/GleanMetric.h index 65ac75191d..c6bf6b4066 100644 --- a/toolkit/components/glean/bindings/GleanMetric.h +++ b/toolkit/components/glean/bindings/GleanMetric.h @@ -11,6 +11,7 @@ #include "nsIGlobalObject.h" #include "nsWrapperCache.h" #include "nsClassHashtable.h" +#include "nsGlobalWindowInner.h" #include "nsTHashMap.h" #include "mozilla/DataMutex.h" diff --git a/toolkit/components/glean/bindings/MetricTypes.h b/toolkit/components/glean/bindings/MetricTypes.h index a7ae09fe19..6d855c2bf4 100644 --- a/toolkit/components/glean/bindings/MetricTypes.h +++ b/toolkit/components/glean/bindings/MetricTypes.h @@ -14,6 +14,7 @@ #include "mozilla/glean/bindings/Labeled.h" #include "mozilla/glean/bindings/MemoryDistribution.h" #include "mozilla/glean/bindings/Numerator.h" +#include "mozilla/glean/bindings/Object.h" #include "mozilla/glean/bindings/Quantity.h" #include "mozilla/glean/bindings/Rate.h" #include "mozilla/glean/bindings/String.h" diff --git a/toolkit/components/glean/bindings/jog/src/lib.rs b/toolkit/components/glean/bindings/jog/src/lib.rs index 4f2d439d80..b62e54f6e8 100644 --- a/toolkit/components/glean/bindings/jog/src/lib.rs +++ b/toolkit/components/glean/bindings/jog/src/lib.rs @@ -138,6 +138,7 @@ pub extern "C" fn jog_test_register_ping( include_client_id: bool, send_if_empty: bool, precise_timestamps: bool, + include_info_sections: bool, reason_codes: &ThinVec<nsCString>, ) -> u32 { let ping_name = name.to_string(); @@ -150,6 +151,7 @@ pub extern "C" fn jog_test_register_ping( include_client_id, send_if_empty, precise_timestamps, + include_info_sections, reason_codes, ) .expect("Creation or registration of ping failed.") // permitted to panic in test-only method. @@ -160,6 +162,7 @@ fn create_and_register_ping( include_client_id: bool, send_if_empty: bool, precise_timestamps: bool, + include_info_sections: bool, reason_codes: Vec<String>, ) -> Result<u32, Box<dyn std::error::Error>> { let ns_name = nsCString::from(&ping_name); @@ -168,6 +171,7 @@ fn create_and_register_ping( include_client_id, send_if_empty, precise_timestamps, + include_info_sections, reason_codes, ); extern "C" { @@ -214,6 +218,7 @@ struct PingDefinitionData { include_client_id: bool, send_if_empty: bool, precise_timestamps: bool, + include_info_sections: bool, reason_codes: Option<Vec<String>>, } @@ -260,6 +265,7 @@ pub extern "C" fn jog_load_jogfile(jogfile_path: &nsAString) -> bool { ping.include_client_id, ping.send_if_empty, ping.precise_timestamps, + ping.include_info_sections, ping.reason_codes.unwrap_or_else(Vec::new), ); } diff --git a/toolkit/components/glean/bindings/private/CustomDistribution.cpp b/toolkit/components/glean/bindings/private/CustomDistribution.cpp index 2f0226cb58..a5a821a558 100644 --- a/toolkit/components/glean/bindings/private/CustomDistribution.cpp +++ b/toolkit/components/glean/bindings/private/CustomDistribution.cpp @@ -59,9 +59,10 @@ CustomDistributionMetric::TestGetValue(const nsACString& aPingName) const { nsTArray<uint64_t> buckets; nsTArray<uint64_t> counts; uint64_t sum; - fog_custom_distribution_test_get_value(mId, &aPingName, &sum, &buckets, - &counts); - return Some(DistributionData(buckets, counts, sum)); + uint64_t count; + fog_custom_distribution_test_get_value(mId, &aPingName, &sum, &count, + &buckets, &counts); + return Some(DistributionData(buckets, counts, sum, count)); } } // namespace impl @@ -92,6 +93,7 @@ void GleanCustomDistribution::TestGetValue( dom::GleanDistributionData ret; ret.mSum = optresult.ref().sum; + ret.mCount = optresult.ref().count; auto& data = optresult.ref().values; for (const auto& entry : data) { dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket; diff --git a/toolkit/components/glean/bindings/private/DistributionData.h b/toolkit/components/glean/bindings/private/DistributionData.h index 6ff995f222..fb9bba720e 100644 --- a/toolkit/components/glean/bindings/private/DistributionData.h +++ b/toolkit/components/glean/bindings/private/DistributionData.h @@ -12,6 +12,7 @@ namespace mozilla::glean { struct DistributionData final { uint64_t sum; + uint64_t count; nsTHashMap<nsUint64HashKey, uint64_t> values; /** @@ -19,8 +20,9 @@ struct DistributionData final { * as returned by `fog_*_distribution_test_get_value`. */ DistributionData(const nsTArray<uint64_t>& aBuckets, - const nsTArray<uint64_t>& aCounts, uint64_t aSum) - : sum(aSum) { + const nsTArray<uint64_t>& aCounts, uint64_t aSum, + uint64_t aCount) + : sum(aSum), count(aCount) { for (size_t i = 0; i < aBuckets.Length(); ++i) { this->values.InsertOrUpdate(aBuckets[i], aCounts[i]); } diff --git a/toolkit/components/glean/bindings/private/MemoryDistribution.cpp b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp index a580c5df3c..64f3bf241c 100644 --- a/toolkit/components/glean/bindings/private/MemoryDistribution.cpp +++ b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp @@ -44,9 +44,10 @@ MemoryDistributionMetric::TestGetValue(const nsACString& aPingName) const { nsTArray<uint64_t> buckets; nsTArray<uint64_t> counts; uint64_t sum; - fog_memory_distribution_test_get_value(mId, &aPingName, &sum, &buckets, - &counts); - return Some(DistributionData(buckets, counts, sum)); + uint64_t count; + fog_memory_distribution_test_get_value(mId, &aPingName, &sum, &count, + &buckets, &counts); + return Some(DistributionData(buckets, counts, sum, count)); } } // namespace impl @@ -76,6 +77,7 @@ void GleanMemoryDistribution::TestGetValue( dom::GleanDistributionData ret; ret.mSum = optresult.ref().sum; + ret.mCount = optresult.ref().count; auto& data = optresult.ref().values; for (const auto& entry : data) { dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket; diff --git a/toolkit/components/glean/bindings/private/Object.cpp b/toolkit/components/glean/bindings/private/Object.cpp new file mode 100644 index 0000000000..817b14dc0f --- /dev/null +++ b/toolkit/components/glean/bindings/private/Object.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/glean/bindings/Object.h" + +#include "Common.h" +#include "mozilla/dom/GleanMetricsBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/Logging.h" +#include "jsapi.h" +#include "js/JSON.h" +#include "nsContentUtils.h" + +using namespace mozilla::dom; + +namespace mozilla::glean { + +/* virtual */ +JSObject* GleanObject::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return dom::GleanObject_Binding::Wrap(aCx, this, aGivenProto); +} + +void GleanObject::Set(JSContext* aCx, JS::Handle<JSObject*> aObj) { + // We take in an `object`. Cannot be `null`! + // But at this point the type system doesn't know that. + JS::Rooted<JS::Value> value(aCx); + value.setObjectOrNull(aObj); + + nsAutoString serializedValue; + bool res = nsContentUtils::StringifyJSON(aCx, value, serializedValue, + UndefinedIsNullStringLiteral); + if (!res) { + // JS_Stringify throws an exception, e.g. on cyclic objects. + // We don't want this rethrown. + JS_ClearPendingException(aCx); + + LogToBrowserConsole(nsIScriptError::warningFlag, + u"passed in object cannot be serialized"_ns); + return; + } + + NS_ConvertUTF16toUTF8 payload(serializedValue); + mObject.SetStr(payload); +} + +void GleanObject::TestGetValue(JSContext* aCx, const nsACString& aPingName, + JS::MutableHandle<JSObject*> aResult, + ErrorResult& aRv) { + aResult.set(nullptr); + + auto result = mObject.TestGetValue(aPingName); + if (result.isErr()) { + aRv.ThrowDataError(result.unwrapErr()); + return; + } + auto optresult = result.unwrap(); + if (optresult.isNothing()) { + return; + } + + const NS_ConvertUTF8toUTF16 str(optresult.ref()); + JS::Rooted<JS::Value> json(aCx); + bool res = JS_ParseJSON(aCx, str.get(), str.Length(), &json); + if (!res) { + aRv.ThrowDataError("couldn't parse stored object"); + return; + } + if (!json.isObject()) { + aRv.ThrowDataError("stored data does not represent a valid object"); + return; + } + + aResult.set(&json.toObject()); +} + +} // namespace mozilla::glean diff --git a/toolkit/components/glean/bindings/private/Object.h b/toolkit/components/glean/bindings/private/Object.h new file mode 100644 index 0000000000..4c81f8b096 --- /dev/null +++ b/toolkit/components/glean/bindings/private/Object.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_glean_GleanObject_h +#define mozilla_glean_GleanObject_h + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/glean/bindings/GleanMetric.h" +#include "mozilla/glean/fog_ffi_generated.h" +#include "mozilla/ResultVariant.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla::glean { + +// forward declaration +class GleanObject; + +namespace impl { + +template <class T> +class ObjectMetric { + friend class mozilla::glean::GleanObject; + + public: + constexpr explicit ObjectMetric(uint32_t id) : mId(id) {} + + private: + const uint32_t mId; + + /* TODO(bug 1881023): Turn this into the public C++ API */ + /** + * **Test-only API** + * + * Gets the currently stored object as a JSON-encoded string. + * + * This function will attempt to await the last parent-process task (if any) + * writing to the the metric's storage engine before returning a value. + * This function will not wait for data from child processes. + * + * This doesn't clear the stored value. + * Parent process only. Panics in child processes. + * + * @param aPingName The (optional) name of the ping to retrieve the metric + * for. Defaults to the first value in `send_in_pings`. + * + * @return value of the stored metric, or Nothing() if there is no value. + */ + Result<Maybe<nsCString>, nsCString> TestGetValue( + const nsACString& aPingName) const { + nsCString err; + if (fog_object_test_get_error(mId, &err)) { + return Err(err); + } + if (!fog_object_test_has_value(mId, &aPingName)) { + return Maybe<nsCString>(); + } + nsCString ret; + fog_object_test_get_value(mId, &aPingName, &ret); + return Some(ret); + } + + void SetStr(const nsACString& aValue) const { + fog_object_set_string(mId, &aValue); + } +}; + +} // namespace impl + +class GleanObject final : public GleanMetric { + public: + explicit GleanObject(uint32_t aId, nsISupports* aParent) + : GleanMetric(aParent), mObject(aId) {} + + virtual JSObject* WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final; + + void Set(JSContext* aCx, JS::Handle<JSObject*> aObj); + + void TestGetValue(JSContext* aCx, const nsACString& aPingName, + JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv); + + virtual ~GleanObject() = default; + + private: + const impl::ObjectMetric<void> mObject; +}; + +} // namespace mozilla::glean + +#endif /* mozilla_glean_GleanObject.h */ diff --git a/toolkit/components/glean/bindings/private/Timespan.cpp b/toolkit/components/glean/bindings/private/Timespan.cpp index 13e57317fa..2ab1f0dbba 100644 --- a/toolkit/components/glean/bindings/private/Timespan.cpp +++ b/toolkit/components/glean/bindings/private/Timespan.cpp @@ -36,6 +36,7 @@ class ScalarIDHashKey : public PLDHashEntryHdr { return static_cast<std::underlying_type<ScalarID>::type>(*aKey); } enum { ALLOW_MEMMOVE = true }; + static_assert(std::is_trivially_copyable_v<ScalarID>); private: const ScalarID mValue; diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.cpp b/toolkit/components/glean/bindings/private/TimingDistribution.cpp index f7a78165ae..036db5f9db 100644 --- a/toolkit/components/glean/bindings/private/TimingDistribution.cpp +++ b/toolkit/components/glean/bindings/private/TimingDistribution.cpp @@ -21,7 +21,10 @@ namespace mozilla::glean { using MetricId = uint32_t; // Same type as in api/src/private/mod.rs -using MetricTimerTuple = std::tuple<MetricId, TimerId>; +struct MetricTimerTuple { + MetricId mMetricId; + TimerId mTimerId; +}; class MetricTimerTupleHashKey : public PLDHashEntryHdr { public: using KeyType = const MetricTimerTuple&; @@ -34,16 +37,17 @@ class MetricTimerTupleHashKey : public PLDHashEntryHdr { KeyType GetKey() const { return mValue; } bool KeyEquals(KeyTypePointer aKey) const { - return std::get<0>(*aKey) == std::get<0>(mValue) && - std::get<1>(*aKey) == std::get<1>(mValue); + return aKey->mMetricId == mValue.mMetricId && + aKey->mTimerId == mValue.mTimerId; } static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { // Chosen because this is how nsIntegralHashKey does it. - return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey)); + return HashGeneric(aKey->mMetricId, aKey->mTimerId); } enum { ALLOW_MEMMOVE = true }; + static_assert(std::is_trivially_copyable_v<MetricTimerTuple>); private: const MetricTimerTuple mValue; @@ -103,7 +107,7 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStart( auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId); if (mirrorId) { mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) { - auto tuple = std::make_tuple(aMetricId, aTimerId); + auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId}; // It should be all but impossible for anyone to have already inserted // this timer for this metric given the monotonicity of timer ids. (void)NS_WARN_IF(lock.ref()->Remove(tuple)); @@ -118,7 +122,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStopAndAccumulate( auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId); if (mirrorId) { mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) { - auto optStart = lock.ref()->Extract(std::make_tuple(aMetricId, aTimerId)); + auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId}; + auto optStart = lock.ref()->Extract(tuple); // The timer might not be in the map to be removed if it's already been // cancelled or stop_and_accumulate'd. if (!NS_WARN_IF(!optStart)) { @@ -145,8 +150,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionCancel( mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) { // The timer might not be in the map to be removed if it's already been // cancelled or stop_and_accumulate'd. - (void)NS_WARN_IF( - !lock.ref()->Remove(std::make_tuple(aMetricId, aTimerId))); + auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId}; + (void)NS_WARN_IF(!lock.ref()->Remove(tuple)); }); } } @@ -187,9 +192,10 @@ TimingDistributionMetric::TestGetValue(const nsACString& aPingName) const { nsTArray<uint64_t> buckets; nsTArray<uint64_t> counts; uint64_t sum; - fog_timing_distribution_test_get_value(mId, &aPingName, &sum, &buckets, - &counts); - return Some(DistributionData(buckets, counts, sum)); + uint64_t count; + fog_timing_distribution_test_get_value(mId, &aPingName, &sum, &count, + &buckets, &counts); + return Some(DistributionData(buckets, counts, sum, count)); } } // namespace impl @@ -225,6 +231,7 @@ void GleanTimingDistribution::TestGetValue( dom::GleanDistributionData ret; ret.mSum = optresult.ref().sum; + ret.mCount = optresult.ref().count; auto& data = optresult.ref().values; for (const auto& entry : data) { dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket; diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py b/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py index 81d92b0f5d..b42827989e 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py @@ -55,6 +55,7 @@ known_ping_args = [ "include_client_id", "send_if_empty", "precise_timestamps", + "include_info_sections", "reason_codes", ] diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py b/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py index 1d7d97cf73..bc9f09f0d3 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py @@ -230,5 +230,32 @@ def jog_file(output_fd, *args): return get_deps() +def ohttp_pings(output_fd, *args): + all_objs, options = parse(args) + ohttp_pings = [] + for ping in all_objs["pings"].values(): + if ping.metadata.get("use_ohttp", False): + if ping.include_info_sections: + raise ParserError( + "Cannot send pings with OHTTP that contain {client|ping}_info sections. Specify `metadata: include_info_sections: false`" + ) + ohttp_pings.append(ping.name) + + env = jinja2.Environment( + loader=jinja2.PackageLoader("run_glean_parser", "templates"), + trim_blocks=True, + lstrip_blocks=True, + ) + env.filters["quote_and_join"] = lambda l: "\n| ".join(f'"{x}"' for x in l) + template = env.get_template("ohttp.jinja2") + output_fd.write( + template.render( + ohttp_pings=ohttp_pings, + ) + ) + output_fd.write("\n") + return get_deps() + + if __name__ == "__main__": main(sys.stdout, *sys.argv[1:]) diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py b/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py index 615784b481..4b2b35886b 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py @@ -68,9 +68,10 @@ def rust_datatypes_filter(value): # CowString is also a 'str' but is a special case. # Ensure its case is handled before str's (below). elif isinstance(value, CowString): - yield f'::std::borrow::Cow::from("{value.inner}")' + value = json.dumps(value) + yield f"::std::borrow::Cow::from({value})" elif isinstance(value, str): - yield '"' + value + '".into()' + yield f"{json.dumps(value)}.into()" elif isinstance(value, Rate): yield "CommonMetricData {" for arg_name in common_metric_data_args: @@ -118,6 +119,10 @@ def type_name(obj): return "{}<{}>".format( class_name(obj.type), util.Camelize(obj.name) + suffix ) + generate_structure = getattr(obj, "_generate_structure", []) + if len(generate_structure): + generic = util.Camelize(obj.name) + "Object" + return "{}<{}>".format(class_name(obj.type), generic) return class_name(obj.type) @@ -136,6 +141,21 @@ def extra_type_name(typ: str) -> str: return "UNSUPPORTED" +def structure_type_name(typ: str) -> str: + """ + Returns the corresponding Rust type for structure items. + """ + + if typ == "boolean": + return "bool" + elif typ == "string": + return "String" + elif typ == "number": + return "i64" + else: + return "UNSUPPORTED" + + def class_name(obj_type): """ Returns the Rust class name for a given metric or ping type. @@ -208,6 +228,14 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}): # 17 -> "test_only::an_event" events_by_id = {} + # Map from a metric ID to the fully qualified path of the object metric in Rust. + # Required for the special handling of object lookups. + # + # Example: + # + # 18 -> "test_only::an_object" + objects_by_id = {} + # Map from a labeled type (e.g. "counter") to a map from metric ID to the # fully qualified path of the labeled metric object in Rust paired with # whether the labeled metric has an enum. @@ -238,6 +266,9 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}): if metric.type == "event": events_by_id[get_metric_id(metric)] = full_path continue + if metric.type == "object": + objects_by_id[get_metric_id(metric)] = full_path + continue if getattr(metric, "labeled", False): labeled_type = metric.type[8:] @@ -261,6 +292,7 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}): ("snake_case", util.snake_case), ("type_name", type_name), ("extra_type_name", extra_type_name), + ("structure_type_name", structure_type_name), ("ctor", ctor), ("extra_keys", extra_keys), ("metric_id", get_metric_id), @@ -275,6 +307,7 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}): metric_by_type=objs_by_type, extra_args=util.extra_args, events_by_id=events_by_id, + objects_by_id=objects_by_id, labeleds_by_id_by_type=labeleds_by_id_by_type, submetric_bit=ID_BITS - ID_SIGNAL_BITS, ping_names_by_app_id=ping_names_by_app_id, diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2 index 2a4e40d6ac..3e9d573232 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2 +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2 @@ -78,6 +78,7 @@ enum class DynamicLabel: uint16_t { }; {% for category_name, objs in all_objs.items() %} namespace {{ category_name|snake_case }} { {% for obj in objs.values() %} + {% if obj.type != "object" %}{# TODO(bug 1881023): Add C++ support #} /** * generated from {{ category_name }}.{{ obj.name }} */ @@ -91,6 +92,7 @@ namespace {{ category_name|snake_case }} { * {{ obj.description|wordwrap() | replace('\n', '\n * ') }} */ constexpr impl::{{ obj|type_name }} {{obj.name|snake_case }}({{obj|metric_id}}); + {% endif %} {% endfor %} } diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2 index 4b7838a47d..a31bdbabf0 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2 +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2 @@ -23,7 +23,9 @@ use crate::private::{ Ping, LabeledMetric, {% for metric_type_name in metric_types.keys() if not metric_type_name.startswith('labeled_') %} + {% if metric_type_name != "object" %}{# TODO(bug 1883857): Add JOG support #} {{ metric_type_name|Camelize }}Metric, + {% endif %} {% endfor %}}; use crate::private::traits::HistogramType; @@ -50,7 +52,7 @@ pub(crate) mod __jog_metric_maps { use std::collections::HashMap; use std::sync::{Arc, RwLock}; -{% for metric_type_name in metric_types.keys() if metric_type_name != "event" and not metric_type_name.startswith('labeled_') %} +{% for metric_type_name in metric_types.keys() if metric_type_name != "event" and not metric_type_name.startswith('labeled_') and metric_type_name != "object" %} pub static {{ metric_type_name.upper() }}_MAP: Lazy<Arc<RwLock<HashMap<MetricId, {{ metric_type_name|Camelize }}Metric>>>> = Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); @@ -67,6 +69,10 @@ pub(crate) mod __jog_metric_maps { {# Event metrics are special because they're EventMetric<K> #} pub static EVENT_MAP: Lazy<Arc<RwLock<HashMap<MetricId, EventMetric<NoExtraKeys>>>>> = Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); +{# Object metrics are special because they're ObjectMetric<K> #} + #[allow(dead_code)] + pub static OBJECT_MAP: Lazy<Arc<RwLock<HashMap<MetricId, ObjectMetric<()>>>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); } #[derive(Debug)] @@ -105,6 +111,9 @@ map of argument name to argument type. I may regret this if I need it again. #} let metric32 = match metric_type { {% for metric_type_name, metric_type in metric_types.items() %} "{{ metric_type_name }}" => { + {% if metric_type_name == "object" %}{# TODO(bug 1883857): Add JOG support #} + return Err(Box::new(MetricTypeNotFoundError(metric_type.to_string()))); + {% else %} let metric = {{ metric_type_name|Camelize if not metric_type_name.startswith('labeled_') else "Labeled"}}Metric::{% if metric_type_name == 'event' %}with_runtime_extra_keys{% else %}new{% endif %}(metric_id.into(), CommonMetricData { {% for arg_name in common_metric_data_args if arg_name in metric_type.args %} {{ arg_name }}, @@ -124,6 +133,7 @@ map of argument name to argument type. I may regret this if I need it again. #} "We should never insert a runtime metric with an already-used id." ); metric32 + {% endif %} } {% endfor %} _ => return Err(Box::new(MetricTypeNotFoundError(metric_type.to_string()))) @@ -137,10 +147,11 @@ pub fn create_and_register_ping( include_client_id: bool, send_if_empty: bool, precise_timestamps: bool, + include_info_sections: bool, reason_codes: Vec<String>, ) -> Result<u32, Box<dyn std::error::Error>> { let ping_id = NEXT_PING_ID.fetch_add(1, Ordering::SeqCst); - let ping = Ping::new(ping_name, include_client_id, send_if_empty, precise_timestamps, reason_codes); + let ping = Ping::new(ping_name, include_client_id, send_if_empty, precise_timestamps, include_info_sections, reason_codes); assert!( __jog_metric_maps::PING_MAP.write()?.insert(ping_id.into(), ping).is_none(), "We should never insert a runtime ping with an already-used id." diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja2 new file mode 100644 index 0000000000..d6e1248021 --- /dev/null +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja2 @@ -0,0 +1,13 @@ +// -*- mode: Rust -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. +{# The rendered source is autogenerated, but this +Jinja2 template is not. Please file bugs! #} + +/* 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/. */ + +pub fn uses_ohttp(ping_name: &str) -> bool { + matches!(ping_name, {{ ohttp_pings|quote_and_join }}) +} diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2 index cc29805099..5723ff5d58 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2 +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2 @@ -8,6 +8,41 @@ Jinja2 template is not. Please file bugs! #} * 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/. */ +{% macro generate_structure(name, struct) %} +{% if struct.type == "array" %} + pub type {{ name }} = Vec<{{ name }}Item>; + + {{ generate_structure(name ~ "Item", struct["items"]) -}} + +{% elif struct.type == "object" %} + #[derive(Debug, Hash, Eq, PartialEq, ::glean::traits::__serde::Serialize, ::glean::traits::__serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct {{ name }} { + {% for itemname, val in struct.properties.items() %} + {% if val.type == "object" %} + pub {{itemname|snake_case}}: {{ name ~ "Item" ~ itemname|Camelize ~ "Object" }}, + {% elif val.type == "array" %} + pub {{itemname|snake_case}}: {{ name ~ "Item" ~ itemname|Camelize }}, + {% else %} + pub {{itemname|snake_case}}: Option<{{val.type|structure_type_name}}>, + {% endif %} + {% endfor %} + } + + {% for itemname, val in struct.properties.items() %} + {% if val.type == "array" %} + {% set nested_name = name ~ "Item" ~ itemname|Camelize %} + {{ generate_structure(nested_name, val) -}} + {% elif val.type == "object" %} + {% set nested_name = name ~ "Item" ~ itemname|Camelize ~ "Object" %} + {{ generate_structure(nested_name, val) -}} + {% endif %} + {% endfor %} +{% else %} +pub type {{ name }} = {{ struct.type|structure_type_name }}; +{% endif %} +{% endmacro %} + {% macro generate_extra_keys(obj) -%} {% for name, _ in obj["_generate_enums"] %} {# we always use the `extra` suffix, because we only expose the new event API #} @@ -81,6 +116,9 @@ pub mod {{ category_name|snake_case }} { use glean::HistogramType; use once_cell::sync::Lazy; + #[allow(unused_imports)] + use std::convert::TryFrom; + {% for obj in objs.values() %} {% if obj|attr("_generate_enums") %} {{ generate_extra_keys(obj) }} @@ -88,6 +126,9 @@ pub mod {{ category_name|snake_case }} { {% if obj.labeled and obj.labels and obj.labels|length %} {{ generate_label_enum(obj)|indent }} {% endif %} + {% if obj|attr("_generate_structure") %} + {{ generate_structure(obj.name|Camelize ~ "Object", obj._generate_structure) -}} + {% endif %} #[allow(non_upper_case_globals)] /// generated from {{ category_name }}.{{ obj.name }} /// @@ -148,6 +189,71 @@ pub(crate) mod __glean_metric_maps { {% endfor %} + pub(crate) fn set_object_by_id(metric_id: u32, value: String) -> Result<(), ()> { + match metric_id { +{% for metric_id, object in objects_by_id.items() %} + {{metric_id}} => { + super::{{object}}.set_string(value); + Ok(()) + } +{% endfor %} + _ => Err(()), + } + } + + /// Wrapper to get the currently stored object for object metric as a string. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `ping_name` - (Optional) The ping name to look into. + /// Defaults to the first value in `send_in_pings`. + /// + /// # Returns + /// + /// Returns the recorded object serialized as a JSON string or `None` if nothing stored. + /// + /// # Panics + /// + /// Panics if no object by the given metric ID could be found. + pub(crate) fn object_test_get_value(metric_id: u32, ping_name: Option<String>) -> Option<String> { + match metric_id { +{% for metric_id, object in objects_by_id.items() %} + {{metric_id}} => super::{{object}}.test_get_value_as_str(ping_name.as_deref()), +{% endfor %} + _ => panic!("No object for metric id {}", metric_id), + } + } + + /// Check the provided object for errors. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// + /// # Returns + /// + /// Returns a string for the recorded error or `None`. + /// + /// # Panics + /// + /// Panics if no object by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn object_test_get_error(metric_id: u32) -> Option<String> { + #[cfg(feature = "with_gecko")] + match metric_id { +{% for metric_id, object in objects_by_id.items() %} + {{metric_id}} => test_get_errors!(super::{{object}}), +{% endfor %} + _ => panic!("No object for metric id {}", metric_id), + } + + #[cfg(not(feature = "with_gecko"))] + { + return None; + } + } + /// Wrapper to record an event based on its metric ID. /// /// # Arguments diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2 index c041f663a6..8afdae61ae 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2 +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2 @@ -20,6 +20,7 @@ pub static {{ obj.name|snake_case }}: Lazy<Ping> = Lazy::new(|| { {{ obj.include_client_id|rust }}, {{ obj.send_if_empty|rust }}, {{ obj.precise_timestamps|rust }}, + {{ obj.include_info_sections|rust }}, {{ obj.reason_codes|rust }}, ) }); diff --git a/toolkit/components/glean/build_scripts/mach_commands.py b/toolkit/components/glean/build_scripts/mach_commands.py index d385e53605..4a0f6dbc68 100644 --- a/toolkit/components/glean/build_scripts/mach_commands.py +++ b/toolkit/components/glean/build_scripts/mach_commands.py @@ -183,8 +183,13 @@ def update_glean(command_context, version): ) replace_in_file_or_die( topsrcdir / "gfx" / "wr" / "webrender" / "Cargo.toml", - r'^glean = "[0-9.]+"', - f'glean = "{version}"', + r'^glean = { version = "[0-9.]+"(.+)}', + f'glean = {{ version = "{version}"\\1}}', + ) + replace_in_file_or_die( + topsrcdir / "gfx" / "wr" / "wr_glyph_rasterizer" / "Cargo.toml", + r'^glean = { version = "[0-9.]+"(.+)}', + f'glean = {{ version = "{version}"\\1}}', ) replace_in_file_or_die( topsrcdir / "python" / "sites" / "mach.txt", @@ -193,13 +198,9 @@ def update_glean(command_context, version): ) instructions = f""" - We've edited most of the necessary files to require Glean SDK {version}. - - You will have to edit the following files yourself: - - gfx/wr/wr_glyph_rasterizer/Cargo.toml + We've edited the necessary files to require Glean SDK {version}. - Then, to ensure Glean and Firefox's other Rust dependencies are appropriately vendored, + To ensure Glean and Firefox's other Rust dependencies are appropriately vendored, please run the following commands: cargo update -p glean diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py index aa7eea6645..8e20658810 100644 --- a/toolkit/components/glean/metrics_index.py +++ b/toolkit/components/glean/metrics_index.py @@ -22,6 +22,7 @@ gecko_metrics = [ "dom/media/webrtc/metrics.yaml", "dom/metrics.yaml", "dom/performance/metrics.yaml", + "dom/security/metrics.yaml", "gfx/metrics.yaml", "image/decoders/metrics.yaml", "js/xpconnect/metrics.yaml", @@ -50,6 +51,7 @@ gecko_metrics = [ # Metrics that are sent by Firefox Desktop # Order is lexicographical, enforced by t/c/glean/tests/pytest/test_yaml_indices.py firefox_desktop_metrics = [ + "browser/components/backup/metrics.yaml", "browser/components/metrics.yaml", "browser/components/migration/metrics.yaml", "browser/components/newtab/metrics.yaml", @@ -124,6 +126,7 @@ firefox_desktop_pings = [ "browser/components/search/pings.yaml", "browser/components/urlbar/pings.yaml", "toolkit/components/crashes/pings.yaml", + "toolkit/components/resistfingerprinting/pings.yaml", "toolkit/components/telemetry/pings.yaml", "toolkit/modules/pings.yaml", ] diff --git a/toolkit/components/glean/moz.build b/toolkit/components/glean/moz.build index d6876167e6..df76dbcbc4 100644 --- a/toolkit/components/glean/moz.build +++ b/toolkit/components/glean/moz.build @@ -42,6 +42,7 @@ EXPORTS.mozilla.glean.bindings += [ "bindings/private/Labeled.h", "bindings/private/MemoryDistribution.h", "bindings/private/Numerator.h", + "bindings/private/Object.h", "bindings/private/Ping.h", "bindings/private/Quantity.h", "bindings/private/Rate.h", @@ -88,6 +89,7 @@ UNIFIED_SOURCES += [ "bindings/private/Labeled.cpp", "bindings/private/MemoryDistribution.cpp", "bindings/private/Numerator.cpp", + "bindings/private/Object.cpp", "bindings/private/Ping.cpp", "bindings/private/Quantity.cpp", "bindings/private/Rate.cpp", @@ -197,6 +199,17 @@ if CONFIG["MOZ_ARTIFACT_BUILDS"]: # Once generated, it needs to be placed in GreD so it can be found. FINAL_TARGET_FILES += ["!jogfile.json"] +# OHTTP support requires the fog_control crate know which pings wish to be sent +# using OHTTP. fog_control has no access to the firefox_on_glean crate, so it +# needs its own codegen. +GeneratedFile( + "src/ohttp_pings.rs", + script="build_scripts/glean_parser_ext/run_glean_parser.py", + entry_point="ohttp_pings", + flags=[CONFIG["MOZ_APP_VERSION"]], + inputs=pings_yamls + tags_yamls, +) + DIRS += [ "tests", # Must be in DIRS, not TEST_DIRS or python-test won't find it. "xpcom", diff --git a/toolkit/components/glean/pings.yaml b/toolkit/components/glean/pings.yaml index d4710a47c2..22c49aab7e 100644 --- a/toolkit/components/glean/pings.yaml +++ b/toolkit/components/glean/pings.yaml @@ -6,8 +6,9 @@ # automatically converted to platform-specific code at build time using the # `glean_parser` PyPI package. -# This file is presently for Internal FOG Use Only. -# You should not add pings here until probably about January of 2021. +# This file is for Internal FOG Use Only. +# You probably don't want to add pings here. For more information consult: +# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html --- $schema: moz://mozilla.org/schemas/glean/pings/2-0-0 diff --git a/toolkit/components/glean/src/init/viaduct_uploader.rs b/toolkit/components/glean/src/init/viaduct_uploader.rs index d9ce4e0488..a009e664bc 100644 --- a/toolkit/components/glean/src/init/viaduct_uploader.rs +++ b/toolkit/components/glean/src/init/viaduct_uploader.rs @@ -2,7 +2,9 @@ // 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 glean::net::{PingUploader, UploadResult}; +use glean::net::{PingUploadRequest, PingUploader, UploadResult}; +use once_cell::sync::OnceCell; +use std::sync::Once; use url::Url; use viaduct::{Error::*, Request}; @@ -19,55 +21,176 @@ impl PingUploader for ViaductUploader { /// /// # Arguments /// - /// * `url` - the URL path to upload the data to. - /// * `body` - the serialized text data to send. - /// * `headers` - a vector of tuples containing the headers to send with - /// the request, i.e. (Name, Value). - fn upload(&self, url: String, body: Vec<u8>, headers: Vec<(String, String)>) -> UploadResult { - log::trace!("FOG Ping Uploader uploading to {}", url); - let url_clone = url.clone(); - let result: std::result::Result<UploadResult, viaduct::Error> = (move || { - // SAFETY NOTE: Safe because it returns a primitive by value. - if unsafe { FOG_TooLateToSend() } { - log::trace!("Attempted to send ping too late into shutdown."); - return Ok(UploadResult::done()); - } - let debug_tagged = headers.iter().any(|(name, _)| name == "X-Debug-ID"); - let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port"); - if localhost_port < 0 - || (localhost_port == 0 && !debug_tagged && cfg!(feature = "disable_upload")) - { - log::info!("FOG Ping uploader faking success"); - return Ok(UploadResult::http_status(200)); - } - let parsed_url = Url::parse(&url_clone)?; - - log::info!("FOG Ping uploader uploading to {:?}", parsed_url); - - let mut req = Request::post(parsed_url.clone()).body(body.clone()); - for (header_key, header_value) in &headers { - req = req.header(header_key.to_owned(), header_value)?; - } - - log::trace!("FOG Ping Uploader sending ping to {}", parsed_url); - let res = req.send()?; - Ok(UploadResult::http_status(res.status as i32)) - })(); + /// * `upload_request` - the ping and its metadata to upload. + fn upload(&self, upload_request: PingUploadRequest) -> UploadResult { + log::trace!("FOG Ping Uploader uploading to {}", upload_request.url); + + // SAFETY NOTE: Safe because it returns a primitive by value. + if unsafe { FOG_TooLateToSend() } { + log::trace!("Attempted to send ping too late into shutdown."); + return UploadResult::done(); + } + + let debug_tagged = upload_request + .headers + .iter() + .any(|(name, _)| name == "X-Debug-ID"); + let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port"); + if localhost_port < 0 + || (localhost_port == 0 && !debug_tagged && cfg!(feature = "disable_upload")) + { + log::info!("FOG Ping uploader faking success"); + return UploadResult::http_status(200); + } + + // Localhost-destined pings are sent without OHTTP, + // even if configured to use OHTTP. + let result = if localhost_port == 0 && should_ohttp_upload(&upload_request) { + ohttp_upload(upload_request) + } else { + viaduct_upload(upload_request) + }; + log::trace!( - "FOG Ping Uploader completed uploading to {} (Result {:?})", - url, + "FOG Ping Uploader completed uploading (Result {:?})", result ); + match result { Ok(result) => result, - Err(NonTlsUrl | UrlError(_)) => UploadResult::unrecoverable_failure(), - Err( + Err(ViaductUploaderError::Viaduct(ve)) => match ve { + NonTlsUrl | UrlError(_) => UploadResult::unrecoverable_failure(), RequestHeaderError(_) | BackendError(_) | NetworkError(_) | BackendNotInitialized - | SetBackendError, - ) => UploadResult::recoverable_failure(), + | SetBackendError => UploadResult::recoverable_failure(), + }, + Err( + ViaductUploaderError::Bhttp(_) + | ViaductUploaderError::Ohttp(_) + | ViaductUploaderError::Fatal, + ) => UploadResult::unrecoverable_failure(), } } } + +fn viaduct_upload(upload_request: PingUploadRequest) -> Result<UploadResult, ViaductUploaderError> { + let parsed_url = Url::parse(&upload_request.url)?; + + log::info!("FOG viaduct uploader uploading to {:?}", parsed_url); + + let mut req = Request::post(parsed_url.clone()).body(upload_request.body); + for (header_key, header_value) in &upload_request.headers { + req = req.header(header_key.to_owned(), header_value)?; + } + + log::trace!("FOG viaduct uploader sending ping to {:?}", parsed_url); + let res = req.send()?; + Ok(UploadResult::http_status(res.status as i32)) +} + +fn should_ohttp_upload(upload_request: &PingUploadRequest) -> bool { + crate::ohttp_pings::uses_ohttp(&upload_request.ping_name) + && !upload_request.body_has_info_sections +} + +fn ohttp_upload(upload_request: PingUploadRequest) -> Result<UploadResult, ViaductUploaderError> { + static CELL: OnceCell<Vec<u8>> = once_cell::sync::OnceCell::new(); + let config = CELL.get_or_try_init(|| get_config())?; + + let binary_request = bhttp_encode(upload_request)?; + + static OHTTP_INIT: Once = Once::new(); + OHTTP_INIT.call_once(|| { + ohttp::init(); + }); + + let ohttp_request = ohttp::ClientRequest::new(config)?; + let (capsule, ohttp_response) = ohttp_request.encapsulate(&binary_request)?; + + const OHTTP_RELAY_URL: &str = "https://mozilla-ohttp.fastly-edge.com/"; + let parsed_relay_url = Url::parse(OHTTP_RELAY_URL)?; + + log::trace!("FOG ohttp uploader uploading to {}", parsed_relay_url); + + const OHTTP_MESSAGE_CONTENT_TYPE: &str = "message/ohttp-req"; + let req = Request::post(parsed_relay_url) + .header( + viaduct::header_names::CONTENT_TYPE, + OHTTP_MESSAGE_CONTENT_TYPE, + )? + .body(capsule); + let res = req.send()?; + + if res.status == 200 { + // This just tells us the HTTP went well. Check OHTTP's status. + let binary_response = ohttp_response.decapsulate(&res.body)?; + let mut cursor = std::io::Cursor::new(binary_response); + let bhttp_message = bhttp::Message::read_bhttp(&mut cursor)?; + let res = bhttp_message + .control() + .status() + .ok_or(ViaductUploaderError::Fatal)?; + Ok(UploadResult::http_status(res as i32)) + } else { + Ok(UploadResult::http_status(res.status as i32)) + } +} + +fn get_config() -> Result<Vec<u8>, ViaductUploaderError> { + const OHTTP_CONFIG_URL: &str = + "https://prod.ohttp-gateway.prod.webservices.mozgcp.net/ohttp-configs"; + log::trace!("Getting OHTTP config from {}", OHTTP_CONFIG_URL); + let parsed_config_url = Url::parse(OHTTP_CONFIG_URL)?; + Ok(Request::get(parsed_config_url).send()?.body) +} + +/// Encode the ping upload request in binary HTTP. +/// (draft-ietf-httpbis-binary-message) +fn bhttp_encode(upload_request: PingUploadRequest) -> Result<Vec<u8>, ViaductUploaderError> { + let parsed_url = Url::parse(&upload_request.url)?; + let mut message = bhttp::Message::request( + "POST".into(), + parsed_url.scheme().into(), + parsed_url + .host_str() + .ok_or(ViaductUploaderError::Fatal)? + .into(), + parsed_url.path().into(), + ); + + upload_request + .headers + .into_iter() + .for_each(|(k, v)| message.put_header(k, v)); + + message.write_content(upload_request.body); + + let mut encoded = vec![]; + message.write_bhttp(bhttp::Mode::KnownLength, &mut encoded)?; + + Ok(encoded) +} + +/// Unioned error across upload backends. +#[derive(Debug, thiserror::Error)] +enum ViaductUploaderError { + #[error("bhttp::Error {0}")] + Bhttp(#[from] bhttp::Error), + + #[error("ohttp::Error {0}")] + Ohttp(#[from] ohttp::Error), + + #[error("viaduct::Error {0}")] + Viaduct(#[from] viaduct::Error), + + #[error("Fatal upload error")] + Fatal, +} + +impl From<url::ParseError> for ViaductUploaderError { + fn from(e: url::ParseError) -> Self { + ViaductUploaderError::Viaduct(viaduct::Error::from(e)) + } +} diff --git a/toolkit/components/glean/src/lib.rs b/toolkit/components/glean/src/lib.rs index 79f3258bb7..b682f43066 100644 --- a/toolkit/components/glean/src/lib.rs +++ b/toolkit/components/glean/src/lib.rs @@ -28,6 +28,7 @@ extern crate cstr; extern crate xpcom; mod init; +mod ohttp_pings; pub use init::fog_init; @@ -46,6 +47,7 @@ static mut PENDING_BUF: Vec<u8> = Vec::new(); // IPC serialization/deserialization methods // Crucially important that the first two not be called on multiple threads. +/// # Safety /// Only safe if only called on a single thread (the same single thread you call /// fog_give_ipc_buf on). #[no_mangle] @@ -59,6 +61,7 @@ pub unsafe extern "C" fn fog_serialize_ipc_buf() -> usize { } } +/// # Safety /// Only safe if called on a single thread (the same single thread you call /// fog_serialize_ipc_buf on), and if buf points to an allocated buffer of at /// least buf_len bytes. @@ -73,6 +76,7 @@ pub unsafe extern "C" fn fog_give_ipc_buf(buf: *mut u8, buf_len: usize) -> usize pending_len } +/// # Safety /// Only safe if buf points to an allocated buffer of at least buf_len bytes. /// No ownership is transfered to Rust by this method: caller owns the memory at /// buf before and after this call. @@ -92,9 +96,9 @@ pub unsafe extern "C" fn fog_use_ipc_buf(buf: *const u8, buf_len: usize) { pub extern "C" fn fog_set_debug_view_tag(value: &nsACString) -> nsresult { let result = glean::set_debug_view_tag(&value.to_string()); if result { - return NS_OK; + NS_OK } else { - return NS_ERROR_FAILURE; + NS_ERROR_FAILURE } } @@ -137,7 +141,7 @@ pub extern "C" fn fog_set_experiment_active( extra_values.len(), "Experiment extra keys and values differ in length." ); - let extra = if extra_keys.len() == 0 { + let extra = if extra_keys.is_empty() { None } else { Some( diff --git a/toolkit/components/glean/src/ohttp_pings.rs b/toolkit/components/glean/src/ohttp_pings.rs new file mode 100644 index 0000000000..71b9512a8b --- /dev/null +++ b/toolkit/components/glean/src/ohttp_pings.rs @@ -0,0 +1,13 @@ +// 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/. + +//! This file contains the generated logic for ohttp pings. +//! +//! The contents of this module are generated by +//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from +//! ping definitions files identified by `toolkit/components/glean/metrics_index.py`. + +include!(mozbuild::objdir_path!( + "toolkit/components/glean/src/ohttp_pings.rs" +)); diff --git a/toolkit/components/glean/tags.yaml b/toolkit/components/glean/tags.yaml index 9c6c09bb9c..c935371b28 100644 --- a/toolkit/components/glean/tags.yaml +++ b/toolkit/components/glean/tags.yaml @@ -35,6 +35,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Core :: Cycle Collector': description: The Bugzilla component which applies to this object. +'Core :: DLL Services': + description: The Bugzilla component which applies to this object. 'Core :: DOM: Animation': description: The Bugzilla component which applies to this object. 'Core :: DOM: Bindings (WebIDL)': @@ -95,8 +97,6 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Core :: Disability Access APIs': description: The Bugzilla component which applies to this object. -'Core :: Document Navigation': - description: The Bugzilla component which applies to this object. 'Core :: Gecko Profiler': description: The Bugzilla component which applies to this object. 'Core :: General': @@ -119,6 +119,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Core :: IPC': description: The Bugzilla component which applies to this object. +'Core :: IPC: MSCOM': + description: The Bugzilla component which applies to this object. 'Core :: Internationalization': description: The Bugzilla component which applies to this object. 'Core :: JavaScript Engine': @@ -169,6 +171,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Core :: MFBT': description: The Bugzilla component which applies to this object. +'Core :: Machine Learning': + description: The Bugzilla component which applies to this object. 'Core :: MathML': description: The Bugzilla component which applies to this object. 'Core :: Memory Allocator': @@ -211,6 +215,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Core :: Security: Process Sandboxing': description: The Bugzilla component which applies to this object. +'Core :: Security: RLBox': + description: The Bugzilla component which applies to this object. 'Core :: Spelling checker': description: The Bugzilla component which applies to this object. 'Core :: Storage: Cache API': @@ -337,6 +343,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Firefox :: Keyboard Navigation': description: The Bugzilla component which applies to this object. +'Firefox :: Launcher Process': + description: The Bugzilla component which applies to this object. 'Firefox :: Menus': description: The Bugzilla component which applies to this object. 'Firefox :: Messaging System': @@ -357,6 +365,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Firefox :: Private Browsing': description: The Bugzilla component which applies to this object. +'Firefox :: Profiles': + description: The Bugzilla component which applies to this object. 'Firefox :: Protections UI': description: The Bugzilla component which applies to this object. 'Firefox :: Remote Settings Client': @@ -497,8 +507,6 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Toolkit :: IOUtils and PathUtils': description: The Bugzilla component which applies to this object. -'Toolkit :: NSIS Installer': - description: The Bugzilla component which applies to this object. 'Toolkit :: Password Manager': description: The Bugzilla component which applies to this object. 'Toolkit :: Performance Monitoring': @@ -525,14 +533,14 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0 description: The Bugzilla component which applies to this object. 'Toolkit :: Themes': description: The Bugzilla component which applies to this object. +'Toolkit :: UI Widgets': + description: The Bugzilla component which applies to this object. 'Toolkit :: UniFFI Bindings': description: The Bugzilla component which applies to this object. 'Toolkit :: Video/Audio Controls': description: The Bugzilla component which applies to this object. 'Toolkit :: View Source': description: The Bugzilla component which applies to this object. -'Toolkit :: UI Widgets': - description: The Bugzilla component which applies to this object. 'Toolkit :: about:memory': description: The Bugzilla component which applies to this object. 'Web Compatibility :: Tooling & Investigations': diff --git a/toolkit/components/glean/tests/gtest/TestFog.cpp b/toolkit/components/glean/tests/gtest/TestFog.cpp index 2de8f9ba4d..0c1621911e 100644 --- a/toolkit/components/glean/tests/gtest/TestFog.cpp +++ b/toolkit/components/glean/tests/gtest/TestFog.cpp @@ -181,6 +181,7 @@ TEST_F(FOGFixture, TestCppMemoryDistWorks) { // Sum is in bytes, test_only::do_you_remember is in megabytes. So // multiplication ahoy! ASSERT_EQ(data.sum, 24UL * 1024 * 1024); + ASSERT_EQ(data.count, 2UL); for (const auto& entry : data.values) { const uint64_t bucket = entry.GetKey(); const uint64_t count = entry.GetData(); @@ -196,6 +197,7 @@ TEST_F(FOGFixture, TestCppCustomDistWorks) { DistributionData data = test_only_ipc::a_custom_dist.TestGetValue("store1"_ns).unwrap().ref(); ASSERT_EQ(data.sum, 7UL + 268435458); + ASSERT_EQ(data.count, 2UL); for (const auto& entry : data.values) { const uint64_t bucket = entry.GetKey(); const uint64_t count = entry.GetData(); @@ -253,6 +255,10 @@ TEST_F(FOGFixture, TestCppTimingDistWorks) { DistributionData data = test_only::what_time_is_it.TestGetValue().unwrap().ref(); + + // Cancelled timers should not increase count. + ASSERT_EQ(data.count, 2UL); + const uint64_t NANOS_IN_MILLIS = 1e6; // bug 1701847 - Sleeps don't necessarily round up as you'd expect. diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Event b/toolkit/components/glean/tests/pytest/gifft_output_Event index ba80e3300b..203dc19a62 100644 --- a/toolkit/components/glean/tests/pytest/gifft_output_Event +++ b/toolkit/components/glean/tests/pytest/gifft_output_Event @@ -26,10 +26,10 @@ using Telemetry::EventID; static inline Maybe<EventID> EventIdForMetric(uint32_t aId) { switch(aId) { - case 17: { // test.nested.event_metric + case 18: { // test.nested.event_metric return Some(EventID::EventMetric_EnumNames_AreStrange); } - case 18: { // test.nested.event_metric_with_extra + case 19: { // test.nested.event_metric_with_extra return Some(EventID::EventMetric_EnumName_WithExtra); } default: { diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Scalar b/toolkit/components/glean/tests/pytest/gifft_output_Scalar index 854455b584..9e1db3e84d 100644 --- a/toolkit/components/glean/tests/pytest/gifft_output_Scalar +++ b/toolkit/components/glean/tests/pytest/gifft_output_Scalar @@ -58,13 +58,13 @@ static inline Maybe<ScalarID> ScalarIdForMetric(uint32_t aId) { case 14: { // test.timespan_metric return Some(ScalarID::SOME_OTHER_UINT_SCALAR); } - case 16: { // test.nested.datetime_metric + case 17: { // test.nested.datetime_metric return Some(ScalarID::SOME_STILL_OTHER_STRING_SCALAR); } - case 21: { // test.nested.quantity_metric + case 22: { // test.nested.quantity_metric return Some(ScalarID::TELEMETRY_TEST_MIRROR_FOR_QUANTITY); } - case 24: { // test.nested.uuid_metric + case 25: { // test.nested.uuid_metric return Some(ScalarID::SOME_OTHER_STRING_SCALAR); } default: { diff --git a/toolkit/components/glean/tests/pytest/jogfile_output b/toolkit/components/glean/tests/pytest/jogfile_output index d75c08c8ba..510b4995c5 100644 --- a/toolkit/components/glean/tests/pytest/jogfile_output +++ b/toolkit/components/glean/tests/pytest/jogfile_output @@ -189,6 +189,15 @@ ], "test.nested": [ [ + "object", + "an_object", + [ + "metrics" + ], + "ping", + false + ], + [ "datetime", "datetime_metric", [ @@ -303,6 +312,7 @@ true, false, true, + true, [ "background", "dirty_startup", @@ -314,6 +324,7 @@ true, true, true, + true, [] ], [ @@ -321,6 +332,7 @@ true, false, true, + true, [ "background", "max_capacity", @@ -332,6 +344,7 @@ true, false, true, + true, [ "overdue", "reschedule", @@ -339,6 +352,14 @@ "tomorrow", "upgrade" ] + ], + [ + "not-ohttp", + false, + true, + true, + false, + [] ] ] }
\ No newline at end of file diff --git a/toolkit/components/glean/tests/pytest/metrics_test.yaml b/toolkit/components/glean/tests/pytest/metrics_test.yaml index cbbb0220b5..6d9a4f8c0e 100644 --- a/toolkit/components/glean/tests/pytest/metrics_test.yaml +++ b/toolkit/components/glean/tests/pytest/metrics_test.yaml @@ -396,3 +396,23 @@ test.nested: - https://bugzilla.mozilla.org/1635260/ data_reviews: - https://example.com + + an_object: + type: object + description: An example object + bugs: + - https://bugzilla.mozilla.org/1839640 + data_reviews: + - http://example.com/reviews + notification_emails: + - CHANGE-ME@example.com + expires: never + structure: + type: array + items: + type: object + properties: + colour: + type: string + diameter: + type: number diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output b/toolkit/components/glean/tests/pytest/metrics_test_output index 76808cc984..0f93eb032d 100644 --- a/toolkit/components/glean/tests/pytest/metrics_test_output +++ b/toolkit/components/glean/tests/pytest/metrics_test_output @@ -6,6 +6,7 @@ * 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/. */ + pub enum DynamicLabel { } pub mod test { @@ -16,6 +17,9 @@ pub mod test { use glean::HistogramType; use once_cell::sync::Lazy; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[allow(non_upper_case_globals)] /// generated from test.boolean_metric /// @@ -361,13 +365,40 @@ pub mod test_nested { use glean::HistogramType; use once_cell::sync::Lazy; + #[allow(unused_imports)] + use std::convert::TryFrom; + + pub type AnObjectObject = Vec<AnObjectObjectItem>; + + #[derive(Debug, Hash, Eq, PartialEq, ::glean::traits::__serde::Serialize, ::glean::traits::__serde::Deserialize)] + #[serde(deny_unknown_fields)] + pub struct AnObjectObjectItem { + pub colour: Option<String>, + pub diameter: Option<i64>, + } + + #[allow(non_upper_case_globals)] + /// generated from test.nested.an_object + /// + /// An example object + pub static an_object: Lazy<ObjectMetric<AnObjectObject>> = Lazy::new(|| { + ObjectMetric::new(16.into(), CommonMetricData { + name: "an_object".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }) + }); + #[allow(non_upper_case_globals)] /// generated from test.nested.datetime_metric /// /// A multi-line /// description pub static datetime_metric: Lazy<DatetimeMetric> = Lazy::new(|| { - DatetimeMetric::new(16.into(), CommonMetricData { + DatetimeMetric::new(17.into(), CommonMetricData { name: "datetime_metric".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], @@ -383,7 +414,7 @@ pub mod test_nested { /// A multi-line /// description pub static event_metric: Lazy<EventMetric<NoExtraKeys>> = Lazy::new(|| { - EventMetric::new(17.into(), CommonMetricData { + EventMetric::new(18.into(), CommonMetricData { name: "event_metric".into(), category: "test.nested".into(), send_in_pings: vec!["events".into()], @@ -415,7 +446,7 @@ pub mod test_nested { /// A multi-line /// description pub static event_metric_with_extra: Lazy<EventMetric<EventMetricWithExtraExtra>> = Lazy::new(|| { - EventMetric::new(18.into(), CommonMetricData { + EventMetric::new(19.into(), CommonMetricData { name: "event_metric_with_extra".into(), category: "test.nested".into(), send_in_pings: vec!["events".into()], @@ -431,7 +462,7 @@ pub mod test_nested { /// A multi-line /// description pub static external_denominator: Lazy<DenominatorMetric> = Lazy::new(|| { - DenominatorMetric::new(19.into(), CommonMetricData { + DenominatorMetric::new(20.into(), CommonMetricData { name: "external_denominator".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], @@ -448,7 +479,7 @@ pub mod test_nested { /// description pub static optimizable_counter_metric: Lazy<CounterMetric> = Lazy::new(|| { CounterMetric::codegen_new( - 20, + 21, "test.nested", "optimizable_counter_metric", "metrics" @@ -461,7 +492,7 @@ pub mod test_nested { /// A multi-line /// description pub static quantity_metric: Lazy<QuantityMetric> = Lazy::new(|| { - QuantityMetric::new(21.into(), CommonMetricData { + QuantityMetric::new(22.into(), CommonMetricData { name: "quantity_metric".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], @@ -477,7 +508,7 @@ pub mod test_nested { /// A multi-line /// description pub static rate_metric: Lazy<RateMetric> = Lazy::new(|| { - RateMetric::new(22.into(), CommonMetricData { + RateMetric::new(23.into(), CommonMetricData { name: "rate_metric".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], @@ -493,7 +524,7 @@ pub mod test_nested { /// A multi-line /// description pub static rate_with_external_denominator: Lazy<NumeratorMetric> = Lazy::new(|| { - NumeratorMetric::new(23.into(), CommonMetricData { + NumeratorMetric::new(24.into(), CommonMetricData { name: "rate_with_external_denominator".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], @@ -509,7 +540,7 @@ pub mod test_nested { /// A multi-line /// description pub static uuid_metric: Lazy<UuidMetric> = Lazy::new(|| { - UuidMetric::new(24.into(), CommonMetricData { + UuidMetric::new(25.into(), CommonMetricData { name: "uuid_metric".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], @@ -538,7 +569,7 @@ pub(crate) mod __glean_metric_maps { pub static COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<CounterMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(2); map.insert(2.into(), &super::test::counter_metric); - map.insert(20.into(), &super::test_nested::optimizable_counter_metric); + map.insert(21.into(), &super::test_nested::optimizable_counter_metric); map }); @@ -586,41 +617,100 @@ pub(crate) mod __glean_metric_maps { pub static DATETIME_MAP: Lazy<HashMap<MetricId, &Lazy<DatetimeMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(1); - map.insert(16.into(), &super::test_nested::datetime_metric); + map.insert(17.into(), &super::test_nested::datetime_metric); map }); pub static DENOMINATOR_MAP: Lazy<HashMap<MetricId, &Lazy<DenominatorMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(1); - map.insert(19.into(), &super::test_nested::external_denominator); + map.insert(20.into(), &super::test_nested::external_denominator); map }); pub static QUANTITY_MAP: Lazy<HashMap<MetricId, &Lazy<QuantityMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(1); - map.insert(21.into(), &super::test_nested::quantity_metric); + map.insert(22.into(), &super::test_nested::quantity_metric); map }); pub static RATE_MAP: Lazy<HashMap<MetricId, &Lazy<RateMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(1); - map.insert(22.into(), &super::test_nested::rate_metric); + map.insert(23.into(), &super::test_nested::rate_metric); map }); pub static NUMERATOR_MAP: Lazy<HashMap<MetricId, &Lazy<NumeratorMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(1); - map.insert(23.into(), &super::test_nested::rate_with_external_denominator); + map.insert(24.into(), &super::test_nested::rate_with_external_denominator); map }); pub static UUID_MAP: Lazy<HashMap<MetricId, &Lazy<UuidMetric>>> = Lazy::new(|| { let mut map = HashMap::with_capacity(1); - map.insert(24.into(), &super::test_nested::uuid_metric); + map.insert(25.into(), &super::test_nested::uuid_metric); map }); + pub(crate) fn set_object_by_id(metric_id: u32, value: String) -> Result<(), ()> { + match metric_id { + 16 => { + super::test_nested::an_object.set_string(value); + Ok(()) + } + _ => Err(()), + } + } + + /// Wrapper to get the currently stored object for object metric as a string. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `ping_name` - (Optional) The ping name to look into. + /// Defaults to the first value in `send_in_pings`. + /// + /// # Returns + /// + /// Returns the recorded object serialized as a JSON string or `None` if nothing stored. + /// + /// # Panics + /// + /// Panics if no object by the given metric ID could be found. + pub(crate) fn object_test_get_value(metric_id: u32, ping_name: Option<String>) -> Option<String> { + match metric_id { + 16 => super::test_nested::an_object.test_get_value_as_str(ping_name.as_deref()), + _ => panic!("No object for metric id {}", metric_id), + } + } + + /// Check the provided object for errors. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// + /// # Returns + /// + /// Returns a string for the recorded error or `None`. + /// + /// # Panics + /// + /// Panics if no object by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn object_test_get_error(metric_id: u32) -> Option<String> { + #[cfg(feature = "with_gecko")] + match metric_id { + 16 => test_get_errors!(super::test_nested::an_object), + _ => panic!("No object for metric id {}", metric_id), + } + + #[cfg(not(feature = "with_gecko"))] + { + return None; + } + } + /// Wrapper to record an event based on its metric ID. /// /// # Arguments @@ -635,7 +725,7 @@ pub(crate) mod __glean_metric_maps { /// or an `EventRecordingError::InvalidExtraKey` if the `extra` map could not be deserialized. pub(crate) fn record_event_by_id(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> { match metric_id { - 17 => { + 18 => { assert!( extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(), "No extra keys allowed, but some were passed" @@ -644,7 +734,7 @@ pub(crate) mod __glean_metric_maps { super::test_nested::event_metric.record_raw(extra); Ok(()) } - 18 => { + 19 => { assert!( extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(), "No extra keys allowed, but some were passed" @@ -673,7 +763,7 @@ pub(crate) mod __glean_metric_maps { /// but some are passed in. pub(crate) fn record_event_by_id_with_time(metric_id: MetricId, timestamp: u64, extra: HashMap<String, String>) -> Result<(), EventRecordingError> { match metric_id { - MetricId(17) => { + MetricId(18) => { if extra_keys_len(&super::test_nested::event_metric) == 0 && !extra.is_empty() { return Err(EventRecordingError::InvalidExtraKey); } @@ -681,7 +771,7 @@ pub(crate) mod __glean_metric_maps { super::test_nested::event_metric.record_with_time(timestamp, extra); Ok(()) } - MetricId(18) => { + MetricId(19) => { if extra_keys_len(&super::test_nested::event_metric_with_extra) == 0 && !extra.is_empty() { return Err(EventRecordingError::InvalidExtraKey); } @@ -710,8 +800,8 @@ pub(crate) mod __glean_metric_maps { /// Panics if no event by the given metric ID could be found. pub(crate) fn event_test_get_value_wrapper(metric_id: u32, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> { match metric_id { - 17 => super::test_nested::event_metric.test_get_value(ping_name.as_deref()), - 18 => super::test_nested::event_metric_with_extra.test_get_value(ping_name.as_deref()), + 18 => super::test_nested::event_metric.test_get_value(ping_name.as_deref()), + 19 => super::test_nested::event_metric_with_extra.test_get_value(ping_name.as_deref()), _ => panic!("No event for metric id {}", metric_id), } } @@ -735,8 +825,8 @@ pub(crate) mod __glean_metric_maps { pub(crate) fn event_test_get_error(metric_id: u32) -> Option<String> { #[cfg(feature = "with_gecko")] match metric_id { - 17 => test_get_errors!(super::test_nested::event_metric), - 18 => test_get_errors!(super::test_nested::event_metric_with_extra), + 18 => test_get_errors!(super::test_nested::event_metric), + 19 => test_get_errors!(super::test_nested::event_metric_with_extra), _ => panic!("No event for metric id {}", metric_id), } diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp index 5b9c7bfae4..615e1c857d 100644 --- a/toolkit/components/glean/tests/pytest/metrics_test_output_cpp +++ b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp @@ -181,6 +181,7 @@ namespace test { } namespace test_nested { + /** * generated from test.nested.datetime_metric */ @@ -188,7 +189,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::DatetimeMetric datetime_metric(16); + constexpr impl::DatetimeMetric datetime_metric(17); /** * generated from test.nested.event_metric @@ -197,7 +198,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::EventMetric<NoExtraKeys> event_metric(17); + constexpr impl::EventMetric<NoExtraKeys> event_metric(18); /** * generated from test.nested.event_metric_with_extra @@ -224,7 +225,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::EventMetric<EventMetricWithExtraExtra> event_metric_with_extra(18); + constexpr impl::EventMetric<EventMetricWithExtraExtra> event_metric_with_extra(19); /** * generated from test.nested.external_denominator @@ -233,7 +234,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::DenominatorMetric external_denominator(19); + constexpr impl::DenominatorMetric external_denominator(20); /** * generated from test.nested.optimizable_counter_metric @@ -242,7 +243,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::CounterMetric optimizable_counter_metric(20); + constexpr impl::CounterMetric optimizable_counter_metric(21); /** * generated from test.nested.quantity_metric @@ -251,7 +252,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::QuantityMetric quantity_metric(21); + constexpr impl::QuantityMetric quantity_metric(22); /** * generated from test.nested.rate_metric @@ -260,7 +261,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::RateMetric rate_metric(22); + constexpr impl::RateMetric rate_metric(23); /** * generated from test.nested.rate_with_external_denominator @@ -269,7 +270,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::NumeratorMetric rate_with_external_denominator(23); + constexpr impl::NumeratorMetric rate_with_external_denominator(24); /** * generated from test.nested.uuid_metric @@ -278,7 +279,7 @@ namespace test_nested { * A multi-line * description */ - constexpr impl::UuidMetric uuid_metric(24); + constexpr impl::UuidMetric uuid_metric(25); } diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp index ba4619bb57..7cb5ce7d12 100644 --- a/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp +++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp @@ -39,8 +39,8 @@ using metric_entry_t = uint64_t; static_assert(GLEAN_INDEX_BITS + GLEAN_TYPE_BITS + GLEAN_ID_BITS == sizeof(metric_entry_t) * 8, "Index, Type, and ID bits need to fit into a metric_entry_t"); static_assert(GLEAN_TYPE_BITS + GLEAN_ID_BITS <= sizeof(uint32_t) * 8, "Metric Types and IDs need to fit into at most 32 bits"); static_assert(2 < UINT32_MAX, "Too many metric categories generated."); -static_assert(24 < 33554432, "Too many metrics generated. Need room for 2 signal bits."); -static_assert(19 < 32, "Too many different metric types."); +static_assert(25 < 33554432, "Too many metrics generated. Need room for 2 signal bits."); +static_assert(20 < 32, "Too many different metric types."); already_AddRefed<GleanMetric> NewMetricFromId(uint32_t id, nsISupports* aParent) { uint32_t typeId = GLEAN_TYPE_ID(id); @@ -95,31 +95,35 @@ already_AddRefed<GleanMetric> NewMetricFromId(uint32_t id, nsISupports* aParent) { return MakeAndAddRef<GleanTimingDistribution>(metricId, aParent); } - case 13: /* datetime */ + case 13: /* object */ + { + return MakeAndAddRef<GleanObject>(metricId, aParent); + } + case 14: /* datetime */ { return MakeAndAddRef<GleanDatetime>(metricId, aParent); } - case 14: /* event */ + case 15: /* event */ { return MakeAndAddRef<GleanEvent>(metricId, aParent); } - case 15: /* denominator */ + case 16: /* denominator */ { return MakeAndAddRef<GleanDenominator>(metricId, aParent); } - case 16: /* quantity */ + case 17: /* quantity */ { return MakeAndAddRef<GleanQuantity>(metricId, aParent); } - case 17: /* rate */ + case 18: /* rate */ { return MakeAndAddRef<GleanRate>(metricId, aParent); } - case 18: /* numerator */ + case 19: /* numerator */ { return MakeAndAddRef<GleanNumerator>(metricId, aParent); } - case 19: /* uuid */ + case 20: /* uuid */ { return MakeAndAddRef<GleanUuid>(metricId, aParent); } @@ -289,45 +293,47 @@ constexpr char gMetricStringTable[] = { /* 310 - "test.textMetric" */ 't', 'e', 's', 't', '.', 't', 'e', 'x', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0', /* 326 - "test.timespanMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'e', 's', 'p', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', /* 346 - "test.timingDistributionMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'i', 'n', 'g', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', - /* 376 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0', - /* 402 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0', - /* 425 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0', - /* 457 - "testNested.externalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0', - /* 488 - "testNested.optimizableCounterMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'o', 'p', 't', 'i', 'm', 'i', 'z', 'a', 'b', 'l', 'e', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0', - /* 524 - "testNested.quantityMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'q', 'u', 'a', 'n', 't', 'i', 't', 'y', 'M', 'e', 't', 'r', 'i', 'c', '\0', - /* 550 - "testNested.rateMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0', - /* 572 - "testNested.rateWithExternalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'W', 'i', 't', 'h', 'E', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0', - /* 611 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 376 - "testNested.anObject" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'a', 'n', 'O', 'b', 'j', 'e', 'c', 't', '\0', + /* 396 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 422 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 445 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0', + /* 477 - "testNested.externalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0', + /* 508 - "testNested.optimizableCounterMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'o', 'p', 't', 'i', 'm', 'i', 'z', 'a', 'b', 'l', 'e', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 544 - "testNested.quantityMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'q', 'u', 'a', 'n', 't', 'i', 't', 'y', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 570 - "testNested.rateMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 592 - "testNested.rateWithExternalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'W', 'i', 't', 'h', 'E', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0', + /* 631 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0', }; static_assert(sizeof(gMetricStringTable) < 4294967296, "Metric string table is too large."); const metric_entry_t sMetricByNameLookupEntries[] = { - 6341068335467200838ull, - 3458764548180279480ull, - 1152921590506193384ull, - 576460756598390784ull, - 2882303787286921342ull, - 6917529092065591642ull, - 9799832883647480358ull, - 2305843026393563204ull, - 7493989848663982456ull, + 5764607578868810038ull, 1152921513196781587ull, - 8070450609557340585ull, - 2305843030688530526ull, - 5188146822270419236ull, - 8646911366155731401ull, 1729382269795172390ull, - 10952754396844261987ull, + 3458764552475246801ull, + 10952754396844261968ull, + 4611686065672028430ull, + 2305843026393563204ull, + 1152921594801160700ull, + 5188146822270419236ull, + 3458764548180279480ull, + 8646911361860764070ull, + 8646911366155731389ull, + 8070450605262373260ull, + 9799832883647480352ull, + 11529215153442652791ull, + 2305843030688530526ull, + 6917529092065591642ull, + 9223372122754122205ull, + 10376293640245871162ull, 4035225309073637616ull, - 10376293640245871164ull, - 9223372127049089548ull, - 5764607578868810038ull, - 8070450605262373266ull, + 2882303787286921342ull, + 576460756598390784ull, + 7493989848663982456ull, 2882303791581888664ull, - 4611686065672028430ull, - 3458764552475246801ull + 6341068335467200838ull }; @@ -365,39 +371,39 @@ MetricByNameLookup(const nsACString& aKey) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, - 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 6, 0, 12, 0, 0, 0, 0, 0, 0, 0, - 11, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 6, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js_h b/toolkit/components/glean/tests/pytest/metrics_test_output_js_h index 6ce9833732..99887c5a74 100644 --- a/toolkit/components/glean/tests/pytest/metrics_test_output_js_h +++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js_h @@ -69,7 +69,7 @@ Maybe<uint32_t> MetricByNameLookup(const nsACString&); Maybe<uint32_t> CategoryByNameLookup(const nsACString&); extern const category_entry_t sCategoryByNameLookupEntries[2]; -extern const metric_entry_t sMetricByNameLookupEntries[24]; +extern const metric_entry_t sMetricByNameLookupEntries[25]; } // namespace mozilla::glean #endif // mozilla_GleanJSMetricsLookup_h diff --git a/toolkit/components/glean/tests/pytest/pings_test.yaml b/toolkit/components/glean/tests/pytest/pings_test.yaml index efa6ba9e0a..558cbb1022 100644 --- a/toolkit/components/glean/tests/pytest/pings_test.yaml +++ b/toolkit/components/glean/tests/pytest/pings_test.yaml @@ -7,7 +7,7 @@ # `glean_parser` PyPI package. --- -$schema: moz://mozilla.org/schemas/glean/pings/1-0-0 +$schema: moz://mozilla.org/schemas/glean/pings/2-0-0 not-baseline: description: > @@ -114,3 +114,19 @@ not-deletion-request: - https://bugzilla.mozilla.org/show_bug.cgi?id=1587095#c6 notification_emails: - glean-team@mozilla.com + +not-ohttp: + description: > + A fake OHTTP-using ping + include_client_id: false + metadata: + include_info_sections: false + use_ohttp: true + send_if_empty: true + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002 + notification_emails: + - chutten@mozilla.com + - glean-team@mozilla.com diff --git a/toolkit/components/glean/tests/pytest/pings_test_output b/toolkit/components/glean/tests/pytest/pings_test_output index 9e037eb22a..97c0793b1f 100644 --- a/toolkit/components/glean/tests/pytest/pings_test_output +++ b/toolkit/components/glean/tests/pytest/pings_test_output @@ -20,6 +20,7 @@ pub static not_baseline: Lazy<Ping> = Lazy::new(|| { true, false, true, + true, vec!["background".into(), "dirty_startup".into(), "foreground".into()], ) }); @@ -36,6 +37,7 @@ pub static not_deletion_request: Lazy<Ping> = Lazy::new(|| { true, true, true, + true, vec![], ) }); @@ -50,6 +52,7 @@ pub static not_events: Lazy<Ping> = Lazy::new(|| { true, false, true, + true, vec!["background".into(), "max_capacity".into(), "startup".into()], ) }); @@ -68,10 +71,24 @@ pub static not_metrics: Lazy<Ping> = Lazy::new(|| { true, false, true, + true, vec!["overdue".into(), "reschedule".into(), "today".into(), "tomorrow".into(), "upgrade".into()], ) }); +#[allow(non_upper_case_globals)] +/// A fake OHTTP-using ping +pub static not_ohttp: Lazy<Ping> = Lazy::new(|| { + Ping::new( + "not-ohttp", + false, + true, + true, + false, + vec![], + ) +}); + /// Instantiate custom pings once to trigger registration. /// @@ -87,6 +104,7 @@ pub fn register_pings(application_id: Option<&str>) { let _ = &*not_deletion_request; let _ = &*not_events; let _ = &*not_metrics; + let _ = &*not_ohttp; } } } @@ -110,6 +128,7 @@ pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) { 2 => not_deletion_request.submit(reason), 3 => not_events.submit(reason), 4 => not_metrics.submit(reason), + 5 => not_ohttp.submit(reason), _ => { // TODO: instrument this error. log::error!("Cannot submit unknown ping {} by id.", id); diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_cpp index 289529b118..8994204634 100644 --- a/toolkit/components/glean/tests/pytest/pings_test_output_cpp +++ b/toolkit/components/glean/tests/pytest/pings_test_output_cpp @@ -56,6 +56,13 @@ constexpr glean::impl::Ping NotEvents(3); */ constexpr glean::impl::Ping NotMetrics(4); +/* + * Generated from not-ohttp. + * + * A fake OHTTP-using ping + */ +constexpr glean::impl::Ping NotOhttp(5); + } // namespace mozilla::glean_pings diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp index 139eb29148..bc4f235d1e 100644 --- a/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp +++ b/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp @@ -33,15 +33,17 @@ constexpr char gPingStringTable[] = { /* 12 - "notDeletionRequest" */ 'n', 'o', 't', 'D', 'e', 'l', 'e', 't', 'i', 'o', 'n', 'R', 'e', 'q', 'u', 'e', 's', 't', '\0', /* 31 - "notEvents" */ 'n', 'o', 't', 'E', 'v', 'e', 'n', 't', 's', '\0', /* 41 - "notMetrics" */ 'n', 'o', 't', 'M', 'e', 't', 'r', 'i', 'c', 's', '\0', + /* 52 - "notOhttp" */ 'n', 'o', 't', 'O', 'h', 't', 't', 'p', '\0', }; const ping_entry_t sPingByNameLookupEntries[] = { 65536, + 327732, + 262185, 131084, - 196639, - 262185 + 196639 }; @@ -88,7 +90,7 @@ PingByNameLookup(const nsACString& aKey) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -108,7 +110,7 @@ PingByNameLookup(const nsACString& aKey) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js_h b/toolkit/components/glean/tests/pytest/pings_test_output_js_h index 0c89de93ae..af21515ccc 100644 --- a/toolkit/components/glean/tests/pytest/pings_test_output_js_h +++ b/toolkit/components/glean/tests/pytest/pings_test_output_js_h @@ -28,6 +28,6 @@ const char* GetPingName(ping_entry_t aEntry); */ Maybe<uint32_t> PingByNameLookup(const nsACString&); -extern const ping_entry_t sPingByNameLookupEntries[4]; +extern const ping_entry_t sPingByNameLookupEntries[5]; } // namespace mozilla::glean #endif // mozilla_GleanJSPingsLookup_h diff --git a/toolkit/components/glean/tests/test_metrics.yaml b/toolkit/components/glean/tests/test_metrics.yaml index bc290d470f..47587241b1 100644 --- a/toolkit/components/glean/tests/test_metrics.yaml +++ b/toolkit/components/glean/tests/test_metrics.yaml @@ -403,6 +403,25 @@ test_only: - test-ping telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_QUANTITY + balloons: + type: object + description: A collection of balloons + bugs: + - https://bugzilla.mozilla.org/1839640 + data_reviews: + - http://example.com/reviews + notification_emails: + - CHANGE-ME@example.com + expires: never + structure: + type: array + items: + type: object + properties: + colour: + type: string + diameter: + type: number test_only.ipc: a_counter: diff --git a/toolkit/components/glean/tests/test_pings.yaml b/toolkit/components/glean/tests/test_pings.yaml index d62f682109..8bae13215e 100644 --- a/toolkit/components/glean/tests/test_pings.yaml +++ b/toolkit/components/glean/tests/test_pings.yaml @@ -40,3 +40,22 @@ test-ping: - glean-team@mozilla.com no_lint: - REDUNDANT_PING + +test-ohttp-ping: + description: | + This ping is for tests only. + Resembles how OHTTP pings are defined. + include_client_id: false + metadata: + include_info_sections: false + use_ohttp: true + send_if_empty: true + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002 + notification_emails: + - chutten@mozilla.com + - glean-team@mozilla.com + no_lint: + - REDUNDANT_PING diff --git a/toolkit/components/glean/tests/xpcshell/head.js b/toolkit/components/glean/tests/xpcshell/head.js index f42bd02822..eaf8fa9c61 100644 --- a/toolkit/components/glean/tests/xpcshell/head.js +++ b/toolkit/components/glean/tests/xpcshell/head.js @@ -4,3 +4,155 @@ const { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); + +ChromeUtils.defineESModuleGetters(this, { + HttpServer: "resource://testing-common/httpd.sys.mjs", + NetUtil: "resource://gre/modules/NetUtil.sys.mjs", +}); + +const PingServer = { + _httpServer: null, + _started: false, + _defers: [Promise.withResolvers()], + _currentDeferred: 0, + + get port() { + return this._httpServer.identity.primaryPort; + }, + + get host() { + return this._httpServer.identity.primaryHost; + }, + + get started() { + return this._started; + }, + + registerPingHandler(handler) { + this._httpServer.registerPrefixHandler("/submit/", handler); + }, + + resetPingHandler() { + this.registerPingHandler(request => { + let r = request; + console.trace( + `defaultPingHandler() - ${r.method} ${r.scheme}://${r.host}:${r.port}${r.path}` + ); + let deferred = this._defers[this._defers.length - 1]; + this._defers.push(Promise.withResolvers()); + deferred.resolve(request); + }); + }, + + start() { + this._httpServer = new HttpServer(); + this._httpServer.start(-1); + this._started = true; + this.clearRequests(); + this.resetPingHandler(); + }, + + stop() { + return new Promise(resolve => { + this._httpServer.stop(resolve); + this._started = false; + }); + }, + + clearRequests() { + this._defers = [Promise.withResolvers()]; + this._currentDeferred = 0; + }, + + promiseNextRequest() { + const deferred = this._defers[this._currentDeferred++]; + // Send the ping to the consumer on the next tick, so that the completion gets + // signaled to Telemetry. + return new Promise(r => + Services.tm.dispatchToMainThread(() => r(deferred.promise)) + ); + }, + + promiseNextPing() { + return this.promiseNextRequest().then(request => + decodeRequestPayload(request) + ); + }, + + async promiseNextRequests(count) { + let results = []; + for (let i = 0; i < count; ++i) { + results.push(await this.promiseNextRequest()); + } + + return results; + }, + + promiseNextPings(count) { + return this.promiseNextRequests(count).then(requests => { + return Array.from(requests, decodeRequestPayload); + }); + }, +}; + +/** + * Decode the payload of an HTTP request into a ping. + * + * @param {object} request The data representing an HTTP request (nsIHttpRequest). + * @returns {object} The decoded ping payload. + */ +function decodeRequestPayload(request) { + let s = request.bodyInputStream; + let payload = null; + + if ( + request.hasHeader("content-encoding") && + request.getHeader("content-encoding") == "gzip" + ) { + let observer = { + buffer: "", + onStreamComplete(loader, context, status, length, result) { + // String.fromCharCode can only deal with 500,000 characters + // at a time, so chunk the result into parts of that size. + const chunkSize = 500000; + for (let offset = 0; offset < result.length; offset += chunkSize) { + this.buffer += String.fromCharCode.apply( + String, + result.slice(offset, offset + chunkSize) + ); + } + }, + }; + + let scs = Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); + let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance( + Ci.nsIStreamLoader + ); + listener.init(observer); + let converter = scs.asyncConvertData( + "gzip", + "uncompressed", + listener, + null + ); + converter.onStartRequest(null, null); + converter.onDataAvailable(null, s, 0, s.available()); + converter.onStopRequest(null, null, null); + // TODO: nsIScriptableUnicodeConverter is deprecated + // But I can't figure out how else to ungzip bodyInputStream. + let unicodeConverter = Cc[ + "@mozilla.org/intl/scriptableunicodeconverter" + ].createInstance(Ci.nsIScriptableUnicodeConverter); + unicodeConverter.charset = "UTF-8"; + let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer); + utf8string += unicodeConverter.Finish(); + payload = JSON.parse(utf8string); + } else { + let bytes = NetUtil.readInputStream(s, s.available()); + payload = JSON.parse(new TextDecoder().decode(bytes)); + } + + return payload; +} diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js index 015b4d4e38..8fcf755d3a 100644 --- a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js +++ b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js @@ -176,10 +176,7 @@ add_task(async function test_gifft_timing_dist() { // But we can guarantee it's only two samples. Assert.equal( 2, - Object.entries(data.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ), + Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0), "Only two buckets with samples" ); @@ -188,10 +185,7 @@ add_task(async function test_gifft_timing_dist() { Assert.greaterOrEqual(data.sum, 13, "Histogram's in milliseconds"); Assert.equal( 2, - Object.entries(data.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ), + Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0), "Only two samples" ); }); diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js index a318d57b9c..ae4c48b6e8 100644 --- a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js +++ b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js @@ -291,10 +291,7 @@ add_task( // but we can assert there are only two samples. Assert.equal( 2, - Object.entries(times.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ) + Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0) ); const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL; Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds."); @@ -303,7 +300,7 @@ add_task( Assert.equal( 2, Object.entries(timingHist.values).reduce( - (acc, [bucket, count]) => acc + count, + (acc, [, count]) => acc + count, 0 ), "Only two samples" diff --git a/toolkit/components/glean/tests/xpcshell/test_Glean.js b/toolkit/components/glean/tests/xpcshell/test_Glean.js index 8375c22a3e..18b450a69b 100644 --- a/toolkit/components/glean/tests/xpcshell/test_Glean.js +++ b/toolkit/components/glean/tests/xpcshell/test_Glean.js @@ -182,6 +182,7 @@ add_task(async function test_fog_memory_distribution_works() { Glean.testOnly.doYouRemember.accumulate(17); let data = Glean.testOnly.doYouRemember.testGetValue("test-ping"); + Assert.equal(2, data.count, "Count of entries is correct"); // `data.sum` is in bytes, but the metric is in MB. Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct"); for (let [bucket, count] of Object.entries(data.values)) { @@ -196,6 +197,7 @@ add_task(async function test_fog_custom_distribution_works() { Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]); let data = Glean.testOnlyIpc.aCustomDist.testGetValue("store1"); + Assert.equal(2, data.count, "Count of entries is correct"); Assert.equal(7 + 268435458, data.sum, "Sum's correct"); for (let [bucket, count] of Object.entries(data.values)) { Assert.ok( @@ -216,7 +218,7 @@ add_task(function test_fog_custom_pings() { Assert.ok("onePingOnly" in GleanPings); let submitted = false; Glean.testOnly.onePingOneBool.set(false); - GleanPings.onePingOnly.testBeforeNextSubmit(reason => { + GleanPings.onePingOnly.testBeforeNextSubmit(() => { submitted = true; Assert.equal(false, Glean.testOnly.onePingOneBool.testGetValue()); }); @@ -227,7 +229,7 @@ add_task(function test_fog_custom_pings() { add_task(function test_recursive_testBeforeNextSubmit() { Assert.ok("onePingOnly" in GleanPings); let submitted = 0; - let rec = reason => { + let rec = () => { submitted++; GleanPings.onePingOnly.testBeforeNextSubmit(rec); }; @@ -255,6 +257,10 @@ add_task(async function test_fog_timing_distribution_works() { Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms let data = Glean.testOnly.whatTimeIsIt.testGetValue(); + + // Cancelled timers should not be counted. + Assert.equal(2, data.count, "Count of entries is correct"); + const NANOS_IN_MILLIS = 1e6; // bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough. const EPSILON = 40000; @@ -266,10 +272,7 @@ add_task(async function test_fog_timing_distribution_works() { // But we can guarantee it's only two samples. Assert.equal( 2, - Object.entries(data.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ), + Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0), "Only two buckets with samples" ); }); @@ -456,3 +459,106 @@ add_task(async function test_fog_text_works_unusual_character() { Assert.greater(rslt.length, 100); }); + +add_task(async function test_fog_object_works() { + if (!Glean.testOnly.balloons) { + // FIXME(bug 1883857): object metric type not available, e.g. in artifact builds. + // Skipping this test. + return; + } + + Assert.equal( + undefined, + Glean.testOnly.balloons.testGetValue(), + "No object stored" + ); + + // Can't store not-objects. + let invalidValues = [1, "str", false, undefined, null, NaN, Infinity]; + for (let value of invalidValues) { + Assert.throws( + () => Glean.testOnly.balloons.set(value), + /is not an object/, + "Should throw a type error" + ); + } + + // No invalid value will be stored. + Assert.equal( + undefined, + Glean.testOnly.balloons.testGetValue(), + "No object stored" + ); + + // `JS_Stringify` internally throws + // an `TypeError: cyclic object value` exception. + // That's cleared and `set` should not throw on it. + // This eventually should log a proper error in Glean. + let selfref = {}; + selfref.a = selfref; + Glean.testOnly.balloons.set(selfref); + Assert.equal( + undefined, + Glean.testOnly.balloons.testGetValue(), + "No object stored" + ); + + let balloons = [ + { colour: "red", diameter: 5 }, + { colour: "blue", diameter: 7 }, + { colour: "orange" }, + ]; + Glean.testOnly.balloons.set(balloons); + + let result = Glean.testOnly.balloons.testGetValue(); + let expected = [ + { colour: "red", diameter: 5 }, + { colour: "blue", diameter: 7 }, + { colour: "orange", diameter: null }, + ]; + Assert.deepEqual(expected, result); + + // These values are coerced to null or removed. + balloons = [ + { colour: "inf", diameter: Infinity }, + { colour: "negative-inf", diameter: -1 / 0 }, + { colour: "nan", diameter: NaN }, + { colour: "undef", diameter: undefined }, + ]; + Glean.testOnly.balloons.set(balloons); + result = Glean.testOnly.balloons.testGetValue(); + expected = [ + { colour: "inf", diameter: null }, + { colour: "negative-inf", diameter: null }, + { colour: "nan", diameter: null }, + { colour: "undef", diameter: null }, + ]; + Assert.deepEqual(expected, result); + + // colour != color. + let invalid = [{ color: "orange" }, { color: "red", diameter: "small" }]; + Glean.testOnly.balloons.set(invalid); + Assert.throws( + () => Glean.testOnly.balloons.testGetValue(), + /invalid_value/, + "Should throw because last object was invalid." + ); + + Services.fog.testResetFOG(); + // set again to ensure it's stored + balloons = [ + { colour: "red", diameter: 5 }, + { colour: "blue", diameter: 7 }, + ]; + Glean.testOnly.balloons.set(balloons); + result = Glean.testOnly.balloons.testGetValue(); + Assert.deepEqual(balloons, result); + + invalid = [{ colour: "red", diameter: 5, extra: "field" }]; + Glean.testOnly.balloons.set(invalid); + Assert.throws( + () => Glean.testOnly.balloons.testGetValue(), + /invalid_value/, + "Should throw because last object was invalid." + ); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js index 3d665c23b9..2db1c53218 100644 --- a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js +++ b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js @@ -127,10 +127,7 @@ add_task( // but we can assert there are only two samples. Assert.equal( 2, - Object.entries(times.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ) + Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0) ); const mabelsCounters = Glean.testOnly.mabelsKitchenCounters; diff --git a/toolkit/components/glean/tests/xpcshell/test_JOG.js b/toolkit/components/glean/tests/xpcshell/test_JOG.js index 53b1d25962..00f3ded135 100644 --- a/toolkit/components/glean/tests/xpcshell/test_JOG.js +++ b/toolkit/components/glean/tests/xpcshell/test_JOG.js @@ -289,11 +289,11 @@ add_task(async function test_jog_custom_pings() { `"ping"`, false ); - Services.fog.testRegisterRuntimePing("jog-ping", true, true, true, []); + Services.fog.testRegisterRuntimePing("jog-ping", true, true, true, true, []); Assert.ok("jogPing" in GleanPings); let submitted = false; Glean.jogCat.jogPingBool.set(false); - GleanPings.jogPing.testBeforeNextSubmit(reason => { + GleanPings.jogPing.testBeforeNextSubmit(() => { submitted = true; Assert.equal(false, Glean.jogCat.jogPingBool.testGetValue()); }); @@ -338,10 +338,7 @@ add_task(async function test_jog_timing_distribution_works() { // But we can guarantee it's only two samples. Assert.equal( 2, - Object.entries(data.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ), + Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0), "Only two buckets with samples" ); }); @@ -642,7 +639,9 @@ add_task(function test_jog_dotted_categories_work() { add_task(async function test_jog_ping_works() { const kReason = "reason-1"; - Services.fog.testRegisterRuntimePing("my-ping", true, true, true, [kReason]); + Services.fog.testRegisterRuntimePing("my-ping", true, true, true, true, [ + kReason, + ]); let submitted = false; GleanPings.myPing.testBeforeNextSubmit(reason => { submitted = true; @@ -652,6 +651,20 @@ add_task(async function test_jog_ping_works() { Assert.ok(submitted, "Ping must have been submitted"); }); +add_task(async function test_jog_noinfo_ping_works() { + const kReason = "reason-1"; + Services.fog.testRegisterRuntimePing("noinfo-ping", true, true, true, false, [ + kReason, + ]); + let submitted = false; + GleanPings.noinfoPing.testBeforeNextSubmit(reason => { + submitted = true; + Assert.equal(kReason, reason); + }); + GleanPings.noinfoPing.submit("reason-1"); + Assert.ok(submitted, "Ping must have been submitted"); +}); + add_task(function test_jog_name_collision() { Assert.ok("aCounter" in Glean.testOnlyJog); Assert.equal(undefined, Glean.testOnlyJog.aCounter.testGetValue()); diff --git a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js index 505dba6825..b43a448c53 100644 --- a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js +++ b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js @@ -226,10 +226,7 @@ add_task( // but we can assert there are only two samples. Assert.equal( 2, - Object.entries(times.values).reduce( - (acc, [bucket, count]) => acc + count, - 0 - ) + Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0) ); const labeledCounter = Glean.jogIpc.jogLabeledCounter; diff --git a/toolkit/components/glean/tests/xpcshell/test_OHTTP.js b/toolkit/components/glean/tests/xpcshell/test_OHTTP.js new file mode 100644 index 0000000000..76d1d2a67b --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_OHTTP.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(async () => { + // FOG needs a profile dir to put its data in. + do_get_profile(); + + PingServer.start(); + + registerCleanupFunction(async () => { + await PingServer.stop(); + }); + + Services.prefs.setIntPref( + "telemetry.fog.test.localhost_port", + PingServer.port + ); + // Port pref needs to be set before init, so let's reset to reinit. + Services.fog.testResetFOG(); +}); + +add_task(async () => { + PingServer.clearRequests(); + GleanPings.testOhttpPing.submit(); + + let ping = await PingServer.promiseNextPing(); + + ok(!("client_info" in ping), "No client_info allowed."); + ok(!("ping_info" in ping), "No ping_info allowed."); +}); diff --git a/toolkit/components/glean/tests/xpcshell/xpcshell.toml b/toolkit/components/glean/tests/xpcshell/xpcshell.toml index 40b1a22bf4..22806a32e6 100644 --- a/toolkit/components/glean/tests/xpcshell/xpcshell.toml +++ b/toolkit/components/glean/tests/xpcshell/xpcshell.toml @@ -30,3 +30,6 @@ skip-if = ["os == 'android'"] # Server Knobs on mobile will be handled by the sp ["test_MillionQ.js"] skip-if = ["os == 'android'"] # Android inits its own FOG, so the test won't work. + +["test_OHTTP.js"] +skip-if = ["os == 'android'"] # FOG isn't responsible for monitoring prefs and controlling upload on Android diff --git a/toolkit/components/glean/xpcom/FOG.cpp b/toolkit/components/glean/xpcom/FOG.cpp index 3673b23707..d4c03a5b9e 100644 --- a/toolkit/components/glean/xpcom/FOG.cpp +++ b/toolkit/components/glean/xpcom/FOG.cpp @@ -419,12 +419,13 @@ FOG::TestRegisterRuntimePing(const nsACString& aName, const bool aIncludeClientId, const bool aSendIfEmpty, const bool aPreciseTimestamps, + const bool aIncludeInfoSections, const nsTArray<nsCString>& aReasonCodes, uint32_t* aPingIdOut) { *aPingIdOut = 0; - *aPingIdOut = - glean::jog::jog_test_register_ping(&aName, aIncludeClientId, aSendIfEmpty, - aPreciseTimestamps, &aReasonCodes); + *aPingIdOut = glean::jog::jog_test_register_ping( + &aName, aIncludeClientId, aSendIfEmpty, aPreciseTimestamps, + aIncludeInfoSections, &aReasonCodes); return NS_OK; } diff --git a/toolkit/components/glean/xpcom/nsIFOG.idl b/toolkit/components/glean/xpcom/nsIFOG.idl index 682df85f1a..dd5d0ec21a 100644 --- a/toolkit/components/glean/xpcom/nsIFOG.idl +++ b/toolkit/components/glean/xpcom/nsIFOG.idl @@ -180,11 +180,14 @@ interface nsIFOG : nsISupports * @param aName - The ping's name. * @param aIncludeClientId - Whether the ping should include the client_id. * @param aSendIfEmpty - Whether the ping should send even if empty. + * @param aIncludeInfoSections - Whether the ping should include + * {client|ping}_info sections. * @param aReasonCodes - The list of valid reasons for ping submission. */ uint32_t testRegisterRuntimePing(in ACString aName, in boolean aIncludeClientId, in boolean aSendIfEmpty, in boolean aPreciseTimestamps, + in boolean aIncludeInfoSections, in Array<ACString> aReasonCodes); }; |