From 30c7ff1a98b4f621720a95878a9a8f2e45870bdb Mon Sep 17 00:00:00 2001 From: Toby Lawrence Date: Fri, 13 Nov 2020 11:06:53 -0500 Subject: [PATCH] a lot of tweaks --- .../src/tracing_integration.rs | 2 +- metrics-util/Cargo.toml | 4 + metrics-util/benches/filter.rs | 61 +++ metrics-util/src/layers/filter.rs | 22 +- metrics/src/cow.rs | 515 ++++++++++++++++++ 5 files changed, 596 insertions(+), 8 deletions(-) create mode 100644 metrics-util/benches/filter.rs create mode 100644 metrics/src/cow.rs diff --git a/metrics-tracing-context/src/tracing_integration.rs b/metrics-tracing-context/src/tracing_integration.rs index f55b8a4..37381ae 100644 --- a/metrics-tracing-context/src/tracing_integration.rs +++ b/metrics-tracing-context/src/tracing_integration.rs @@ -20,7 +20,7 @@ impl Visit for Labels { } fn record_bool(&mut self, field: &Field, value: bool) { - let label = Label::new(field.name(), if value { "true" } else { "false" }); + let label = Label::from_static_parts(field.name(), if value { "true" } else { "false" }); self.0.push(label); } diff --git a/metrics-util/Cargo.toml b/metrics-util/Cargo.toml index 40c7ff0..ae2da86 100644 --- a/metrics-util/Cargo.toml +++ b/metrics-util/Cargo.toml @@ -30,6 +30,10 @@ harness = false name = "prefix" harness = false +[[bench]] +name = "filter" +harness = false + [dependencies] metrics = { version = "0.13.0-alpha.1", path = "../metrics", features = ["std"] } crossbeam-epoch = { version = "0.9", optional = true } diff --git a/metrics-util/benches/filter.rs b/metrics-util/benches/filter.rs new file mode 100644 index 0000000..ac5b73d --- /dev/null +++ b/metrics-util/benches/filter.rs @@ -0,0 +1,61 @@ +use criterion::{criterion_group, criterion_main, Benchmark, Criterion}; +use metrics::{Key, KeyData, Label, NoopRecorder, Recorder, SharedString}; +use metrics_util::layers::{FilterLayer, Layer}; + +fn layer_benchmark(c: &mut Criterion) { + c.bench( + "filter", + Benchmark::new("match", |b| { + 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_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; + static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS); + + b.iter(|| { + recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1); + }) + }) + .with_function("no match", |b| { + 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: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS); + + b.iter(|| { + recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1); + }) + }) + .with_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_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; + static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS); + + b.iter(|| { + recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1); + }) + }) + .with_function("noop recorder overhead (increment_counter)", |b| { + let recorder = NoopRecorder; + static KEY_NAME: [SharedString; 1] = [SharedString::const_str("tokio.foo")]; + static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; + static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS); + + b.iter(|| { + recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1); + }) + }), + ); +} + +criterion_group!(benches, layer_benchmark); +criterion_main!(benches); diff --git a/metrics-util/src/layers/filter.rs b/metrics-util/src/layers/filter.rs index 83ce19d..95c59bf 100644 --- a/metrics-util/src/layers/filter.rs +++ b/metrics-util/src/layers/filter.rs @@ -13,7 +13,9 @@ pub struct Filter { impl Filter { fn should_filter(&self, key: &Key) -> bool { - self.automaton.is_match(key.name().as_ref()) + key.name() + .parts() + .any(|s| self.automaton.is_match(s.as_ref())) } } @@ -75,11 +77,14 @@ impl FilterLayer { /// Creates a `FilterLayer` from an existing set of patterns. pub fn from_patterns(patterns: P) -> Self where - P: Iterator, + P: IntoIterator, I: AsRef, { FilterLayer { - patterns: patterns.map(|s| s.as_ref().to_string()).collect(), + patterns: patterns + .into_iter() + .map(|s| s.as_ref().to_string()) + .collect(), case_insensitive: false, use_dfa: true, } @@ -142,7 +147,7 @@ mod tests { let patterns = &["tokio", "bb8"]; let recorder = DebuggingRecorder::new(); let snapshotter = recorder.snapshotter(); - let filter = FilterLayer::from_patterns(patterns.iter()); + let filter = FilterLayer::from_patterns(patterns); let layered = filter.layer(recorder); let before = snapshotter.snapshot(); @@ -186,7 +191,10 @@ mod tests { assert_eq!(after.len(), 2); for (_kind, key, unit, desc, _value) in after { - assert!(!key.name().contains("tokio") && !key.name().contains("bb8")); + assert!( + !key.name().to_string().contains("tokio") + && !key.name().to_string().contains("bb8") + ); // We cheat here since we're not comparing one-to-one with the source data, // but we know which metrics are going to make it through so we can hard code. assert_eq!(Some(Unit::Bytes), unit); @@ -217,8 +225,8 @@ mod tests { for (_kind, key, _unit, _desc, _value) in &after { assert!( - !key.name().to_lowercase().contains("tokio") - && !key.name().to_lowercase().contains("bb8") + !key.name().to_string().to_lowercase().contains("tokio") + && !key.name().to_string().to_lowercase().contains("bb8") ); } } diff --git a/metrics/src/cow.rs b/metrics/src/cow.rs new file mode 100644 index 0000000..862fc5e --- /dev/null +++ b/metrics/src/cow.rs @@ -0,0 +1,515 @@ +use crate::label::Label; +use alloc::borrow::Borrow; +use alloc::string::String; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::marker::PhantomData; +use core::mem::ManuallyDrop; +use core::ptr::{slice_from_raw_parts, NonNull}; + +/// A clone-on-write smart pointer with an optimized memory layout. +pub struct Cow<'a, T: Cowable + ?Sized + 'a> { + /// Pointer to data. + ptr: NonNull, + + /// Pointer metadata: length and capacity. + meta: Metadata, + + /// Lifetime marker. + marker: PhantomData<&'a T>, +} + +impl Cow<'_, T> +where + T: Cowable + ?Sized, +{ + #[inline] + pub fn owned(val: T::Owned) -> Self { + let (ptr, meta) = T::owned_into_parts(val); + + Cow { + ptr, + meta, + marker: PhantomData, + } + } +} + +impl<'a, T> Cow<'a, T> +where + T: Cowable + ?Sized, +{ + #[inline] + pub fn borrowed(val: &'a T) -> Self { + let (ptr, meta) = T::ref_into_parts(val); + + Cow { + ptr, + meta, + marker: PhantomData, + } + } + + #[inline] + pub fn into_owned(self) -> T::Owned { + let cow = ManuallyDrop::new(self); + + if cow.is_borrowed() { + unsafe { T::clone_from_parts(cow.ptr, &cow.meta) } + } else { + unsafe { T::owned_from_parts(cow.ptr, &cow.meta) } + } + } + + #[inline] + pub fn is_borrowed(&self) -> bool { + self.meta.capacity() == 0 + } + + #[inline] + pub fn is_owned(&self) -> bool { + self.meta.capacity() != 0 + } + + #[inline] + fn borrow(&self) -> &T { + unsafe { &*T::ref_from_parts(self.ptr, &self.meta) } + } +} + +// Implementations of constant functions for creating `Cow` via static strings, static string +// slices, and static label slices. +impl<'a> Cow<'a, str> { + pub const fn const_str(val: &'a str) -> Self { + Cow { + // We are casting *const T to *mut T, however for all borrowed values + // this raw pointer is only ever dereferenced back to &T. + ptr: unsafe { NonNull::new_unchecked(val.as_ptr() as *mut u8) }, + meta: Metadata::from_ref(val.len()), + marker: PhantomData, + } + } +} + +impl<'a> Cow<'a, [Cow<'static, str>]> { + pub const fn const_slice(val: &'a [Cow<'static, str>]) -> Self { + Cow { + ptr: unsafe { NonNull::new_unchecked(val.as_ptr() as *mut Cow<'static, str>) }, + meta: Metadata::from_ref(val.len()), + marker: PhantomData, + } + } +} + +impl<'a> Cow<'a, [Label]> { + pub const fn const_slice(val: &'a [Label]) -> Self { + Cow { + ptr: unsafe { NonNull::new_unchecked(val.as_ptr() as *mut Label) }, + meta: Metadata::from_ref(val.len()), + marker: PhantomData, + } + } +} + +impl Hash for Cow<'_, T> +where + T: Hash + Cowable + ?Sized, +{ + #[inline] + fn hash(&self, state: &mut H) { + self.borrow().hash(state) + } +} + +impl<'a, T> Default for Cow<'a, T> +where + T: Cowable + ?Sized, + &'a T: Default, +{ + #[inline] + fn default() -> Self { + Cow::borrowed(Default::default()) + } +} + +impl Eq for Cow<'_, T> where T: Eq + Cowable + ?Sized {} + +impl PartialOrd> for Cow<'_, A> +where + A: Cowable + ?Sized + PartialOrd, + B: Cowable + ?Sized, +{ + #[inline] + fn partial_cmp(&self, other: &Cow<'_, B>) -> Option { + PartialOrd::partial_cmp(self.borrow(), other.borrow()) + } +} + +impl Ord for Cow<'_, T> +where + T: Ord + Cowable + ?Sized, +{ + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + Ord::cmp(self.borrow(), other.borrow()) + } +} + +impl<'a, T> From<&'a T> for Cow<'a, T> +where + T: Cowable + ?Sized, +{ + #[inline] + fn from(val: &'a T) -> Self { + Cow::borrowed(val) + } +} + +impl From for Cow<'_, str> { + #[inline] + fn from(s: String) -> Self { + Cow::owned(s) + } +} + +impl From> for Cow<'_, [Label]> { + #[inline] + fn from(v: Vec