Quelle mod.rs
Sprache: unbekannt
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! The different metric types supported by the Glean SDK to handle data.
// Re-export of `glean` types we can re-use.
// That way a user only needs to depend on this crate, not on glean (and there can't be a
// version mismatch).
pub use glean::{
traits, CommonMetricData, DistributionData, ErrorType, LabeledMetricData, Lifetime, Me moryUnit,
RecordedEvent, TimeUnit, TimerId,
};
mod boolean;
mod counter;
mod custom_distribution;
mod datetime;
mod denominator;
mod event;
mod labeled;
mod labeled_boolean;
mod labeled_counter;
mod labeled_custom_distribution;
mod labeled_memory_distribution;
mod labeled_timing_distribution;
mod memory_distribution;
mod metric_getter;
mod numerator;
mod object;
mod ping;
mod quantity;
mod rate;
pub(crate) mod string;
mod string_list;
mod text;
mod timespan;
mod timing_distribution;
mod url;
mod uuid;
pub use self::boolean::BooleanMetric;
pub use self::counter::CounterMetric;
pub use self::custom_distribution::{CustomDistributionMetric, LocalCustomDistribution};
pub use self::datetime::DatetimeMetric;
pub use self::denominator::DenominatorMetric;
pub use self::event::{EventMetric, EventRecordingError, ExtraKeys, NoExtraKeys};
pub use self::labeled::LabeledMetric;
pub use self::labeled_boolean::LabeledBooleanMetric;
pub use self::labeled_counter::LabeledCounterMetric;
pub use self::labeled_custom_distribution::LabeledCustomDistributionMetric;
pub use self::labeled_memory_distribution::LabeledMemoryDistributionMetric;
pub use self::labeled_timing_distribution::LabeledTimingDistributionMetric;
pub use self::memory_distribution::{LocalMemoryDistribution, MemoryDistributionMetric};
pub use self::metric_getter::{MetricGetter, MetricId, SubMetricId};
pub use self::numerator::NumeratorMetric;
pub use self::object::ObjectMetric;
pub use self::ping::Ping;
pub use self::quantity::QuantityMetric as LabeledQuantityMetric;
pub use self::quantity::QuantityMetric;
pub use self::rate::RateMetric;
pub use self::string::StringMetric;
pub use self::string::StringMetric as LabeledStringMetric;
pub use self::string_list::StringListMetric;
pub use self::text::TextMetric;
pub use self::timespan::TimespanMetric;
pub use self::timing_distribution::TimingDistributionMetric;
pub use self::url::UrlMetric;
pub use self::uuid::UuidMetric;
// We only access the methods here when we're building with Gecko, as that's
// when we have access to the profiler. We don't need alternative (i.e.
// non-gecko) implementations, as any imports from this sub-module are also
// gated with the same #[cfg(feature...)]
#[cfg(feature = "with_gecko")]
pub(crate) mod profiler_utils {
use super::max_string_byte_length;
pub(crate) use super::truncate_string_for_marker;
// Declare the telemetry profiling category as a constant here.
// This lets us avoid re-importing gecko_profiler ... within metric files,
// which keeps the importing a bit cleaner, and reduces profiler intrusion.
#[allow(non_upper_case_globals)]
pub const TelemetryProfilerCategory: gecko_profiler::ProfilingCategoryPair =
gecko_profiler::ProfilingCategoryPair::Telemetry(None);
// Get the datetime *now*
// From https://searchfox.org/mozilla-central/source/third_party/rust/glean-core/src/util.rs#51
// This should be removed when Bug 1925313 is fixed.
/// Get the current date & time with a fixed-offset timezone.
///
/// This converts from the `Local` timezone into its fixed-offset equivalent.
/// If a timezone outside of [-24h, +24h] is detected it corrects the timezone offset to UTC (+0).
pub(crate) fn local_now_with_offset() -> chrono::DateTime<chrono::FixedOffset> {
use chrono::{DateTime, Local};
#[cfg(target_os = "windows")]
{
// `Local::now` takes the user's timezone offset
// and panics if it's not within a range of [-24, +24] hours.
// This causes crashes in a small number of clients on Windows.
//
// We can't determine the faulty clients
// or the circumstancens under which this happens,
// so the best we can do is have a workaround:
//
// We try getting the time and timezone first,
// then manually check that it is a valid timezone offset.
// If it is, we proceed and use that time and offset.
// If it isn't we fallback to UTC.
//
// This has the small downside that it will use 2 calls to get the time,
// but only on Windows.
//
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1611770.
use chrono::{FixedOffset, Utc};
// Get timespec, including the user's timezone.
let tm = time::now();
// Same as chrono:
// https://docs.rs/chrono/0.4.10/src/chrono/offset/local.rs.html#37
let offset = tm.tm_utcoff;
if let None = FixedOffset::east_opt(offset) {
log::warn!(
"Detected invalid timezone offset: {}. Using UTC fallback.",
offset
);
let now: DateTime<Utc> = Utc::now();
let utc_offset = FixedOffset::east(0);
return now.with_timezone(&utc_offset);
}
}
let now: DateTime<Local> = Local::now();
now.with_timezone(now.offset())
}
/// Try to convert a glean::Datetime into a chrono::DateTime Returns none if
/// the glean::Datetime offset is not a valid timezone We would prefer to
/// use .into or similar, but we need to wait until this is implemented in
/// the Glean SDK. See Bug 1925313 for more details.
pub(crate) fn glean_to_chrono_datetime(
gdt: &glean::Datetime,
) -> Option<chrono::LocalResult<chrono::DateTime<chrono::FixedOffset>>> {
use chrono::{FixedOffset, TimeZone};
let tz = FixedOffset::east_opt(gdt.offset_seconds);
if tz.is_none() {
return None;
}
Some(
FixedOffset::east(gdt.offset_seconds)
.ymd_opt(gdt.year, gdt.month, gdt.day)
.and_hms_nano_opt(gdt.hour, gdt.minute, gdt.second, gdt.nanosecond),
)
}
// Truncate a vector down to a maximum size.
// We want to avoid storing large vectors of values in the profiler buffer,
// so this helper method allows markers to explicitly limit the size of
// vectors of values that might originate from Glean
pub(crate) fn truncate_vector_for_marker<T>(vec: &Vec<T>) -> Vec<T>
where
T: Clone,
{
const MAX_VECTOR_LENGTH: usize = 1024;
if vec.len() > MAX_VECTOR_LENGTH {
vec[0..MAX_VECTOR_LENGTH - 1].to_vec()
} else {
vec.clone()
}
}
// Generic marker structs:
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub(crate) struct StringLikeMetricMarker {
id: super::MetricGetter,
val: String,
}
impl StringLikeMetricMarker {
pub fn new(id: super::MetricGetter, val: &String) -> StringLikeMetricMarker {
StringLikeMetricMarker {
id: id,
val: truncate_string_for_marker(val.clone()),
}
}
pub fn new_owned(id: super::MetricGetter, val: String) -> StringLikeMetricMarker {
StringLikeMetricMarker {
id: id,
val: truncate_string_for_marker(val),
}
}
}
impl gecko_profiler::ProfilerMarker for StringLikeMetricMarker {
fn marker_type_name() -> &'static str {
"StringLikeMetric"
}
fn marker_type_display() -> gecko_profiler::MarkerSchema {
use gecko_profiler::schema::*;
let mut schema = MarkerSchema::new(&[Location::MarkerChart, Location::MarkerTable]);
schema.set_tooltip_label("{marker.data.id}");
schema.set_table_label("{marker.name} - {marker.data.id}: {marker.data.value}");
schema.add_key_label_format_searchable(
"id",
"Metric",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format_searchable(
"label",
"Label",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format("val", "Value", Format::String);
schema
}
fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
let (name, label) = self.id.get_identifiers();
json_writer.unique_string_property("id", &name);
if let Some(l) = label {
json_writer.unique_string_property("label", &l);
};
debug_assert!(self.val.len() <= max_string_byte_length());
json_writer.string_property("val", self.val.as_str());
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub(crate) struct IntLikeMetricMarker<T>
where
T: Into<i64>,
{
id: super::MetricGetter,
label: Option<String>,
val: T,
}
impl<T> IntLikeMetricMarker<T>
where
T: Into<i64>,
{
pub fn new(
id: super::MetricGetter,
label: Option<String>,
val: T,
) -> IntLikeMetricMarker<T> {
IntLikeMetricMarker { id, label, val }
}
}
impl<T> gecko_profiler::ProfilerMarker for IntLikeMetricMarker<T>
where
T: serde::Serialize + serde::de::DeserializeOwned + Into<i64> + Copy,
{
fn marker_type_name() -> &'static str {
"IntLikeMetric"
}
fn marker_type_display() -> gecko_profiler::MarkerSchema {
use gecko_profiler::schema::*;
let mut schema = MarkerSchema::new(&[Location::MarkerChart, Location::MarkerTable]);
schema.set_tooltip_label("{marker.data.id} {marker.data.label} {marker.data.val}");
schema.set_table_label(
"{marker.name} - {marker.data.id} {marker.data.label}: {marker.data.val}",
);
schema.add_key_label_format_searchable(
"id",
"Metric",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format_searchable(
"label",
"Label",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format("val", "Value", Format::Integer);
schema
}
fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
let (name, label) = self.id.get_identifiers();
json_writer.unique_string_property("id", &name);
if let Some(l) = self.label.as_ref().or(label.as_ref()) {
json_writer.unique_string_property("label", &l);
};
json_writer.int_property("val", self.val.clone().into());
}
}
// This might seem like overkill for discerning between a single element and
// a vector of elements. However, from the perspective of the profiler buffer
// this is quite reasonable, as it has a lower memory overhead. Doing the maths
// (and assuming a 64-bit system, so usize = 8 bytes):
// Enum: i64 value (8-bytes), enum discernment byte = 9 bytes,
// Vector: i64 values (at least 8 bytes), usize length, usize capacity, data
// pointer = 32 bytes
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub(crate) enum DistributionValues<T> {
Sample(T),
Samples(Vec<T>),
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub(crate) struct DistributionMetricMarker<T> {
id: super::MetricGetter,
label: Option<String>,
value: DistributionValues<T>,
}
impl<T> DistributionMetricMarker<T> {
pub fn new(
id: super::MetricGetter,
label: Option<String>,
value: DistributionValues<T>,
) -> DistributionMetricMarker<T> {
DistributionMetricMarker { id, label, value }
}
}
impl<T> gecko_profiler::ProfilerMarker for DistributionMetricMarker<T>
where
T: serde::Serialize + serde::de::DeserializeOwned + Copy + std::fmt::Display,
{
fn marker_type_name() -> &'static str {
"DistMetric"
}
fn marker_type_display() -> gecko_profiler::MarkerSchema {
use gecko_profiler::schema::*;
let mut schema = MarkerSchema::new(&[Location::MarkerChart, Location::MarkerTable]);
schema.set_tooltip_label("{marker.data.id} {marker.data.label} {marker.data.sample}");
schema.set_table_label(
"{marker.name} - {marker.data.id} {marker.data.label}: {marker.data.sample}{marker.data.samples}",
);
schema.set_chart_label("{marker.data.id}");
schema.add_key_label_format_searchable(
"id",
"Metric",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format_searchable(
"label",
"Label",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format("sample", "Sample", Format::String);
schema.add_key_label_format("samples", "Samples", Format::String);
schema
}
fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
let (name, label) = self.id.get_identifiers();
json_writer.unique_string_property("id", &name);
if let Some(l) = self.label.as_ref().or(label.as_ref()) {
json_writer.unique_string_property("label", &l);
};
match &self.value {
DistributionValues::Sample(s) => {
let s = format!("{}", s);
json_writer.string_property("sample", s.as_str());
}
DistributionValues::Samples(s) => {
let s = format!(
"[{}]",
s.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(",")
);
json_writer.string_property("samples", s.as_str());
}
};
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub(crate) struct BooleanMetricMarker {
id: super::MetricGetter,
label: Option<String>,
val: bool,
}
impl BooleanMetricMarker {
pub fn new(
id: super::MetricGetter,
label: Option<String>,
val: bool,
) -> BooleanMetricMarker {
BooleanMetricMarker { id, label, val }
}
}
impl gecko_profiler::ProfilerMarker for BooleanMetricMarker {
fn marker_type_name() -> &'static str {
"BooleanMetric"
}
fn marker_type_display() -> gecko_profiler::MarkerSchema {
use gecko_profiler::schema::*;
let mut schema = MarkerSchema::new(&[Location::MarkerChart, Location::MarkerTable]);
schema.set_tooltip_label("{marker.data.id} {marker.data.val}");
schema.set_table_label("{marker.name} - {marker.data.id}: {marker.data.val}");
schema.add_key_label_format_searchable(
"id",
"Metric",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format_searchable(
"label",
"Label",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format("val", "Value", Format::String);
schema
}
fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
let (name, label) = self.id.get_identifiers();
json_writer.unique_string_property("id", &name);
if let Some(l) = self.label.as_ref().or(label.as_ref()) {
json_writer.unique_string_property("label", &l);
};
json_writer.bool_property("val", self.val);
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub(crate) struct PingMarker {
name: String,
reason: Option<String>,
}
impl PingMarker {
pub(crate) fn new_for_submit(name: String, reason: Option<String>) -> Self {
PingMarker { name, reason }
}
}
impl gecko_profiler::ProfilerMarker for PingMarker {
fn marker_type_name() -> &'static str {
"Ping"
}
fn marker_type_display() -> gecko_profiler::MarkerSchema {
use gecko_profiler::schema::*;
let mut schema = MarkerSchema::new(&[Location::MarkerChart, Location::MarkerTable]);
schema.set_tooltip_label("{marker.data.id} {marker.data.reason}");
schema.set_table_label("{marker.name} - {marker.data.id} {marker.data.reason}");
schema.add_key_label_format_searchable(
"id",
"Ping",
Format::UniqueString,
Searchable::Searchable,
);
schema.add_key_label_format_searchable(
"reason",
"Submission reason",
Format::String,
Searchable::Searchable,
);
schema
}
fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
json_writer.unique_string_property("id", self.name.as_str());
if let Some(reason) = &self.reason {
json_writer.string_property("reason", reason);
};
}
}
}
// These two methods, and the constant function, "live" within profiler_utils,
// but as we need them available for testing, when we might not have gecko
// available, we use a different set of cfg features to enable them in both
// cases. Note that we re-export the main truncation method within
// `profiler_utils` to correct the namespace.
#[cfg(any(feature = "with_gecko", test))]
pub(crate) fn truncate_string_for_marker(input: String) -> String {
truncate_string_for_marker_to_length(input, max_string_byte_length())
}
#[cfg(any(feature = "with_gecko", test))]
const fn max_string_byte_length() -> usize {
1024
}
#[cfg(any(feature = "with_gecko", test))]
#[inline]
fn truncate_string_for_marker_to_length(mut input: String, byte_length: usize) -> String {
// Truncating an arbitrary string in Rust is not not exactly easy, as
// Strings are UTF-8 encoded. The "built-in" String::truncate, however,
// operates on bytes, and panics if the truncation crosses a character
// boundary.
// To avoid this, we need to find the first unicode char boundary that
// is less than the size that we're looking for. Note that we're
// interested in how many *bytes* the string takes up (when we add it
// to a marker), so we truncate to `MAX_STRING_BYTE_LENGTH` bytes, or
// (by walking the truncation point back) to a number of bytes that
// still represents valid UTF-8.
// Note, this truncation may not provide a valid json result, and
// truncation acts on glyphs, not graphemes, so the resulting text
// may not render exactly the same as before it was truncated.
// Copied from src/core/num/mod.rs
// Check if a given byte is a utf8 character boundary
#[inline]
const fn is_utf8_char_boundary(b: u8) -> bool {
// This is bit magic equivalent to: b < 128 || b >= 192
(b as i8) >= -0x40
}
// Check if our truncation point is a char boundary. If it isn't, move
// it "back" along the string until it is.
// Note, this is an almost direct port of the rust standard library
// function `str::floor_char_boundary`. We re-produce it as this API is
// not yet stable, and we make some small changes (such as modifying
// the input in-place) that are more convenient for this method.
if byte_length < input.len() {
let lower_bound = byte_length.saturating_sub(3);
let new_byte_length = input.as_bytes()[lower_bound..=byte_length]
.iter()
.rposition(|b| is_utf8_char_boundary(*b));
// SAFETY: we know that the character boundary will be within four bytes
let truncation_point = unsafe { lower_bound + new_byte_length.unwrap_unchecked() };
input.truncate(truncation_point)
}
input
}
#[cfg(test)]
mod truncation_tests {
use crate::private::truncate_string_for_marker;
use crate::private::truncate_string_for_marker_to_length;
// Testing is heavily inspired/copied from the existing tests for the
// standard library function `floor_char_boundary`.
// See: https://github.com/rust-lang/rust/blob/bca5fdebe0e539d123f33df5f2149d5976392e76/library/alloc/tests/str.rs#L2363
// Check a series of truncation points (i.e. string lengths), and assert
// that they all produce the same trunctated string from the input.
fn check_many(s: &str, arg: impl IntoIterator<Item = usize>, truncated: &str) {
for len in arg {
assert_eq!(
truncate_string_for_marker_to_length(s.to_string(), len),
truncated,
"truncate_string_for_marker_to_length({:?}, {:?}) != {:?}",
len,
s,
truncated
);
}
}
#[test]
fn truncate_1byte_chars() {
check_many("jp", [0], "");
check_many("jp", [1], "j");
check_many("jp", 2..4, "jp");
}
#[test]
fn truncate_2byte_chars() {
check_many("ĵƥ", 0..2, "");
check_many("ĵƥ", 2..4, "ĵ");
check_many("ĵƥ", 4..6, "ĵƥ");
}
#[test]
fn truncate_3byte_chars() {
check_many("日本", 0..3, "");
check_many("日本", 3..6, "日");
check_many("日本", 6..8, "日本");
}
#[test]
fn truncate_4byte_chars() {
check_many("", 0..4, "");
check_many("", 4..8, "");
check_many("", 8..10, "");
}
// Check a single string against it's expected truncated outcome
fn check_one(s: String, truncated: String) {
assert_eq!(
truncate_string_for_marker(s.clone()),
truncated,
"truncate_string_for_marker({:?}) != {:?}",
s,
truncated
);
}
#[test]
fn full_truncation() {
// Keep the values in this up to date with MAX_STRING_BYTE_LENGTH
// For each of these tests, we use a padding value to get near to 1024
// bytes, then add on a variety of further characters that push us up
// to or over the limit. We then check that we correctly truncated to
// the correct character or grapheme.
let pad = |reps: usize| -> String { "-".repeat(reps) };
// Note: len(jpjpj) = 5
check_one(pad(1020) + "jpjpj", pad(1020) + "jpjp");
// Note: len(ĵƥ) = 4
check_one(pad(1020) + "ĵƥ", pad(1020) + "ĵƥ");
check_one(pad(1021) + "ĵƥ", pad(1021) + "ĵ");
// Note: len(日本) = 6
check_one(pad(1018) + "日本", pad(1018) + "日本");
check_one(pad(1020) + "日本", pad(1020) + "日");
check_one(pad(1022) + "日本", pad(1022));
// Note: len() = 8, len() = 4
check_one(pad(1016) + "", pad(1016) + "");
check_one(pad(1017) + "", pad(1017) + "");
check_one(pad(1021) + "", pad(1021) + "");
}
}
[ Dauer der Verarbeitung: 0.50 Sekunden
(vorverarbeitet)
]
|
2026-04-02
|