diff --git a/metrics-macros/src/lib.rs b/metrics-macros/src/lib.rs index 2d901477..f24534a8 100644 --- a/metrics-macros/src/lib.rs +++ b/metrics-macros/src/lib.rs @@ -334,7 +334,7 @@ fn generate_statics(name: &Expr, labels: &Option) -> TokenStream2 { let use_name_static = name_is_fast_path(name); let name_static = if use_name_static { quote! { - static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)]; + static METRIC_NAME: &'static str = #name; } } else { quote! {} @@ -374,11 +374,11 @@ fn generate_statics(name: &Expr, labels: &Option) -> TokenStream2 { let key_static = if use_name_static && use_labels_static { if has_labels { quote! { - static METRIC_KEY: metrics::Key = metrics::Key::from_static_parts(&METRIC_NAME, &METRIC_LABELS); + static METRIC_KEY: metrics::Key = metrics::Key::from_static_parts(METRIC_NAME, &METRIC_LABELS); } } else { quote! { - static METRIC_KEY: metrics::Key = metrics::Key::from_static_name(&METRIC_NAME); + static METRIC_KEY: metrics::Key = metrics::Key::from_static_name(METRIC_NAME); } } } else { @@ -413,7 +413,7 @@ fn generate_metric_key(name: &Expr, labels: &Option) -> (TokenStream2, T let labels = labels.as_ref().unwrap(); let quoted_labels = labels_to_quoted(labels); quote! { - let key = metrics::Key::from_parts(&METRIC_NAME[..], #quoted_labels); + let key = metrics::Key::from_parts(METRIC_NAME, #quoted_labels); } } else if !use_name_static && !use_labels_static { // The name is not static, and neither are the labels. Since `use_labels_static` diff --git a/metrics-macros/src/tests.rs b/metrics-macros/src/tests.rs index e2eabf1a..af192004 100644 --- a/metrics-macros/src/tests.rs +++ b/metrics-macros/src/tests.rs @@ -11,8 +11,8 @@ fn test_get_expanded_registration() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", - "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (& METRIC_NAME) ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", + "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (METRIC_NAME) ; ", "if let Some (recorder) = metrics :: try_recorder () { ", "recorder . register_mytype (& METRIC_KEY , None , None) ; ", "} ", @@ -36,8 +36,8 @@ fn test_get_expanded_registration_with_unit() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", - "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (& METRIC_NAME) ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", + "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (METRIC_NAME) ; ", "if let Some (recorder) = metrics :: try_recorder () { ", "recorder . register_mytype (& METRIC_KEY , Some (metrics :: Unit :: Nanoseconds) , None) ; ", "} ", @@ -60,8 +60,8 @@ fn test_get_expanded_registration_with_description() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", - "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (& METRIC_NAME) ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", + "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (METRIC_NAME) ; ", "if let Some (recorder) = metrics :: try_recorder () { ", "recorder . register_mytype (& METRIC_KEY , None , Some (\"flerkin\")) ; ", "} ", @@ -85,8 +85,8 @@ fn test_get_expanded_registration_with_unit_and_description() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", - "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (& METRIC_NAME) ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", + "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (METRIC_NAME) ; ", "if let Some (recorder) = metrics :: try_recorder () { ", "recorder . register_mytype (& METRIC_KEY , Some (metrics :: Unit :: Nanoseconds) , Some (\"flerkin\")) ; ", "} ", @@ -108,8 +108,8 @@ fn test_get_expanded_callsite_static_name_no_labels() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", - "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (& METRIC_NAME) ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", + "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_name (METRIC_NAME) ; ", "if let Some (recorder) = metrics :: try_recorder () { ", "recorder . myop_mytype (& METRIC_KEY , 1) ; ", "} }", @@ -131,9 +131,9 @@ fn test_get_expanded_callsite_static_name_static_inline_labels() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", "static METRIC_LABELS : [metrics :: Label ; 1usize] = [metrics :: Label :: from_static_parts (\"key1\" , \"value1\")] ; ", - "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_parts (& METRIC_NAME , & METRIC_LABELS) ; ", + "static METRIC_KEY : metrics :: Key = metrics :: Key :: from_static_parts (METRIC_NAME , & METRIC_LABELS) ; ", "if let Some (recorder) = metrics :: try_recorder () { ", "recorder . myop_mytype (& METRIC_KEY , 1) ; ", "} ", @@ -156,9 +156,9 @@ fn test_get_expanded_callsite_static_name_dynamic_inline_labels() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", "if let Some (recorder) = metrics :: try_recorder () { ", - "let key = metrics :: Key :: from_parts (& METRIC_NAME [..] , vec ! [metrics :: Label :: new (\"key1\" , & value1)]) ; ", + "let key = metrics :: Key :: from_parts (METRIC_NAME , vec ! [metrics :: Label :: new (\"key1\" , & value1)]) ; ", "recorder . myop_mytype (& key , 1) ; ", "} ", "}", @@ -180,9 +180,9 @@ fn test_get_expanded_callsite_static_name_existing_labels() { let expected = concat!( "{ ", - "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ", + "static METRIC_NAME : & 'static str = \"mykeyname\" ; ", "if let Some (recorder) = metrics :: try_recorder () { ", - "let key = metrics :: Key :: from_parts (& METRIC_NAME [..] , mylabels) ; ", + "let key = metrics :: Key :: from_parts (METRIC_NAME , mylabels) ; ", "recorder . myop_mytype (& key , 1) ; ", "} ", "}", diff --git a/metrics-tracing-context/benches/layer.rs b/metrics-tracing-context/benches/layer.rs index db6ec682..0f193db7 100644 --- a/metrics-tracing-context/benches/layer.rs +++ b/metrics-tracing-context/benches/layer.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use metrics::{Key, Label, NoopRecorder, Recorder, SharedString}; +use metrics::{Key, Label, NoopRecorder, Recorder}; use metrics_tracing_context::{MetricsLayer, TracingContextLayer}; use metrics_util::layers::Layer; use tracing::{ @@ -12,7 +12,7 @@ fn layer_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("layer"); group.bench_function("base case", |b| { let recorder = NoopRecorder; - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")]; + static KEY_NAME: &'static str = "key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -30,7 +30,7 @@ fn layer_benchmark(c: &mut Criterion) { let _guard = span.enter(); let recorder = NoopRecorder; - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")]; + static KEY_NAME: &'static str = "key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -49,7 +49,7 @@ fn layer_benchmark(c: &mut Criterion) { let _guard = span.enter(); let recorder = NoopRecorder; - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")]; + static KEY_NAME: &'static str = "key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -69,7 +69,7 @@ fn layer_benchmark(c: &mut Criterion) { let tracing_layer = TracingContextLayer::all(); let recorder = tracing_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")]; + static KEY_NAME: &'static str = "key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -89,7 +89,7 @@ fn layer_benchmark(c: &mut Criterion) { let tracing_layer = TracingContextLayer::all(); let recorder = tracing_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")]; + static KEY_NAME: &'static str = "key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); diff --git a/metrics-tracing-context/tests/integration.rs b/metrics-tracing-context/tests/integration.rs index 440f74fe..70540dec 100644 --- a/metrics-tracing-context/tests/integration.rs +++ b/metrics-tracing-context/tests/integration.rs @@ -1,4 +1,4 @@ -use metrics::{counter, Key, Label, SharedString}; +use metrics::{counter, Key, Label}; use metrics_tracing_context::{LabelFilter, MetricsLayer, TracingContextLayer}; use metrics_util::{ layers::Layer, CompositeKey, DebugValue, DebuggingRecorder, MetricKind, Snapshotter, @@ -9,17 +9,12 @@ use tracing::{span, Level}; use tracing_subscriber::{layer::SubscriberExt, Registry}; static TEST_MUTEX: Mutex<()> = const_mutex(()); -static LOGIN_ATTEMPTS: &'static [SharedString] = &[SharedString::const_str("login_attempts")]; -static LOGIN_ATTEMPTS_NONE: &'static [SharedString] = - &[SharedString::const_str("login_attempts_no_labels")]; -static LOGIN_ATTEMPTS_STATIC: &'static [SharedString] = - &[SharedString::const_str("login_attempts_static_labels")]; -static LOGIN_ATTEMPTS_DYNAMIC: &'static [SharedString] = - &[SharedString::const_str("login_attempts_dynamic_labels")]; -static LOGIN_ATTEMPTS_BOTH: &'static [SharedString] = &[SharedString::const_str( - "login_attempts_static_and_dynamic_labels", -)]; -static MY_COUNTER: &'static [SharedString] = &[SharedString::const_str("my_counter")]; +static LOGIN_ATTEMPTS: &'static str = "login_attempts"; +static LOGIN_ATTEMPTS_NONE: &'static str = "login_attempts_no_labels"; +static LOGIN_ATTEMPTS_STATIC: &'static str = "login_attempts_static_labels"; +static LOGIN_ATTEMPTS_DYNAMIC: &'static str = "login_attempts_dynamic_labels"; +static LOGIN_ATTEMPTS_BOTH: &'static str = "login_attempts_static_and_dynamic_labels"; +static MY_COUNTER: &'static str = "my_counter"; static USER_EMAIL: &'static [Label] = &[ Label::from_static_parts("user", "ferris"), Label::from_static_parts("user.email", "ferris@rust-lang.org"), diff --git a/metrics-util/CHANGELOG.md b/metrics-util/CHANGELOG.md index 650d7f45..603b70c1 100644 --- a/metrics-util/CHANGELOG.md +++ b/metrics-util/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Added +- New layer -- `Router` -- for routing specific metrics to target downstream recorders. + +### Changed +- Updated all deprecated usages of `crossbeam_epoch::Atomic::compare_and_set` to `compare_exchange`. + ## [0.7.0] - 2021-05-03 ### Changed diff --git a/metrics-util/Cargo.toml b/metrics-util/Cargo.toml index 14820681..79ec2630 100644 --- a/metrics-util/Cargo.toml +++ b/metrics-util/Cargo.toml @@ -34,6 +34,10 @@ harness = false name = "filter" harness = false +[[bench]] +name = "router" +harness = false + [[bench]] name = "absolute" harness = false @@ -49,6 +53,7 @@ indexmap = { version = "1.6", optional = true } parking_lot = { version = "0.11", optional = true } quanta = { version = "0.7", optional = true } sketches-ddsketch = { version = "0.1", optional = true } +radix_trie = { version = "0.2", optional = true } ordered-float = "2.0" num_cpus = "1" t1ha = "0.1" @@ -74,9 +79,11 @@ crossbeam-queue = "0.3" quickcheck = "1" quickcheck_macros = "1" textplots = "0.6" +mockall = "0.9" [features] -default = ["std", "layer-filter", "layer-absolute"] +default = ["std", "layer-filter", "layer-absolute", "layer-router"] std = ["atomic-shim", "crossbeam-epoch", "crossbeam-utils", "dashmap", "indexmap", "parking_lot", "quanta", "sketches-ddsketch"] layer-filter = ["aho-corasick"] layer-absolute = ["aho-corasick", "parking_lot"] +layer-router = ["radix_trie"] \ No newline at end of file diff --git a/metrics-util/benches/absolute.rs b/metrics-util/benches/absolute.rs index 7020ca9f..761e197e 100644 --- a/metrics-util/benches/absolute.rs +++ b/metrics-util/benches/absolute.rs @@ -1,7 +1,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; #[cfg(feature = "layer-absolute")] -use metrics::{Key, Label, NoopRecorder, Recorder, SharedString}; +use metrics::{Key, Label, NoopRecorder, Recorder}; #[cfg(feature = "layer-absolute")] use metrics_util::layers::{AbsoluteLayer, Layer}; @@ -15,7 +15,7 @@ fn layer_benchmark(c: &mut Criterion) { let patterns = vec!["rdkafka"]; let absolute_layer = AbsoluteLayer::from_patterns(patterns); let recorder = absolute_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("counter")]; + static KEY_NAME: &'static str = "counter"; static KEY_DATA: Key = Key::from_static_name(&KEY_NAME); b.iter(|| { @@ -26,7 +26,7 @@ fn layer_benchmark(c: &mut Criterion) { let patterns = vec!["rdkafka"]; let absolute_layer = AbsoluteLayer::from_patterns(patterns); let recorder = absolute_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("rdkafka.bytes")]; + static KEY_NAME: &'static str = "rdkafka.bytes"; static KEY_DATA: Key = Key::from_static_name(&KEY_NAME); b.iter(|| { @@ -37,7 +37,7 @@ fn layer_benchmark(c: &mut Criterion) { let patterns = vec!["tokio"]; let absolute_layer = AbsoluteLayer::from_patterns(patterns); let recorder = absolute_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("rdkafka.bytes")]; + static KEY_NAME: &'static str = "rdkafka.bytes"; static KEY_DATA: Key = Key::from_static_name(&KEY_NAME); let mut counter = 1; @@ -49,7 +49,7 @@ fn layer_benchmark(c: &mut Criterion) { }); group.bench_function("noop recorder overhead (increment_counter)", |b| { let recorder = NoopRecorder; - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("counter")]; + static KEY_NAME: &'static str = "counter"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); diff --git a/metrics-util/benches/filter.rs b/metrics-util/benches/filter.rs index deb4b7a2..1ab4f991 100644 --- a/metrics-util/benches/filter.rs +++ b/metrics-util/benches/filter.rs @@ -1,7 +1,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; #[cfg(feature = "layer-filter")] -use metrics::{Key, Label, NoopRecorder, Recorder, SharedString}; +use metrics::{Key, Label, NoopRecorder, Recorder}; #[cfg(feature = "layer-filter")] use metrics_util::layers::{FilterLayer, Layer}; @@ -15,7 +15,7 @@ fn layer_benchmark(c: &mut Criterion) { let patterns = vec!["tokio"]; let filter_layer = FilterLayer::from_patterns(patterns); let recorder = filter_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("tokio.foo")]; + static KEY_NAME: &'static str = "tokio.foo"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -27,22 +27,7 @@ fn layer_benchmark(c: &mut Criterion) { let patterns = vec!["tokio"]; let filter_layer = FilterLayer::from_patterns(patterns); let recorder = filter_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("hyper.foo")]; - static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; - static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); - - b.iter(|| { - recorder.increment_counter(&KEY_DATA, 1); - }) - }); - group.bench_function("deep match", |b| { - let patterns = vec!["tokio"]; - let filter_layer = FilterLayer::from_patterns(patterns); - let recorder = filter_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 2] = [ - SharedString::const_str("prefix"), - SharedString::const_str("tokio.foo"), - ]; + static KEY_NAME: &'static str = "hyper.foo"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -52,7 +37,7 @@ fn layer_benchmark(c: &mut Criterion) { }); group.bench_function("noop recorder overhead (increment_counter)", |b| { let recorder = NoopRecorder; - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("tokio.foo")]; + static KEY_NAME: &'static str = "tokio.foo"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); diff --git a/metrics-util/benches/prefix.rs b/metrics-util/benches/prefix.rs index 35d08711..30e63a24 100644 --- a/metrics-util/benches/prefix.rs +++ b/metrics-util/benches/prefix.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use metrics::{Key, Label, NoopRecorder, Recorder, SharedString}; +use metrics::{Key, Label, NoopRecorder, Recorder}; use metrics_util::layers::{Layer, PrefixLayer}; fn layer_benchmark(c: &mut Criterion) { @@ -7,7 +7,7 @@ fn layer_benchmark(c: &mut Criterion) { group.bench_function("basic", |b| { let prefix_layer = PrefixLayer::new("prefix"); let recorder = prefix_layer.layer(NoopRecorder); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")]; + static KEY_NAME: &'static str = "simple_key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -17,7 +17,7 @@ fn layer_benchmark(c: &mut Criterion) { }); group.bench_function("noop recorder overhead (increment_counter)", |b| { let recorder = NoopRecorder; - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")]; + static KEY_NAME: &'static str = "simple_key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); diff --git a/metrics-util/benches/registry.rs b/metrics-util/benches/registry.rs index 2d8ee65a..7252e912 100644 --- a/metrics-util/benches/registry.rs +++ b/metrics-util/benches/registry.rs @@ -1,19 +1,19 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; -use metrics::{Key, Label, SharedString}; +use metrics::{Key, Label}; use metrics_util::{MetricKind, Registry}; fn registry_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("registry"); group.bench_function("cached op (basic)", |b| { let registry: Registry = Registry::new(); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")]; + static KEY_NAME: &'static str = "simple_key"; static KEY_DATA: Key = Key::from_static_name(&KEY_NAME); b.iter(|| registry.op(MetricKind::Counter, &KEY_DATA, |_| (), || ())) }); group.bench_function("cached op (labels)", |b| { let registry: Registry = Registry::new(); - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")]; + static KEY_NAME: &'static str = "simple_key"; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")]; static KEY_DATA: Key = Key::from_static_parts(&KEY_NAME, &KEY_LABELS); @@ -49,13 +49,13 @@ fn registry_benchmark(c: &mut Criterion) { }); group.bench_function("const key overhead (basic)", |b| { b.iter(|| { - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")]; + static KEY_NAME: &'static str = "simple_key"; Key::from_static_name(&KEY_NAME) }) }); group.bench_function("const key data overhead (labels)", |b| { b.iter(|| { - static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")]; + static KEY_NAME: &'static str = "simple_key"; static LABELS: [Label; 1] = [Label::from_static_parts("type", "http")]; Key::from_static_parts(&KEY_NAME, &LABELS) }) diff --git a/metrics-util/benches/router.rs b/metrics-util/benches/router.rs new file mode 100644 index 00000000..c68147a8 --- /dev/null +++ b/metrics-util/benches/router.rs @@ -0,0 +1,47 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +#[cfg(feature = "layer-router")] +use metrics::{Key, NoopRecorder, Recorder}; + +#[cfg(feature = "layer-router")] +use metrics_util::layers::RouterBuilder; +use metrics_util::MetricKindMask; + +#[allow(unused_variables)] +fn layer_benchmark(c: &mut Criterion) { + #[cfg(feature = "layer-router")] + { + let mut group = c.benchmark_group("router"); + group.bench_function("default target (via mask)", |b| { + let recorder = RouterBuilder::from_recorder(NoopRecorder).build(); + let key = Key::from_name("test_key"); + + b.iter(|| { + recorder.increment_counter(&key, 1); + }) + }); + group.bench_function("default target (via fallback)", |b| { + let mut builder = RouterBuilder::from_recorder(NoopRecorder); + builder.add_route(MetricKindMask::COUNTER, "override", NoopRecorder); + let recorder = builder.build(); + let key = Key::from_name("normal_key"); + + b.iter(|| { + recorder.increment_counter(&key, 1); + }) + }); + group.bench_function("routed target", |b| { + let mut builder = RouterBuilder::from_recorder(NoopRecorder); + builder.add_route(MetricKindMask::COUNTER, "override", NoopRecorder); + let recorder = builder.build(); + let key = Key::from_name("override_key"); + + b.iter(|| { + recorder.increment_counter(&key, 1); + }) + }); + } +} + +criterion_group!(benches, layer_benchmark); +criterion_main!(benches); diff --git a/metrics-util/src/bucket.rs b/metrics-util/src/bucket.rs index 81869126..e3bf7894 100644 --- a/metrics-util/src/bucket.rs +++ b/metrics-util/src/bucket.rs @@ -168,10 +168,11 @@ impl AtomicBucket { let mut tail = self.tail.load(Ordering::Acquire, guard); if tail.is_null() { // No blocks at all yet. We need to create one. - match self.tail.compare_and_set( + match self.tail.compare_exchange( Shared::null(), Owned::new(Block::new()), Ordering::AcqRel, + Ordering::Acquire, guard, ) { // We won the race to install the new block. @@ -189,10 +190,11 @@ impl AtomicBucket { Ok(_) => return, // The block was full, so we've been given the value back and we need to install a new block. Err(value) => { - match self.tail.compare_and_set( + match self.tail.compare_exchange( tail, Owned::new(Block::new()), Ordering::AcqRel, + Ordering::Acquire, guard, ) { // We managed to install the block, so we need to link this new block to @@ -309,7 +311,13 @@ impl AtomicBucket { if !block_ptr.is_null() && self .tail - .compare_and_set(block_ptr, Shared::null(), Ordering::SeqCst, guard) + .compare_exchange( + block_ptr, + Shared::null(), + Ordering::SeqCst, + Ordering::SeqCst, + guard, + ) .is_ok() { let backoff = Backoff::new(); diff --git a/metrics-util/src/layers/absolute.rs b/metrics-util/src/layers/absolute.rs index 7815aec5..0069c411 100644 --- a/metrics-util/src/layers/absolute.rs +++ b/metrics-util/src/layers/absolute.rs @@ -16,9 +16,7 @@ pub struct Absolute { impl Absolute { fn should_convert(&self, key: &Key) -> bool { - key.name() - .parts() - .any(|s| self.automaton.is_match(s.as_ref())) + self.automaton.is_match(key.name()) } } diff --git a/metrics-util/src/layers/filter.rs b/metrics-util/src/layers/filter.rs index 7399bdec..d601a344 100644 --- a/metrics-util/src/layers/filter.rs +++ b/metrics-util/src/layers/filter.rs @@ -12,9 +12,7 @@ pub struct Filter { impl Filter { fn should_filter(&self, key: &Key) -> bool { - key.name() - .parts() - .any(|s| self.automaton.is_match(s.as_ref())) + self.automaton.is_match(key.name()) } } diff --git a/metrics-util/src/layers/mod.rs b/metrics-util/src/layers/mod.rs index d584789e..3236f23f 100644 --- a/metrics-util/src/layers/mod.rs +++ b/metrics-util/src/layers/mod.rs @@ -17,12 +17,7 @@ //! //! impl StairwayDeny { //! fn is_invalid_key(&self, key: &Key) -> bool { -//! for part in key.name().parts() { -//! if part.contains("stairway") || part.contains("heaven") { -//! return true -//! } -//! } -//! false +//! key.name().contains("stairway") || key.name().contains("heaven") //! } //! } //! @@ -127,6 +122,10 @@ pub use fanout::{Fanout, FanoutBuilder}; mod absolute; #[cfg(feature = "layer-absolute")] pub use absolute::{Absolute, AbsoluteLayer}; +#[cfg(feature = "layer-router")] +mod router; +#[cfg(feature = "layer-router")] +pub use router::{Router, RouterBuilder}; /// Decorates an object by wrapping it within another type. pub trait Layer { diff --git a/metrics-util/src/layers/prefix.rs b/metrics-util/src/layers/prefix.rs index 55d826b0..db622cf4 100644 --- a/metrics-util/src/layers/prefix.rs +++ b/metrics-util/src/layers/prefix.rs @@ -11,7 +11,12 @@ pub struct Prefix { impl Prefix { fn prefix_key(&self, key: &Key) -> Key { - key.clone().prepend_name(self.prefix.clone()).into() + let mut new_name = String::with_capacity(self.prefix.len() + 1 + key.name().len()); + new_name.push_str(self.prefix.as_ref()); + new_name.push('.'); + new_name.push_str(key.name()); + + Key::from_parts(new_name, key.labels()) } } diff --git a/metrics-util/src/layers/router.rs b/metrics-util/src/layers/router.rs new file mode 100644 index 00000000..664f8670 --- /dev/null +++ b/metrics-util/src/layers/router.rs @@ -0,0 +1,277 @@ +use metrics::{GaugeValue, Key, Recorder, Unit}; +use radix_trie::{Trie, TrieCommon}; + +use crate::{MetricKind, MetricKindMask}; + +/// Routes metrics to specific target recorders. +/// +/// More information on the behavior of the layer can be found in [`RouterBuilder`]. +pub struct Router { + default: Box, + global_mask: MetricKindMask, + targets: Vec>, + counter_routes: Trie, + gauge_routes: Trie, + histogram_routes: Trie, +} + +impl Router { + fn route( + &self, + kind: MetricKind, + key: &Key, + search_routes: &Trie, + ) -> &dyn Recorder { + // The global mask is essentially a Bloom filter of overridden route types. If it doesn't + // match our metric, we know for a fact there's no route and must use the default recorder. + if !self.global_mask.matches(kind) { + self.default.as_ref() + } else { + // SAFETY: We derive the `idx` value that is inserted into our route maps by using the + // length of `targets` itself before adding a new target. Ergo, the index is provably + // populated if the `idx` has been stored. + search_routes + .get_ancestor(key.name()) + .map(|st| unsafe { self.targets.get_unchecked(*st.value().unwrap()).as_ref() }) + .unwrap_or(self.default.as_ref()) + } + } +} + +impl Recorder for Router { + fn register_counter(&self, key: &Key, unit: Option, description: Option<&'static str>) { + let target = self.route(MetricKind::Counter, key, &self.counter_routes); + target.register_counter(key, unit, description) + } + + fn register_gauge(&self, key: &Key, unit: Option, description: Option<&'static str>) { + let target = self.route(MetricKind::Gauge, key, &self.gauge_routes); + target.register_gauge(key, unit, description) + } + + fn register_histogram(&self, key: &Key, unit: Option, description: Option<&'static str>) { + let target = self.route(MetricKind::Histogram, key, &self.histogram_routes); + target.register_histogram(key, unit, description) + } + + fn increment_counter(&self, key: &Key, value: u64) { + let target = self.route(MetricKind::Counter, key, &self.counter_routes); + target.increment_counter(key, value); + } + + fn update_gauge(&self, key: &Key, value: GaugeValue) { + let target = self.route(MetricKind::Gauge, key, &self.gauge_routes); + target.update_gauge(key, value); + } + + fn record_histogram(&self, key: &Key, value: f64) { + let target = self.route(MetricKind::Histogram, key, &self.histogram_routes); + target.record_histogram(key, value); + } +} + +/// Routes metrics to specific target recorders. +/// +/// Routes are defined as a prefix to check against the metric name, and a mask for the metric type. +/// For example, a route with the pattern of "foo" would match "foo", "or "foo.submetric", but not +/// "something.foo". Likewise, a metric mask of "all" would apply this route to counters, gauges, +/// and histograms, while any specific mask would only apply to the given metric kind. +/// +/// A default route (recorder) is always present and used in the case that no specific route exists. +pub struct RouterBuilder { + default: Box, + global_mask: MetricKindMask, + targets: Vec>, + counter_routes: Trie, + gauge_routes: Trie, + histogram_routes: Trie, +} + +impl RouterBuilder { + /// Creates a [`RouterBuilder`] from a [`Recorder`]. + /// + /// The given recorder is used as the default route when no other specific route exists. + pub fn from_recorder(recorder: R) -> Self + where + R: Recorder + 'static, + { + RouterBuilder { + default: Box::new(recorder), + global_mask: MetricKindMask::NONE, + targets: Vec::new(), + counter_routes: Trie::new(), + gauge_routes: Trie::new(), + histogram_routes: Trie::new(), + } + } + + /// Adds a route. + /// + /// `mask` defines which metric kinds will match the given route, and `pattern` is a prefix + /// string used to match against metric names. + /// + /// If a matching route already exists, it will be overwritten. + pub fn add_route( + &mut self, + mask: MetricKindMask, + pattern: P, + recorder: R, + ) -> &mut RouterBuilder + where + P: AsRef, + R: Recorder + 'static, + { + let target_idx = self.targets.len(); + self.targets.push(Box::new(recorder)); + + self.global_mask = self.global_mask | mask; + + match mask { + MetricKindMask::ALL => { + let _ = self + .counter_routes + .insert(pattern.as_ref().to_string(), target_idx); + let _ = self + .gauge_routes + .insert(pattern.as_ref().to_string(), target_idx); + let _ = self + .histogram_routes + .insert(pattern.as_ref().to_string(), target_idx); + } + MetricKindMask::COUNTER => { + let _ = self + .counter_routes + .insert(pattern.as_ref().to_string(), target_idx); + } + MetricKindMask::GAUGE => { + let _ = self + .gauge_routes + .insert(pattern.as_ref().to_string(), target_idx); + } + MetricKindMask::HISTOGRAM => { + let _ = self + .histogram_routes + .insert(pattern.as_ref().to_string(), target_idx); + } + _ => panic!("cannot add route for unknown or empty metric kind mask"), + }; + self + } + + /// Builds the configured [`Router`]. + pub fn build(self) -> Router { + Router { + default: self.default, + global_mask: self.global_mask, + targets: self.targets, + counter_routes: self.counter_routes, + gauge_routes: self.gauge_routes, + histogram_routes: self.histogram_routes, + } + } +} + +#[cfg(test)] +mod tests { + use mockall::{mock, predicate::eq, Sequence}; + use std::borrow::Cow; + + use super::RouterBuilder; + use crate::MetricKindMask; + use metrics::{GaugeValue, Key, Recorder, Unit}; + + mock! { + pub TestRecorder { + } + + impl Recorder for TestRecorder { + fn register_counter(&self, key: &Key, unit: Option, description: Option<&'static str>); + fn register_gauge(&self, key: &Key, unit: Option, description: Option<&'static str>); + fn register_histogram(&self, key: &Key, unit: Option, description: Option<&'static str>); + fn increment_counter(&self, key: &Key, value: u64); + fn update_gauge(&self, key: &Key, value: GaugeValue); + fn record_histogram(&self, key: &Key, value: f64); + } + } + + #[test] + fn test_construction() { + let _ = RouterBuilder::from_recorder(MockTestRecorder::new()).build(); + + let mut builder = RouterBuilder::from_recorder(MockTestRecorder::new()); + builder + .add_route(MetricKindMask::COUNTER, "foo", MockTestRecorder::new()) + .add_route( + MetricKindMask::GAUGE, + "bar".to_owned(), + MockTestRecorder::new(), + ) + .add_route( + MetricKindMask::HISTOGRAM, + Cow::Borrowed("baz"), + MockTestRecorder::new(), + ) + .add_route(MetricKindMask::ALL, "quux", MockTestRecorder::new()); + let _ = builder.build(); + } + + #[test] + #[should_panic] + fn test_bad_construction() { + let mut builder = RouterBuilder::from_recorder(MockTestRecorder::new()); + builder.add_route(MetricKindMask::NONE, "foo", MockTestRecorder::new()); + let _ = builder.build(); + } + + #[test] + fn test_basic_functionality() { + let default_counter: Key = "counter_default.foo".into(); + let override_counter: Key = "counter_override.foo".into(); + let all_override: Key = "all_override.foo".into(); + + let mut default_mock = MockTestRecorder::new(); + let mut counter_mock = MockTestRecorder::new(); + let mut all_mock = MockTestRecorder::new(); + + let mut seq = Sequence::new(); + + default_mock + .expect_increment_counter() + .times(1) + .in_sequence(&mut seq) + .with(eq(default_counter.clone()), eq(42u64)) + .return_const(()); + + counter_mock + .expect_increment_counter() + .times(1) + .in_sequence(&mut seq) + .with(eq(override_counter.clone()), eq(49u64)) + .return_const(()); + + all_mock + .expect_increment_counter() + .times(1) + .in_sequence(&mut seq) + .with(eq(all_override.clone()), eq(420u64)) + .return_const(()); + + all_mock + .expect_record_histogram() + .times(1) + .in_sequence(&mut seq) + .with(eq(all_override.clone()), eq(0.0)) + .return_const(()); + + let mut builder = RouterBuilder::from_recorder(default_mock); + builder + .add_route(MetricKindMask::COUNTER, "counter_override", counter_mock) + .add_route(MetricKindMask::ALL, "all_override", all_mock); + let recorder = builder.build(); + + recorder.increment_counter(&default_counter, 42); + recorder.increment_counter(&override_counter, 49); + recorder.increment_counter(&all_override, 420); + recorder.record_histogram(&all_override, 0.0); + } +} diff --git a/metrics/CHANGELOG.md b/metrics/CHANGELOG.md index fd36f1b1..691c8e88 100644 --- a/metrics/CHANGELOG.md +++ b/metrics/CHANGELOG.md @@ -8,7 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Removed +- `NameParts` has been removed to simplify metric names, again relying on a single string which is + still backed by copy-on-write storage. + ## [0.15.1] - 2021-05-03 +### Changed +- Nothing. Fixed an issue with using the wrong dependency version during a mass release of + workspace crates. ## [0.15.0] - 2021-05-03 ### Changed diff --git a/metrics/Cargo.toml b/metrics/Cargo.toml index 36c8b5cb..53eaeea1 100644 --- a/metrics/Cargo.toml +++ b/metrics/Cargo.toml @@ -27,10 +27,6 @@ bench = false name = "macros" harness = false -[[bench]] -name = "key" -harness = false - [dependencies] metrics-macros = { version = "^0.3", path = "../metrics-macros" } proc-macro-hack = "0.5" diff --git a/metrics/benches/key.rs b/metrics/benches/key.rs deleted file mode 100644 index b635918c..00000000 --- a/metrics/benches/key.rs +++ /dev/null @@ -1,27 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; - -use metrics::{NameParts, SharedString}; - -fn key_benchmark(c: &mut Criterion) { - let mut group = c.benchmark_group("key"); - group.bench_function("name_parts/to_string", |b| { - static NAME_PARTS: [SharedString; 2] = [ - SharedString::const_str("part1"), - SharedString::const_str("part2"), - ]; - let name = NameParts::from_static_names(&NAME_PARTS); - b.iter(|| name.to_string()) - }); - group.bench_function("name_parts/Display::to_string", |b| { - static NAME_PARTS: [SharedString; 2] = [ - SharedString::const_str("part1"), - SharedString::const_str("part2"), - ]; - let name = NameParts::from_static_names(&NAME_PARTS); - b.iter(|| std::fmt::Display::to_string(&name)) - }); - group.finish(); -} - -criterion_group!(benches, key_benchmark); -criterion_main!(benches); diff --git a/metrics/examples/sizes.rs b/metrics/examples/sizes.rs index 48b91644..83cc7d69 100644 --- a/metrics/examples/sizes.rs +++ b/metrics/examples/sizes.rs @@ -1,10 +1,9 @@ //! This example is purely for development. -use metrics::{Key, Label, NameParts, SharedString}; +use metrics::{Key, Label, SharedString}; use std::borrow::Cow; fn main() { println!("Key: {} bytes", std::mem::size_of::()); - println!("NameParts: {} bytes", std::mem::size_of::()); println!("Label: {} bytes", std::mem::size_of::