зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1623300 - Replace fogotype with the actual FOG crate now. r=chutten
This also upgrades the vendored Glean version, which thanks to our upstream work doesn't change anything else (except one small Mozilla-developed dependency) It's still feature-gated to nightly. In C++ there's now a `MOZ_GLEAN` define. For Rust it's behind the `glean` feature (enabled on nightly only). The `fog` crate is empty, so no Glean is actually instantiated. Differential Revision: https://phabricator.services.mozilla.com/D68539 --HG-- rename : third_party/rust/glean-preview/src/metrics/mod.rs => toolkit/components/glean/src/lib.rs extra : moz-landing-system : lando
This commit is contained in:
Родитель
afdcacc413
Коммит
311c2d9ea7
|
@ -1240,12 +1240,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ffi-support"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efee06d8ac3e85a6e9759a0ed2682235a70832ebe10953849b92cdced8688660"
|
||||
checksum = "087be066eb6e85d7150f0c5400018a32802f99d688b2d3868c526f7bbfe17960"
|
||||
dependencies = [
|
||||
"failure",
|
||||
"failure_derive",
|
||||
"lazy_static",
|
||||
"log",
|
||||
]
|
||||
|
@ -1366,8 +1364,7 @@ checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
|||
name = "fog"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cstr",
|
||||
"glean-preview",
|
||||
"glean-core",
|
||||
"log",
|
||||
"nserror",
|
||||
"nsstring",
|
||||
|
@ -1740,9 +1737,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glean-core"
|
||||
version = "24.0.0"
|
||||
version = "25.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40afd759281422c5a9d17c5a2cfd6b703fbc6fea0ce1d82d09c4c53a4d98f4cf"
|
||||
checksum = "a893aa65acba615064c53e2ab091a54227a0253852b8e4a2c813c8338f6095a7"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
|
@ -1757,17 +1754,6 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glean-preview"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "182218f42395f369a1a5372d334699458a606aa557dd2e23998ba25704964e2c"
|
||||
dependencies = [
|
||||
"glean-core",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"f522bcd6e15aa3817fbc327ac33ae663ba494f1d32b9d91a5a35b773f0a0edbb","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"63e747d86bdeb67638f26b4b75107f129c5f12de432ae83ccdb1ccbe28debf30","README.md":"67780fbfcaf2cd01e3b7f5c7d1ef8b9e385b7cd4435358954aec24a85755ced2","src/error.rs":"bee4653bcdfac1c903c41c6ae647fbeeb8ce45818886c69cead324156e77a9c5","src/ffistr.rs":"44460d6b0879a76274af8508b9cdbab5b8170f646b05a9415a2e5e51bd2f040b","src/handle_map.rs":"a2f25411c953d07daba18a8a39e5731b01d7b07c78414824b3b66ed13f8f3c2f","src/into_ffi.rs":"05c4a1c9f3aebb4570ac6578f946ba9d9fc90c54abb76f30704868b277df2f9d","src/lib.rs":"6c111cdd9fa2251a9013c19c89930e46bc7357d3ea2f76040cdeb6223d9583e7","src/macros.rs":"1f05d94853bbf5cfb1ece0333dd36e6b8e352ecdcaafc1c6f491934d05e4b140","src/string.rs":"966d2b41fae4e7a6083eb142a57e669e4bafd833f01c8b24fc67dff4fb4a5595"},"package":"efee06d8ac3e85a6e9759a0ed2682235a70832ebe10953849b92cdced8688660"}
|
||||
{"files":{"Cargo.toml":"7a7e6f298886b5427ccc30719c24816a12c5ec5344ec7d8e610e4d9fdc7d65d4","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"63e747d86bdeb67638f26b4b75107f129c5f12de432ae83ccdb1ccbe28debf30","README.md":"67780fbfcaf2cd01e3b7f5c7d1ef8b9e385b7cd4435358954aec24a85755ced2","src/error.rs":"e4c87fe305f7fbb830348c0f5b181385c96aa9561dfa1536ef1ce09065e6ce83","src/ffistr.rs":"12a4f351c248e150da18b6ea3797eca65f63e8fa24c62828a2510b9c3a4b8ca5","src/handle_map.rs":"d5f22ad76260f8c1c9cc019c4fec579281b75ae670742b1c4f2470ae56cce87c","src/into_ffi.rs":"bde79bf6b2bbc3108654bf14ac4216c4c5f2a91962de6f814d0bac1bde243c1c","src/lib.rs":"3aa48de38137e2c8784c65b7d981af01b668e1543fc1cf35d52535828bace13f","src/macros.rs":"479153198e1676fdca0be53cbd437a05cd807a2520bacfdb8849a742188f1359","src/string.rs":"966d2b41fae4e7a6083eb142a57e669e4bafd833f01c8b24fc67dff4fb4a5595"},"package":"087be066eb6e85d7150f0c5400018a32802f99d688b2d3868c526f7bbfe17960"}
|
|
@ -13,7 +13,7 @@
|
|||
[package]
|
||||
edition = "2018"
|
||||
name = "ffi-support"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"]
|
||||
description = "A crate to help expose Rust functions over the FFI."
|
||||
readme = "README.md"
|
||||
|
@ -22,28 +22,22 @@ categories = ["development-tools::ffi"]
|
|||
license = "Apache-2.0 / MIT"
|
||||
repository = "https://github.com/mozilla/application-services"
|
||||
[dependencies.backtrace]
|
||||
version = "0.3.9"
|
||||
version = "0.3.38"
|
||||
optional = true
|
||||
|
||||
[dependencies.failure]
|
||||
version = "0.1.5"
|
||||
|
||||
[dependencies.failure_derive]
|
||||
version = "0.1.5"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
[dev-dependencies.env_logger]
|
||||
version = "0.6.2"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.7.0"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.7.2"
|
||||
|
||||
[dev-dependencies.rayon]
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -258,7 +258,7 @@ impl From<Box<dyn std::any::Any + Send + 'static>> for ExternError {
|
|||
fn from(e: Box<dyn std::any::Any + Send + 'static>) -> Self {
|
||||
// The documentation suggests that it will *usually* be a str or String.
|
||||
let message = if let Some(s) = e.downcast_ref::<&'static str>() {
|
||||
s.to_string()
|
||||
(*s).to_string()
|
||||
} else if let Some(s) = e.downcast_ref::<String>() {
|
||||
s.clone()
|
||||
} else {
|
||||
|
|
|
@ -72,6 +72,10 @@ impl<'a> FfiStr<'a> {
|
|||
///
|
||||
/// This should not be needed most of the time, and users should instead
|
||||
/// accept `FfiStr` in function parameter lists.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Dereferences a pointer and is thus unsafe.
|
||||
#[inline]
|
||||
pub unsafe fn from_raw(ptr: *const c_char) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -64,7 +64,8 @@
|
|||
|
||||
use crate::error::{ErrorCode, ExternError};
|
||||
use crate::into_ffi::IntoFfi;
|
||||
use failure_derive::Fail;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::ops;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
|
@ -170,33 +171,45 @@ pub const MAX_CAPACITY: usize = (1 << 15) - 1;
|
|||
const MIN_CAPACITY: usize = 4;
|
||||
|
||||
/// An error representing the ways a `Handle` may be invalid.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Fail)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum HandleError {
|
||||
/// Identical to invalid handle, but has a slightly more helpful
|
||||
/// message for the most common case 0.
|
||||
#[fail(display = "Tried to use a null handle (this object has probably been closed)")]
|
||||
NullHandle,
|
||||
|
||||
/// Returned from [`Handle::from_u64`] if [`Handle::is_valid`] fails.
|
||||
#[fail(display = "u64 could not encode a valid Handle")]
|
||||
InvalidHandle,
|
||||
|
||||
/// Returned from get/get_mut/delete if the handle is stale (this indicates
|
||||
/// something equivalent to a use-after-free / double-free, etc).
|
||||
#[fail(display = "Handle has stale version number")]
|
||||
StaleVersion,
|
||||
|
||||
/// Returned if the handle index references an index past the end of the
|
||||
/// HandleMap.
|
||||
#[fail(display = "Handle references a index past the end of this HandleMap")]
|
||||
IndexPastEnd,
|
||||
|
||||
/// The handle has a map_id for a different map than the one it was
|
||||
/// attempted to be used with.
|
||||
#[fail(display = "Handle is from a different map")]
|
||||
WrongMap,
|
||||
}
|
||||
|
||||
impl StdError for HandleError {}
|
||||
|
||||
impl fmt::Display for HandleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use HandleError::*;
|
||||
match self {
|
||||
NullHandle => {
|
||||
f.write_str("Tried to use a null handle (this object has probably been closed)")
|
||||
}
|
||||
InvalidHandle => f.write_str("u64 could not encode a valid Handle"),
|
||||
StaleVersion => f.write_str("Handle has stale version number"),
|
||||
IndexPastEnd => f.write_str("Handle references a index past the end of this HandleMap"),
|
||||
WrongMap => f.write_str("Handle is from a different map"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HandleError> for ExternError {
|
||||
fn from(e: HandleError) -> Self {
|
||||
ExternError::new_error(ErrorCode::INVALID_HANDLE, e.to_string())
|
||||
|
@ -1303,5 +1316,4 @@ mod test {
|
|||
inner.assert_valid();
|
||||
assert_eq!(inner.len(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -143,16 +143,17 @@ use std::ptr;
|
|||
/// // Note: this type manages memory, so you still will want to expose a destructor for this,
|
||||
/// // and possibly implement Drop as well.
|
||||
/// ```
|
||||
pub unsafe trait IntoFfi {
|
||||
pub unsafe trait IntoFfi: Sized {
|
||||
/// This type must be:
|
||||
///
|
||||
/// 1. Compatible with C, which is to say `#[repr(C)]`, a numeric primitive, another type that
|
||||
/// has guarantees made about it's layout, or a `#[repr(transparent)]` wrapper around one of
|
||||
/// those.
|
||||
/// 1. Compatible with C, which is to say `#[repr(C)]`, a numeric primitive,
|
||||
/// another type that has guarantees made about it's layout, or a
|
||||
/// `#[repr(transparent)]` wrapper around one of those.
|
||||
///
|
||||
/// One could even use `&T`, so long as `T: Sized`, although it's extremely dubious to return
|
||||
/// a reference to borrowed memory over the FFI, since it's very difficult for the caller
|
||||
/// to know how long it remains valid.
|
||||
/// One could even use `&T`, so long as `T: Sized`, although it's
|
||||
/// extremely dubious to return a reference to borrowed memory over the
|
||||
/// FFI, since it's very difficult for the caller to know how long it
|
||||
/// remains valid.
|
||||
///
|
||||
/// 2. Capable of storing an empty/ignorable/default value.
|
||||
///
|
||||
|
@ -164,19 +165,23 @@ pub unsafe trait IntoFfi {
|
|||
///
|
||||
/// - #[repr(C)] structs containing only things on this list.
|
||||
///
|
||||
/// - Raw pointers: `*const T`, and `*mut T`
|
||||
/// - `Option<Box<T>>`, but only if `T` is `Sized`. (Internally this is
|
||||
/// guaranteed to be represented equivalently to a pointer)
|
||||
///
|
||||
/// - Enums with a fixed `repr`, although it's a good idea avoid `#[repr(C)]` enums in favor of
|
||||
/// `#[repr(i32)]` (for example, any fixed type there should be fine), as it's potentially
|
||||
/// error prone to access `#[repr(C)]` enums from Android over JNA (it's only safe if C's
|
||||
/// - Raw pointers such as `*const T`, and `*mut T`, but again, only if `T`
|
||||
/// is `Sized` (`*const [T]`, `*mut dyn SomeTrait` etc are not valid).
|
||||
///
|
||||
/// - Enums with a fixed `repr`, although it's a good idea avoid
|
||||
/// `#[repr(C)]` enums in favor of, say, `#[repr(i32)]` (for example, any
|
||||
/// fixed type there should be fine), as it's potentially error prone to
|
||||
/// access `#[repr(C)]` enums from Android over JNA (it's only safe if C's
|
||||
/// `sizeof(int) == 4`, which is very common, but not universally true).
|
||||
///
|
||||
/// - `&T`/`&mut T` where `T: Sized` but only if you really know what you're doing, because this is
|
||||
/// probably a mistake.
|
||||
/// - `&T`/`&mut T` where `T: Sized` but only if you really know what you're
|
||||
/// doing, because this is probably a mistake.
|
||||
///
|
||||
/// Invalid examples include things like `&str`, `&[T]`, `String`, `Vec<T>`, `Box<T>`,
|
||||
/// `std::ffi::CString`, `&std::ffi::CStr`, etc. (Note that eventually, `Box<T>` may be valid
|
||||
/// `where T: Sized`, but currently it is not).
|
||||
/// Invalid examples include things like `&str`, `&[T]`, `String`, `Vec<T>`,
|
||||
/// `std::ffi::CString`, `&std::ffi::CStr`, etc.
|
||||
type Value;
|
||||
|
||||
/// Return an 'empty' value. This is what's passed back to C in the case of an error,
|
||||
|
|
|
@ -304,12 +304,11 @@ pub mod abort_on_panic {
|
|||
|
||||
#[cfg(feature = "log_panics")]
|
||||
fn init_panic_handling_once() {
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
static INIT_BACKTRACES: Once = ONCE_INIT;
|
||||
use std::sync::Once;
|
||||
static INIT_BACKTRACES: Once = Once::new();
|
||||
INIT_BACKTRACES.call_once(move || {
|
||||
#[cfg(all(feature = "log_backtraces", not(target_os = "android")))]
|
||||
{
|
||||
// Turn on backtraces for failure, if it's still listening.
|
||||
std::env::set_var("RUST_BACKTRACE", "1");
|
||||
}
|
||||
// Turn on a panic hook which logs both backtraces and the panic
|
||||
|
@ -325,10 +324,6 @@ fn init_panic_handling_once() {
|
|||
("<unknown>", 0)
|
||||
};
|
||||
log::error!("### Rust `panic!` hit at file '{}', line {}", file, line);
|
||||
// We could use failure for failure::Backtrace (and we enable RUST_BACKTRACE
|
||||
// to opt-in to backtraces on failure errors if possible), however:
|
||||
// - `failure` only checks the RUST_BACKTRACE variable once, and we could have errors
|
||||
// before this. So we just use the backtrace crate directly.
|
||||
#[cfg(all(feature = "log_backtraces", not(target_os = "android")))]
|
||||
{
|
||||
log::error!(" Complete stack trace:\n{:?}", backtrace::Backtrace::new());
|
||||
|
|
|
@ -193,7 +193,15 @@ macro_rules! implement_into_ffi_by_delegation {
|
|||
#[macro_export]
|
||||
macro_rules! define_string_destructor {
|
||||
($mylib_destroy_string:ident) => {
|
||||
#[doc = "Public destructor for strings managed by the other side of the FFI."]
|
||||
/// Public destructor for strings managed by the other side of the FFI.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This will free the string pointer it gets passed in as an argument,
|
||||
/// and thus can be wildly unsafe if misused.
|
||||
///
|
||||
/// See the documentation of `ffi_support::destroy_c_string` and
|
||||
/// `ffi_support::define_string_destructor!` for further info.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn $mylib_destroy_string(s: *mut std::os::raw::c_char) {
|
||||
// Note: This should never happen, but in the case of a bug aborting
|
||||
|
@ -208,17 +216,22 @@ macro_rules! define_string_destructor {
|
|||
};
|
||||
}
|
||||
|
||||
/// Define a (public) destructor for a type that was allocated by `Box::into_raw(Box::new(value))`
|
||||
/// (e.g. a pointer which is probably opaque).
|
||||
/// Define a (public) destructor for a type that was allocated by
|
||||
/// `Box::into_raw(Box::new(value))` (e.g. a pointer which is probably opaque).
|
||||
///
|
||||
/// ## Caveats
|
||||
///
|
||||
/// This can go wrong in a ridiculous number of ways, and we can't really prevent any of them. But
|
||||
/// essentially, the caller (on the other side of the FFI) needs to be extremely careful to ensure
|
||||
/// that it stops using the pointer after it's freed.
|
||||
/// When called over the FFI, this can go wrong in a ridiculous number of ways,
|
||||
/// and we can't really prevent any of them. But essentially, the caller (on the
|
||||
/// other side of the FFI) needs to be extremely careful to ensure that it stops
|
||||
/// using the pointer after it's freed.
|
||||
///
|
||||
/// Also, to avoid name collisions, it is strongly recommended that you provide an name for this
|
||||
/// function unique to your library. (This is true for all functions you expose).
|
||||
/// Also, to avoid name collisions, it is strongly recommended that you provide
|
||||
/// an name for this function unique to your library. (This is true for all
|
||||
/// functions you expose).
|
||||
///
|
||||
/// However, when called from rust, this is safe, as it becomes a function that
|
||||
/// just drops a `Option<Box<T>>` with some panic handling.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
@ -231,6 +244,10 @@ macro_rules! define_string_destructor {
|
|||
#[macro_export]
|
||||
macro_rules! define_box_destructor {
|
||||
($T:ty, $destructor_name:ident) => {
|
||||
/// # Safety
|
||||
/// This is equivalent to calling Box::from_raw with panic handling, and
|
||||
/// thus inherits [`Box::from_raw`]'s safety properties. That is to say,
|
||||
/// this function is wildly unsafe.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn $destructor_name(v: *mut $T) {
|
||||
// We should consider passing an error parameter in here rather than
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -2,666 +2,633 @@
|
|||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.2"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "c2-chacha"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
|
||||
dependencies = [
|
||||
"ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ppv-lite86",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
|
||||
dependencies = [
|
||||
"num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47c5e5ac752e18207b12e16b10631ae5f7f68f8805f335f9b817ead83d9ffce1"
|
||||
dependencies = [
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
dependencies = [
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
|
||||
dependencies = [
|
||||
"backtrace 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffi-support"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "087be066eb6e85d7150f0c5400018a32802f99d688b2d3868c526f7bbfe17960"
|
||||
dependencies = [
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glean-core"
|
||||
version = "24.0.0"
|
||||
version = "25.1.0"
|
||||
dependencies = [
|
||||
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi-support 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iso8601 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rkv 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"ffi-support",
|
||||
"iso8601",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"rkv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"uuid 0.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
|
||||
dependencies = [
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
|
||||
dependencies = [
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iso8601"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f"
|
||||
dependencies = [
|
||||
"nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.66"
|
||||
version = "0.2.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
||||
|
||||
[[package]]
|
||||
name = "lmdb-rkv"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lmdb-rkv-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"lmdb-rkv-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lmdb-rkv-sys"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8"
|
||||
dependencies = [
|
||||
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.0"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
||||
dependencies = [
|
||||
"memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518"
|
||||
dependencies = [
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.7"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
||||
dependencies = [
|
||||
"c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"c2-chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.13"
|
||||
version = "0.6.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkv"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aab7c645d32e977e186448b0a5c2c3139a91a7f630cfd8a8c314d1d145e78bf"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayref",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"failure",
|
||||
"lazy_static",
|
||||
"lmdb-rkv",
|
||||
"ordered-float",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"url",
|
||||
"uuid 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.44"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
|
||||
dependencies = [
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.13"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
dependencies = [
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||
dependencies = [
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
|
||||
dependencies = [
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||
dependencies = [
|
||||
"idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
|
||||
dependencies = [
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
"checksum backtrace 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)" = "a4ed64ae6d9ebfd9893193c4b2532b1292ec97bd8271c9d7d0fa90cd78a34cba"
|
||||
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
"checksum bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
|
||||
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
|
||||
"checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc"
|
||||
"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
|
||||
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
|
||||
"checksum ffi-support 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "efee06d8ac3e85a6e9759a0ed2682235a70832ebe10953849b92cdced8688660"
|
||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
|
||||
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
|
||||
"checksum iso8601 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum lmdb-rkv 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "605061e5465304475be2041f19967a900175ea1b6d8f47fbab84a84fb8c48452"
|
||||
"checksum lmdb-rkv-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7982ba0460e939e26a52ee12c8075deab0ebd44ed21881f656841b70e021b7c8"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
|
||||
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
||||
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
|
||||
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||
"checksum once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed"
|
||||
"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518"
|
||||
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
"checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc"
|
||||
"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
||||
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum regex 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
|
||||
"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum rkv 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9aab7c645d32e977e186448b0a5c2c3139a91a7f630cfd8a8c314d1d145e78bf"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
|
||||
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
|
||||
"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4"
|
||||
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
|
||||
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||
"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||
"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
||||
"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
|
||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
[package]
|
||||
edition = "2018"
|
||||
name = "glean-core"
|
||||
version = "24.0.0"
|
||||
version = "25.1.0"
|
||||
authors = ["Jan-Erik Rediger <jrediger@mozilla.com>", "The Glean Team <glean-team@mozilla.com>"]
|
||||
include = ["README.md", "LICENSE", "src/**/*", "examples/**/*", "tests/**/*", "Cargo.toml"]
|
||||
description = "A modern Telemetry library"
|
||||
|
@ -29,7 +29,7 @@ version = "0.4.10"
|
|||
features = ["serde"]
|
||||
|
||||
[dependencies.ffi-support]
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.4.0"
|
||||
|
@ -71,6 +71,9 @@ version = "0.3"
|
|||
|
||||
[dev-dependencies.tempfile]
|
||||
version = "3.1.0"
|
||||
|
||||
[features]
|
||||
upload = []
|
||||
[badges.circle-ci]
|
||||
branch = "master"
|
||||
repository = "mozilla/glean"
|
||||
|
|
|
@ -32,7 +32,7 @@ let cfg = Configuration {
|
|||
max_events: None,
|
||||
};
|
||||
let mut glean = Glean::new(cfg).unwrap();
|
||||
let ping = PingType::new("sample", true);
|
||||
let ping = PingType::new("sample", true, true, vec![]);
|
||||
glean.register_ping_type(&ping);
|
||||
|
||||
let call_counter: CounterMetric = CounterMetric::new(CommonMetricData {
|
||||
|
@ -44,7 +44,7 @@ let call_counter: CounterMetric = CounterMetric::new(CommonMetricData {
|
|||
|
||||
call_counter.add(&glean, 1);
|
||||
|
||||
glean.submit_ping(&ping).unwrap();
|
||||
glean.submit_ping(&ping, None).unwrap();
|
||||
```
|
||||
|
||||
## License
|
||||
|
|
|
@ -25,8 +25,8 @@ fn main() {
|
|||
delay_ping_lifetime_io: false,
|
||||
};
|
||||
let mut glean = Glean::new(cfg).unwrap();
|
||||
glean.register_ping_type(&PingType::new("baseline", true, false));
|
||||
glean.register_ping_type(&PingType::new("metrics", true, false));
|
||||
glean.register_ping_type(&PingType::new("baseline", true, false, vec![]));
|
||||
glean.register_ping_type(&PingType::new("metrics", true, false, vec![]));
|
||||
|
||||
let local_metric: StringMetric = StringMetric::new(CommonMetricData {
|
||||
name: "local_metric".into(),
|
||||
|
@ -67,10 +67,10 @@ fn main() {
|
|||
|
||||
let ping_maker = PingMaker::new();
|
||||
let ping = ping_maker
|
||||
.collect_string(&glean, glean.get_ping_by_name("baseline").unwrap())
|
||||
.collect_string(&glean, glean.get_ping_by_name("baseline").unwrap(), None)
|
||||
.unwrap();
|
||||
println!("Baseline Ping:\n{}", ping);
|
||||
|
||||
let ping = ping_maker.collect_string(&glean, glean.get_ping_by_name("metrics").unwrap());
|
||||
let ping = ping_maker.collect_string(&glean, glean.get_ping_by_name("metrics").unwrap(), None);
|
||||
println!("Metrics Ping: {:?}", ping);
|
||||
}
|
||||
|
|
|
@ -16,15 +16,36 @@ use crate::Glean;
|
|||
use crate::Lifetime;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Database {
|
||||
/// Handle to the database environment.
|
||||
rkv: Rkv,
|
||||
// If the `delay_ping_lifetime_io` Glean config option is `true`,
|
||||
// we will save metrics with 'ping' lifetime data in a map temporarily
|
||||
// so as to persist them to disk using rkv in bulk on demand.
|
||||
|
||||
/// Handles to the "lifetime" stores.
|
||||
///
|
||||
/// A "store" is a handle to the underlying database.
|
||||
/// We keep them open for fast and frequent access.
|
||||
user_store: SingleStore,
|
||||
ping_store: SingleStore,
|
||||
application_store: SingleStore,
|
||||
|
||||
/// If the `delay_ping_lifetime_io` Glean config option is `true`,
|
||||
/// we will save metrics with 'ping' lifetime data in a map temporarily
|
||||
/// so as to persist them to disk using rkv in bulk on demand.
|
||||
ping_lifetime_data: Option<RwLock<BTreeMap<String, Metric>>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Database {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct("Database")
|
||||
.field("rkv", &self.rkv)
|
||||
.field("user_store", &"SingleStore")
|
||||
.field("ping_store", &"SingleStore")
|
||||
.field("application_store", &"SingleStore")
|
||||
.field("ping_lifetime_data", &self.ping_lifetime_data)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// Initialize the data store.
|
||||
///
|
||||
|
@ -34,13 +55,23 @@ impl Database {
|
|||
/// It also loads any Lifetime::Ping data that might be
|
||||
/// persisted, in case `delay_ping_lifetime_io` is set.
|
||||
pub fn new(data_path: &str, delay_ping_lifetime_io: bool) -> Result<Self> {
|
||||
let rkv = Self::open_rkv(data_path)?;
|
||||
let user_store = rkv.open_single(Lifetime::User.as_str(), StoreOptions::create())?;
|
||||
let ping_store = rkv.open_single(Lifetime::Ping.as_str(), StoreOptions::create())?;
|
||||
let application_store =
|
||||
rkv.open_single(Lifetime::Application.as_str(), StoreOptions::create())?;
|
||||
let ping_lifetime_data = if delay_ping_lifetime_io {
|
||||
Some(RwLock::new(BTreeMap::new()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let db = Self {
|
||||
rkv: Self::open_rkv(data_path)?,
|
||||
ping_lifetime_data: if delay_ping_lifetime_io {
|
||||
Some(RwLock::new(BTreeMap::new()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
rkv,
|
||||
user_store,
|
||||
ping_store,
|
||||
application_store,
|
||||
ping_lifetime_data,
|
||||
};
|
||||
|
||||
db.load_ping_lifetime_data();
|
||||
|
@ -48,6 +79,14 @@ impl Database {
|
|||
Ok(db)
|
||||
}
|
||||
|
||||
fn get_store(&self, lifetime: Lifetime) -> &SingleStore {
|
||||
match lifetime {
|
||||
Lifetime::User => &self.user_store,
|
||||
Lifetime::Ping => &self.ping_store,
|
||||
Lifetime::Application => &self.application_store,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the storage directories and inits rkv.
|
||||
fn open_rkv(path: &str) -> Result<Rkv> {
|
||||
let path = std::path::Path::new(path).join("db");
|
||||
|
@ -89,13 +128,8 @@ impl Database {
|
|||
.write()
|
||||
.expect("Can't read ping lifetime data");
|
||||
|
||||
let store: SingleStore = unwrap_or!(
|
||||
self.rkv
|
||||
.open_single(Lifetime::Ping.as_str(), StoreOptions::create()),
|
||||
return
|
||||
);
|
||||
|
||||
let reader = unwrap_or!(self.rkv.read(), return);
|
||||
let store = self.get_store(Lifetime::Ping);
|
||||
let mut iter = unwrap_or!(store.iter_start(&reader), return);
|
||||
|
||||
while let Some(Ok((metric_name, value))) = iter.next() {
|
||||
|
@ -164,15 +198,12 @@ impl Database {
|
|||
}
|
||||
}
|
||||
|
||||
let store: SingleStore = unwrap_or!(
|
||||
self.rkv
|
||||
.open_single(lifetime.as_str(), StoreOptions::create()),
|
||||
let reader = unwrap_or!(self.rkv.read(), return);
|
||||
let mut iter = unwrap_or!(
|
||||
self.get_store(lifetime).iter_from(&reader, &iter_start),
|
||||
return
|
||||
);
|
||||
|
||||
let reader = unwrap_or!(self.rkv.read(), return);
|
||||
let mut iter = unwrap_or!(store.iter_from(&reader, &iter_start), return);
|
||||
|
||||
while let Some(Ok((metric_name, value))) = iter.next() {
|
||||
if !metric_name.starts_with(iter_start.as_bytes()) {
|
||||
break;
|
||||
|
@ -219,13 +250,11 @@ impl Database {
|
|||
}
|
||||
}
|
||||
|
||||
let store: SingleStore = unwrap_or!(
|
||||
self.rkv
|
||||
.open_single(lifetime.as_str(), StoreOptions::create()),
|
||||
return false
|
||||
);
|
||||
let reader = unwrap_or!(self.rkv.read(), return false);
|
||||
store.get(&reader, &key).unwrap_or(None).is_some()
|
||||
self.get_store(lifetime)
|
||||
.get(&reader, &key)
|
||||
.unwrap_or(None)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Write to the specified storage with the provided transaction function.
|
||||
|
@ -237,14 +266,11 @@ impl Database {
|
|||
/// * This function will **not** panic on database errors.
|
||||
pub fn write_with_store<F>(&self, store_name: Lifetime, mut transaction_fn: F) -> Result<()>
|
||||
where
|
||||
F: FnMut(rkv::Writer, SingleStore) -> Result<()>,
|
||||
F: FnMut(rkv::Writer, &SingleStore) -> Result<()>,
|
||||
{
|
||||
let store: SingleStore = self
|
||||
.rkv
|
||||
.open_single(store_name.as_str(), StoreOptions::create())?;
|
||||
let writer = self.rkv.write()?;
|
||||
transaction_fn(writer, store)?;
|
||||
Ok(())
|
||||
let writer = self.rkv.write().unwrap();
|
||||
let store = self.get_store(store_name);
|
||||
transaction_fn(writer, store)
|
||||
}
|
||||
|
||||
/// Records a metric in the underlying storage system.
|
||||
|
@ -293,11 +319,9 @@ impl Database {
|
|||
let encoded = bincode::serialize(&metric).expect("IMPOSSIBLE: Serializing metric failed");
|
||||
let value = rkv::Value::Blob(&encoded);
|
||||
|
||||
let store_name = lifetime.as_str();
|
||||
let store = self.rkv.open_single(store_name, StoreOptions::create())?;
|
||||
|
||||
let mut writer = self.rkv.write()?;
|
||||
store.put(&mut writer, final_key, &value)?;
|
||||
self.get_store(lifetime)
|
||||
.put(&mut writer, final_key, &value)?;
|
||||
writer.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -363,10 +387,8 @@ impl Database {
|
|||
}
|
||||
}
|
||||
|
||||
let store_name = lifetime.as_str();
|
||||
let store = self.rkv.open_single(store_name, StoreOptions::create())?;
|
||||
|
||||
let mut writer = self.rkv.write()?;
|
||||
let store = self.get_store(lifetime);
|
||||
let new_value: Metric = {
|
||||
let old_value = store.get(&writer, &final_key)?;
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ impl EventDatabase {
|
|||
|
||||
let mut ping_sent = false;
|
||||
for store_name in store_names {
|
||||
if let Err(err) = glean.submit_ping_by_name(&store_name) {
|
||||
if let Err(err) = glean.submit_ping_by_name(&store_name, None) {
|
||||
log::error!(
|
||||
"Error flushing existing events to the '{}' ping: {}",
|
||||
store_name,
|
||||
|
@ -199,7 +199,7 @@ impl EventDatabase {
|
|||
// If any of the event stores reached maximum size, submit the pings
|
||||
// containing those events immediately.
|
||||
for store_name in stores_to_submit {
|
||||
if let Err(err) = glean.submit_ping_by_name(store_name) {
|
||||
if let Err(err) = glean.submit_ping_by_name(store_name, None) {
|
||||
log::error!(
|
||||
"Got more than {} events, but could not send {} ping: {}",
|
||||
glean.get_max_events(),
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Bucketing, Histogram};
|
||||
|
@ -51,7 +51,7 @@ fn exponential_range(min: u64, max: u64, bucket_count: usize) -> Vec<u64> {
|
|||
///
|
||||
/// Buckets are pre-computed at instantiation with an exponential distribution from `min` to `max`
|
||||
/// and `bucket_count` buckets.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PrecomputedExponential {
|
||||
// Don't serialize the (potentially large) array of ranges, instead compute them on first
|
||||
// access.
|
||||
|
|
|
@ -16,7 +16,7 @@ use super::{Bucketing, Histogram};
|
|||
/// i = ⌊n log<sub>base</sub>(𝑥)⌋
|
||||
///
|
||||
/// In other words, there are n buckets for each power of `base` magnitude.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Functional {
|
||||
exponent: f64,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Bucketing, Histogram};
|
||||
|
@ -36,7 +36,7 @@ fn linear_range(min: u64, max: u64, count: usize) -> Vec<u64> {
|
|||
///
|
||||
/// Buckets are pre-computed at instantiation with a linear distribution from `min` to `max`
|
||||
/// and `bucket_count` buckets.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PrecomputedLinear {
|
||||
// Don't serialize the (potentially large) array of ranges, instead compute them on first
|
||||
// access.
|
||||
|
|
|
@ -58,7 +58,7 @@ impl TryFrom<i32> for HistogramType {
|
|||
/// assert_eq!(10, hist.count());
|
||||
/// assert_eq!(55, hist.sum());
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Histogram<B> {
|
||||
/// Mapping bucket's minimum to sample count.
|
||||
values: HashMap<u64, u64>,
|
||||
|
|
|
@ -21,10 +21,21 @@ pub struct InternalPings {
|
|||
impl InternalPings {
|
||||
pub fn new() -> InternalPings {
|
||||
InternalPings {
|
||||
baseline: PingType::new("baseline", true, false),
|
||||
metrics: PingType::new("metrics", true, false),
|
||||
events: PingType::new("events", true, false),
|
||||
deletion_request: PingType::new("deletion-request", true, true),
|
||||
baseline: PingType::new("baseline", true, false, vec![]),
|
||||
metrics: PingType::new(
|
||||
"metrics",
|
||||
true,
|
||||
false,
|
||||
vec![
|
||||
"overdue".to_string(),
|
||||
"reschedule".to_string(),
|
||||
"today".to_string(),
|
||||
"tomorrow".to_string(),
|
||||
"upgrade".to_string(),
|
||||
],
|
||||
),
|
||||
events: PingType::new("events", true, false, vec![]),
|
||||
deletion_request: PingType::new("deletion-request", true, true, vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
// This needs to be included first, and the space below prevents rustfmt from
|
||||
|
@ -34,6 +36,8 @@ mod internal_pings;
|
|||
pub mod metrics;
|
||||
pub mod ping;
|
||||
pub mod storage;
|
||||
#[cfg(feature = "upload")]
|
||||
mod upload;
|
||||
mod util;
|
||||
|
||||
pub use crate::common_metric_data::{CommonMetricData, Lifetime};
|
||||
|
@ -43,9 +47,11 @@ pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
|
|||
use crate::event_database::EventDatabase;
|
||||
use crate::internal_metrics::CoreMetrics;
|
||||
use crate::internal_pings::InternalPings;
|
||||
use crate::metrics::PingType;
|
||||
use crate::metrics::{Metric, MetricType, PingType};
|
||||
use crate::ping::PingMaker;
|
||||
use crate::storage::StorageManager;
|
||||
#[cfg(feature = "upload")]
|
||||
use crate::upload::{PingUploadManager, PingUploadTask};
|
||||
use crate::util::{local_now_with_offset, sanitize_application_id};
|
||||
|
||||
const GLEAN_SCHEMA_VERSION: u32 = 1;
|
||||
|
@ -55,6 +61,40 @@ lazy_static! {
|
|||
Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap();
|
||||
}
|
||||
|
||||
// An internal ping name, not to be touched by anything else
|
||||
pub(crate) const INTERNAL_STORAGE: &str = "glean_internal_info";
|
||||
|
||||
// The names of the pings directories.
|
||||
pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
|
||||
pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
|
||||
|
||||
/// The global Glean instance.
|
||||
///
|
||||
/// This is the singleton used by all wrappers to allow for a nice API.
|
||||
/// All state for Glean is kept inside this object (such as the database handle and `upload_enabled` flag).
|
||||
///
|
||||
/// It should be initialized with `glean_core::initialize` at the start of the application using
|
||||
/// Glean.
|
||||
static GLEAN: OnceCell<Mutex<Glean>> = OnceCell::new();
|
||||
|
||||
/// Get a reference to the global Glean object.
|
||||
///
|
||||
/// Panics if no global Glean object was set.
|
||||
pub fn global_glean() -> &'static Mutex<Glean> {
|
||||
GLEAN.get().unwrap()
|
||||
}
|
||||
|
||||
/// Set or replace the global Glean object.
|
||||
pub fn setup_glean(glean: Glean) -> Result<()> {
|
||||
if GLEAN.get().is_none() {
|
||||
GLEAN.set(Mutex::new(glean)).unwrap();
|
||||
} else {
|
||||
let mut lock = GLEAN.get().unwrap().lock().unwrap();
|
||||
*lock = glean;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The Glean configuration.
|
||||
///
|
||||
/// Optional values will be filled in with default values.
|
||||
|
@ -89,7 +129,7 @@ pub struct Configuration {
|
|||
/// delay_ping_lifetime_io: false,
|
||||
/// };
|
||||
/// let mut glean = Glean::new(cfg).unwrap();
|
||||
/// let ping = PingType::new("sample", true, false);
|
||||
/// let ping = PingType::new("sample", true, false, vec![]);
|
||||
/// glean.register_ping_type(&ping);
|
||||
///
|
||||
/// let call_counter: CounterMetric = CounterMetric::new(CommonMetricData {
|
||||
|
@ -101,7 +141,7 @@ pub struct Configuration {
|
|||
///
|
||||
/// call_counter.add(&glean, 1);
|
||||
///
|
||||
/// glean.submit_ping(&ping).unwrap();
|
||||
/// glean.submit_ping(&ping, None).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// ## Note
|
||||
|
@ -111,7 +151,7 @@ pub struct Configuration {
|
|||
#[derive(Debug)]
|
||||
pub struct Glean {
|
||||
upload_enabled: bool,
|
||||
data_store: Database,
|
||||
data_store: Option<Database>,
|
||||
event_data_store: EventDatabase,
|
||||
core_metrics: CoreMetrics,
|
||||
internal_pings: InternalPings,
|
||||
|
@ -121,6 +161,8 @@ pub struct Glean {
|
|||
start_time: DateTime<FixedOffset>,
|
||||
max_events: usize,
|
||||
is_first_run: bool,
|
||||
#[cfg(feature = "upload")]
|
||||
upload_manager: PingUploadManager,
|
||||
}
|
||||
|
||||
impl Glean {
|
||||
|
@ -135,7 +177,7 @@ impl Glean {
|
|||
|
||||
// Creating the data store creates the necessary path as well.
|
||||
// If that fails we bail out and don't initialize further.
|
||||
let data_store = Database::new(&cfg.data_path, cfg.delay_ping_lifetime_io)?;
|
||||
let data_store = Some(Database::new(&cfg.data_path, cfg.delay_ping_lifetime_io)?);
|
||||
let event_data_store = EventDatabase::new(&cfg.data_path)?;
|
||||
|
||||
let mut glean = Self {
|
||||
|
@ -144,6 +186,8 @@ impl Glean {
|
|||
event_data_store,
|
||||
core_metrics: CoreMetrics::new(),
|
||||
internal_pings: InternalPings::new(),
|
||||
#[cfg(feature = "upload")]
|
||||
upload_manager: PingUploadManager::new(&cfg.data_path),
|
||||
data_path: PathBuf::from(cfg.data_path),
|
||||
application_id,
|
||||
ping_registry: HashMap::new(),
|
||||
|
@ -173,6 +217,12 @@ impl Glean {
|
|||
Self::new(cfg)
|
||||
}
|
||||
|
||||
/// Destroy the database.
|
||||
/// After this Glean needs to be reinitialized.
|
||||
pub fn destroy_db(&mut self) {
|
||||
self.data_store = None;
|
||||
}
|
||||
|
||||
/// Initialize the core metrics managed by Glean's Rust core.
|
||||
fn initialize_core_metrics(&mut self) {
|
||||
let need_new_client_id = match self
|
||||
|
@ -235,14 +285,14 @@ impl Glean {
|
|||
pub fn set_upload_enabled(&mut self, flag: bool) -> bool {
|
||||
log::info!("Upload enabled: {:?}", flag);
|
||||
|
||||
// When upload is disabled, submit a deletion-request ping
|
||||
if !flag {
|
||||
if let Err(err) = self.internal_pings.deletion_request.submit(self) {
|
||||
log::error!("Failed to send deletion-request ping on optout: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if self.upload_enabled != flag {
|
||||
// When upload is disabled, submit a deletion-request ping
|
||||
if !flag {
|
||||
if let Err(err) = self.internal_pings.deletion_request.submit(self, None) {
|
||||
log::error!("Failed to submit deletion-request ping on optout: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
self.upload_enabled = flag;
|
||||
self.on_change_upload_enabled(flag);
|
||||
true
|
||||
|
@ -278,6 +328,11 @@ impl Glean {
|
|||
|
||||
/// Clear any pending metrics when telemetry is disabled.
|
||||
fn clear_metrics(&mut self) {
|
||||
// Clear the pending pings queue and acquire the lock
|
||||
// so that it can't be accessed until this function is done.
|
||||
#[cfg(feature = "upload")]
|
||||
let _lock = self.upload_manager.clear_ping_queue();
|
||||
|
||||
// There is only one metric that we want to survive after clearing all
|
||||
// metrics: first_run_date. Here, we store its value so we can restore
|
||||
// it after clearing the metrics.
|
||||
|
@ -295,7 +350,9 @@ impl Glean {
|
|||
// Delete all stored metrics.
|
||||
// Note that this also includes the ping sequence numbers, so it has
|
||||
// the effect of resetting those to their initial values.
|
||||
self.data_store.clear_all();
|
||||
if let Some(data) = self.data_store.as_ref() {
|
||||
data.clear_all()
|
||||
}
|
||||
if let Err(err) = self.event_data_store.clear_all() {
|
||||
log::error!("Error clearing pending events: {}", err);
|
||||
}
|
||||
|
@ -342,7 +399,7 @@ impl Glean {
|
|||
|
||||
/// Get a handle to the database.
|
||||
pub fn storage(&self) -> &Database {
|
||||
&self.data_store
|
||||
&self.data_store.as_ref().expect("No database found")
|
||||
}
|
||||
|
||||
/// Get a handle to the event database.
|
||||
|
@ -355,6 +412,32 @@ impl Glean {
|
|||
self.max_events
|
||||
}
|
||||
|
||||
/// Gets the next task for an uploader. Which can be either:
|
||||
///
|
||||
/// * Wait - which means the requester should ask again later;
|
||||
/// * Upload(PingRequest) - which means there is a ping to upload. This wraps the actual request object;
|
||||
/// * Done - which means there are no more pings queued right now.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// `PingUploadTask` - an enum representing the possible tasks.
|
||||
#[cfg(feature = "upload")]
|
||||
pub fn get_upload_task(&self) -> PingUploadTask {
|
||||
self.upload_manager.get_upload_task()
|
||||
}
|
||||
|
||||
/// Processes the response from an attempt to upload a ping.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `uuid` - The UUID of the ping in question.
|
||||
/// `status` - The HTTP status of the response.
|
||||
#[cfg(feature = "upload")]
|
||||
pub fn process_ping_upload_response(&self, uuid: &str, status: u16) {
|
||||
self.upload_manager
|
||||
.process_ping_upload_response(uuid, status);
|
||||
}
|
||||
|
||||
/// Take a snapshot for the given store and optionally clear it.
|
||||
///
|
||||
/// ## Arguments
|
||||
|
@ -392,7 +475,11 @@ impl Glean {
|
|||
///
|
||||
/// Returns true if a ping was assembled and queued, false otherwise.
|
||||
/// Returns an error if collecting or writing the ping to disk failed.
|
||||
pub fn submit_ping(&self, ping: &PingType) -> Result<bool> {
|
||||
///
|
||||
/// ## Arguments
|
||||
/// * `ping`: The ping to submit
|
||||
/// * `reason`: A reason code to include in the ping
|
||||
pub fn submit_ping(&self, ping: &PingType, reason: Option<&str>) -> Result<bool> {
|
||||
if !self.is_upload_enabled() {
|
||||
log::error!("Glean must be enabled before sending pings.");
|
||||
return Ok(false);
|
||||
|
@ -401,7 +488,7 @@ impl Glean {
|
|||
let ping_maker = PingMaker::new();
|
||||
let doc_id = Uuid::new_v4().to_string();
|
||||
let url_path = self.make_path(&ping.name, &doc_id);
|
||||
match ping_maker.collect(self, &ping) {
|
||||
match ping_maker.collect(self, &ping, reason) {
|
||||
None => {
|
||||
log::info!(
|
||||
"No content for ping '{}', therefore no ping queued.",
|
||||
|
@ -421,6 +508,10 @@ impl Glean {
|
|||
return Err(e.into());
|
||||
}
|
||||
|
||||
#[cfg(feature = "upload")]
|
||||
self.upload_manager
|
||||
.enqueue_ping(&doc_id, &url_path, content);
|
||||
|
||||
log::info!(
|
||||
"The ping '{}' was submitted and will be sent as soon as possible",
|
||||
ping.name
|
||||
|
@ -430,25 +521,6 @@ impl Glean {
|
|||
}
|
||||
}
|
||||
|
||||
/// Collect and submit a ping for eventual uploading by name.
|
||||
///
|
||||
/// See `submit_ping` for detailed information.
|
||||
///
|
||||
/// Returns true if at least one ping was assembled and queued, false otherwise.
|
||||
pub fn submit_pings_by_name(&self, ping_names: &[String]) -> bool {
|
||||
// TODO: 1553813: glean-ac collects and stores pings in parallel and then joins them all before queueing the worker.
|
||||
// This here is writing them out sequentially.
|
||||
|
||||
let mut result = false;
|
||||
|
||||
for ping_name in ping_names {
|
||||
if let Ok(true) = self.submit_ping_by_name(ping_name) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Collect and submit a ping by name for eventual uploading.
|
||||
///
|
||||
/// The ping content is assembled as soon as possible, but upload is not
|
||||
|
@ -459,13 +531,17 @@ impl Glean {
|
|||
///
|
||||
/// Returns true if a ping was assembled and queued, false otherwise.
|
||||
/// Returns an error if collecting or writing the ping to disk failed.
|
||||
pub fn submit_ping_by_name(&self, ping_name: &str) -> Result<bool> {
|
||||
///
|
||||
/// ## Arguments
|
||||
/// * `ping_name`: The name of the ping to submit
|
||||
/// * `reason`: A reason code to include in the ping
|
||||
pub fn submit_ping_by_name(&self, ping_name: &str, reason: Option<&str>) -> Result<bool> {
|
||||
match self.get_ping_by_name(ping_name) {
|
||||
None => {
|
||||
log::error!("Attempted to submit unknown ping '{}'", ping_name);
|
||||
Ok(false)
|
||||
}
|
||||
Some(ping) => self.submit_ping(ping),
|
||||
Some(ping) => self.submit_ping(ping, reason),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,7 +604,11 @@ impl Glean {
|
|||
///
|
||||
/// If there is no data to persist, this function does nothing.
|
||||
pub fn persist_ping_lifetime_data(&self) -> Result<()> {
|
||||
self.data_store.persist_ping_lifetime_data()
|
||||
if let Some(data) = self.data_store.as_ref() {
|
||||
return data.persist_ping_lifetime_data();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ** This is not meant to be used directly.**
|
||||
|
@ -536,7 +616,9 @@ impl Glean {
|
|||
/// Clear all the metrics that have `Lifetime::Application`.
|
||||
pub fn clear_application_lifetime_metrics(&self) {
|
||||
log::debug!("Clearing Lifetime::Application metrics");
|
||||
self.data_store.clear_lifetime(Lifetime::Application);
|
||||
if let Some(data) = self.data_store.as_ref() {
|
||||
data.clear_lifetime(Lifetime::Application);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether or not this is the first run on this profile.
|
||||
|
@ -544,6 +626,50 @@ impl Glean {
|
|||
self.is_first_run
|
||||
}
|
||||
|
||||
fn get_dirty_bit_metric(&self) -> metrics::BooleanMetric {
|
||||
metrics::BooleanMetric::new(CommonMetricData {
|
||||
name: "dirtybit".into(),
|
||||
// We don't need a category, the name is already unique
|
||||
category: "".into(),
|
||||
send_in_pings: vec![INTERNAL_STORAGE.into()],
|
||||
lifetime: Lifetime::User,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// ** This is not meant to be used directly.**
|
||||
///
|
||||
/// Set the value of a "dirty flag" in the permanent storage.
|
||||
/// The "dirty flag" is meant to have the following behaviour, implemented
|
||||
/// by the consumers of the FFI layer:
|
||||
///
|
||||
/// - on mobile: set to `false` when going to background or shutting down,
|
||||
/// set to `true` at startup and when going to foreground.
|
||||
/// - on non-mobile platforms: set to `true` at startup and `false` at
|
||||
/// shutdown.
|
||||
///
|
||||
/// At startup, before setting its new value, if the "dirty flag" value is
|
||||
/// `true`, then Glean knows it did not exit cleanly and can implement
|
||||
/// coping mechanisms (e.g. sending a `baseline` ping).
|
||||
pub fn set_dirty_flag(&self, new_value: bool) {
|
||||
self.get_dirty_bit_metric().set(self, new_value);
|
||||
}
|
||||
|
||||
/// ** This is not meant to be used directly.**
|
||||
///
|
||||
/// Check the stored value of the "dirty flag".
|
||||
pub fn is_dirty_flag_set(&self) -> bool {
|
||||
let dirty_bit_metric = self.get_dirty_bit_metric();
|
||||
match StorageManager.snapshot_metric(
|
||||
self.storage(),
|
||||
INTERNAL_STORAGE,
|
||||
&dirty_bit_metric.meta().identifier(self),
|
||||
) {
|
||||
Some(Metric::Boolean(b)) => b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// **Test-only API (exported for FFI purposes).**
|
||||
///
|
||||
/// Check if an experiment is currently active.
|
||||
|
@ -584,7 +710,9 @@ impl Glean {
|
|||
/// Note that this also includes the ping sequence numbers, so it has
|
||||
/// the effect of resetting those to their initial values.
|
||||
pub fn test_clear_all_stores(&self) {
|
||||
self.data_store.clear_all();
|
||||
if let Some(data) = self.data_store.as_ref() {
|
||||
data.clear_all()
|
||||
}
|
||||
// We don't care about this failing, maybe the data does just not exist.
|
||||
let _ = self.event_data_store.clear_all();
|
||||
}
|
||||
|
|
|
@ -10,8 +10,11 @@ use crate::metrics::RecordedExperimentData;
|
|||
use crate::metrics::StringMetric;
|
||||
|
||||
const GLOBAL_APPLICATION_ID: &str = "org.mozilla.glean.test.app";
|
||||
pub fn new_glean() -> (Glean, tempfile::TempDir) {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
pub fn new_glean(tempdir: Option<tempfile::TempDir>) -> (Glean, tempfile::TempDir) {
|
||||
let dir = match tempdir {
|
||||
Some(tempdir) => tempdir,
|
||||
None => tempfile::tempdir().unwrap(),
|
||||
};
|
||||
let tmpname = dir.path().display().to_string();
|
||||
let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true).unwrap();
|
||||
(glean, dir)
|
||||
|
@ -19,7 +22,7 @@ pub fn new_glean() -> (Glean, tempfile::TempDir) {
|
|||
|
||||
#[test]
|
||||
fn path_is_constructed_from_data() {
|
||||
let (glean, _) = new_glean();
|
||||
let (glean, _) = new_glean(None);
|
||||
|
||||
assert_eq!(
|
||||
"/submit/org-mozilla-glean-test-app/baseline/1/this-is-a-docid",
|
||||
|
@ -169,7 +172,7 @@ fn client_id_and_first_run_date_must_be_regenerated() {
|
|||
{
|
||||
let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true).unwrap();
|
||||
|
||||
glean.data_store.clear_all();
|
||||
glean.data_store.as_ref().unwrap().clear_all();
|
||||
|
||||
assert!(glean
|
||||
.core_metrics
|
||||
|
@ -200,7 +203,7 @@ fn client_id_and_first_run_date_must_be_regenerated() {
|
|||
|
||||
#[test]
|
||||
fn basic_metrics_should_be_cleared_when_uploading_is_disabled() {
|
||||
let (mut glean, _t) = new_glean();
|
||||
let (mut glean, _t) = new_glean(None);
|
||||
let metric = StringMetric::new(CommonMetricData::new(
|
||||
"category",
|
||||
"string_metric",
|
||||
|
@ -225,7 +228,7 @@ fn basic_metrics_should_be_cleared_when_uploading_is_disabled() {
|
|||
|
||||
#[test]
|
||||
fn first_run_date_is_managed_correctly_when_toggling_uploading() {
|
||||
let (mut glean, _) = new_glean();
|
||||
let (mut glean, _) = new_glean(None);
|
||||
|
||||
let original_first_run_date = glean
|
||||
.core_metrics
|
||||
|
@ -253,7 +256,7 @@ fn first_run_date_is_managed_correctly_when_toggling_uploading() {
|
|||
|
||||
#[test]
|
||||
fn client_id_is_managed_correctly_when_toggling_uploading() {
|
||||
let (mut glean, _) = new_glean();
|
||||
let (mut glean, _) = new_glean(None);
|
||||
|
||||
let original_client_id = glean
|
||||
.core_metrics
|
||||
|
@ -394,6 +397,145 @@ fn correct_order() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip] // Let's not merge lines
|
||||
fn backwards_compatible_deserialization() {
|
||||
use std::env;
|
||||
use std::time::Duration;
|
||||
use chrono::prelude::*;
|
||||
use histogram::Histogram;
|
||||
use metrics::{Metric::*, TimeUnit};
|
||||
|
||||
// Prepare some data to fill in
|
||||
let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 12);
|
||||
|
||||
let mut custom_dist_exp = Histogram::exponential(1, 500, 10);
|
||||
custom_dist_exp.accumulate(10);
|
||||
|
||||
let mut custom_dist_linear = Histogram::linear(1, 500, 10);
|
||||
custom_dist_linear.accumulate(10);
|
||||
|
||||
let mut time_dist = Histogram::functional(2.0, 8.0);
|
||||
time_dist.accumulate(10);
|
||||
|
||||
let mut mem_dist = Histogram::functional(2.0, 16.0);
|
||||
mem_dist.accumulate(10);
|
||||
|
||||
// One of every metric type. The values are arbitrary, but stable.
|
||||
let all_metrics = vec![
|
||||
(
|
||||
"boolean",
|
||||
vec![0, 0, 0, 0, 1],
|
||||
Boolean(true)
|
||||
),
|
||||
(
|
||||
"counter",
|
||||
vec![1, 0, 0, 0, 20, 0, 0, 0],
|
||||
Counter(20)
|
||||
),
|
||||
(
|
||||
"custom exponential distribution",
|
||||
vec![2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 10, 0,
|
||||
0, 0, 0, 0, 0, 0],
|
||||
CustomDistributionExponential(custom_dist_exp)
|
||||
),
|
||||
(
|
||||
"custom linear distribution",
|
||||
vec![3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0],
|
||||
CustomDistributionLinear(custom_dist_linear)
|
||||
),
|
||||
(
|
||||
"datetime",
|
||||
vec![4, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 50, 48, 49, 52, 45, 49, 49, 45,
|
||||
50, 56, 84, 50, 49, 58, 52, 53, 58, 53, 57, 46, 48, 48, 48, 48, 48,
|
||||
48, 48, 49, 50, 43, 48, 57, 58, 48, 48, 3, 0, 0, 0],
|
||||
Datetime(dt, TimeUnit::Second),
|
||||
),
|
||||
(
|
||||
"experiment",
|
||||
vec![5, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 98, 114, 97, 110, 99, 104, 0],
|
||||
Experiment(RecordedExperimentData { branch: "branch".into(), extra: None, }),
|
||||
),
|
||||
(
|
||||
"quantity",
|
||||
vec![6, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0],
|
||||
Quantity(17)
|
||||
),
|
||||
(
|
||||
"string",
|
||||
vec![7, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 103, 108, 101, 97, 110],
|
||||
String("glean".into())
|
||||
),
|
||||
(
|
||||
"string list",
|
||||
vec![8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0,
|
||||
103, 108, 101, 97, 110],
|
||||
StringList(vec!["glean".into()])
|
||||
),
|
||||
(
|
||||
"uuid",
|
||||
vec![9, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 48, 56, 50, 99, 51, 101, 53, 50,
|
||||
45, 48, 97, 49, 56, 45, 49, 49, 101, 97, 45, 57, 52, 54, 102, 45, 48,
|
||||
102, 101, 48, 99, 57, 56, 99, 51, 54, 49, 99],
|
||||
Uuid("082c3e52-0a18-11ea-946f-0fe0c98c361c".into()),
|
||||
),
|
||||
(
|
||||
"timespan",
|
||||
vec![10, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0],
|
||||
Timespan(Duration::new(5, 0), TimeUnit::Second),
|
||||
),
|
||||
(
|
||||
"timing distribution",
|
||||
vec![11, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 123, 81, 125,
|
||||
60, 184, 114, 241, 63],
|
||||
TimingDistribution(time_dist),
|
||||
),
|
||||
(
|
||||
"memory distribution",
|
||||
vec![12, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 15, 137, 249,
|
||||
108, 88, 181, 240, 63],
|
||||
MemoryDistribution(mem_dist),
|
||||
),
|
||||
];
|
||||
|
||||
for (name, data, metric) in all_metrics {
|
||||
// Helper to print serialization data if instructed by environment variable
|
||||
// Run with:
|
||||
//
|
||||
// ```text
|
||||
// PRINT_DATA=1 cargo test -p glean-core --lib -- --nocapture backwards
|
||||
// ```
|
||||
//
|
||||
// This should not be necessary to re-run and change here, unless a bincode upgrade
|
||||
// requires us to also migrate existing data.
|
||||
if env::var("PRINT_DATA").is_ok() {
|
||||
let bindata = bincode::serialize(&metric).unwrap();
|
||||
println!("(\n {:?},\n vec!{:?},", name, bindata);
|
||||
} else {
|
||||
// Otherwise run the test
|
||||
let deserialized = bincode::deserialize(&data).unwrap();
|
||||
if let CustomDistributionExponential(hist) = &deserialized {
|
||||
hist.snapshot_values(); // Force initialization of the ranges
|
||||
}
|
||||
if let CustomDistributionLinear(hist) = &deserialized {
|
||||
hist.snapshot_values(); // Force initialization of the ranges
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
metric, deserialized,
|
||||
"Expected properly deserialized {}",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_run() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
@ -410,3 +552,36 @@ fn test_first_run() {
|
|||
assert!(!glean.is_first_run());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dirty_bit() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let tmpname = dir.path().display().to_string();
|
||||
{
|
||||
let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true).unwrap();
|
||||
// The dirty flag must not be set the first time Glean runs.
|
||||
assert!(!glean.is_dirty_flag_set());
|
||||
|
||||
// Set the dirty flag and check that it gets correctly set.
|
||||
glean.set_dirty_flag(true);
|
||||
assert!(glean.is_dirty_flag_set());
|
||||
}
|
||||
|
||||
{
|
||||
// Check that next time Glean runs, it correctly picks up the "dirty flag".
|
||||
// It is expected to be 'true'.
|
||||
let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true).unwrap();
|
||||
assert!(glean.is_dirty_flag_set());
|
||||
|
||||
// Set the dirty flag to false.
|
||||
glean.set_dirty_flag(false);
|
||||
assert!(!glean.is_dirty_flag_set());
|
||||
}
|
||||
|
||||
{
|
||||
// Check that next time Glean runs, it correctly picks up the "dirty flag".
|
||||
// It is expected to be 'false'.
|
||||
let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true).unwrap();
|
||||
assert!(!glean.is_dirty_flag_set());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use serde_json::{json, Map as JsonMap, Value as JsonValue};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::error_recording::{record_error, ErrorType};
|
||||
|
@ -14,10 +14,7 @@ use crate::util::{truncate_string_at_boundary, truncate_string_at_boundary_with_
|
|||
use crate::CommonMetricData;
|
||||
use crate::Glean;
|
||||
use crate::Lifetime;
|
||||
|
||||
// FIXME: this should be shared?
|
||||
// An internal ping name, not to be touched by anything else
|
||||
const INTERNAL_STORAGE: &str = "glean_internal_info";
|
||||
use crate::INTERNAL_STORAGE;
|
||||
|
||||
/// The maximum length of the experiment id, the branch id, and the keys of the
|
||||
/// `extra` map. Identifiers longer than this number of characters are truncated.
|
||||
|
@ -37,6 +34,21 @@ pub struct RecordedExperimentData {
|
|||
pub extra: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl RecordedExperimentData {
|
||||
// For JSON, we don't want to include {"extra": null} -- we just want to skip
|
||||
// extra entirely. Unfortunately, we can't use a serde field annotation for this,
|
||||
// since that would break bincode serialization, which doesn't support skipping
|
||||
// fields. Therefore, we use a custom serialization function just for JSON here.
|
||||
pub fn as_json(&self) -> JsonValue {
|
||||
let mut value = JsonMap::new();
|
||||
value.insert("branch".to_string(), json!(self.branch));
|
||||
if self.extra.is_some() {
|
||||
value.insert("extra".to_string(), json!(self.extra));
|
||||
}
|
||||
JsonValue::Object(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// An experiment metric.
|
||||
///
|
||||
/// Used to store active experiments.
|
||||
|
|
|
@ -24,7 +24,6 @@ lazy_static! {
|
|||
/// * `this_is_fine_too`
|
||||
/// * `this.is_still_fine`
|
||||
/// * `thisisfine`
|
||||
/// * `this.is_fine.2`
|
||||
/// * `_.is_fine`
|
||||
/// * `this.is-fine`
|
||||
/// * `this-is-fine`
|
||||
|
@ -33,7 +32,9 @@ lazy_static! {
|
|||
/// * `1.not_fine`
|
||||
/// * `this.$isnotfine`
|
||||
/// * `-.not_fine`
|
||||
static ref LABEL_REGEX: Regex = Regex::new("^[a-z_][a-z0-9_-]{0,29}(\\.[a-z0-9_-]{0,29})*$").unwrap();
|
||||
static ref LABEL_REGEX: Regex = Regex::new(
|
||||
"^[a-z_][a-z0-9_-]{0,29}(\\.[a-z_][a-z0-9_-]{0,29})*$"
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
/// A labeled metric.
|
||||
|
|
|
@ -67,7 +67,7 @@ pub use self::uuid::UuidMetric;
|
|||
/// Do not reorder the variants.
|
||||
///
|
||||
/// **Any new metric must be added at the end.**
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum Metric {
|
||||
/// A boolean metric. See [`BooleanMetric`](struct.BooleanMetric.html) for more information.
|
||||
Boolean(bool),
|
||||
|
@ -150,7 +150,7 @@ impl Metric {
|
|||
}
|
||||
Metric::CustomDistributionLinear(hist) => json!(custom_distribution::snapshot(hist)),
|
||||
Metric::Datetime(d, time_unit) => json!(get_iso_time_string(*d, *time_unit)),
|
||||
Metric::Experiment(e) => json!(e),
|
||||
Metric::Experiment(e) => e.as_json(),
|
||||
Metric::Quantity(q) => json!(q),
|
||||
Metric::String(s) => json!(s),
|
||||
Metric::StringList(v) => json!(v),
|
||||
|
|
|
@ -17,6 +17,8 @@ pub struct PingType {
|
|||
pub include_client_id: bool,
|
||||
/// Whether the ping should be sent if it is empty
|
||||
pub send_if_empty: bool,
|
||||
/// The "reason" codes that this ping can send
|
||||
pub reason_codes: Vec<String>,
|
||||
}
|
||||
|
||||
impl PingType {
|
||||
|
@ -28,11 +30,17 @@ impl PingType {
|
|||
/// * `name` - The name of the ping.
|
||||
/// * `include_client_id` - Whether to include the client ID in the assembled ping when.
|
||||
/// sending.
|
||||
pub fn new<A: Into<String>>(name: A, include_client_id: bool, send_if_empty: bool) -> Self {
|
||||
pub fn new<A: Into<String>>(
|
||||
name: A,
|
||||
include_client_id: bool,
|
||||
send_if_empty: bool,
|
||||
reason_codes: Vec<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
include_client_id,
|
||||
send_if_empty,
|
||||
reason_codes,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,11 +49,25 @@ impl PingType {
|
|||
/// ## Arguments
|
||||
///
|
||||
/// * `glean` - the Glean instance to use to send the ping.
|
||||
/// * `reason` - the reason the ping was triggered. Included in the
|
||||
/// `ping_info.reason` part of the payload.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// See [`Glean#submit_ping`](../struct.Glean.html#method.submit_ping) for details.
|
||||
pub fn submit(&self, glean: &Glean) -> Result<bool> {
|
||||
glean.submit_ping(self)
|
||||
pub fn submit(&self, glean: &Glean, reason: Option<&str>) -> Result<bool> {
|
||||
let corrected_reason = match reason {
|
||||
Some(reason) => {
|
||||
if self.reason_codes.contains(&reason.to_string()) {
|
||||
Some(reason)
|
||||
} else {
|
||||
log::error!("Invalid reason code {} for ping {}", reason, self.name);
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
glean.submit_ping(self, corrected_reason)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::error::{Error, ErrorKind};
|
|||
|
||||
/// Different resolutions supported by the time related
|
||||
/// metric types (e.g. DatetimeMetric).
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TimeUnit {
|
||||
/// Truncate to nanosecond precision.
|
||||
|
|
|
@ -63,15 +63,20 @@ impl Timings {
|
|||
///
|
||||
/// This API exists to satisfy the FFI requirements, where the clock is handled on the
|
||||
/// application side and passed in as a timestamp.
|
||||
fn set_stop(&mut self, id: TimerId, stop_time: u64) -> Result<u64, &str> {
|
||||
fn set_stop(&mut self, id: TimerId, stop_time: u64) -> Result<u64, (ErrorType, &str)> {
|
||||
let start_time = match self.start_times.remove(&id) {
|
||||
Some(start_time) => start_time,
|
||||
None => return Err("Timing not running"),
|
||||
None => return Err((ErrorType::InvalidState, "Timing not running")),
|
||||
};
|
||||
|
||||
let duration = match stop_time.checked_sub(start_time) {
|
||||
Some(duration) => duration,
|
||||
None => return Err("Timer stopped with negative duration"),
|
||||
None => {
|
||||
return Err((
|
||||
ErrorType::InvalidValue,
|
||||
"Timer stopped with negative duration",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(duration)
|
||||
|
@ -162,8 +167,8 @@ impl TimingDistributionMetric {
|
|||
pub fn set_stop_and_accumulate(&mut self, glean: &Glean, id: TimerId, stop_time: u64) {
|
||||
// Duration is in nanoseconds.
|
||||
let mut duration = match self.timings.set_stop(id, stop_time) {
|
||||
Err(error) => {
|
||||
record_error(glean, &self.meta, ErrorType::InvalidValue, error, None);
|
||||
Err((err_type, err_msg)) => {
|
||||
record_error(glean, &self.meta, err_type, err_msg, None);
|
||||
return;
|
||||
}
|
||||
Ok(duration) => duration,
|
||||
|
|
|
@ -15,10 +15,9 @@ use crate::common_metric_data::{CommonMetricData, Lifetime};
|
|||
use crate::metrics::{CounterMetric, DatetimeMetric, Metric, MetricType, PingType, TimeUnit};
|
||||
use crate::storage::StorageManager;
|
||||
use crate::util::{get_iso_time_string, local_now_with_offset};
|
||||
use crate::{Glean, Result};
|
||||
|
||||
// An internal ping name, not to be touched by anything else
|
||||
const INTERNAL_STORAGE: &str = "glean_internal_info";
|
||||
use crate::{
|
||||
Glean, Result, DELETION_REQUEST_PINGS_DIRECTORY, INTERNAL_STORAGE, PENDING_PINGS_DIRECTORY,
|
||||
};
|
||||
|
||||
/// Collect a ping's data, assemble it into its full payload and store it on disk.
|
||||
pub struct PingMaker;
|
||||
|
@ -108,15 +107,20 @@ impl PingMaker {
|
|||
(start_time_data, end_time_data)
|
||||
}
|
||||
|
||||
fn get_ping_info(&self, glean: &Glean, storage_name: &str) -> JsonValue {
|
||||
fn get_ping_info(&self, glean: &Glean, storage_name: &str, reason: Option<&str>) -> JsonValue {
|
||||
let (start_time, end_time) = self.get_start_end_times(glean, storage_name);
|
||||
let mut map = json!({
|
||||
"ping_type": storage_name,
|
||||
"seq": self.get_ping_seq(glean, storage_name),
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
});
|
||||
|
||||
if let Some(reason) = reason {
|
||||
map.as_object_mut()
|
||||
.unwrap() // safe unwrap, we created the object above
|
||||
.insert("reason".to_string(), JsonValue::String(reason.to_string()));
|
||||
};
|
||||
|
||||
// Get the experiment data, if available.
|
||||
if let Some(experiment_data) =
|
||||
StorageManager.snapshot_experiments_as_json(glean.storage(), INTERNAL_STORAGE)
|
||||
|
@ -162,12 +166,18 @@ impl PingMaker {
|
|||
///
|
||||
/// * `glean` - the Glean instance to collect data from.
|
||||
/// * `ping` - the ping to collect for.
|
||||
/// * `reason` - an optional reason code to include in the ping.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// Returns a fully assembled JSON representation of the ping payload.
|
||||
/// If there is no data stored for the ping, `None` is returned.
|
||||
pub fn collect(&self, glean: &Glean, ping: &PingType) -> Option<JsonValue> {
|
||||
pub fn collect(
|
||||
&self,
|
||||
glean: &Glean,
|
||||
ping: &PingType,
|
||||
reason: Option<&str>,
|
||||
) -> Option<JsonValue> {
|
||||
info!("Collecting {}", ping.name);
|
||||
|
||||
let metrics_data = StorageManager.snapshot_as_json(glean.storage(), &ping.name, true);
|
||||
|
@ -181,7 +191,7 @@ impl PingMaker {
|
|||
info!("Storage for {} empty. Ping will still be sent.", ping.name);
|
||||
}
|
||||
|
||||
let ping_info = self.get_ping_info(glean, &ping.name);
|
||||
let ping_info = self.get_ping_info(glean, &ping.name, reason);
|
||||
let client_info = self.get_client_info(glean, ping.include_client_id);
|
||||
|
||||
let mut json = json!({
|
||||
|
@ -206,13 +216,19 @@ impl PingMaker {
|
|||
///
|
||||
/// * `glean` - the Glean instance to collect data from.
|
||||
/// * `ping` - the ping to collect for.
|
||||
/// * `reason` - an optional reason code to include in the ping.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// Returns a fully assembled ping payload in a string encoded as JSON.
|
||||
/// If there is no data stored for the ping, `None` is returned.
|
||||
pub fn collect_string(&self, glean: &Glean, ping: &PingType) -> Option<String> {
|
||||
self.collect(glean, ping)
|
||||
pub fn collect_string(
|
||||
&self,
|
||||
glean: &Glean,
|
||||
ping: &PingType,
|
||||
reason: Option<&str>,
|
||||
) -> Option<String> {
|
||||
self.collect(glean, ping, reason)
|
||||
.map(|ping| ::serde_json::to_string_pretty(&ping).unwrap())
|
||||
}
|
||||
|
||||
|
@ -224,9 +240,9 @@ impl PingMaker {
|
|||
// Use a special directory for deletion-request pings
|
||||
let pings_dir = match ping_type {
|
||||
Some(ping_type) if ping_type == "deletion-request" => {
|
||||
data_path.join("deletion_request")
|
||||
data_path.join(DELETION_REQUEST_PINGS_DIRECTORY)
|
||||
}
|
||||
_ => data_path.join("pending_pings"),
|
||||
_ => data_path.join(PENDING_PINGS_DIRECTORY),
|
||||
};
|
||||
|
||||
create_dir_all(&pings_dir)?;
|
||||
|
@ -301,7 +317,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn sequence_numbers_should_be_reset_when_toggling_uploading() {
|
||||
let (mut glean, _) = new_glean();
|
||||
let (mut glean, _) = new_glean(None);
|
||||
let ping_maker = PingMaker::new();
|
||||
|
||||
assert_eq!(0, ping_maker.get_ping_seq(&glean, "custom"));
|
||||
|
|
|
@ -231,4 +231,28 @@ mod test {
|
|||
StorageManager.snapshot_experiments_as_json(glean.storage(), "glean_internal_info");
|
||||
assert!(empty_snapshot.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_experiments_json_serialization_empty() {
|
||||
let t = tempfile::tempdir().unwrap();
|
||||
let name = t.path().display().to_string();
|
||||
let glean = Glean::with_options(&name, "org.mozilla.glean", true).unwrap();
|
||||
|
||||
let metric = ExperimentMetric::new(&glean, "some-experiment".to_string());
|
||||
|
||||
metric.set_active(&glean, "test-branch".to_string(), None);
|
||||
let snapshot = StorageManager
|
||||
.snapshot_experiments_as_json(glean.storage(), "glean_internal_info")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
json!({"some-experiment": {"branch": "test-branch"}}),
|
||||
snapshot
|
||||
);
|
||||
|
||||
metric.set_inactive(&glean);
|
||||
|
||||
let empty_snapshot =
|
||||
StorageManager.snapshot_experiments_as_json(glean.storage(), "glean_internal_info");
|
||||
assert!(empty_snapshot.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,389 @@
|
|||
// 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/.
|
||||
|
||||
//! Pings directory processing utilities.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log;
|
||||
use serde_json::Value as JsonValue;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::PingRequest;
|
||||
use crate::{DELETION_REQUEST_PINGS_DIRECTORY, PENDING_PINGS_DIRECTORY};
|
||||
|
||||
/// Get the file name from a path as a &str.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Won't panic if not able to get file name.
|
||||
fn get_file_name_as_str(path: &Path) -> Option<&str> {
|
||||
match path.file_name() {
|
||||
None => {
|
||||
log::warn!("Error getting file name from path: {}", path.display());
|
||||
None
|
||||
}
|
||||
Some(file_name) => {
|
||||
let file_name = file_name.to_str();
|
||||
if file_name.is_none() {
|
||||
log::warn!("File name is not valid unicode: {}", path.display());
|
||||
}
|
||||
file_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the pending pings directories.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PingDirectoryManager {
|
||||
/// Paths to the pings directories.
|
||||
pings_dirs: [PathBuf; 2],
|
||||
}
|
||||
|
||||
impl PingDirectoryManager {
|
||||
/// Creates a new directory manager.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data_path` - Path to the pending pings directory.
|
||||
pub fn new<P: Into<PathBuf>>(data_path: P) -> Self {
|
||||
let data_path = data_path.into();
|
||||
Self {
|
||||
pings_dirs: [
|
||||
data_path.join(PENDING_PINGS_DIRECTORY),
|
||||
data_path.join(DELETION_REQUEST_PINGS_DIRECTORY),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to delete a ping file.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// * `uuid` - The UUID of the ping file to be deleted
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Won't panic if unable to delete the file.
|
||||
pub fn delete_file(&self, uuid: &str) {
|
||||
let path = match self.get_file_path(uuid) {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
log::error!("Cannot find ping file to delete {}", uuid);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match fs::remove_file(&path) {
|
||||
Err(e) => log::error!("Error deleting file {}. {}", path.display(), e),
|
||||
_ => log::info!("Files was deleted {}", path.display()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Reads a ping file and returns a `PingRequest` from it.
|
||||
///
|
||||
/// If the file is not properly formatted, it will be deleted and `None` will be returned.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// * `uuid` - The UUID of the ping file to be processed
|
||||
pub fn process_file(&self, uuid: &str) -> Option<PingRequest> {
|
||||
let path = match self.get_file_path(uuid) {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
log::error!("Cannot find ping file to process {}", uuid);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
log::error!("Error reading ping file {}. {}", path.display(), e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("Processing ping at: {}", path.display());
|
||||
|
||||
// The way the ping file is structured,
|
||||
// first line should always have the path
|
||||
// and second line should have the body with the ping contents in JSON format
|
||||
let mut lines = BufReader::new(file).lines();
|
||||
if let (Some(Ok(path)), Some(Ok(body))) = (lines.next(), lines.next()) {
|
||||
if let Ok(parsed_body) = serde_json::from_str::<JsonValue>(&body) {
|
||||
return Some(PingRequest::new(uuid, &path, parsed_body));
|
||||
} else {
|
||||
log::warn!(
|
||||
"Error processing ping file: {}. Can't parse ping contents as JSON.",
|
||||
uuid
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::warn!(
|
||||
"Error processing ping file: {}. Ping file is not formatted as expected.",
|
||||
uuid
|
||||
);
|
||||
}
|
||||
self.delete_file(uuid);
|
||||
None
|
||||
}
|
||||
|
||||
/// Process the pings directory and return a vector of `PingRequest`s
|
||||
/// corresponding to each valid ping file in the directory.
|
||||
/// This vector will be ordered by file `modified_date`.
|
||||
///
|
||||
/// Any files that don't match the UUID regex will be deleted
|
||||
/// to prevent files from polluting the pings directory.
|
||||
///
|
||||
/// Files that are not correctly formatted will also be deleted.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// `Vec<PingRequest>` - see [`PingRequest`](struct.PingRequest.html) for more information.
|
||||
pub fn process_dir(&self) -> Vec<PingRequest> {
|
||||
log::info!("Processing persisted pings.");
|
||||
|
||||
// Walk the pings directory and process each file in it,
|
||||
// deleting invalid ones and ignoring unreadable ones.
|
||||
// Create a vector of tuples: (modified_date, PingRequest)
|
||||
// using the contents and metadata of all valid files.
|
||||
let mut pending_pings: Vec<_> = self
|
||||
.get_ping_entries()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
if let Some(file_name) = get_file_name_as_str(&path) {
|
||||
// Delete file if it doesn't match the pattern.
|
||||
if Uuid::parse_str(file_name).is_err() {
|
||||
log::warn!("Pattern mismatch. Deleting {}", path.display());
|
||||
self.delete_file(file_name);
|
||||
return None;
|
||||
}
|
||||
// In case we can't process the file we just ignore it.
|
||||
if let Some(request) = self.process_file(file_name) {
|
||||
// Get the modified date of the file, which will later be used
|
||||
// for sorting the resulting vector.
|
||||
let modified_date = fs::metadata(&path).and_then(|data| data.modified());
|
||||
return Some((modified_date, request));
|
||||
}
|
||||
};
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort by `modified_date`.
|
||||
pending_pings.sort_by(|(a, _), (b, _)| {
|
||||
// We might not be able to get the modified date for a given file,
|
||||
// in which case we just put it at the end.
|
||||
if let (Ok(a), Ok(b)) = (a, b) {
|
||||
a.partial_cmp(b).unwrap()
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
});
|
||||
|
||||
// Return the vector leaving only the `PingRequest`s in it
|
||||
pending_pings
|
||||
.into_iter()
|
||||
.map(|(_, request)| request)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all the ping entries in all ping directories.
|
||||
fn get_ping_entries(&self) -> Vec<fs::DirEntry> {
|
||||
let mut result = Vec::new();
|
||||
for dir in &self.pings_dirs {
|
||||
if let Ok(entries) = dir.read_dir() {
|
||||
result.extend(entries.filter_map(|entry| entry.ok()))
|
||||
};
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the path for a ping file based on its uuid.
|
||||
///
|
||||
/// Will look for files in each ping directory until something is found.
|
||||
/// If nothing is found, returns `None`.
|
||||
fn get_file_path(&self, uuid: &str) -> Option<PathBuf> {
|
||||
for dir in &self.pings_dirs {
|
||||
let path = dir.join(uuid);
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
use crate::metrics::PingType;
|
||||
use crate::tests::new_glean;
|
||||
|
||||
#[test]
|
||||
fn test_doesnt_panic_if_no_pending_pings_directory() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let directory_manager = PingDirectoryManager::new(dir.path());
|
||||
|
||||
// Verify that processing the directory didn't panic
|
||||
assert_eq!(directory_manager.process_dir().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_creates_requests_correctly_from_valid_ping_file() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit the ping to populate the pending_pings directory
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
let directory_manager = PingDirectoryManager::new(dir.path());
|
||||
|
||||
// Try and process the pings folder
|
||||
let requests = directory_manager.process_dir();
|
||||
|
||||
// Verify there is just the one request
|
||||
assert_eq!(requests.len(), 1);
|
||||
|
||||
// Verify request was returned for the "test" ping
|
||||
let request_ping_type = requests[0].path.split('/').nth(3).unwrap();
|
||||
assert_eq!(request_ping_type, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_uuid_files_are_deleted_and_ignored() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit the ping to populate the pending_pings directory
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
let directory_manager = PingDirectoryManager::new(&dir.path());
|
||||
|
||||
let not_uuid_path = dir
|
||||
.path()
|
||||
.join(PENDING_PINGS_DIRECTORY)
|
||||
.join("not-uuid-file-name.txt");
|
||||
File::create(¬_uuid_path).unwrap();
|
||||
|
||||
// Try and process the pings folder
|
||||
let requests = directory_manager.process_dir();
|
||||
|
||||
// Verify there is just the one request
|
||||
assert_eq!(requests.len(), 1);
|
||||
|
||||
// Verify request was returned for the "test" ping
|
||||
let request_ping_type = requests[0].path.split('/').nth(3).unwrap();
|
||||
assert_eq!(request_ping_type, "test");
|
||||
|
||||
// Verify that file was indeed deleted
|
||||
assert!(!not_uuid_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrongly_formatted_files_are_deleted_and_ignored() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit the ping to populate the pending_pings directory
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
let directory_manager = PingDirectoryManager::new(&dir.path());
|
||||
|
||||
let wrong_contents_file_path = dir
|
||||
.path()
|
||||
.join(PENDING_PINGS_DIRECTORY)
|
||||
.join(Uuid::new_v4().to_string());
|
||||
File::create(&wrong_contents_file_path).unwrap();
|
||||
|
||||
// Try and process the pings folder
|
||||
let requests = directory_manager.process_dir();
|
||||
|
||||
// Verify there is just the one request
|
||||
assert_eq!(requests.len(), 1);
|
||||
|
||||
// Verify request was returned for the "test" ping
|
||||
let request_ping_type = requests[0].path.split('/').nth(3).unwrap();
|
||||
assert_eq!(request_ping_type, "test");
|
||||
|
||||
// Verify that file was indeed deleted
|
||||
assert!(!wrong_contents_file_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_json_ping_body_files_are_deleted_and_ignored() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit the ping to populate the pending_pings directory
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
let directory_manager = PingDirectoryManager::new(&dir.path());
|
||||
|
||||
let non_json_body_file_path = dir
|
||||
.path()
|
||||
.join(PENDING_PINGS_DIRECTORY)
|
||||
.join(Uuid::new_v4().to_string());
|
||||
let mut non_json_body_file = File::create(&non_json_body_file_path).unwrap();
|
||||
non_json_body_file
|
||||
.write_all(
|
||||
b"https://doc.rust-lang.org/std/fs/struct.File.html
|
||||
This is not JSON!!!!",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Try and process the pings folder
|
||||
let requests = directory_manager.process_dir();
|
||||
|
||||
// Verify there is just the one request
|
||||
assert_eq!(requests.len(), 1);
|
||||
|
||||
// Verify request was returned for the "test" ping
|
||||
let request_ping_type = requests[0].path.split('/').nth(3).unwrap();
|
||||
assert_eq!(request_ping_type, "test");
|
||||
|
||||
// Verify that file was indeed deleted
|
||||
assert!(!non_json_body_file_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_takes_deletion_request_pings_into_account_while_processing() {
|
||||
let (glean, dir) = new_glean(None);
|
||||
|
||||
// Submit a deletion request ping to populate deletion request folder.
|
||||
glean
|
||||
.internal_pings
|
||||
.deletion_request
|
||||
.submit(&glean, None)
|
||||
.unwrap();
|
||||
|
||||
let directory_manager = PingDirectoryManager::new(dir.path());
|
||||
|
||||
// Try and process the pings folder
|
||||
let requests = directory_manager.process_dir();
|
||||
|
||||
assert_eq!(requests.len(), 1);
|
||||
|
||||
assert!(requests[0].is_deletion_request());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,506 @@
|
|||
// 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/.
|
||||
|
||||
//! Manages the pending pings queue and directory.
|
||||
//!
|
||||
//! * Keeps track of pending pings, loading any unsent ping from disk on startup;
|
||||
//! * Exposes `get_upload_task` API for the platform layer to request next upload task;
|
||||
//! * Exposes `process_ping_upload_response` API to check the HTTP response from the ping upload
|
||||
//! and either delete the corresponding ping from disk or re-enqueue it for sending.
|
||||
|
||||
// !IMPORTANT!
|
||||
// Remove the next line when this module's functionality is in the Glean object.
|
||||
// This is here just to not have lint error for now.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, RwLock, RwLockWriteGuard};
|
||||
use std::thread;
|
||||
|
||||
use log;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use directory::PingDirectoryManager;
|
||||
use request::PingRequest;
|
||||
|
||||
mod directory;
|
||||
mod request;
|
||||
|
||||
/// When asking for the next ping request to upload,
|
||||
/// the requester may receive one out of three possible tasks.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum PingUploadTask {
|
||||
/// A PingRequest popped from the front of the queue.
|
||||
/// See [`PingRequest`](struct.PingRequest.html) for more information.
|
||||
Upload(PingRequest),
|
||||
/// A flag signaling that the pending pings directories are not done being processed,
|
||||
/// thus the requester should wait and come back later.
|
||||
Wait,
|
||||
/// A flag signaling that the pending pings queue is empty and requester is done.
|
||||
Done,
|
||||
}
|
||||
|
||||
/// Manages the pending pings queue and directory.
|
||||
#[derive(Debug)]
|
||||
pub struct PingUploadManager {
|
||||
/// A FIFO queue storing a `PingRequest` for each pending ping.
|
||||
queue: Arc<RwLock<VecDeque<PingRequest>>>,
|
||||
/// A manager for the pending pings directories.
|
||||
directory_manager: PingDirectoryManager,
|
||||
/// A flag signaling if we are done processing the pending pings directories.
|
||||
processed_pending_pings: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl PingUploadManager {
|
||||
/// Create a new PingUploadManager.
|
||||
///
|
||||
/// Spawns a new thread and processes the pending pings directory,
|
||||
/// filling up the queue with whatever pings are in there.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data_path` - Path to the pending pings directory.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if unable to spawn a new thread.
|
||||
pub fn new<P: Into<PathBuf>>(data_path: P) -> Self {
|
||||
let queue = Arc::new(RwLock::new(VecDeque::new()));
|
||||
let directory_manager = PingDirectoryManager::new(data_path);
|
||||
let processed_pending_pings = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let local_queue = queue.clone();
|
||||
let local_flag = processed_pending_pings.clone();
|
||||
let local_manager = directory_manager.clone();
|
||||
let _ = thread::Builder::new()
|
||||
.name("glean.ping_directory_manager.process_dir".to_string())
|
||||
.spawn(move || {
|
||||
let mut local_queue = local_queue
|
||||
.write()
|
||||
.expect("Can't write to pending pings queue.");
|
||||
local_queue.extend(local_manager.process_dir());
|
||||
local_flag.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Unable to spawn thread to process pings directories.");
|
||||
|
||||
Self {
|
||||
queue,
|
||||
processed_pending_pings,
|
||||
directory_manager,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_processed_pings_dir(&self) -> bool {
|
||||
self.processed_pending_pings.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Creates a `PingRequest` and adds it to the queue.
|
||||
pub fn enqueue_ping(&self, uuid: &str, path: &str, body: JsonValue) {
|
||||
let mut queue = self
|
||||
.queue
|
||||
.write()
|
||||
.expect("Can't write to pending pings queue.");
|
||||
let request = PingRequest::new(uuid, path, body);
|
||||
queue.push_back(request);
|
||||
}
|
||||
|
||||
/// Clears the pending pings queue, leaves the deletion-request pings.
|
||||
pub fn clear_ping_queue(&self) -> RwLockWriteGuard<'_, VecDeque<PingRequest>> {
|
||||
let mut queue = self
|
||||
.queue
|
||||
.write()
|
||||
.expect("Can't write to pending pings queue.");
|
||||
|
||||
queue.retain(|ping| ping.is_deletion_request());
|
||||
queue
|
||||
}
|
||||
|
||||
/// Gets the next `PingUploadTask`.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// `PingUploadTask` - see [`PingUploadTask`](enum.PingUploadTask.html) for more information.
|
||||
pub fn get_upload_task(&self) -> PingUploadTask {
|
||||
if !self.has_processed_pings_dir() {
|
||||
return PingUploadTask::Wait;
|
||||
}
|
||||
|
||||
let mut queue = self
|
||||
.queue
|
||||
.write()
|
||||
.expect("Can't write to pending pings queue.");
|
||||
match queue.pop_front() {
|
||||
Some(request) => PingUploadTask::Upload(request),
|
||||
None => PingUploadTask::Done,
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the response from an attempt to upload a ping.
|
||||
///
|
||||
/// Based on the HTTP status of said response,
|
||||
/// the possible outcomes are:
|
||||
///
|
||||
/// * **200 - 299 Success**
|
||||
/// Any status on the 2XX range is considered a succesful upload,
|
||||
/// which means the corresponding ping file can be deleted.
|
||||
/// _Known 2XX status:_
|
||||
/// * 200 - OK. Request accepted into the pipeline.
|
||||
///
|
||||
/// * **400 - 499 Unrecoverable error**
|
||||
/// Any status on the 4XX range means something our client did is not correct.
|
||||
/// It is unlikely that the client is going to recover from this by retrying,
|
||||
/// so in this case the corresponding ping file can also be deleted.
|
||||
/// _Known 4XX status:_
|
||||
/// * 404 - not found - POST/PUT to an unknown namespace
|
||||
/// * 405 - wrong request type (anything other than POST/PUT)
|
||||
/// * 411 - missing content-length header
|
||||
/// * 413 - request body too large Note that if we have badly-behaved clients that
|
||||
/// retry on 4XX, we should send back 202 on body/path too long).
|
||||
/// * 414 - request path too long (See above)
|
||||
///
|
||||
/// * **Any other error**
|
||||
/// For any other error, a warning is logged and the ping is re-enqueued.
|
||||
/// _Known other errors:_
|
||||
/// * 500 - internal error
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The disk I/O performed by this function is not done off-thread,
|
||||
/// as it is expected to be called off-thread by the platform.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `uuid` - The UUID of the ping in question.
|
||||
/// `status` - The HTTP status of the response.
|
||||
pub fn process_ping_upload_response(&self, uuid: &str, status: u16) {
|
||||
match status {
|
||||
200..=299 => {
|
||||
log::info!("Ping {} successfully sent {}.", uuid, status);
|
||||
self.directory_manager.delete_file(uuid);
|
||||
}
|
||||
400..=499 => {
|
||||
log::error!(
|
||||
"Server returned client error code {} while attempting to send ping {}.",
|
||||
status,
|
||||
uuid
|
||||
);
|
||||
self.directory_manager.delete_file(uuid);
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"Server returned response code {} while attempting to send ping {}.",
|
||||
status,
|
||||
uuid
|
||||
);
|
||||
if let Some(request) = self.directory_manager.process_file(uuid) {
|
||||
let mut queue = self
|
||||
.queue
|
||||
.write()
|
||||
.expect("Can't write to pending pings queue.");
|
||||
queue.push_back(request);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
use crate::metrics::PingType;
|
||||
use crate::{tests::new_glean, PENDING_PINGS_DIRECTORY};
|
||||
|
||||
const UUID: &str = "40e31919-684f-43b0-a5aa-e15c2d56a674"; // Just a random UUID.
|
||||
const PATH: &str = "/submit/app_id/ping_name/schema_version/doc_id";
|
||||
|
||||
#[test]
|
||||
fn test_doesnt_error_when_there_are_no_pending_pings() {
|
||||
// Create a new upload_manager
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let upload_manager = PingUploadManager::new(dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
while upload_manager.get_upload_task() == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
// Try and get the next request.
|
||||
// Verify request was not returned
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_returns_ping_request_when_there_is_one() {
|
||||
// Create a new upload_manager
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let upload_manager = PingUploadManager::new(dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
while upload_manager.get_upload_task() == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
// Enqueue a ping
|
||||
upload_manager.enqueue_ping(UUID, PATH, json!({}));
|
||||
|
||||
// Try and get the next request.
|
||||
// Verify request was returned
|
||||
match upload_manager.get_upload_task() {
|
||||
PingUploadTask::Upload(_) => {}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_returns_as_many_ping_requests_as_there_are() {
|
||||
// Create a new upload_manager
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let upload_manager = PingUploadManager::new(dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
while upload_manager.get_upload_task() == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
// Enqueue a ping multiple times
|
||||
let n = 10;
|
||||
for _ in 0..n {
|
||||
upload_manager.enqueue_ping(UUID, PATH, json!({}));
|
||||
}
|
||||
|
||||
// Verify a request is returned for each submitted ping
|
||||
for _ in 0..n {
|
||||
match upload_manager.get_upload_task() {
|
||||
PingUploadTask::Upload(_) => {}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that after all requests are returned, none are left
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clearing_the_queue_works_correctly() {
|
||||
// Create a new upload_manager
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let upload_manager = PingUploadManager::new(dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
while upload_manager.get_upload_task() == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
// Enqueue a ping multiple times
|
||||
for _ in 0..10 {
|
||||
upload_manager.enqueue_ping(UUID, PATH, json!({}));
|
||||
}
|
||||
|
||||
// Clear the queue
|
||||
let _ = upload_manager.clear_ping_queue();
|
||||
|
||||
// Verify there really isn't any ping in the queue
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clearing_the_queue_doesnt_clear_deletion_request_pings() {
|
||||
let (mut glean, _) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, /* send_if_empty */ true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit the ping multiple times
|
||||
let n = 10;
|
||||
for _ in 0..n {
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
}
|
||||
|
||||
glean
|
||||
.internal_pings
|
||||
.deletion_request
|
||||
.submit(&glean, None)
|
||||
.unwrap();
|
||||
|
||||
// Clear the queue
|
||||
let _ = glean.upload_manager.clear_ping_queue();
|
||||
|
||||
let upload_task = glean.get_upload_task();
|
||||
match upload_task {
|
||||
PingUploadTask::Upload(request) => assert!(request.is_deletion_request()),
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
|
||||
// Verify there really isn't any other pings in the queue
|
||||
assert_eq!(glean.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fills_up_queue_successfully_from_disk() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, /* send_if_empty */ true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit the ping multiple times
|
||||
let n = 10;
|
||||
for _ in 0..n {
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
}
|
||||
|
||||
// Create a new upload_manager
|
||||
let upload_manager = PingUploadManager::new(dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
let mut upload_task = upload_manager.get_upload_task();
|
||||
while upload_task == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
upload_task = upload_manager.get_upload_task();
|
||||
}
|
||||
|
||||
// Verify the requests were properly enqueued
|
||||
for _ in 0..n {
|
||||
match upload_task {
|
||||
PingUploadTask::Upload(_) => {}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
|
||||
upload_task = upload_manager.get_upload_task();
|
||||
}
|
||||
|
||||
// Verify that after all requests are returned, none are left
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_processes_correctly_success_upload_response() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, /* send_if_empty */ true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit a ping
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
// Create a new upload_manager
|
||||
let upload_manager = PingUploadManager::new(&dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
let mut upload_task = upload_manager.get_upload_task();
|
||||
while upload_task == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
upload_task = upload_manager.get_upload_task();
|
||||
}
|
||||
|
||||
// Get the pending ping directory path
|
||||
let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
|
||||
|
||||
// Get the submitted PingRequest
|
||||
match upload_task {
|
||||
PingUploadTask::Upload(request) => {
|
||||
// Simulate the processing of a sucessfull request
|
||||
let uuid = request.uuid;
|
||||
upload_manager.process_ping_upload_response(&uuid, 200);
|
||||
// Verify file was deleted
|
||||
assert!(!pending_pings_dir.join(uuid).exists());
|
||||
}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
|
||||
// Verify that after request is returned, none are left
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_processes_correctly_client_error_upload_response() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, /* send_if_empty */ true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit a ping
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
// Create a new upload_manager
|
||||
let upload_manager = PingUploadManager::new(&dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
let mut upload_task = upload_manager.get_upload_task();
|
||||
while upload_task == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
upload_task = upload_manager.get_upload_task();
|
||||
}
|
||||
|
||||
// Get the pending ping directory path
|
||||
let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
|
||||
|
||||
// Get the submitted PingRequest
|
||||
match upload_task {
|
||||
PingUploadTask::Upload(request) => {
|
||||
// Simulate the processing of a client error
|
||||
let uuid = request.uuid;
|
||||
upload_manager.process_ping_upload_response(&uuid, 404);
|
||||
// Verify file was deleted
|
||||
assert!(!pending_pings_dir.join(uuid).exists());
|
||||
}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
|
||||
// Verify that after request is returned, none are left
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_processes_correctly_server_error_upload_response() {
|
||||
let (mut glean, dir) = new_glean(None);
|
||||
|
||||
// Register a ping for testing
|
||||
let ping_type = PingType::new("test", true, /* send_if_empty */ true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Submit a ping
|
||||
glean.submit_ping(&ping_type, None).unwrap();
|
||||
|
||||
// Create a new upload_manager
|
||||
let upload_manager = PingUploadManager::new(dir.path());
|
||||
|
||||
// Wait for processing of pending pings directory to finish.
|
||||
let mut upload_task = upload_manager.get_upload_task();
|
||||
while upload_task == PingUploadTask::Wait {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
upload_task = upload_manager.get_upload_task();
|
||||
}
|
||||
|
||||
// Get the submitted PingRequest
|
||||
match upload_task {
|
||||
PingUploadTask::Upload(request) => {
|
||||
// Simulate the processing of a client error
|
||||
let uuid = request.uuid;
|
||||
upload_manager.process_ping_upload_response(&uuid, 500);
|
||||
// Verify this ping was indeed re-enqueued
|
||||
match upload_manager.get_upload_task() {
|
||||
PingUploadTask::Upload(request) => {
|
||||
assert_eq!(uuid, request.uuid);
|
||||
}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected upload manager to return the next request!"),
|
||||
}
|
||||
|
||||
// Verify that after request is returned, none are left
|
||||
assert_eq!(upload_manager.get_upload_task(), PingUploadTask::Done);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// 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/.
|
||||
|
||||
//! Ping request representation.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// Represents a request to upload a ping.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct PingRequest {
|
||||
/// The Job ID to identify this request,
|
||||
/// this is the same as the ping UUID.
|
||||
pub uuid: String,
|
||||
/// The path for the server to upload the ping to.
|
||||
pub path: String,
|
||||
/// The body of the request.
|
||||
pub body: JsonValue,
|
||||
/// A map with all the headers to be sent with the request.
|
||||
pub headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl PingRequest {
|
||||
/// Creates a new PingRequest.
|
||||
///
|
||||
/// Automatically creates the default request headers.
|
||||
/// Clients may add more headers such as `userAgent` to this list.
|
||||
pub fn new(uuid: &str, path: &str, body: JsonValue) -> Self {
|
||||
Self {
|
||||
uuid: uuid.into(),
|
||||
path: path.into(),
|
||||
body,
|
||||
headers: Self::create_request_headers(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_deletion_request(&self) -> bool {
|
||||
// The path format should be `/submit/<app_id>/<ping_name>/<schema_version/<doc_id>`
|
||||
self.path
|
||||
.split('/')
|
||||
.nth(3)
|
||||
.map(|url| url == "deletion-request")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Creates the default request headers.
|
||||
fn create_request_headers() -> HashMap<String, String> {
|
||||
let mut headers = HashMap::new();
|
||||
let date: DateTime<Utc> = Utc::now();
|
||||
headers.insert("Date".to_string(), date.to_string());
|
||||
headers.insert("X-Client-Type".to_string(), "Glean".to_string());
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"application/json; charset=utf-8".to_string(),
|
||||
);
|
||||
headers.insert(
|
||||
"X-Client-Version".to_string(),
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
);
|
||||
headers
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
// 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 chrono::offset::TimeZone;
|
||||
use chrono::{DateTime, FixedOffset, Local};
|
||||
|
||||
use crate::error_recording::{record_error, ErrorType};
|
||||
|
@ -19,7 +18,7 @@ pub fn sanitize_application_id(application_id: &str) -> String {
|
|||
.filter_map(|x| match x {
|
||||
'A'..='Z' | 'a'..='z' | '0'..='9' => {
|
||||
last_dash = false;
|
||||
Some(x)
|
||||
Some(x.to_ascii_lowercase())
|
||||
}
|
||||
_ => {
|
||||
let result = if last_dash { None } else { Some('-') };
|
||||
|
@ -49,12 +48,8 @@ pub fn get_iso_time_string(datetime: DateTime<FixedOffset>, truncate_to: TimeUni
|
|||
///
|
||||
/// This converts from the `Local` timezone into its fixed-offset equivalent.
|
||||
pub(crate) fn local_now_with_offset() -> DateTime<FixedOffset> {
|
||||
// This looks more complicated than I imagined.
|
||||
|
||||
let now: DateTime<Local> = Local::now();
|
||||
let naive = now.naive_utc();
|
||||
let fixed_tz = Local.offset_from_utc_datetime(&naive);
|
||||
fixed_tz.from_utc_datetime(&naive)
|
||||
now.with_timezone(now.offset())
|
||||
}
|
||||
|
||||
/// Truncates a string, ensuring that it doesn't end in the middle of a codepoint.
|
||||
|
@ -119,6 +114,7 @@ pub(crate) fn truncate_string_at_boundary_with_error<S: Into<String>>(
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use chrono::offset::TimeZone;
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_application_id() {
|
||||
|
@ -134,6 +130,10 @@ mod test {
|
|||
"org-mozilla-test-app",
|
||||
sanitize_application_id("org-mozilla-test-app")
|
||||
);
|
||||
assert_eq!(
|
||||
"org-mozilla-test-app",
|
||||
sanitize_application_id("org.mozilla.Test.App")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -152,7 +152,7 @@ fn test_sending_of_event_ping_when_it_fills_up() {
|
|||
let store_names: Vec<String> = vec!["events".into()];
|
||||
|
||||
for store_name in &store_names {
|
||||
glean.register_ping_type(&PingType::new(store_name.clone(), true, false));
|
||||
glean.register_ping_type(&PingType::new(store_name.clone(), true, false, vec![]));
|
||||
}
|
||||
|
||||
let click = EventMetric::new(
|
||||
|
|
|
@ -226,7 +226,7 @@ fn dynamic_labels_too_long() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_labels_regex_mimsatch() {
|
||||
fn dynamic_labels_regex_mismatch() {
|
||||
let (glean, _t) = new_glean(None);
|
||||
let mut labeled = LabeledMetric::new(
|
||||
CounterMetric::new(CommonMetricData {
|
||||
|
@ -247,6 +247,7 @@ fn dynamic_labels_regex_mimsatch() {
|
|||
"1.not_fine",
|
||||
"this.$isnotfine",
|
||||
"-.not_fine",
|
||||
"this.is_not_fine.2",
|
||||
];
|
||||
let num_non_validating = labels_not_validating.len();
|
||||
|
||||
|
@ -291,7 +292,6 @@ fn dynamic_labels_regex_allowed() {
|
|||
"this_is_fine_too",
|
||||
"this.is_still_fine",
|
||||
"thisisfine",
|
||||
"this.is_fine.2",
|
||||
"_.is_fine",
|
||||
"this.is-fine",
|
||||
"this-is-fine",
|
||||
|
@ -313,7 +313,6 @@ fn dynamic_labels_regex_allowed() {
|
|||
"this_is_fine_too": 1,
|
||||
"this.is_still_fine": 1,
|
||||
"thisisfine": 1,
|
||||
"this.is_fine.2": 1,
|
||||
"_.is_fine": 1,
|
||||
"this.is-fine": 1,
|
||||
"this-is-fine": 1
|
||||
|
|
|
@ -12,7 +12,7 @@ use glean_core::CommonMetricData;
|
|||
fn write_ping_to_disk() {
|
||||
let (mut glean, _temp) = new_glean(None);
|
||||
|
||||
let ping = PingType::new("metrics", true, false);
|
||||
let ping = PingType::new("metrics", true, false, vec![]);
|
||||
glean.register_ping_type(&ping);
|
||||
|
||||
// We need to store a metric as an empty ping is not stored.
|
||||
|
@ -24,7 +24,7 @@ fn write_ping_to_disk() {
|
|||
});
|
||||
counter.add(&glean, 1);
|
||||
|
||||
assert!(ping.submit(&glean).unwrap());
|
||||
assert!(ping.submit(&glean, None).unwrap());
|
||||
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ fn write_ping_to_disk() {
|
|||
fn disabling_upload_clears_pending_pings() {
|
||||
let (mut glean, _) = new_glean(None);
|
||||
|
||||
let ping = PingType::new("metrics", true, false);
|
||||
let ping = PingType::new("metrics", true, false, vec![]);
|
||||
glean.register_ping_type(&ping);
|
||||
|
||||
// We need to store a metric as an empty ping is not stored.
|
||||
|
@ -45,7 +45,7 @@ fn disabling_upload_clears_pending_pings() {
|
|||
});
|
||||
|
||||
counter.add(&glean, 1);
|
||||
assert!(ping.submit(&glean).unwrap());
|
||||
assert!(ping.submit(&glean, None).unwrap());
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
// At this point no deletion_request ping should exist
|
||||
// (that is: it's directory should not exist at all)
|
||||
|
@ -60,26 +60,44 @@ fn disabling_upload_clears_pending_pings() {
|
|||
assert_eq!(0, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
counter.add(&glean, 1);
|
||||
assert!(ping.submit(&glean).unwrap());
|
||||
assert!(ping.submit(&glean, None).unwrap());
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletion_request_only_when_toggled_from_on_to_off() {
|
||||
let (mut glean, _) = new_glean(None);
|
||||
|
||||
// Disabling upload generates a deletion ping
|
||||
glean.set_upload_enabled(false);
|
||||
assert_eq!(1, get_deletion_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
// Re-setting it to `false` should not generate an additional ping.
|
||||
// As we didn't clear the pending ping, that's the only one that sticks around.
|
||||
glean.set_upload_enabled(false);
|
||||
assert_eq!(1, get_deletion_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
// Toggling back to true won't generate a ping either.
|
||||
glean.set_upload_enabled(true);
|
||||
assert_eq!(1, get_deletion_pings(glean.get_data_path()).unwrap().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_pings_with_flag_are_sent() {
|
||||
let (mut glean, _) = new_glean(None);
|
||||
|
||||
let ping1 = PingType::new("custom-ping1", true, true);
|
||||
let ping1 = PingType::new("custom-ping1", true, true, vec![]);
|
||||
glean.register_ping_type(&ping1);
|
||||
let ping2 = PingType::new("custom-ping2", true, false);
|
||||
let ping2 = PingType::new("custom-ping2", true, false, vec![]);
|
||||
glean.register_ping_type(&ping2);
|
||||
|
||||
// No data is stored in either of the custom pings
|
||||
|
||||
// Sending this should succeed.
|
||||
assert_eq!(true, ping1.submit(&glean).unwrap());
|
||||
assert_eq!(true, ping1.submit(&glean, None).unwrap());
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
// Sending this should fail.
|
||||
assert_eq!(false, ping2.submit(&glean).unwrap());
|
||||
assert_eq!(false, ping2.submit(&glean, None).unwrap());
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ fn set_up_basic_ping() -> (Glean, PingMaker, PingType, tempfile::TempDir) {
|
|||
};
|
||||
let mut glean = Glean::new(cfg).unwrap();
|
||||
let ping_maker = PingMaker::new();
|
||||
let ping_type = PingType::new("store1", true, false);
|
||||
let ping_type = PingType::new("store1", true, false, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Record something, so the ping will have data
|
||||
|
@ -43,7 +43,7 @@ fn set_up_basic_ping() -> (Glean, PingMaker, PingType, tempfile::TempDir) {
|
|||
fn ping_info_must_contain_a_nonempty_start_and_end_time() {
|
||||
let (glean, ping_maker, ping_type, _t) = set_up_basic_ping();
|
||||
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let ping_info = content["ping_info"].as_object().unwrap();
|
||||
|
||||
let start_time_str = ping_info["start_time"].as_str().unwrap();
|
||||
|
@ -59,10 +59,9 @@ fn ping_info_must_contain_a_nonempty_start_and_end_time() {
|
|||
fn get_ping_info_must_report_all_the_required_fields() {
|
||||
let (glean, ping_maker, ping_type, _t) = set_up_basic_ping();
|
||||
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let ping_info = content["ping_info"].as_object().unwrap();
|
||||
|
||||
assert_eq!("store1", ping_info["ping_type"].as_str().unwrap());
|
||||
assert!(ping_info.get("start_time").is_some());
|
||||
assert!(ping_info.get("end_time").is_some());
|
||||
assert!(ping_info.get("seq").is_some());
|
||||
|
@ -72,7 +71,7 @@ fn get_ping_info_must_report_all_the_required_fields() {
|
|||
fn get_client_info_must_report_all_the_available_data() {
|
||||
let (glean, ping_maker, ping_type, _t) = set_up_basic_ping();
|
||||
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let client_info = content["client_info"].as_object().unwrap();
|
||||
|
||||
client_info["telemetry_sdk_build"].as_str().unwrap();
|
||||
|
@ -89,10 +88,12 @@ fn collect_must_report_none_when_no_data_is_stored() {
|
|||
|
||||
let (mut glean, ping_maker, ping_type, _t) = set_up_basic_ping();
|
||||
|
||||
let unknown_ping_type = PingType::new("unknown", true, false);
|
||||
let unknown_ping_type = PingType::new("unknown", true, false, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
assert!(ping_maker.collect(&glean, &unknown_ping_type).is_none());
|
||||
assert!(ping_maker
|
||||
.collect(&glean, &unknown_ping_type, None)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -111,8 +112,8 @@ fn seq_number_must_be_sequential() {
|
|||
|
||||
for i in 0..=1 {
|
||||
for ping_name in ["store1", "store2"].iter() {
|
||||
let ping_type = PingType::new(*ping_name, true, false);
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let ping_type = PingType::new(*ping_name, true, false, vec![]);
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let seq_num = content["ping_info"]["seq"].as_i64().unwrap();
|
||||
// Ensure sequence numbers in different stores are independent of
|
||||
// each other
|
||||
|
@ -122,33 +123,33 @@ fn seq_number_must_be_sequential() {
|
|||
|
||||
// Test that ping sequence numbers increase independently.
|
||||
{
|
||||
let ping_type = PingType::new("store1", true, false);
|
||||
let ping_type = PingType::new("store1", true, false, vec![]);
|
||||
|
||||
// 3rd ping of store1
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let seq_num = content["ping_info"]["seq"].as_i64().unwrap();
|
||||
assert_eq!(2, seq_num);
|
||||
|
||||
// 4th ping of store1
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let seq_num = content["ping_info"]["seq"].as_i64().unwrap();
|
||||
assert_eq!(3, seq_num);
|
||||
}
|
||||
|
||||
{
|
||||
let ping_type = PingType::new("store2", true, false);
|
||||
let ping_type = PingType::new("store2", true, false, vec![]);
|
||||
|
||||
// 3rd ping of store2
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let seq_num = content["ping_info"]["seq"].as_i64().unwrap();
|
||||
assert_eq!(2, seq_num);
|
||||
}
|
||||
|
||||
{
|
||||
let ping_type = PingType::new("store1", true, false);
|
||||
let ping_type = PingType::new("store1", true, false, vec![]);
|
||||
|
||||
// 5th ping of store1
|
||||
let content = ping_maker.collect(&glean, &ping_type).unwrap();
|
||||
let content = ping_maker.collect(&glean, &ping_type, None).unwrap();
|
||||
let seq_num = content["ping_info"]["seq"].as_i64().unwrap();
|
||||
assert_eq!(4, seq_num);
|
||||
}
|
||||
|
@ -158,7 +159,7 @@ fn seq_number_must_be_sequential() {
|
|||
fn test_clear_pending_pings() {
|
||||
let (mut glean, _) = new_glean(None);
|
||||
let ping_maker = PingMaker::new();
|
||||
let ping_type = PingType::new("store1", true, false);
|
||||
let ping_type = PingType::new("store1", true, false, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
// Record something, so the ping will have data
|
||||
|
@ -172,7 +173,7 @@ fn test_clear_pending_pings() {
|
|||
});
|
||||
metric.set(&glean, true);
|
||||
|
||||
assert!(glean.submit_ping(&ping_type).is_ok());
|
||||
assert!(glean.submit_ping(&ping_type, None).is_ok());
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
assert!(ping_maker
|
||||
|
@ -186,19 +187,19 @@ fn test_no_pings_submitted_if_upload_disabled() {
|
|||
// Regression test, bug 1603571
|
||||
|
||||
let (mut glean, _) = new_glean(None);
|
||||
let ping_type = PingType::new("store1", true, true);
|
||||
let ping_type = PingType::new("store1", true, true, vec![]);
|
||||
glean.register_ping_type(&ping_type);
|
||||
|
||||
assert!(glean.submit_ping(&ping_type).is_ok());
|
||||
assert!(glean.submit_ping(&ping_type, None).is_ok());
|
||||
assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
// Disable upload, then try to sumbit
|
||||
glean.set_upload_enabled(false);
|
||||
|
||||
assert!(glean.submit_ping(&ping_type).is_ok());
|
||||
assert!(glean.submit_ping(&ping_type, None).is_ok());
|
||||
assert_eq!(0, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
|
||||
// Test again through the direct call
|
||||
assert!(ping_type.submit(&glean).is_ok());
|
||||
assert!(ping_type.submit(&glean, None).is_ok());
|
||||
assert_eq!(0, get_queued_pings(glean.get_data_path()).unwrap().len());
|
||||
}
|
||||
|
|
|
@ -314,3 +314,33 @@ fn large_nanoseconds_values() {
|
|||
// Check that we got the right sum and number of samples.
|
||||
assert_eq!(val.sum() as u64, time);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stopping_non_existing_id_records_an_error() {
|
||||
let (glean, _t) = new_glean(None);
|
||||
|
||||
let mut metric = TimingDistributionMetric::new(
|
||||
CommonMetricData {
|
||||
name: "non_existing_id".into(),
|
||||
category: "test".into(),
|
||||
send_in_pings: vec!["store1".into()],
|
||||
disabled: false,
|
||||
lifetime: Lifetime::Ping,
|
||||
..Default::default()
|
||||
},
|
||||
TimeUnit::Nanosecond,
|
||||
);
|
||||
|
||||
metric.set_stop_and_accumulate(&glean, 3785, 60);
|
||||
|
||||
// 1 error should be reported.
|
||||
assert_eq!(
|
||||
Ok(1),
|
||||
test_get_num_recorded_errors(
|
||||
&glean,
|
||||
metric.meta(),
|
||||
ErrorType::InvalidState,
|
||||
Some("store1")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"files":{"CHANGELOG.md":"82c0aee7d1e503b71a31115208e303f6e689a4da91c98a95c8827bdc81080bf4","Cargo.toml":"b4dccc5d6540f08052daec134025c0510b8ea87f7d3a12d893616b56ba619efa","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"f234c25515132b7205fcf05545f1d5661bba6f2c189aca848290c7e32b832952","src/configuration.rs":"16e3ec9be802ac37b39d2ad4d1a4702d7e5b462071ef7f0265aea84de520641f","src/core_metrics.rs":"0490dfefaccdbb36ead40af02fa4aa75ebb98056884f2eef4340fe27a24f6cfd","src/lib.rs":"f8dc2468495e9c145bad61adad365e76cb99df3968c64f70ae2a77ec28010506","src/metrics/mod.rs":"3f0dc73758bc5836362742b4ab424032f4f398151c6e85ea879a4e7641614347","src/metrics/ping.rs":"27f5153b33060b817304e11ec58cb17184a6264f29fc45cd7042464c3a9263b5","src/system.rs":"7dbe5007bdaa3d4547c992757c26b7543e059f179078f06b946adad0a0bb4e34","src/test.rs":"c9870591227a849eaf52f914abdd310ec5e01ce6ddf9946a3ac3990b601f8fb6"},"package":"182218f42395f369a1a5372d334699458a606aa557dd2e23998ba25704964e2c"}
|
|
@ -1,21 +0,0 @@
|
|||
# v0.0.5 (2020-01-15)
|
||||
|
||||
* Upgraded Glean dependency
|
||||
* See [full Glean changelog](https://github.com/mozilla/glean/blob/v24.0.0/CHANGELOG.md)
|
||||
* Reset core client metrics when re-enabling upload ([#620](https://github.com/mozilla/glean/pull/620))
|
||||
|
||||
# v0.0.4 (2019-12-20)
|
||||
|
||||
* Set target architecture in `client_info` ([#603](https://github.com/mozilla/glean/pull/603))
|
||||
|
||||
# v0.0.3 (2019-12-19)
|
||||
|
||||
* **Breaking Change**: Stub out client info values and provide a way to set app version information ([#592](https://github.com/mozilla/glean/pull/592))
|
||||
|
||||
# v0.0.2 (2019-12-09)
|
||||
|
||||
* Removed glean-ffi dependency in favor of implementing state in this crate
|
||||
|
||||
# v0.0.1 (2019-12-05)
|
||||
|
||||
First release of the Glean Rust API preview.
|
|
@ -1,47 +0,0 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies
|
||||
#
|
||||
# If you believe there's an error in this file please file an
|
||||
# issue against the rust-lang/cargo repository. If you're
|
||||
# editing this file be aware that the upstream Cargo.toml
|
||||
# will likely look very different (and much more reasonable)
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "glean-preview"
|
||||
version = "0.0.5"
|
||||
authors = ["Jan-Erik Rediger <jrediger@mozilla.com>", "The Glean Team <glean-team@mozilla.com>"]
|
||||
include = ["README.md", "LICENSE", "CHANGELOG.md", "src/**/*", "tests/**/*", "Cargo.toml"]
|
||||
description = "Nice Glean SDK Rust API"
|
||||
readme = "README.md"
|
||||
keywords = ["telemetry", "glean"]
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/mozilla/glean"
|
||||
[dependencies.glean-core]
|
||||
version = "24.0.0"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.4.0"
|
||||
|
||||
[dependencies.once_cell]
|
||||
version = "1.2.0"
|
||||
[dev-dependencies.env_logger]
|
||||
version = "0.7.1"
|
||||
features = ["termcolor", "atty", "humantime"]
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.log]
|
||||
version = "0.4.8"
|
||||
|
||||
[dev-dependencies.tempfile]
|
||||
version = "3.1.0"
|
||||
[badges.circle-ci]
|
||||
branch = "master"
|
||||
repository = "mozilla/glean"
|
||||
|
||||
[badges.maintenance]
|
||||
status = "actively-developed"
|
|
@ -1,373 +0,0 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
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/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
|
@ -1,47 +0,0 @@
|
|||
# glean-preview
|
||||
|
||||
The `Glean SDK` is a modern approach for a Telemetry library and is part of the [Glean project](https://docs.telemetry.mozilla.org/concepts/glean/glean.html).
|
||||
|
||||
## `glean-preview`
|
||||
|
||||
|
||||
This library provides a Rust API on top of Glean, targeted to Rust consumers.
|
||||
|
||||
**Note: `glean-preview` is currently under development and not yet ready for use.**
|
||||
|
||||
## Documentation
|
||||
|
||||
All documentation is available online:
|
||||
|
||||
* [The Glean SDK Book][book]
|
||||
* [API documentation][apidocs]
|
||||
|
||||
[book]: https://mozilla.github.io/glean/
|
||||
[apidocs]: https://mozilla.github.io/glean/docs/glean_preview/index.html
|
||||
|
||||
## Example
|
||||
|
||||
```rust,no_run
|
||||
use glean_preview::{Configuration, Error, metrics::*};
|
||||
|
||||
let cfg = Configuration {
|
||||
data_path: "/tmp/data".into(),
|
||||
application_id: "org.mozilla.glean_core.example".into(),
|
||||
upload_enabled: true,
|
||||
max_events: None,
|
||||
delay_ping_lifetime_io: false,
|
||||
};
|
||||
glean_preview::initialize(cfg)?;
|
||||
|
||||
let prototype_ping = PingType::new("prototype", true, true);
|
||||
|
||||
glean_preview::register_ping_type(&prototype_ping);
|
||||
|
||||
prototype_ping.submit();
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
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/
|
|
@ -1,22 +0,0 @@
|
|||
// 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 Glean configuration.
|
||||
///
|
||||
/// Optional values will be filled in with default values.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Configuration {
|
||||
/// Whether upload should be enabled.
|
||||
pub upload_enabled: bool,
|
||||
/// Path to a directory to store all data in.
|
||||
pub data_path: String,
|
||||
/// The application ID (will be sanitized during initialization).
|
||||
pub application_id: String,
|
||||
/// The maximum number of events to store before sending a ping containing events.
|
||||
pub max_events: Option<usize>,
|
||||
/// Whether Glean should delay persistence of data from metrics with ping lifetime.
|
||||
pub delay_ping_lifetime_io: bool,
|
||||
/// The release channel the application is on, if known.
|
||||
pub channel: Option<String>,
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
// 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 glean_core::{metrics::StringMetric, CommonMetricData, Lifetime};
|
||||
|
||||
/// Metrics included in every ping as `client_info`.
|
||||
#[derive(Debug)]
|
||||
pub struct ClientInfoMetrics {
|
||||
/// The build identifier generated by the CI system (e.g. "1234/A").
|
||||
pub app_build: String,
|
||||
/// The user visible version string (e.g. "1.0.3").
|
||||
pub app_display_version: String,
|
||||
}
|
||||
|
||||
impl ClientInfoMetrics {
|
||||
/// Create the client info with dummy values for all.
|
||||
pub fn unknown() -> Self {
|
||||
ClientInfoMetrics {
|
||||
app_build: "unknown".to_string(),
|
||||
app_display_version: "unknown".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InternalMetrics {
|
||||
pub app_build: StringMetric,
|
||||
pub app_display_version: StringMetric,
|
||||
pub app_channel: StringMetric,
|
||||
pub os: StringMetric,
|
||||
pub os_version: StringMetric,
|
||||
pub architecture: StringMetric,
|
||||
pub device_manufacturer: StringMetric,
|
||||
pub device_model: StringMetric,
|
||||
}
|
||||
|
||||
impl InternalMetrics {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
app_build: StringMetric::new(CommonMetricData {
|
||||
name: "app_build".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
app_display_version: StringMetric::new(CommonMetricData {
|
||||
name: "app_display_version".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
app_channel: StringMetric::new(CommonMetricData {
|
||||
name: "app_channel".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
os: StringMetric::new(CommonMetricData {
|
||||
name: "os".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
os_version: StringMetric::new(CommonMetricData {
|
||||
name: "os_version".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
architecture: StringMetric::new(CommonMetricData {
|
||||
name: "architecture".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
device_manufacturer: StringMetric::new(CommonMetricData {
|
||||
name: "device_manufacturer".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
device_model: StringMetric::new(CommonMetricData {
|
||||
name: "device_model".into(),
|
||||
category: "".into(),
|
||||
send_in_pings: vec!["glean_client_info".into()],
|
||||
lifetime: Lifetime::Application,
|
||||
disabled: false,
|
||||
dynamic_label: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
// 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/.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Glean is a modern approach for recording and sending Telemetry data.
|
||||
//!
|
||||
//! It's in use at Mozilla.
|
||||
//!
|
||||
//! All documentation can be found online:
|
||||
//!
|
||||
//! ## [The Glean SDK Book](https://mozilla.github.io/glean)
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! Initialize Glean, register a ping and then send it.
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use glean_preview::{Configuration, ClientInfoMetrics, Error, metrics::*};
|
||||
//! # fn main() -> Result<(), Error> {
|
||||
//! let cfg = Configuration {
|
||||
//! data_path: "/tmp/data".into(),
|
||||
//! application_id: "org.mozilla.glean_core.example".into(),
|
||||
//! upload_enabled: true,
|
||||
//! max_events: None,
|
||||
//! delay_ping_lifetime_io: false,
|
||||
//! channel: None,
|
||||
//! };
|
||||
//! glean_preview::initialize(cfg, ClientInfoMetrics::unknown())?;
|
||||
//!
|
||||
//! let prototype_ping = PingType::new("prototype", true, true);
|
||||
//!
|
||||
//! glean_preview::register_ping_type(&prototype_ping);
|
||||
//!
|
||||
//! prototype_ping.submit();
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub use configuration::Configuration;
|
||||
pub use core_metrics::ClientInfoMetrics;
|
||||
pub use glean_core::{CommonMetricData, Error, Glean, Lifetime, Result};
|
||||
|
||||
mod configuration;
|
||||
mod core_metrics;
|
||||
pub mod metrics;
|
||||
mod system;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GleanWrapper {
|
||||
instance: Glean,
|
||||
channel: Option<String>,
|
||||
client_info: ClientInfoMetrics,
|
||||
}
|
||||
|
||||
static GLEAN: OnceCell<Mutex<GleanWrapper>> = OnceCell::new();
|
||||
|
||||
/// Get a reference to the global Glean object.
|
||||
///
|
||||
/// Panics if no global Glean object was set.
|
||||
fn global_glean() -> &'static Mutex<GleanWrapper> {
|
||||
GLEAN.get().unwrap()
|
||||
}
|
||||
|
||||
/// Set or replace the global Glean object.
|
||||
fn setup_glean(glean: GleanWrapper) -> Result<()> {
|
||||
if GLEAN.get().is_none() {
|
||||
GLEAN.set(Mutex::new(glean)).unwrap();
|
||||
} else {
|
||||
let mut lock = GLEAN.get().unwrap().lock().unwrap();
|
||||
*lock = glean;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn with_glean<F, R>(f: F) -> R
|
||||
where
|
||||
F: Fn(&Glean) -> R,
|
||||
{
|
||||
let lock = global_glean().lock().unwrap();
|
||||
f(&lock.instance)
|
||||
}
|
||||
|
||||
fn with_glean_wrapper_mut<F, R>(f: F) -> R
|
||||
where
|
||||
F: Fn(&mut GleanWrapper) -> R,
|
||||
{
|
||||
let mut lock = global_glean().lock().unwrap();
|
||||
f(&mut lock)
|
||||
}
|
||||
|
||||
fn with_glean_mut<F, R>(f: F) -> R
|
||||
where
|
||||
F: Fn(&mut Glean) -> R,
|
||||
{
|
||||
let mut lock = global_glean().lock().unwrap();
|
||||
f(&mut lock.instance)
|
||||
}
|
||||
|
||||
/// Create and initialize a new Glean object.
|
||||
///
|
||||
/// See `glean_core::Glean::new`.
|
||||
pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) -> Result<()> {
|
||||
let core_cfg = glean_core::Configuration {
|
||||
upload_enabled: cfg.upload_enabled,
|
||||
data_path: cfg.data_path.clone(),
|
||||
application_id: cfg.application_id.clone(),
|
||||
max_events: cfg.max_events,
|
||||
delay_ping_lifetime_io: cfg.delay_ping_lifetime_io,
|
||||
};
|
||||
let glean = Glean::new(core_cfg)?;
|
||||
|
||||
// First initialize core metrics
|
||||
initialize_core_metrics(&glean, &client_info, cfg.channel.clone());
|
||||
|
||||
// Now make this the global object available to others.
|
||||
let wrapper = GleanWrapper {
|
||||
instance: glean,
|
||||
channel: cfg.channel,
|
||||
client_info,
|
||||
};
|
||||
setup_glean(wrapper)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initialize_core_metrics(
|
||||
glean: &Glean,
|
||||
client_info: &ClientInfoMetrics,
|
||||
channel: Option<String>,
|
||||
) {
|
||||
let core_metrics = core_metrics::InternalMetrics::new();
|
||||
|
||||
core_metrics
|
||||
.app_build
|
||||
.set(glean, &client_info.app_build[..]);
|
||||
core_metrics
|
||||
.app_display_version
|
||||
.set(glean, &client_info.app_display_version[..]);
|
||||
if let Some(app_channel) = channel {
|
||||
core_metrics.app_channel.set(glean, app_channel);
|
||||
}
|
||||
core_metrics.os.set(glean, system::OS.to_string());
|
||||
core_metrics.os_version.set(glean, "unknown".to_string());
|
||||
core_metrics
|
||||
.architecture
|
||||
.set(glean, system::ARCH.to_string());
|
||||
core_metrics
|
||||
.device_manufacturer
|
||||
.set(glean, "unknown".to_string());
|
||||
core_metrics.device_model.set(glean, "unknown".to_string());
|
||||
}
|
||||
|
||||
/// Set whether upload is enabled or not.
|
||||
///
|
||||
/// See `glean_core::Glean.set_upload_enabled`.
|
||||
pub fn set_upload_enabled(enabled: bool) -> bool {
|
||||
with_glean_wrapper_mut(|glean| {
|
||||
let old_enabled = glean.instance.is_upload_enabled();
|
||||
glean.instance.set_upload_enabled(enabled);
|
||||
|
||||
if !old_enabled && enabled {
|
||||
// If uploading is being re-enabled, we have to restore the
|
||||
// application-lifetime metrics.
|
||||
initialize_core_metrics(&glean.instance, &glean.client_info, glean.channel.clone());
|
||||
}
|
||||
|
||||
enabled
|
||||
})
|
||||
}
|
||||
|
||||
/// Determine whether upload is enabled.
|
||||
///
|
||||
/// See `glean_core::Glean.is_upload_enabled`.
|
||||
pub fn is_upload_enabled() -> bool {
|
||||
with_glean(|glean| glean.is_upload_enabled())
|
||||
}
|
||||
|
||||
/// Register a new [`PingType`](metrics/struct.PingType.html).
|
||||
pub fn register_ping_type(ping: &metrics::PingType) {
|
||||
with_glean_mut(|glean| {
|
||||
glean.register_ping_type(&ping.ping_type);
|
||||
})
|
||||
}
|
||||
|
||||
/// Collect and submit a ping for eventual uploading.
|
||||
///
|
||||
/// See `glean_core::Glean.submit_ping`.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// Returns true if a ping was assembled and queued, false otherwise.
|
||||
pub fn submit_ping(ping: &metrics::PingType) -> bool {
|
||||
submit_ping_by_name(&ping.name)
|
||||
}
|
||||
|
||||
/// Collect and submit a ping for eventual uploading by name.
|
||||
///
|
||||
/// See `glean_core::Glean.submit_ping_by_name`.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// Returns true if a ping was assembled and queued, false otherwise.
|
||||
pub fn submit_ping_by_name(ping: &str) -> bool {
|
||||
submit_pings_by_name(&[ping.to_string()])
|
||||
}
|
||||
|
||||
/// Collect and submit multiple pings by name for eventual uploading.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// Returns true if at least one ping was assembled and queued, false otherwise.
|
||||
pub fn submit_pings_by_name(pings: &[String]) -> bool {
|
||||
with_glean(|glean| glean.submit_pings_by_name(pings))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -1,39 +0,0 @@
|
|||
// 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/.
|
||||
|
||||
/// Stores information about a ping.
|
||||
///
|
||||
/// This is required so that given metric data queued on disk we can send
|
||||
/// pings with the correct settings, e.g. whether it has a client_id.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PingType {
|
||||
pub(crate) name: String,
|
||||
pub(crate) ping_type: glean_core::metrics::PingType,
|
||||
}
|
||||
|
||||
impl PingType {
|
||||
/// Create a new ping type for the given name, whether to include the client ID and whether to
|
||||
/// send this ping empty.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// * `name` - The name of the ping.
|
||||
/// * `include_client_id` - Whether to include the client ID in the assembled ping when.
|
||||
/// * `send_if_empty` - Whether the ping should be sent empty or not.
|
||||
pub fn new<A: Into<String>>(name: A, include_client_id: bool, send_if_empty: bool) -> Self {
|
||||
let name = name.into();
|
||||
let ping_type =
|
||||
glean_core::metrics::PingType::new(name.clone(), include_client_id, send_if_empty);
|
||||
Self { name, ping_type }
|
||||
}
|
||||
|
||||
/// Submit the ping.
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// Returns true if a ping was assembled and queued, false otherwise.
|
||||
pub fn submit(&self) -> bool {
|
||||
crate::submit_ping(self)
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
// 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/.
|
||||
|
||||
// Detect and expose `target_os` as a constant.
|
||||
// Whether this is a good idea is somewhat debatable.
|
||||
//
|
||||
// Code adopted from the "platforms" crate: <https://github.com/RustSec/platforms-crate>.
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
/// `target_os` when building this crate: `android`
|
||||
pub const OS: &str = "Android";
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
/// `target_os` when building this crate: `ios`
|
||||
pub const OS: &str = "iOS";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
/// `target_os` when building this crate: `linux`
|
||||
pub const OS: &str = "Linux";
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// `target_os` when building this crate: `macos`
|
||||
pub const OS: &str = "MacOS";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
/// `target_os` when building this crate: `windows`
|
||||
pub const OS: &str = "Windows";
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
)))]
|
||||
pub const OS: &str = "unknown";
|
||||
|
||||
// Detect and expose `target_arch` as a constant
|
||||
// Whether this is a good idea is somewhat debatable
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// `target_arch` when building this crate: `aarch64`
|
||||
pub const ARCH: &str = "aarch64";
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
/// `target_arch` when building this crate: `arm`
|
||||
pub const ARCH: &str = "arm";
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
/// `target_arch` when building this crate: `x86`
|
||||
pub const ARCH: &str = "x86";
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// `target_arch` when building this crate: `x86_64`
|
||||
pub const ARCH: &str = "x86_64";
|
||||
|
||||
#[cfg(not(any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "arm",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
)))]
|
||||
/// `target_arch` when building this crate: unknown!
|
||||
pub const ARCH: &str = "unknown";
|
|
@ -1,77 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
const GLOBAL_APPLICATION_ID: &str = "org.mozilla.fogotype.test";
|
||||
|
||||
// Create a new instance of Glean with a temporary directory.
|
||||
// We need to keep the `TempDir` alive, so that it's not deleted before we stop using it.
|
||||
fn new_glean() -> tempfile::TempDir {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let tmpname = dir.path().display().to_string();
|
||||
|
||||
let cfg = Configuration {
|
||||
data_path: tmpname,
|
||||
application_id: GLOBAL_APPLICATION_ID.into(),
|
||||
upload_enabled: true,
|
||||
max_events: None,
|
||||
delay_ping_lifetime_io: false,
|
||||
channel: Some("testing".into()),
|
||||
};
|
||||
|
||||
initialize(cfg, ClientInfoMetrics::unknown()).unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_initializes() {
|
||||
env_logger::try_init().ok();
|
||||
let _ = new_glean();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_toggles_upload() {
|
||||
env_logger::try_init().ok();
|
||||
|
||||
let _t = new_glean();
|
||||
|
||||
assert!(crate::is_upload_enabled());
|
||||
crate::set_upload_enabled(false);
|
||||
assert!(!crate::is_upload_enabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_info_reset_after_toggle() {
|
||||
env_logger::try_init().ok();
|
||||
|
||||
let _t = new_glean();
|
||||
|
||||
assert!(crate::is_upload_enabled());
|
||||
|
||||
// Metrics are identified by category.name, so it's safe to recreate the objects here.
|
||||
let core_metrics = core_metrics::InternalMetrics::new();
|
||||
|
||||
// At start we should have a value.
|
||||
with_glean(|glean| {
|
||||
assert!(core_metrics
|
||||
.os
|
||||
.test_get_value(glean, "glean_client_info")
|
||||
.is_some());
|
||||
});
|
||||
|
||||
// Disabling upload clears everything.
|
||||
crate::set_upload_enabled(false);
|
||||
with_glean(|glean| {
|
||||
assert!(!core_metrics
|
||||
.os
|
||||
.test_get_value(glean, "glean_client_info")
|
||||
.is_some());
|
||||
});
|
||||
|
||||
// Re-enabling upload should reset the values.
|
||||
crate::set_upload_enabled(true);
|
||||
with_glean(|glean| {
|
||||
assert!(core_metrics
|
||||
.os
|
||||
.test_get_value(glean, "glean_client_info")
|
||||
.is_some());
|
||||
});
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "fog"
|
||||
version = "0.1.0"
|
||||
authors = ["The Mozilla Project Developers"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
glean-core = "25.1.0"
|
||||
log = "0.4"
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../xpcom/rust/nsstring" }
|
||||
static_prefs = { path = "../../../modules/libpref/init/static_prefs" }
|
||||
xpcom = { path = "../../../xpcom/rust/xpcom" }
|
|
@ -2,8 +2,10 @@
|
|||
// 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.
|
||||
use nserror::{nsresult, NS_OK};
|
||||
use nsstring::nsAString;
|
||||
|
||||
mod ping;
|
||||
|
||||
pub use ping::PingType;
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fog_init(_data_dir: &nsAString, _pingsender_path: &nsAString) -> nsresult {
|
||||
NS_OK
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
[package]
|
||||
name = "fog"
|
||||
version = "0.1.0"
|
||||
authors = ["The Mozilla Project Developers"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cstr = "0.1"
|
||||
glean-preview = "0.0.5"
|
||||
log = "0.4"
|
||||
nserror = { path = "../../../../xpcom/rust/nserror" }
|
||||
nsstring = { path = "../../../../xpcom/rust/nsstring" }
|
||||
static_prefs = { path = "../../../../modules/libpref/init/static_prefs" }
|
||||
xpcom = { path = "../../../../xpcom/rust/xpcom" }
|
|
@ -1,24 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This file defines the "prototype" ping used by the FOGotype.
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
|
||||
|
||||
prototype:
|
||||
description: >
|
||||
A ping to show that ping sending works in the FOGotype.
|
||||
Sent hourly, it has no payload.
|
||||
The `client_id` it carries is not guaranteed to be stable.
|
||||
Also, the `client_id` is not the Telemetry `client_id`.
|
||||
include_client_id: true
|
||||
send_if_empty: true
|
||||
notification_emails:
|
||||
- chutten@mozilla.com
|
||||
- glean-team@mozilla.com
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1591564
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1591564#c12
|
|
@ -1,167 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
#[macro_use]
|
||||
extern crate cstr;
|
||||
|
||||
use glean_preview::metrics::PingType;
|
||||
use glean_preview::{ClientInfoMetrics, Configuration};
|
||||
use log::error;
|
||||
use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
|
||||
use nsstring::{nsAString, nsString};
|
||||
use std::ffi::CString;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufRead, BufReader, Error, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread::JoinHandle;
|
||||
use std::{thread, time};
|
||||
use xpcom::interfaces::{nsIFile, nsIProcess};
|
||||
use xpcom::RefPtr;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fog_init(data_dir: &nsAString, pingsender_path: &nsAString) -> nsresult {
|
||||
let upload_enabled = static_prefs::pref!("datareporting.healthreport.uploadEnabled");
|
||||
|
||||
let pingsender_path = pingsender_path.to_string();
|
||||
let data_dir = data_dir.to_string();
|
||||
if thread::Builder::new()
|
||||
.name("fogotype_init".to_owned())
|
||||
.spawn(move || {
|
||||
let cfg = Configuration {
|
||||
data_path: data_dir.clone(),
|
||||
application_id: "org.mozilla.fogotype".into(),
|
||||
upload_enabled,
|
||||
max_events: None,
|
||||
delay_ping_lifetime_io: false, // We will want this eventually.
|
||||
channel: Some("nightly".into()),
|
||||
};
|
||||
|
||||
// TODO: Build our own ClientInfoMetrics instead of using unknown().
|
||||
if let Err(e) = glean_preview::initialize(cfg, ClientInfoMetrics::unknown()) {
|
||||
error!("Failed to init glean_preview due to {:?}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut data_path = PathBuf::from(data_dir);
|
||||
data_path.push("pending_pings");
|
||||
|
||||
// We ignore the returned JoinHandle for the nonce.
|
||||
// The detached thread will live until this process (the main process) dies.
|
||||
if let Err(e) = prototype_ping_init(data_path, pingsender_path) {
|
||||
error!("Failed to init fogtotype prototype ping due to {:?}", e);
|
||||
}
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_OK
|
||||
}
|
||||
|
||||
fn prototype_ping_init(
|
||||
ping_dir: PathBuf,
|
||||
pingsender_path: String,
|
||||
) -> Result<JoinHandle<()>, Error> {
|
||||
thread::Builder::new()
|
||||
.name("fogotype_ping".to_owned())
|
||||
.spawn(move || {
|
||||
let prototype_ping = PingType::new("prototype", true, true);
|
||||
glean_preview::register_ping_type(&prototype_ping);
|
||||
|
||||
let an_hour = time::Duration::from_secs(60 * 60);
|
||||
loop {
|
||||
thread::sleep(an_hour);
|
||||
let upload_enabled =
|
||||
static_prefs::pref!("datareporting.healthreport.uploadEnabled");
|
||||
glean_preview::set_upload_enabled(upload_enabled);
|
||||
if !upload_enabled {
|
||||
continue;
|
||||
}
|
||||
prototype_ping.submit();
|
||||
if let Err(e) = send_all_pings(&ping_dir, &pingsender_path) {
|
||||
error!("Failed to send all pings due to {:?}", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn send_all_pings(
|
||||
ping_dir: &Path,
|
||||
pingsender_path: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
assert!(ping_dir.is_dir());
|
||||
// This will be a multi-step process:
|
||||
// 1. Ensure we have an (empty) subdirectory in ping_dir called "telemetry" we can work within.
|
||||
// 2. Split the endpoint out of the glean-format ping in ping_dir, writing the rest to telemetry/.
|
||||
// 3. Invoke pingsender{.exe} for the ping in telemetry to the endpoint from the glean-format ping file we most timely ripp'd.
|
||||
// 4. Return to 2 while pings remain in ping_dir
|
||||
|
||||
let telemetry_dir = ping_dir.join("telemetry");
|
||||
let _ = fs::remove_dir_all(&telemetry_dir);
|
||||
fs::create_dir(&telemetry_dir)?;
|
||||
|
||||
for entry in fs::read_dir(ping_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
// Do the things.
|
||||
let file = File::open(&path)?;
|
||||
let reader = BufReader::new(file);
|
||||
let lines: Vec<String> = reader.lines().filter_map(io::Result::ok).collect();
|
||||
|
||||
// Sanity check: Glean SDK ping file format is two lines.
|
||||
// First line is the path of the ingestion endpoint.
|
||||
// Second line is the payload.
|
||||
if lines.len() != 2 {
|
||||
// Doesn't look like a Glean SDK-format ping. Get rid of it.
|
||||
fs::remove_file(path)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let telemetry_ping_path =
|
||||
telemetry_dir.join(path.file_name().ok_or("ping dir file name invalid")?);
|
||||
let mut telemetry_ping_file = File::create(&telemetry_ping_path)?;
|
||||
write!(telemetry_ping_file, "{}", lines[1])?;
|
||||
|
||||
fs::remove_file(path)?;
|
||||
|
||||
let pingsender_file: RefPtr<nsIFile> =
|
||||
xpcom::create_instance(&cstr!("@mozilla.org/file/local;1"))
|
||||
.ok_or("couldn't create nsIFile")?;
|
||||
let process: RefPtr<nsIProcess> =
|
||||
xpcom::create_instance(&cstr!("@mozilla.org/process/util;1"))
|
||||
.ok_or("couldn't create nsIProcess")?;
|
||||
unsafe {
|
||||
pingsender_file
|
||||
.InitWithPath(&*nsString::from(pingsender_path) as &nsAString)
|
||||
.to_result()?;
|
||||
process.Init(&*pingsender_file).to_result()?;
|
||||
process.SetStartHidden(true).to_result()?;
|
||||
process.SetNoShell(true).to_result()?;
|
||||
};
|
||||
let server_url = CString::new(format!(
|
||||
"https://incoming.telemetry.mozilla.org{}",
|
||||
lines[0]
|
||||
))?;
|
||||
let telemetry_ping_path_cstr = CString::new(
|
||||
telemetry_ping_path
|
||||
.to_str()
|
||||
.expect("non-unicode ping path character"),
|
||||
)?;
|
||||
let mut args = [server_url.as_ptr(), telemetry_ping_path_cstr.as_ptr()];
|
||||
let args_length = 2;
|
||||
// Block while running the process.
|
||||
// We should feel free to do this because we're on our own thread.
|
||||
// Also, if we run async, nsIProcess tries to get the ObserverService on this thread, and asserts.
|
||||
unsafe {
|
||||
process
|
||||
.Run(true /* blocking */, args.as_mut_ptr(), args_length)
|
||||
.to_result()?;
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -31,7 +31,7 @@ webrtc = ["gkrust-shared/webrtc"]
|
|||
wasm_library_sandboxing = ["gkrust-shared/wasm_library_sandboxing"]
|
||||
webgpu = ["gkrust-shared/webgpu"]
|
||||
remote_agent = ["gkrust-shared/remote"]
|
||||
fogotype = ["gkrust-shared/fogotype"]
|
||||
glean = ["gkrust-shared/glean"]
|
||||
|
||||
[dependencies]
|
||||
bench-collections-gtest = { path = "../../../../xpcom/rust/gtest/bench-collections" }
|
||||
|
|
|
@ -32,7 +32,7 @@ webrtc = ["gkrust-shared/webrtc"]
|
|||
wasm_library_sandboxing = ["gkrust-shared/wasm_library_sandboxing"]
|
||||
webgpu = ["gkrust-shared/webgpu"]
|
||||
remote_agent = ["gkrust-shared/remote"]
|
||||
fogotype = ["gkrust-shared/fogotype"]
|
||||
glean = ["gkrust-shared/glean"]
|
||||
|
||||
[dependencies]
|
||||
gkrust-shared = { path = "shared" }
|
||||
|
|
|
@ -74,8 +74,8 @@ if CONFIG['MOZ_WEBRTC']:
|
|||
if CONFIG['ENABLE_REMOTE_AGENT']:
|
||||
gkrust_features += ['remote_agent']
|
||||
|
||||
if CONFIG['MOZ_FOGOTYPE']:
|
||||
gkrust_features += ['fogotype']
|
||||
if CONFIG['MOZ_GLEAN']:
|
||||
gkrust_features += ['glean']
|
||||
|
||||
if CONFIG['MOZ_USING_WASM_SANDBOXING']:
|
||||
gkrust_features += ['wasm_library_sandboxing']
|
||||
|
|
|
@ -47,7 +47,7 @@ rlbox_lucet_sandbox = { version = "0.1.0", optional = true }
|
|||
wgpu-remote = { path = "../../../../gfx/wgpu/wgpu-remote", optional = true }
|
||||
mapped_hyph = { git = "https://github.com/jfkthame/mapped_hyph.git", tag = "v0.3.0" }
|
||||
remote = { path = "../../../../remote", optional = true }
|
||||
fog = { path = "../../../components/telemetry/fog", optional = true }
|
||||
fog = { path = "../../../components/glean", optional = true }
|
||||
|
||||
unic-langid = { version = "0.8", features = ["likelysubtags"] }
|
||||
unic-langid-ffi = { path = "../../../../intl/locale/rust/unic-langid-ffi" }
|
||||
|
@ -87,7 +87,7 @@ webrtc = ["mdns_service"]
|
|||
wasm_library_sandboxing = ["rlbox_lucet_sandbox"]
|
||||
webgpu = ["wgpu-remote"]
|
||||
remote_agent = ["remote"]
|
||||
fogotype = ["fog"]
|
||||
glean = ["fog"]
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
|
|
@ -25,7 +25,7 @@ extern crate cubeb_coreaudio;
|
|||
extern crate cubeb_pulse;
|
||||
extern crate encoding_glue;
|
||||
extern crate env_logger;
|
||||
#[cfg(feature = "fogotype")]
|
||||
#[cfg(feature = "glean")]
|
||||
extern crate fog;
|
||||
extern crate gkrust_utils;
|
||||
extern crate jsrust_shared;
|
||||
|
|
|
@ -1978,16 +1978,16 @@ set_config('MOZ_NEW_NOTIFICATION_STORE', True, when=new_notification_store)
|
|||
set_define('MOZ_NEW_NOTIFICATION_STORE', True, when=new_notification_store)
|
||||
|
||||
|
||||
# FOGotype prototype Glean SDK Integration Crate
|
||||
# Glean SDK Integration Crate
|
||||
# ==============================================================
|
||||
|
||||
@depends(milestone)
|
||||
def fogotype(milestone):
|
||||
def glean(milestone):
|
||||
if milestone.is_nightly:
|
||||
return True
|
||||
|
||||
set_config('MOZ_FOGOTYPE', True, when=fogotype)
|
||||
set_define('MOZ_FOGOTYPE', True, when=fogotype)
|
||||
set_config('MOZ_GLEAN', True, when=glean)
|
||||
set_define('MOZ_GLEAN', True, when=glean)
|
||||
|
||||
# dump_syms
|
||||
# ==============================================================
|
||||
|
|
|
@ -40,7 +40,7 @@ clippy:
|
|||
- testing/mozbase/rust/mozversion/
|
||||
- testing/webdriver/
|
||||
- toolkit/components/kvstore/
|
||||
- toolkit/components/telemetry/fog/
|
||||
- toolkit/components/glean/
|
||||
- toolkit/components/xulstore/tests/gtest/
|
||||
- toolkit/library/rust/
|
||||
- tools/fuzzing/rust/
|
||||
|
|
Загрузка…
Ссылка в новой задаче