Bug 1881021 - Implement the new object metric type (JS only) r=chutten,webidl,saschanaz,smaug

For now this only provides a JavaScript API, as it's easier to generate
and to use (we just use JavaScript objects, validation is done by (de)serialization on the Rust side).

No JOG support. Parent-process only right now.

Differential Revision: https://phabricator.services.mozilla.com/D202224
This commit is contained in:
Jan-Erik Rediger 2024-03-06 14:00:01 +00:00
parent a7f4e39ff2
commit 84afc3d817
26 changed files with 855 additions and 93 deletions

1
Cargo.lock generated
View File

@ -1706,6 +1706,7 @@ dependencies = [
"nsstring",
"once_cell",
"serde",
"serde_json",
"tempfile",
"thin-vec",
"uuid",

View File

@ -1527,6 +1527,10 @@ DOMInterfaces = {
'nativeType': 'mozilla::glean::GleanText',
'headerFile': 'mozilla/glean/bindings/Text.h',
},
'GleanObject': {
'nativeType': 'mozilla::glean::GleanObject',
'headerFile': 'mozilla/glean/bindings/Object.h',
},
'Window': {
'nativeType': 'nsGlobalWindowInner',

View File

@ -643,3 +643,35 @@ interface GleanText : GleanMetric {
[Throws, ChromeOnly]
UTF8String? testGetValue(optional UTF8String aPingName = "");
};
[Func="nsGlobalWindowInner::IsGleanNeeded", Exposed=Window]
interface GleanObject : GleanMetric {
/**
* Set to the specified object.
*
* The structure of the metric is validated against the predefined structure.
*
* @param object The object to set the metric to.
*/
undefined set(object value);
/**
* **Test-only API**
*
* Gets the currently stored value as an object.
*
* 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 undefined if there is no value.
*/
[Throws, ChromeOnly]
object? testGetValue(optional UTF8String aPingName = "");
};

View File

@ -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"

View File

@ -16,6 +16,7 @@ mod event;
mod labeled;
mod memory_distribution;
mod numerator;
mod object;
mod ping;
mod quantity;
mod rate;

View File

@ -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()
}

View File

@ -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;

View File

@ -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!")
}
}
}
}

View File

@ -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"

View File

@ -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

View File

@ -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 */

View File

@ -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,

View File

@ -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 %}
}

View File

@ -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())))

View File

@ -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

View File

@ -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",

View File

@ -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: {

View File

@ -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: {

View File

@ -188,6 +188,15 @@
]
],
"test.nested": [
[
"object",
"an_object",
[
"metrics"
],
"ping",
false
],
[
"datetime",
"datetime_metric",

View File

@ -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

View File

@ -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),
}

View File

@ -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);
}

View File

@ -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,
1152921513196781587ull,
8070450609557340585ull,
2305843030688530526ull,
5188146822270419236ull,
8646911366155731401ull,
1729382269795172390ull,
10952754396844261987ull,
4035225309073637616ull,
10376293640245871164ull,
9223372127049089548ull,
5764607578868810038ull,
8070450605262373266ull,
2882303791581888664ull,
1152921513196781587ull,
1729382269795172390ull,
3458764552475246801ull,
10952754396844261968ull,
4611686065672028430ull,
3458764552475246801ull
2305843026393563204ull,
1152921594801160700ull,
5188146822270419236ull,
3458764548180279480ull,
8646911361860764070ull,
8646911366155731389ull,
8070450605262373260ull,
9799832883647480352ull,
11529215153442652791ull,
2305843030688530526ull,
6917529092065591642ull,
9223372122754122205ull,
10376293640245871162ull,
4035225309073637616ull,
2882303787286921342ull,
576460756598390784ull,
7493989848663982456ull,
2882303791581888664ull,
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,
};

View File

@ -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

View File

@ -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:

View File

@ -459,3 +459,100 @@ add_task(async function test_fog_text_works_unusual_character() {
Assert.greater(rslt.length, 100);
});
add_task(async function test_fog_object_works() {
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."
);
});