remove scoping and switch to pure statics for fast path

This commit is contained in:
Toby Lawrence 2020-10-27 08:58:18 -04:00
parent cec241dec9
commit cb54c40d13
14 changed files with 241 additions and 349 deletions

View File

@ -25,3 +25,6 @@ proc-macro2 = "1.0"
proc-macro-hack = "0.5"
lazy_static = "1.4"
regex = "1.3"
[dev-dependencies]
syn = { version = "1.0", features = ["full"] }

View File

@ -3,7 +3,6 @@ extern crate proc_macro;
use self::proc_macro::TokenStream;
use lazy_static::lazy_static;
use proc_macro2::Span;
use proc_macro_hack::proc_macro_hack;
use quote::{format_ident, quote, ToTokens};
use regex::Regex;
@ -14,38 +13,24 @@ use syn::{parse_macro_input, Expr, LitStr, Token};
#[cfg(test)]
mod tests;
enum Key {
NotScoped(LitStr),
Scoped(LitStr),
}
impl Key {
pub fn span(&self) -> Span {
match self {
Key::Scoped(s) => s.span(),
Key::NotScoped(s) => s.span(),
}
}
}
enum Labels {
Existing(Expr),
Inline(Vec<(LitStr, Expr)>),
}
struct WithoutExpression {
key: Key,
key: LitStr,
labels: Option<Labels>,
}
struct WithExpression {
key: Key,
key: LitStr,
op_value: Expr,
labels: Option<Labels>,
}
struct Registration {
key: Key,
key: LitStr,
unit: Option<Expr>,
description: Option<LitStr>,
labels: Option<Labels>,
@ -239,7 +224,7 @@ pub fn histogram(input: TokenStream) -> TokenStream {
fn get_expanded_registration(
metric_type: &str,
key: Key,
key: LitStr,
unit: Option<Expr>,
description: Option<LitStr>,
labels: Option<Labels>,
@ -272,45 +257,64 @@ fn get_expanded_registration(
fn get_expanded_callsite<V>(
metric_type: &str,
op_type: &str,
key: Key,
key: LitStr,
labels: Option<Labels>,
op_values: V,
) -> proc_macro2::TokenStream
where
V: ToTokens,
{
let use_fast_path = can_use_fast_path(&labels);
let key = key_to_quoted(key, labels);
// We use a helper method for histogram values to coerce into u64, but otherwise,
// just pass through whatever the caller gave us.
let op_values = if metric_type == "histogram" {
quote! {
metrics::__into_u64(#op_values)
}
quote! { metrics::__into_u64(#op_values) }
} else {
quote! { #op_values }
};
let op_ident = format_ident!("{}_{}", op_type, metric_type);
let use_fast_path = can_use_fast_path(&labels);
if use_fast_path {
// We're on the fast path here, so we'll build our key, statically cache it,
// and use a borrowed reference to it for this and future operations.
let statics = match labels {
Some(Labels::Inline(pairs)) => {
let labels = pairs
.into_iter()
.map(|(key, val)| quote! { metrics::Label::from_static_parts(#key, #val) })
.collect::<Vec<_>>();
let labels_len = labels.len();
let labels_len = quote! { #labels_len };
quote! {
static METRIC_LABELS: [metrics::Label; #labels_len] = [#(#labels),*];
static METRIC_KEY: metrics::KeyData =
metrics::KeyData::from_static_parts(#key, &METRIC_LABELS);
}
}
None => {
quote! {
static METRIC_KEY: metrics::KeyData =
metrics::KeyData::from_static_name(#key);
}
}
_ => unreachable!("use_fast_path == true, but found expression-based labels"),
};
quote! {
{
static CACHED_KEY: metrics::OnceKeyData = metrics::OnceKeyData::new();
#statics
// Only do this work if there's a recorder installed.
if let Some(recorder) = metrics::try_recorder() {
// Initialize our fast path.
let key = CACHED_KEY.get_or_init(|| { #key });
recorder.#op_ident(metrics::Key::Borrowed(&key), #op_values);
recorder.#op_ident(metrics::Key::Borrowed(&METRIC_KEY), #op_values);
}
}
}
} else {
// We're on the slow path, so basically we register every single time.
//
// Recorders are expected to deduplicate any duplicate registrations.
// We're on the slow path, so we allocate, womp.
let key = key_to_quoted(key, labels);
quote! {
{
// Only do this work if there's a recorder installed.
@ -332,20 +336,9 @@ fn can_use_fast_path(labels: &Option<Labels>) -> bool {
}
}
fn read_key(input: &mut ParseStream) -> Result<Key> {
let key = if let Ok(_) = input.parse::<Token![<]>() {
let s = input.parse::<LitStr>()?;
input.parse::<Token![>]>()?;
Key::Scoped(s)
} else {
let s = input.parse::<LitStr>()?;
Key::NotScoped(s)
};
let inner = match key {
Key::Scoped(ref s) => s.value(),
Key::NotScoped(ref s) => s.value(),
};
fn read_key(input: &mut ParseStream) -> Result<LitStr> {
let key = input.parse::<LitStr>()?;
let inner = key.value();
lazy_static! {
static ref RE: Regex = Regex::new("^[a-zA-Z][a-zA-Z0-9_:\\.]*$").unwrap();
@ -360,22 +353,7 @@ fn read_key(input: &mut ParseStream) -> Result<Key> {
Ok(key)
}
fn quote_key_name(key: Key) -> proc_macro2::TokenStream {
match key {
Key::NotScoped(s) => {
quote! { #s }
}
Key::Scoped(s) => {
quote! {
format!("{}.{}", std::module_path!().replace("::", "."), #s)
}
}
}
}
fn key_to_quoted(key: Key, labels: Option<Labels>) -> proc_macro2::TokenStream {
let name = quote_key_name(key);
fn key_to_quoted(name: LitStr, labels: Option<Labels>) -> proc_macro2::TokenStream {
match labels {
None => quote! { metrics::KeyData::from_name(#name) },
Some(labels) => match labels {
@ -383,9 +361,9 @@ fn key_to_quoted(key: Key, labels: Option<Labels>) -> proc_macro2::TokenStream {
let labels = pairs
.into_iter()
.map(|(key, val)| quote! { metrics::Label::new(#key, #val) });
quote! { metrics::KeyData::from_name_and_labels(#name, vec![#(#labels),*]) }
quote! { metrics::KeyData::from_parts(#name, vec![#(#labels),*]) }
}
Labels::Existing(e) => quote! { metrics::KeyData::from_name_and_labels(#name, #e) },
Labels::Existing(e) => quote! { metrics::KeyData::from_parts(#name, #e) },
},
}
}

View File

@ -3,31 +3,11 @@ use syn::{Expr, ExprPath};
use super::*;
#[test]
fn test_quote_key_name_scoped() {
let stream = quote_key_name(Key::Scoped(parse_quote! { "qwerty" }));
let expected =
"format ! (\"{}.{}\" , std :: module_path ! () . replace (\"::\" , \".\") , \"qwerty\")";
assert_eq!(stream.to_string(), expected);
}
#[test]
fn test_quote_key_name_not_scoped() {
let stream = quote_key_name(Key::NotScoped(parse_quote! { "qwerty" }));
let expected = "\"qwerty\"";
assert_eq!(stream.to_string(), expected);
}
#[test]
fn test_get_expanded_registration() {
// Basic registration.
let stream = get_expanded_registration(
"mytype",
Key::NotScoped(parse_quote! { "mykeyname" }),
None,
None,
None,
);
let stream =
get_expanded_registration("mytype", parse_quote! { "mykeyname" }, None, None, None);
let expected = concat!(
"{ if let Some (recorder) = metrics :: try_recorder () { ",
@ -48,7 +28,7 @@ fn test_get_expanded_registration_with_unit() {
let units: ExprPath = parse_quote! { metrics::Unit::Nanoseconds };
let stream = get_expanded_registration(
"mytype",
Key::NotScoped(parse_quote! { "mykeyname" }),
parse_quote! { "mykeyname" },
Some(Expr::Path(units)),
None,
None,
@ -72,7 +52,7 @@ fn test_get_expanded_registration_with_description() {
// And with description.
let stream = get_expanded_registration(
"mytype",
Key::NotScoped(parse_quote! { "mykeyname" }),
parse_quote! { "mykeyname" },
None,
Some(parse_quote! { "flerkin" }),
None,
@ -97,7 +77,7 @@ fn test_get_expanded_registration_with_unit_and_description() {
let units: ExprPath = parse_quote! { metrics::Unit::Nanoseconds };
let stream = get_expanded_registration(
"mytype",
Key::NotScoped(parse_quote! { "mykeyname" }),
parse_quote! { "mykeyname" },
Some(Expr::Path(units)),
Some(parse_quote! { "flerkin" }),
None,
@ -116,38 +96,82 @@ fn test_get_expanded_registration_with_unit_and_description() {
assert_eq!(stream.to_string(), expected);
}
/// If there are no dynamic labels - generate an invocation with caching.
#[test]
fn test_get_expanded_callsite_fast_path() {
fn test_get_expanded_callsite_fast_path_no_labels() {
let stream = get_expanded_callsite(
"mytype",
"myop",
Key::NotScoped(parse_quote! {"mykeyname"}),
parse_quote! {"mykeyname"},
None,
quote! { 1 },
);
let expected = concat!(
"{ ",
"static CACHED_KEY : metrics :: OnceKeyData = metrics :: OnceKeyData :: new () ; ",
"static METRIC_KEY : metrics :: KeyData = metrics :: KeyData :: from_static_name (\"mykeyname\") ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"let key = CACHED_KEY . get_or_init (|| { ",
"metrics :: KeyData :: from_name (\"mykeyname\") ",
"}) ; ",
"recorder . myop_mytype (metrics :: Key :: Borrowed (& key) , 1) ; ",
"recorder . myop_mytype (metrics :: Key :: Borrowed (& METRIC_KEY) , 1) ; ",
"} }",
);
assert_eq!(stream.to_string(), expected);
}
#[test]
fn test_get_expanded_callsite_fast_path_static_labels() {
let labels = Labels::Inline(vec![(parse_quote! { "key1" }, parse_quote! { "value1" })]);
let stream = get_expanded_callsite(
"mytype",
"myop",
parse_quote! {"mykeyname"},
Some(labels),
quote! { 1 },
);
let expected = concat!(
"{ ",
"static METRIC_LABELS : [metrics :: Label ; 1usize] = [metrics :: Label :: from_static_parts (\"key1\" , \"value1\")] ; ",
"static METRIC_KEY : metrics :: KeyData = metrics :: KeyData :: from_static_parts (\"mykeyname\" , & METRIC_LABELS) ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (metrics :: Key :: Borrowed (& METRIC_KEY) , 1) ; ",
"} ",
"}",
);
assert_eq!(stream.to_string(), expected);
}
#[test]
fn test_get_expanded_callsite_fast_path_dynamic_labels() {
let labels = Labels::Inline(vec![(parse_quote! { "key1" }, parse_quote! { &value1 })]);
let stream = get_expanded_callsite(
"mytype",
"myop",
parse_quote! {"mykeyname"},
Some(labels),
quote! { 1 },
);
let expected = concat!(
"{ ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (metrics :: Key :: Owned (",
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [metrics :: Label :: new (\"key1\" , & value1)])",
") , 1) ; ",
"} ",
"}",
);
assert_eq!(stream.to_string(), expected);
}
/// If there are dynamic labels - generate a direct invocation.
#[test]
fn test_get_expanded_callsite_regular_path() {
let stream = get_expanded_callsite(
"mytype",
"myop",
Key::NotScoped(parse_quote! {"mykeyname"}),
parse_quote! {"mykeyname"},
Some(Labels::Existing(parse_quote! { mylabels })),
quote! { 1 },
);
@ -156,7 +180,7 @@ fn test_get_expanded_callsite_regular_path() {
"{ ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name_and_labels (\"mykeyname\" , mylabels)) , ",
"metrics :: Key :: Owned (metrics :: KeyData :: from_parts (\"mykeyname\" , mylabels)) , ",
"1",
") ; ",
"} }",
@ -167,7 +191,7 @@ fn test_get_expanded_callsite_regular_path() {
#[test]
fn test_key_to_quoted_no_labels() {
let stream = key_to_quoted(Key::NotScoped(parse_quote! {"mykeyname"}), None);
let stream = key_to_quoted(parse_quote! {"mykeyname"}, None);
let expected = "metrics :: KeyData :: from_name (\"mykeyname\")";
assert_eq!(stream.to_string(), expected);
}
@ -175,10 +199,10 @@ fn test_key_to_quoted_no_labels() {
#[test]
fn test_key_to_quoted_existing_labels() {
let stream = key_to_quoted(
Key::NotScoped(parse_quote! {"mykeyname"}),
parse_quote! {"mykeyname"},
Some(Labels::Existing(Expr::Path(parse_quote! { mylabels }))),
);
let expected = "metrics :: KeyData :: from_name_and_labels (\"mykeyname\" , mylabels)";
let expected = "metrics :: KeyData :: from_parts (\"mykeyname\" , mylabels)";
assert_eq!(stream.to_string(), expected);
}
@ -187,14 +211,14 @@ fn test_key_to_quoted_existing_labels() {
#[test]
fn test_key_to_quoted_inline_labels() {
let stream = key_to_quoted(
Key::NotScoped(parse_quote! {"mykeyname"}),
parse_quote! {"mykeyname"},
Some(Labels::Inline(vec![
(parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }),
(parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }),
])),
);
let expected = concat!(
"metrics :: KeyData :: from_name_and_labels (\"mykeyname\" , vec ! [",
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [",
"metrics :: Label :: new (\"mylabel1\" , mylabel1) , ",
"metrics :: Label :: new (\"mylabel2\" , \"mylabel2\")",
"])"
@ -204,12 +228,9 @@ fn test_key_to_quoted_inline_labels() {
#[test]
fn test_key_to_quoted_inline_labels_empty() {
let stream = key_to_quoted(
Key::NotScoped(parse_quote! {"mykeyname"}),
Some(Labels::Inline(vec![])),
);
let stream = key_to_quoted(parse_quote! {"mykeyname"}, Some(Labels::Inline(vec![])));
let expected = concat!(
"metrics :: KeyData :: from_name_and_labels (\"mykeyname\" , vec ! [",
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [",
"])"
);
assert_eq!(stream.to_string(), expected);

View File

@ -127,7 +127,7 @@ where
fn enhance_key(&self, key: Key) -> Key {
let (name, mut labels) = key.into_owned().into_parts();
self.enhance_labels(&mut labels);
KeyData::from_name_and_labels(name, labels).into()
KeyData::from_parts(name, labels).into()
}
}

View File

@ -52,7 +52,7 @@ fn test_basic_functionality() {
snapshot,
vec![(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"login_attempts",
vec![
Label::new("service", "login_service"),
@ -95,7 +95,7 @@ fn test_macro_forms() {
vec![
(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"login_attempts_no_labels",
vec![
Label::new("user", "ferris"),
@ -109,7 +109,7 @@ fn test_macro_forms() {
),
(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"login_attempts_static_labels",
vec![
Label::new("service", "login_service"),
@ -124,7 +124,7 @@ fn test_macro_forms() {
),
(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"login_attempts_dynamic_labels",
vec![
Label::new("node_name", "localhost"),
@ -139,7 +139,7 @@ fn test_macro_forms() {
),
(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"login_attempts_static_and_dynamic_labels",
vec![
Label::new("service", "login_service"),
@ -224,7 +224,7 @@ fn test_multiple_paths_to_the_same_callsite() {
vec![
(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"my_counter",
vec![
Label::new("shared_field", "path1"),
@ -239,7 +239,7 @@ fn test_multiple_paths_to_the_same_callsite() {
),
(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"my_counter",
vec![
Label::new("shared_field", "path2"),
@ -295,7 +295,7 @@ fn test_nested_spans() {
snapshot,
vec![(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"my_counter",
vec![
Label::new("shared_field", "inner"),
@ -340,7 +340,7 @@ fn test_label_filtering() {
snapshot,
vec![(
MetricKind::Counter,
KeyData::from_name_and_labels(
KeyData::from_parts(
"login_attempts",
vec![
Label::new("service", "login_service"),

View File

@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, BatchSize, Benchmark, Criterion};
use metrics::{Key, KeyData, Label, OnceKeyData};
use metrics::{Key, KeyData, Label};
use metrics_util::Registry;
fn registry_benchmark(c: &mut Criterion) {
@ -7,22 +7,20 @@ fn registry_benchmark(c: &mut Criterion) {
"registry",
Benchmark::new("cached op (basic)", |b| {
let registry: Registry<Key, ()> = Registry::new();
static KEY_DATA: OnceKeyData = OnceKeyData::new();
static KEY_DATA: KeyData = KeyData::from_static_name("simple_key");
b.iter(|| {
let key = Key::Borrowed(KEY_DATA.get_or_init(|| KeyData::from_name("simple_key")));
let key = Key::Borrowed(&KEY_DATA);
registry.op(key, |_| (), || ())
})
})
.with_function("cached op (labels)", |b| {
let registry: Registry<Key, ()> = Registry::new();
static KEY_DATA: OnceKeyData = OnceKeyData::new();
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts("simple_key", &KEY_LABELS);
b.iter(|| {
let key = Key::Borrowed(KEY_DATA.get_or_init(|| {
let labels = vec![Label::new("type", "http")];
KeyData::from_name_and_labels("simple_key", labels)
}));
let key = Key::Borrowed(&KEY_DATA);
registry.op(key, |_| (), || ())
})
})
@ -64,7 +62,20 @@ fn registry_benchmark(c: &mut Criterion) {
b.iter(|| {
let key = "simple_key";
let labels = vec![Label::new("type", "http")];
KeyData::from_name_and_labels(key, labels)
KeyData::from_parts(key, labels)
})
})
.with_function("const key data overhead (basic)", |b| {
b.iter(|| {
let key = "simple_key";
KeyData::from_static_name(key)
})
})
.with_function("const key data overhead (labels)", |b| {
b.iter(|| {
let key = "simple_key";
static LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
KeyData::from_static_parts(key, &LABELS)
})
})
.with_function("owned key overhead (basic)", |b| {
@ -77,29 +88,17 @@ fn registry_benchmark(c: &mut Criterion) {
b.iter(|| {
let key = "simple_key";
let labels = vec![Label::new("type", "http")];
Key::Owned(KeyData::from_name_and_labels(key, labels))
Key::Owned(KeyData::from_parts(key, labels))
})
})
.with_function("cached key overhead (basic)", |b| {
static KEY_DATA: OnceKeyData = OnceKeyData::new();
b.iter(|| {
let key_data = KEY_DATA.get_or_init(|| {
let key = "simple_key";
KeyData::from_name(key)
});
Key::Borrowed(key_data)
})
static KEY_DATA: KeyData = KeyData::from_static_name("simple_key");
b.iter(|| Key::Borrowed(&KEY_DATA))
})
.with_function("cached key overhead (labels)", |b| {
static KEY_DATA: OnceKeyData = OnceKeyData::new();
b.iter(|| {
let key_data = KEY_DATA.get_or_init(|| {
let key = "simple_key";
let labels = vec![Label::new("type", "http")];
KeyData::from_name_and_labels(key, labels)
});
Key::Borrowed(key_data)
})
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts("simple_key", &KEY_LABELS);
b.iter(|| Key::Borrowed(&KEY_DATA))
}),
);
}

View File

@ -31,7 +31,6 @@ harness = false
beef = "0.4"
metrics-macros = { version = "0.1.0-alpha.1", path = "../metrics-macros" }
proc-macro-hack = "0.5"
once_cell = "1"
[dev-dependencies]
log = "0.4"

View File

@ -33,11 +33,13 @@ fn macro_benchmark(c: &mut Criterion) {
c.bench(
"macros",
Benchmark::new("uninitialized/no_labels", |b| {
metrics::clear_recorder();
b.iter(|| {
counter!("counter_bench", 42);
})
})
.with_function("uninitialized/with_static_labels", |b| {
metrics::clear_recorder();
b.iter(|| {
counter!("counter_bench", 42, "request" => "http", "svc" => "admin");
})

View File

@ -86,38 +86,22 @@ fn main() {
increment!("requests_processed", "request_type" => "admin");
increment!("requests_processed", "request_type" => "admin", "server" => server_name.clone());
increment!("requests_processed", common_labels);
increment!(<"requests_processed">);
increment!(<"requests_processed">, "request_type" => "admin");
increment!(<"requests_processed">, "request_type" => "admin", "server" => server_name.clone());
increment!(<"requests_processed">, common_labels);
// All the supported permutations of `counter!`:
counter!("bytes_sent", 64);
counter!("bytes_sent", 64, "listener" => "frontend");
counter!("bytes_sent", 64, "listener" => "frontend", "server" => server_name.clone());
counter!("bytes_sent", 64, common_labels);
counter!(<"bytes_sent">, 64);
counter!(<"bytes_sent">, 64, "listener" => "frontend");
counter!(<"bytes_sent">, 64, "listener" => "frontend", "server" => server_name.clone());
counter!(<"bytes_sent">, 64, common_labels);
// All the supported permutations of `gauge!`:
gauge!("connection_count", 300.0);
gauge!("connection_count", 300.0, "listener" => "frontend");
gauge!("connection_count", 300.0, "listener" => "frontend", "server" => server_name.clone());
gauge!("connection_count", 300.0, common_labels);
gauge!(<"connection_count">, 300.0);
gauge!(<"connection_count">, 300.0, "listener" => "frontend");
gauge!(<"connection_count">, 300.0, "listener" => "frontend", "server" => server_name.clone());
gauge!(<"connection_count">, 300.0, common_labels);
// All the supported permutations of `histogram!`:
histogram!("svc.execution_time", 70);
histogram!("svc.execution_time", 70, "type" => "users");
histogram!("svc.execution_time", 70, "type" => "users", "server" => server_name.clone());
histogram!("svc.execution_time", 70, common_labels);
histogram!(<"svc.execution_time">, 70);
histogram!(<"svc.execution_time">, 70, "type" => "users");
histogram!(<"svc.execution_time">, 70, "type" => "users", "server" => server_name.clone());
histogram!(<"svc.execution_time">, 70, common_labels);
}

View File

@ -5,10 +5,10 @@ use beef::Cow;
/// An allocation-optimized string.
///
/// We specify `ScopedString` to attempt to get the best of both worlds: flexibility to provide a
/// We specify `SharedString` to attempt to get the best of both worlds: flexibility to provide a
/// static or dynamic (owned) string, while retaining the performance benefits of being able to
/// take ownership of owned strings and borrows of completely static strings.
pub type ScopedString = Cow<'static, str>;
pub type SharedString = Cow<'static, str>;
/// Units for a given metric.
///
@ -254,7 +254,7 @@ impl IntoU64 for u64 {
}
}
impl IntoU64 for std::time::Duration {
impl IntoU64 for core::time::Duration {
fn into_u64(self) -> u64 {
self.as_nanos() as u64
}

View File

@ -1,7 +1,9 @@
use crate::{IntoLabels, Label, ScopedString};
use crate::{IntoLabels, Label, SharedString};
use alloc::{borrow::Cow, string::String, vec::Vec};
use core::{
fmt,
hash::{Hash, Hasher},
ops,
slice::Iter,
};
@ -11,28 +13,28 @@ use core::{
/// responsible for the actual storage of the name and label data.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyData {
name: ScopedString,
labels: Vec<Label>,
name: SharedString,
labels: Cow<'static, [Label]>,
}
impl KeyData {
/// Creates a [`KeyData`] from a name.
pub fn from_name<N>(name: N) -> Self
where
N: Into<ScopedString>,
N: Into<SharedString>,
{
Self::from_name_and_labels(name, Vec::new())
Self::from_parts(name, Vec::new())
}
/// Creates a [`KeyData`] from a name and vector of [`Label`]s.
pub fn from_name_and_labels<N, L>(name: N, labels: L) -> Self
pub fn from_parts<N, L>(name: N, labels: L) -> Self
where
N: Into<ScopedString>,
N: Into<SharedString>,
L: IntoLabels,
{
Self {
name: name.into(),
labels: labels.into_labels(),
labels: labels.into_labels().into(),
}
}
@ -41,13 +43,23 @@ impl KeyData {
/// This function is `const`, so it can be used in a static context.
pub const fn from_static_name(name: &'static str) -> Self {
Self {
name: ScopedString::const_str(name),
labels: Vec::new(),
name: SharedString::const_str(name),
labels: Cow::Owned(Vec::new()),
}
}
/// Creates a [`KeyData`] from a static name and static set of labels.
///
/// This function is `const`, so it can be used in a static context.
pub const fn from_static_parts(name: &'static str, labels: &'static [Label]) -> Self {
Self {
name: SharedString::const_str(name),
labels: Cow::Borrowed(labels),
}
}
/// Name of this key.
pub fn name(&self) -> &ScopedString {
pub fn name(&self) -> &SharedString {
&self.name
}
@ -61,7 +73,7 @@ impl KeyData {
/// The value returned by `f` becomes the new name of the key.
pub fn map_name<F>(mut self, f: F) -> Self
where
F: Fn(ScopedString) -> String,
F: Fn(SharedString) -> String,
{
let new_name = f(self.name);
self.name = new_name.into();
@ -69,8 +81,8 @@ impl KeyData {
}
/// Consumes this [`Key`], returning the name and any labels.
pub fn into_parts(self) -> (ScopedString, Vec<Label>) {
(self.name, self.labels)
pub fn into_parts(self) -> (SharedString, Vec<Label>) {
(self.name, self.labels.into_owned())
}
/// Clones this [`Key`], and expands the existing set of labels.
@ -80,10 +92,13 @@ impl KeyData {
}
let name = self.name.clone();
let mut labels = self.labels.clone();
let mut labels = self.labels.clone().into_owned();
labels.extend(extra_labels);
Self { name, labels }
Self {
name,
labels: labels.into(),
}
}
}
@ -94,7 +109,7 @@ impl fmt::Display for KeyData {
} else {
write!(f, "KeyData({}, [", self.name)?;
let mut first = true;
for label in &self.labels {
for label in self.labels.as_ref() {
if first {
write!(f, "{} = {}", label.0, label.1)?;
first = false;
@ -121,11 +136,11 @@ impl From<&'static str> for KeyData {
impl<N, L> From<(N, L)> for KeyData
where
N: Into<ScopedString>,
N: Into<SharedString>,
L: IntoLabels,
{
fn from(parts: (N, L)) -> Self {
Self::from_name_and_labels(parts.0, parts.1)
Self::from_parts(parts.0, parts.1)
}
}
@ -180,7 +195,7 @@ impl Key {
}
}
impl std::ops::Deref for Key {
impl ops::Deref for Key {
type Target = KeyData;
#[must_use]
@ -223,44 +238,37 @@ impl From<&'static KeyData> for Key {
}
}
/// A thread-safe cell which can only be written to once, for key data.
///
/// Allows for efficient caching of static [`KeyData`] at metric callsites.
pub type OnceKeyData = once_cell::sync::OnceCell<KeyData>;
#[cfg(test)]
mod tests {
use super::{Key, KeyData, OnceKeyData};
use super::{Key, KeyData};
use crate::Label;
use std::collections::HashMap;
static BORROWED_BASIC: OnceKeyData = OnceKeyData::new();
static BORROWED_LABELS: OnceKeyData = OnceKeyData::new();
static BORROWED_BASIC: KeyData = KeyData::from_static_name("name");
static LABELS: [Label; 1] = [Label::from_static_parts("key", "value")];
static BORROWED_LABELS: KeyData = KeyData::from_static_parts("name", &LABELS);
#[test]
fn test_keydata_eq_and_hash() {
let mut keys = HashMap::new();
let owned_basic = KeyData::from_name("name");
let borrowed_basic = BORROWED_BASIC.get_or_init(|| KeyData::from_name("name"));
assert_eq!(&owned_basic, borrowed_basic);
assert_eq!(&owned_basic, &BORROWED_BASIC);
let previous = keys.insert(owned_basic, 42);
assert!(previous.is_none());
let previous = keys.get(&borrowed_basic);
let previous = keys.get(&BORROWED_BASIC);
assert_eq!(previous, Some(&42));
let labels = vec![Label::new("key", "value")];
let owned_labels = KeyData::from_name_and_labels("name", labels.clone());
let borrowed_labels =
BORROWED_LABELS.get_or_init(|| KeyData::from_name_and_labels("name", labels.clone()));
assert_eq!(&owned_labels, borrowed_labels);
let labels = LABELS.to_vec();
let owned_labels = KeyData::from_parts("name", labels);
assert_eq!(&owned_labels, &BORROWED_LABELS);
let previous = keys.insert(owned_labels, 43);
assert!(previous.is_none());
let previous = keys.get(&borrowed_labels);
let previous = keys.get(&BORROWED_LABELS);
assert_eq!(previous, Some(&43));
}
@ -269,9 +277,7 @@ mod tests {
let mut keys = HashMap::new();
let owned_basic: Key = KeyData::from_name("name").into();
let borrowed_basic: Key = BORROWED_BASIC
.get_or_init(|| KeyData::from_name("name"))
.into();
let borrowed_basic: Key = Key::from(&BORROWED_BASIC);
assert_eq!(owned_basic, borrowed_basic);
let previous = keys.insert(owned_basic, 42);
@ -280,11 +286,9 @@ mod tests {
let previous = keys.get(&borrowed_basic);
assert_eq!(previous, Some(&42));
let labels = vec![Label::new("key", "value")];
let owned_labels = Key::from(KeyData::from_name_and_labels("name", labels.clone()));
let borrowed_labels = Key::from(
BORROWED_LABELS.get_or_init(|| KeyData::from_name_and_labels("name", labels.clone())),
);
let labels = LABELS.to_vec();
let owned_labels = Key::from(KeyData::from_parts("name", labels));
let borrowed_labels = Key::from(&BORROWED_LABELS);
assert_eq!(owned_labels, borrowed_labels);
let previous = keys.insert(owned_labels, 43);
@ -300,18 +304,18 @@ mod tests {
let result1 = key1.to_string();
assert_eq!(result1, "KeyData(foobar)");
let key2 = KeyData::from_name_and_labels("foobar", vec![Label::new("system", "http")]);
let key2 = KeyData::from_parts("foobar", vec![Label::new("system", "http")]);
let result2 = key2.to_string();
assert_eq!(result2, "KeyData(foobar, [system = http])");
let key3 = KeyData::from_name_and_labels(
let key3 = KeyData::from_parts(
"foobar",
vec![Label::new("system", "http"), Label::new("user", "joe")],
);
let result3 = key3.to_string();
assert_eq!(result3, "KeyData(foobar, [system = http, user = joe])");
let key4 = KeyData::from_name_and_labels(
let key4 = KeyData::from_parts(
"foobar",
vec![
Label::new("black", "black"),
@ -331,27 +335,24 @@ mod tests {
let owned_a = KeyData::from_name("a");
let owned_b = KeyData::from_name("b");
static STATIC_A: OnceKeyData = OnceKeyData::new();
static STATIC_B: OnceKeyData = OnceKeyData::new();
let borrowed_a = STATIC_A.get_or_init(|| owned_a.clone());
let borrowed_b = STATIC_B.get_or_init(|| owned_b.clone());
static STATIC_A: KeyData = KeyData::from_static_name("a");
static STATIC_B: KeyData = KeyData::from_static_name("b");
assert_eq!(Key::Owned(owned_a.clone()), Key::Owned(owned_a.clone()));
assert_eq!(Key::Owned(owned_b.clone()), Key::Owned(owned_b.clone()));
assert_eq!(Key::Borrowed(borrowed_a), Key::Borrowed(borrowed_a));
assert_eq!(Key::Borrowed(borrowed_b), Key::Borrowed(borrowed_b));
assert_eq!(Key::Borrowed(&STATIC_A), Key::Borrowed(&STATIC_A));
assert_eq!(Key::Borrowed(&STATIC_B), Key::Borrowed(&STATIC_B));
assert_eq!(Key::Owned(owned_a.clone()), Key::Borrowed(borrowed_a));
assert_eq!(Key::Owned(owned_b.clone()), Key::Borrowed(borrowed_b));
assert_eq!(Key::Owned(owned_a.clone()), Key::Borrowed(&STATIC_A));
assert_eq!(Key::Owned(owned_b.clone()), Key::Borrowed(&STATIC_B));
assert_eq!(Key::Borrowed(borrowed_a), Key::Owned(owned_a.clone()));
assert_eq!(Key::Borrowed(borrowed_b), Key::Owned(owned_b.clone()));
assert_eq!(Key::Borrowed(&STATIC_A), Key::Owned(owned_a.clone()));
assert_eq!(Key::Borrowed(&STATIC_B), Key::Owned(owned_b.clone()));
assert_ne!(Key::Owned(owned_a.clone()), Key::Owned(owned_b.clone()),);
assert_ne!(Key::Borrowed(borrowed_a), Key::Borrowed(borrowed_b));
assert_ne!(Key::Owned(owned_a.clone()), Key::Borrowed(borrowed_b));
assert_ne!(Key::Owned(owned_b.clone()), Key::Borrowed(borrowed_a));
assert_ne!(Key::Borrowed(&STATIC_A), Key::Borrowed(&STATIC_B));
assert_ne!(Key::Owned(owned_a.clone()), Key::Borrowed(&STATIC_B));
assert_ne!(Key::Owned(owned_b.clone()), Key::Borrowed(&STATIC_A));
}
}

View File

@ -1,4 +1,5 @@
use crate::ScopedString;
use crate::SharedString;
use alloc::vec::Vec;
/// Metadata for a metric key in the for of a key/value pair.
///
@ -11,18 +12,23 @@ use crate::ScopedString;
/// branched internally -- for example, an optimized path and a fallback path -- you may wish to
/// add a label that tracks which codepath was taken.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Label(pub(crate) ScopedString, pub(crate) ScopedString);
pub struct Label(pub(crate) SharedString, pub(crate) SharedString);
impl Label {
/// Creates a [`Label`] from a key and value.
pub fn new<K, V>(key: K, value: V) -> Self
where
K: Into<ScopedString>,
V: Into<ScopedString>,
K: Into<SharedString>,
V: Into<SharedString>,
{
Label(key.into(), value.into())
}
/// Creates a [`Label`] from a static key and value.
pub const fn from_static_parts(key: &'static str, value: &'static str) -> Self {
Label(SharedString::const_str(key), SharedString::const_str(value))
}
/// Key of this label.
pub fn key(&self) -> &str {
self.0.as_ref()
@ -34,15 +40,15 @@ impl Label {
}
/// Consumes this [`Label`], returning the key and value.
pub fn into_parts(self) -> (ScopedString, ScopedString) {
pub fn into_parts(self) -> (SharedString, SharedString) {
(self.0, self.1)
}
}
impl<K, V> From<&(K, V)> for Label
where
K: Into<ScopedString> + Clone,
V: Into<ScopedString> + Clone,
K: Into<SharedString> + Clone,
V: Into<SharedString> + Clone,
{
fn from(pair: &(K, V)) -> Label {
Label::new(pair.0.clone(), pair.1.clone())

View File

@ -215,6 +215,10 @@
//! [metrics-util]: https://docs.rs/metrics-util
//! [AtomicBucket]: https://docs.rs/metrics-util/0.4.0-alpha.6/metrics_util/struct.AtomicBucket.html
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use proc_macro_hack::proc_macro_hack;
mod common;
@ -238,29 +242,14 @@ pub use self::recorder::*;
/// recorder does anything with the description is implementation defined. Labels can also be
/// specified when registering a metric.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_counter_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_counter_metric`
/// - scoped: `service.my_counter_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_counter_metric`.
///
/// # Example
/// ```
/// # use metrics::register_counter;
/// # use metrics::Unit;
/// # fn main() {
/// // A regular, unscoped counter:
/// // A basic counter:
/// register_counter!("some_metric_name");
///
/// // A scoped counter. This inherits a scope derived by the current module:
/// register_counter!(<"some_metric_name">);
///
/// // Providing a unit for a counter:
/// register_counter!("some_metric_name", Unit::Bytes);
///
@ -297,29 +286,14 @@ pub use metrics_macros::register_counter;
/// recorder does anything with the description is implementation defined. Labels can also be
/// specified when registering a metric.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_gauge_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_gauge_metric`
/// - scoped: `service.my_gauge_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_gauge_metric`.
///
/// # Example
/// ```
/// # use metrics::register_gauge;
/// # use metrics::Unit;
/// # fn main() {
/// // A regular, unscoped gauge:
/// // A basic gauge:
/// register_gauge!("some_metric_name");
///
/// // A scoped gauge. This inherits a scope derived by the current module:
/// register_gauge!(<"some_metric_name">);
///
/// // Providing a unit for a gauge:
/// register_gauge!("some_metric_name", Unit::Bytes);
///
@ -356,29 +330,14 @@ pub use metrics_macros::register_gauge;
/// recorder does anything with the description is implementation defined. Labels can also be
/// specified when registering a metric.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_histogram_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_histogram_metric`
/// - scoped: `service.my_histogram_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_histogram_metric`.
///
/// # Example
/// ```
/// # use metrics::register_histogram;
/// # use metrics::Unit;
/// # fn main() {
/// // A regular, unscoped histogram:
/// // A basic histogram:
/// register_histogram!("some_metric_name");
///
/// // A scoped histogram. This inherits a scope derived by the current module:
/// register_histogram!(<"some_metric_name">);
///
/// // Providing a unit for a histogram:
/// register_histogram!("some_metric_name", Unit::Nanoseconds);
///
@ -411,28 +370,13 @@ pub use metrics_macros::register_histogram;
/// Counters represent a single monotonic value, which means the value can only be incremented, not
/// decremented, and always starts out with an initial value of zero.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_counter_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_counter_metric`
/// - scoped: `service.my_counter_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_counter_metric`.
///
/// # Example
/// ```
/// # use metrics::increment;
/// # fn main() {
/// // A regular, unscoped increment:
/// // A basic increment:
/// increment!("some_metric_name");
///
/// // A scoped increment. This inherits a scope derived by the current module:
/// increment!(<"some_metric_name">);
///
/// // Specifying labels:
/// increment!("some_metric_name", "service" => "http");
///
@ -450,28 +394,13 @@ pub use metrics_macros::increment;
/// Counters represent a single monotonic value, which means the value can only be incremented, not
/// decremented, and always starts out with an initial value of zero.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_counter_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_counter_metric`
/// - scoped: `service.my_counter_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_counter_metric`.
///
/// # Example
/// ```
/// # use metrics::counter;
/// # fn main() {
/// // A regular, unscoped counter:
/// // A basic counter:
/// counter!("some_metric_name", 12);
///
/// // A scoped counter. This inherits a scope derived by the current module:
/// counter!(<"some_metric_name">, 12);
///
/// // Specifying labels:
/// counter!("some_metric_name", 12, "service" => "http");
///
@ -489,28 +418,13 @@ pub use metrics_macros::counter;
/// Gauges represent a single value that can go up or down over time, and always starts out with an
/// initial value of zero.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_gauge_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_gauge_metric`
/// - scoped: `service.my_gauge_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_gauge_metric`.
///
/// # Example
/// ```
/// # use metrics::gauge;
/// # fn main() {
/// // A regular, unscoped gauge:
/// // A basic gauge:
/// gauge!("some_metric_name", 42.2222);
///
/// // A scoped gauge. This inherits a scope derived by the current module:
/// gauge!(<"some_metric_name">, 33.3333);
///
/// // Specifying labels:
/// gauge!("some_metric_name", 66.6666, "service" => "http");
///
@ -528,18 +442,6 @@ pub use metrics_macros::gauge;
/// Histograms measure the distribution of values for a given set of measurements, and start with no
/// initial values.
///
/// # Scoped versus unscoped
/// Metrics can be unscoped or scoped, where the scoping is derived by the current module the call
/// is taking place in. This scope is used as a prefix to the provided metric name. For example,
/// take an example metric of "my_histogram_metric" being emitted in a binary application "service":
///
/// - unscoped: `my_histogram_metric`
/// - scoped: `service.my_histogram_metric`
///
/// If the metric was emitted in a nested module within the application, the scope would represent
/// the full module path to where the metric is being emitted, such as
/// `service.some_module.my_histogram_metric`.
///
/// # Implicit conversions
/// Histograms are represented as `u64` values, but often come from another source, such as a time
/// measurement. By default, `histogram!` will accept a `u64` directly or a
@ -553,16 +455,13 @@ pub use metrics_macros::gauge;
/// # use metrics::histogram;
/// # use std::time::Duration;
/// # fn main() {
/// // A regular, unscoped histogram:
/// // A basic histogram:
/// histogram!("some_metric_name", 34);
///
/// // An implicit conversion from `Duration`:
/// let d = Duration::from_millis(17);
/// histogram!("some_metric_name", d);
///
/// // A scoped histogram. This inherits a scope derived by the current module:
/// histogram!(<"some_metric_name">, 38);
///
/// // Specifying labels:
/// histogram!("some_metric_name", 38, "service" => "http");
///

View File

@ -196,7 +196,7 @@ pub fn recorder() -> &'static dyn Recorder {
/// If a recorder has not been set, returns `None`.
pub fn try_recorder() -> Option<&'static dyn Recorder> {
unsafe {
if STATE.load(Ordering::SeqCst) != INITIALIZED {
if STATE.load(Ordering::Relaxed) != INITIALIZED {
None
} else {
Some(RECORDER)