wip: inline vs dynamic custom enum approach

This commit is contained in:
Toby Lawrence 2020-11-01 10:44:44 -05:00
parent b67840d7d1
commit 5fa7e8fd1d
8 changed files with 246 additions and 51 deletions

View File

@ -288,15 +288,17 @@ where
let labels_len = quote! { #labels_len };
quote! {
static METRIC_NAME: metrics::NameParts = metrics::NameParts::from_static_name(#key);
static METRIC_LABELS: [metrics::Label; #labels_len] = [#(#labels),*];
static METRIC_KEY: metrics::KeyData =
metrics::KeyData::from_static_parts(#key, &METRIC_LABELS);
metrics::KeyData::from_static_parts(&METRIC_NAME, &METRIC_LABELS);
}
}
None => {
quote! {
static METRIC_NAME: metrics::NameParts = metrics::NameParts::from_static_name(#key);
static METRIC_KEY: metrics::KeyData =
metrics::KeyData::from_static_name(#key);
metrics::KeyData::from_static_name(&METRIC_NAME);
}
}
_ => unreachable!("use_fast_path == true, but found expression-based labels"),

View File

@ -26,6 +26,10 @@ harness = false
name = "registry"
harness = false
[[bench]]
name = "prefix"
harness = false
[dependencies]
metrics = { version = "0.13.0-alpha.1", path = "../metrics", features = ["std"] }
crossbeam-epoch = { version = "0.9", optional = true }

View File

@ -0,0 +1,33 @@
use criterion::{criterion_group, criterion_main, Benchmark, Criterion};
use metrics::{Key, KeyData, Label, NameParts, NoopRecorder, Recorder};
use metrics_util::layers::{Layer, PrefixLayer};
fn layer_benchmark(c: &mut Criterion) {
c.bench(
"prefix",
Benchmark::new("basic", |b| {
let prefix_layer = PrefixLayer::new("prefix");
let recorder = prefix_layer.layer(NoopRecorder);
static NAME: NameParts = NameParts::from_static_name("key");
static LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&NAME, &LABELS);
b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
})
})
.with_function("noop recorder overhead (increment_counter)", |b| {
let recorder = NoopRecorder;
static NAME: NameParts = NameParts::from_static_name("key");
static LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&NAME, &LABELS);
b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
})
}),
);
}
criterion_group!(benches, layer_benchmark);
criterion_main!(benches);

View File

@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, BatchSize, Benchmark, Criterion};
use metrics::{Key, KeyData, Label};
use metrics::{Key, KeyData, Label, NameParts};
use metrics_util::Registry;
fn registry_benchmark(c: &mut Criterion) {
@ -7,7 +7,8 @@ fn registry_benchmark(c: &mut Criterion) {
"registry",
Benchmark::new("cached op (basic)", |b| {
let registry: Registry<Key, ()> = Registry::new();
static KEY_DATA: KeyData = KeyData::from_static_name("simple_key");
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key");
static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME);
b.iter(|| {
let key = Key::Borrowed(&KEY_DATA);
@ -16,8 +17,9 @@ fn registry_benchmark(c: &mut Criterion) {
})
.with_function("cached op (labels)", |b| {
let registry: Registry<Key, ()> = Registry::new();
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key");
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts("simple_key", &KEY_LABELS);
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| {
let key = Key::Borrowed(&KEY_DATA);
@ -67,15 +69,15 @@ fn registry_benchmark(c: &mut Criterion) {
})
.with_function("const key data overhead (basic)", |b| {
b.iter(|| {
let key = "simple_key";
KeyData::from_static_name(key)
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key");
KeyData::from_static_name(&KEY_NAME)
})
})
.with_function("const key data overhead (labels)", |b| {
b.iter(|| {
let key = "simple_key";
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key");
static LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
KeyData::from_static_parts(key, &LABELS)
KeyData::from_static_parts(&KEY_NAME, &LABELS)
})
})
.with_function("owned key overhead (basic)", |b| {
@ -92,12 +94,14 @@ fn registry_benchmark(c: &mut Criterion) {
})
})
.with_function("cached key overhead (basic)", |b| {
static KEY_DATA: KeyData = KeyData::from_static_name("simple_key");
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key");
static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME);
b.iter(|| Key::Borrowed(&KEY_DATA))
})
.with_function("cached key overhead (labels)", |b| {
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key");
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts("simple_key", &KEY_LABELS);
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| Key::Borrowed(&KEY_DATA))
}),
);

View File

@ -17,7 +17,12 @@
//!
//! impl<R> StairwayDeny<R> {
//! fn is_invalid_key(&self, key: &Key) -> bool {
//! key.name().contains("stairway") || key.name().contains("heaven")
//! for part in key.name().parts() {
//! if part.contains("stairway") || part.contains("heaven") {
//! return true
//! }
//! }
//! false
//! }
//! }
//!

View File

@ -5,15 +5,15 @@ use metrics::{Key, Recorder, Unit};
///
/// Keys will be prefixed in the format of `<prefix>.<remaining>`.
pub struct Prefix<R> {
prefix: String,
prefix: &'static str,
inner: R,
}
impl<R> Prefix<R> {
fn prefix_key(&self, key: Key) -> Key {
key.into_owned()
.map_name(|old| format!("{}.{}", self.prefix, old))
.into()
let mut owned = key.into_owned();
owned.prepend_name(self.prefix);
owned.into()
}
}
@ -52,12 +52,12 @@ impl<R: Recorder> Recorder for Prefix<R> {
/// A layer for applying a prefix to every metric key.
///
/// More information on the behavior of the layer can be found in [`Prefix`].
pub struct PrefixLayer(String);
pub struct PrefixLayer(&'static str);
impl PrefixLayer {
/// Creates a new `PrefixLayer` based on the given prefix.
pub fn new<S: Into<String>>(prefix: S) -> PrefixLayer {
PrefixLayer(prefix.into())
PrefixLayer(Box::leak(prefix.into().into_boxed_str()))
}
}
@ -66,7 +66,7 @@ impl<R> Layer<R> for PrefixLayer {
fn layer(&self, inner: R) -> Self::Output {
Prefix {
prefix: self.0.clone(),
prefix: self.0,
inner,
}
}
@ -115,7 +115,7 @@ mod tests {
assert_eq!(after.len(), 3);
for (i, (_kind, key, unit, desc, _value)) in after.iter().enumerate() {
assert!(key.name().starts_with("testing"));
assert!(key.name().to_string().starts_with("testing"));
assert_eq!(&Some(ud[i].0.clone()), unit);
assert_eq!(&Some(ud[i].1), desc);
}

View File

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

View File

@ -7,13 +7,157 @@ use core::{
slice::Iter,
};
/// Parts compromising a metric name.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum NameParts {
/// Optimized path for inline storage.
///
/// This variant will be used primarily when the metric name is not scoped, and has not been
/// modified via `append`/`prepend`.
Inline([Option<SharedString>; 2]),
/// Dynamic number of name parts.
///
/// If we do not have an open slot for appending/prepending, this variant will be used.
Dynamic(Vec<SharedString>),
}
impl NameParts {
/// Creates a [`NameParts`] from the given name.
pub fn from_name<N: Into<SharedString>>(name: N) -> Self {
NameParts::Inline([Some(name.into()), None])
}
/// Creates a [`NameParts`] from the given static name.
pub const fn from_static_name(name: &'static str) -> Self {
NameParts::Inline([Some(SharedString::const_str(name)), None])
}
/// Creates a [`NameParts`] from the given static name.
pub const fn from_static_names(first: &'static str, second: &'static str) -> Self {
NameParts::Inline([
Some(SharedString::const_str(first)),
Some(SharedString::const_str(second)),
])
}
/// Appends a name part.
pub fn append<S: Into<SharedString>>(&mut self, part: S) {
match *self {
NameParts::Inline(ref mut inner) => {
if inner[1].is_none() {
// Open slot, so we can utilize it.
inner[1] = Some(part.into());
} else {
// Have to spill over.
let mut parts = Vec::with_capacity(4);
parts.push(inner[0].clone().unwrap());
parts.push(inner[1].clone().unwrap());
parts.push(part.into());
*self = NameParts::Dynamic(parts);
}
},
NameParts::Dynamic(ref mut parts) => {
parts.push(part.into())
},
}
}
/// Prepends a name part.
pub fn prepend<S: Into<SharedString>>(&mut self, part: S) {
match *self {
NameParts::Inline(ref mut inner) => {
if inner[1].is_none() {
// Open slot, so we can utilize it.
inner[1] = inner[0].take();
inner[0] = Some(part.into());
} else {
// Have to spill over.
let mut parts = Vec::with_capacity(4);
parts.push(part.into());
parts.push(inner[0].clone().unwrap());
parts.push(inner[1].clone().unwrap());
*self = NameParts::Dynamic(parts);
}
},
NameParts::Dynamic(ref mut parts) => {
parts.insert(0, part.into())
},
}
}
/// Gets a reference to the parts for this name.
pub fn parts(&self) -> PartsIter {
PartsIter::from(self)
}
}
enum PartsIterInner<'a> {
Inline(&'a [Option<SharedString>; 2]),
Dynamic(Iter<'a, SharedString>),
}
/// Name parts iterator.
pub struct PartsIter<'a> {
idx: usize,
inner: PartsIterInner<'a>,
}
impl<'a> From<&'a NameParts> for PartsIter<'a> {
fn from(parts: &'a NameParts) -> Self {
let inner = match parts {
NameParts::Inline(inner) => PartsIterInner::Inline(inner),
NameParts::Dynamic(parts) => PartsIterInner::Dynamic(parts.iter()),
};
PartsIter { idx: 0, inner }
}
}
impl<'a> Iterator for PartsIter<'a> {
type Item = &'a SharedString;
fn next(&mut self) -> Option<Self::Item> {
match self.inner {
PartsIterInner::Inline(inner) => {
if self.idx > 1 { return None }
let item = inner[self.idx].as_ref();
self.idx += 1;
item
},
PartsIterInner::Dynamic(ref mut iter) => iter.next(),
}
}
}
impl fmt::Display for NameParts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NameParts::Inline(inner) => if inner[1].is_none() {
write!(f, "{}", inner[0].as_ref().unwrap())
} else {
write!(f, "{}.{}", inner[0].as_ref().unwrap(), inner[1].as_ref().unwrap())
},
NameParts::Dynamic(parts) => {
for (i, s) in parts.iter().enumerate() {
if i != parts.len() - 1 {
write!(f, "{}.", s)?;
} else {
write!(f, "{}", s)?;
}
}
Ok(())
},
}
}
}
/// Inner representation of [`Key`].
///
/// While [`Key`] is the type that users will interact with via [`Recorder`][crate::Recorder`,
/// [`KeyData`] is responsible for the actual storage of the name and label data.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyData {
name: SharedString,
name_parts: Cow<'static, NameParts>,
labels: Cow<'static, [Label]>,
}
@ -33,7 +177,7 @@ impl KeyData {
L: IntoLabels,
{
Self {
name: name.into(),
name_parts: Cow::Owned(NameParts::from_name(name)),
labels: labels.into_labels().into(),
}
}
@ -41,9 +185,9 @@ impl KeyData {
/// Creates a [`KeyData`] from a static name.
///
/// This function is `const`, so it can be used in a static context.
pub const fn from_static_name(name: &'static str) -> Self {
pub const fn from_static_name(name_parts: &'static NameParts) -> Self {
Self {
name: SharedString::const_str(name),
name_parts: Cow::Borrowed(name_parts),
labels: Cow::Owned(Vec::new()),
}
}
@ -51,16 +195,16 @@ impl KeyData {
/// 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 {
pub const fn from_static_parts(name_parts: &'static NameParts, labels: &'static [Label]) -> Self {
Self {
name: SharedString::const_str(name),
name_parts: Cow::Borrowed(name_parts),
labels: Cow::Borrowed(labels),
}
}
/// Name of this key.
pub fn name(&self) -> &SharedString {
&self.name
/// Name parts of this key.
pub fn name(&self) -> &NameParts {
&self.name_parts
}
/// Labels of this key, if they exist.
@ -68,21 +212,19 @@ impl KeyData {
self.labels.iter()
}
/// Map the name of this key to a new name, based on `f`.
///
/// 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(SharedString) -> String,
{
let new_name = f(self.name);
self.name = new_name.into();
self
/// Appends a part to the name,
pub fn append_name<S: Into<SharedString>>(&mut self, part: S) {
self.name_parts.to_mut().append(part)
}
/// Consumes this [`Key`], returning the name and any labels.
pub fn into_parts(self) -> (SharedString, Vec<Label>) {
(self.name, self.labels.into_owned())
/// Prepends a part to the name.
pub fn prepend_name<S: Into<SharedString>>(&mut self, part: S) {
self.name_parts.to_mut().prepend(part)
}
/// Consumes this [`Key`], returning the name parts and any labels.
pub fn into_parts(self) -> (NameParts, Vec<Label>) {
(self.name_parts.into_owned(), self.labels.into_owned())
}
/// Clones this [`Key`], and expands the existing set of labels.
@ -91,12 +233,12 @@ impl KeyData {
return self.clone();
}
let name = self.name.clone();
let name_parts = self.name_parts.clone();
let mut labels = self.labels.clone().into_owned();
labels.extend(extra_labels);
Self {
name,
name_parts,
labels: labels.into(),
}
}
@ -105,9 +247,9 @@ impl KeyData {
impl fmt::Display for KeyData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.labels.is_empty() {
write!(f, "KeyData({})", self.name)
write!(f, "KeyData({})", self.name_parts)
} else {
write!(f, "KeyData({}, [", self.name)?;
write!(f, "KeyData({}, [", self.name_parts)?;
let mut first = true;
for label in self.labels.as_ref() {
if first {
@ -240,13 +382,14 @@ impl From<&'static KeyData> for Key {
#[cfg(test)]
mod tests {
use super::{Key, KeyData};
use super::{Key, KeyData, NameParts};
use crate::Label;
use std::collections::HashMap;
static BORROWED_BASIC: KeyData = KeyData::from_static_name("name");
static BORROWED_NAME: NameParts = NameParts::from_static_name("name");
static BORROWED_BASIC: KeyData = KeyData::from_static_name(&BORROWED_NAME);
static LABELS: [Label; 1] = [Label::from_static_parts("key", "value")];
static BORROWED_LABELS: KeyData = KeyData::from_static_parts("name", &LABELS);
static BORROWED_LABELS: KeyData = KeyData::from_static_parts(&BORROWED_NAME, &LABELS);
#[test]
fn test_keydata_eq_and_hash() {
@ -335,8 +478,11 @@ mod tests {
let owned_a = KeyData::from_name("a");
let owned_b = KeyData::from_name("b");
static STATIC_A: KeyData = KeyData::from_static_name("a");
static STATIC_B: KeyData = KeyData::from_static_name("b");
static A_NAME: NameParts = NameParts::from_static_name("a");
static STATIC_A: KeyData = KeyData::from_static_name(&A_NAME);
static B_NAME: NameParts = NameParts::from_static_name("b");
static STATIC_B: KeyData = KeyData::from_static_name(&B_NAME);
assert_eq!(Key::Owned(owned_a.clone()), Key::Owned(owned_a.clone()));
assert_eq!(Key::Owned(owned_b.clone()), Key::Owned(owned_b.clone()));