// 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). pubuse glean::{
traits, CommonMetricData, DistributionData, ErrorType, LabeledMetricData, Lifetime, MemoryUnit,
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;
// 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 { usesuper::max_string_byte_length; pub(crate) usesuper::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)] pubconst 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; iflet 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;
}
// 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()
}
}
// 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>),
}
// 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))] #[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] constfn 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 { usecrate::private::truncate_string_for_marker; usecrate::private::truncate_string_for_marker_to_length;
// 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
);
}
}
// 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) };
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.