This commit is contained in:
Toby Lawrence 2020-11-12 23:23:32 -05:00
parent d72a744a76
commit 7a8f3da859
15 changed files with 288 additions and 315 deletions

146
COPYRIGHT
View File

@ -1,102 +1,94 @@
Short version for non-lawyers: Short version for non-lawyers:
metrics is MIT licensed. `metrics` is MIT licensed.
Longer version: Longer version:
Copyrights in the metrics project are retained by their contributors. No Copyrights in the `metrics` project are retained by their contributors. No copyright assignment is
copyright assignment is required to contribute to the metrics project. required to contribute to the `metrics` project.
Some files include explicit copyright notices and/or license notices. Some files include explicit copyright notices and/or license notices. For full authorship
For full authorship information, see the version control history. information, see the version control history.
Except as otherwise noted (below and/or in individual files), metrics Except as otherwise noted (below and/or in individual files), `metrics` is licensed under the MIT
is licensed under the MIT license <LICENSE> or license <LICENSE> or <http://opensource.org/licenses/MIT>.
<http://opensource.org/licenses/MIT>.
metrics includes packages written by third parties. `metrics` includes packages written by third parties. The following third party packages are
The following third party packages are included, and carry included, and carry their own copyright notices and license terms:
their own copyright notices and license terms:
* Portions of the API design are derived from tic * Portions of the API design are derived from the `tic` crate which carries the following license:
<https://github.com/brayniac/tic>, which carries the following
license:
Copyright (c) 2016 Brian Martin Copyright (c) 2016 Brian Martin
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person obtaining a copy of this software
obtaining a copy of this software and associated documentation and associated documentation files (the "Software"), to deal in the Software without
files (the "Software"), to deal in the Software without restriction, restriction, including without limitation the rights to use, copy, modify, merge, publish,
including without limitation the rights to use, copy, modify, merge, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
publish, distribute, sublicense, and/or sell copies of the Software, Software is furnished to do so, subject to the following conditions:
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be The above copyright notice and this permission notice shall be included in all copies or
included in all copies or substantial portions of the Software. substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
* metrics is a fork of rust-lang-nursery/log which carries the following * metrics is a fork of `rust-lang-nursery/log` which carries the following license:
license:
Copyright (c) 2014 The Rust Project Developers Copyright (c) 2014 The Rust Project Developers
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any person obtaining a copy of this software
person obtaining a copy of this software and associated and associated documentation files (the "Software"), to deal in the Software without
documentation files (the "Software"), to deal in the restriction, including without limitation the rights to use, copy, modify, merge, publish,
Software without restriction, including without distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
limitation the rights to use, copy, modify, merge, Software is furnished to do so, subject to the following conditions:
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice The above copyright notice and this permission notice shall be included in all copies or
shall be included in all copies or substantial portions substantial portions of the Software.
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
* metrics-observer reuses code from `std::time::Duration` which carries * metrics-observer reuses code from `std::time::Duration` which carries the following license:
the following license:
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any person obtaining a copy of this software
person obtaining a copy of this software and associated and associated documentation files (the "Software"), to deal in the Software without
documentation files (the "Software"), to deal in the restriction, including without limitation the rights to use, copy, modify, merge, publish,
Software without restriction, including without distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
limitation the rights to use, copy, modify, merge, Software is furnished to do so, subject to the following conditions:
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice The above copyright notice and this permission notice shall be included in all copies or
shall be included in all copies or substantial portions substantial portions of the Software.
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR * metrics includes code from the `beef` crate which carries the following license:
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. Copyright (c) 2020 Maciej Hirsz <hello@maciej.codes>
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -80,7 +80,7 @@ impl Generator {
impl Drop for Generator { impl Drop for Generator {
fn drop(&mut self) { fn drop(&mut self) {
info!( info!(
" sender latency: min: {:9} p50: {:9} p95: {:9} p99: {:9} p999: {:9} max: {:9}", " sender latency: min: {:8} p50: {:8} p95: {:8} p99: {:8} p999: {:8} max: {:8}",
nanos_to_readable(self.hist.min()), nanos_to_readable(self.hist.min()),
nanos_to_readable(self.hist.value_at_percentile(50.0)), nanos_to_readable(self.hist.value_at_percentile(50.0)),
nanos_to_readable(self.hist.value_at_percentile(95.0)), nanos_to_readable(self.hist.value_at_percentile(95.0)),
@ -196,7 +196,7 @@ fn main() {
info!("--------------------------------------------------------------------------------"); info!("--------------------------------------------------------------------------------");
info!(" ingested samples total: {}", total); info!(" ingested samples total: {}", total);
info!( info!(
"snapshot retrieval: min: {:9} p50: {:9} p95: {:9} p99: {:9} p999: {:9} max: {:9}", "snapshot retrieval: min: {:8} p50: {:8} p95: {:8} p99: {:8} p999: {:8} max: {:8}",
nanos_to_readable(snapshot_hist.min()), nanos_to_readable(snapshot_hist.min()),
nanos_to_readable(snapshot_hist.value_at_percentile(50.0)), nanos_to_readable(snapshot_hist.value_at_percentile(50.0)),
nanos_to_readable(snapshot_hist.value_at_percentile(95.0)), nanos_to_readable(snapshot_hist.value_at_percentile(95.0)),

View File

@ -334,7 +334,7 @@ impl PrometheusRecorder {
fn add_description_if_missing(&self, key: &Key, description: Option<&'static str>) { fn add_description_if_missing(&self, key: &Key, description: Option<&'static str>) {
if let Some(description) = description { if let Some(description) = description {
let mut descriptions = self.inner.descriptions.write(); let mut descriptions = self.inner.descriptions.write();
if !descriptions.contains_key(key.name().as_ref()) { if !descriptions.contains_key(key.name().to_string().as_str()) {
descriptions.insert(key.name().to_string(), description); descriptions.insert(key.name().to_string(), description);
} }
} }
@ -552,7 +552,11 @@ fn key_to_parts(key: Key) -> (String, Vec<String>) {
let name = key.name(); let name = key.name();
let labels = key.labels(); let labels = key.labels();
let sanitize = |c| c == '.' || c == '=' || c == '{' || c == '}' || c == '+' || c == '-'; let sanitize = |c| c == '.' || c == '=' || c == '{' || c == '}' || c == '+' || c == '-';
let name = name.replace(sanitize, "_"); let name = name
.parts()
.map(|s| s.replace(sanitize, "_"))
.collect::<Vec<_>>()
.join("_");
let labels = labels let labels = labels
.into_iter() .into_iter()
.map(|label| { .map(|label| {

View File

@ -224,13 +224,13 @@ pub fn histogram(input: TokenStream) -> TokenStream {
fn get_expanded_registration( fn get_expanded_registration(
metric_type: &str, metric_type: &str,
key: LitStr, name: LitStr,
unit: Option<Expr>, unit: Option<Expr>,
description: Option<LitStr>, description: Option<LitStr>,
labels: Option<Labels>, labels: Option<Labels>,
) -> proc_macro2::TokenStream { ) -> proc_macro2::TokenStream {
let register_ident = format_ident!("register_{}", metric_type); let register_ident = format_ident!("register_{}", metric_type);
let key = key_to_quoted(key, labels); let key = key_to_quoted(labels);
let unit = match unit { let unit = match unit {
Some(e) => quote! { Some(#e) }, Some(e) => quote! { Some(#e) },
@ -244,6 +244,7 @@ fn get_expanded_registration(
quote! { quote! {
{ {
static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
// Only do this work if there's a recorder installed. // Only do this work if there's a recorder installed.
if let Some(recorder) = metrics::try_recorder() { if let Some(recorder) = metrics::try_recorder() {
// Registrations are fairly rare, don't attempt to cache here // Registrations are fairly rare, don't attempt to cache here
@ -257,7 +258,7 @@ fn get_expanded_registration(
fn get_expanded_callsite<V>( fn get_expanded_callsite<V>(
metric_type: &str, metric_type: &str,
op_type: &str, op_type: &str,
key: LitStr, name: LitStr,
labels: Option<Labels>, labels: Option<Labels>,
op_values: V, op_values: V,
) -> proc_macro2::TokenStream ) -> proc_macro2::TokenStream
@ -288,7 +289,7 @@ where
let labels_len = quote! { #labels_len }; let labels_len = quote! { #labels_len };
quote! { quote! {
static METRIC_NAME: metrics::NameParts = metrics::NameParts::from_static_name(#key); static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
static METRIC_LABELS: [metrics::Label; #labels_len] = [#(#labels),*]; static METRIC_LABELS: [metrics::Label; #labels_len] = [#(#labels),*];
static METRIC_KEY: metrics::KeyData = static METRIC_KEY: metrics::KeyData =
metrics::KeyData::from_static_parts(&METRIC_NAME, &METRIC_LABELS); metrics::KeyData::from_static_parts(&METRIC_NAME, &METRIC_LABELS);
@ -296,7 +297,7 @@ where
} }
None => { None => {
quote! { quote! {
static METRIC_NAME: metrics::NameParts = metrics::NameParts::from_static_name(#key); static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
static METRIC_KEY: metrics::KeyData = static METRIC_KEY: metrics::KeyData =
metrics::KeyData::from_static_name(&METRIC_NAME); metrics::KeyData::from_static_name(&METRIC_NAME);
} }
@ -316,9 +317,11 @@ where
} }
} else { } else {
// We're on the slow path, so we allocate, womp. // We're on the slow path, so we allocate, womp.
let key = key_to_quoted(key, labels); let key = key_to_quoted(labels);
quote! { quote! {
{ {
static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
// Only do this work if there's a recorder installed. // Only do this work if there's a recorder installed.
if let Some(recorder) = metrics::try_recorder() { if let Some(recorder) = metrics::try_recorder() {
recorder.#op_ident(metrics::Key::Owned(#key), #op_values); recorder.#op_ident(metrics::Key::Owned(#key), #op_values);
@ -355,17 +358,19 @@ fn read_key(input: &mut ParseStream) -> Result<LitStr> {
Ok(key) Ok(key)
} }
fn key_to_quoted(name: LitStr, labels: Option<Labels>) -> proc_macro2::TokenStream { fn key_to_quoted(labels: Option<Labels>) -> proc_macro2::TokenStream {
match labels { match labels {
None => quote! { metrics::KeyData::from_name(#name) }, None => quote! { metrics::KeyData::from_static_name(&METRIC_NAME) },
Some(labels) => match labels { Some(labels) => match labels {
Labels::Inline(pairs) => { Labels::Inline(pairs) => {
let labels = pairs let labels = pairs
.into_iter() .into_iter()
.map(|(key, val)| quote! { metrics::Label::new(#key, #val) }); .map(|(key, val)| quote! { metrics::Label::new(#key, #val) });
quote! { metrics::KeyData::from_parts(#name, vec![#(#labels),*]) } quote! {
metrics::KeyData::from_hybrid_parts(&METRIC_NAME, vec![#(#labels),*])
}
} }
Labels::Existing(e) => quote! { metrics::KeyData::from_parts(#name, #e) }, Labels::Existing(e) => quote! { metrics::KeyData::from_hybrid_parts(&METRIC_NAME, #e) },
}, },
} }
} }

View File

@ -10,9 +10,11 @@ fn test_get_expanded_registration() {
get_expanded_registration("mytype", parse_quote! { "mykeyname" }, None, None, None); get_expanded_registration("mytype", parse_quote! { "mykeyname" }, None, None, None);
let expected = concat!( let expected = concat!(
"{ if let Some (recorder) = metrics :: try_recorder () { ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . register_mytype (", "recorder . register_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ", "metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"None , ", "None , ",
"None", "None",
") ; ", ") ; ",
@ -35,9 +37,11 @@ fn test_get_expanded_registration_with_unit() {
); );
let expected = concat!( let expected = concat!(
"{ if let Some (recorder) = metrics :: try_recorder () { ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . register_mytype (", "recorder . register_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ", "metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"Some (metrics :: Unit :: Nanoseconds) , ", "Some (metrics :: Unit :: Nanoseconds) , ",
"None", "None",
") ; ", ") ; ",
@ -59,9 +63,11 @@ fn test_get_expanded_registration_with_description() {
); );
let expected = concat!( let expected = concat!(
"{ if let Some (recorder) = metrics :: try_recorder () { ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . register_mytype (", "recorder . register_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ", "metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"None , ", "None , ",
"Some (\"flerkin\")", "Some (\"flerkin\")",
") ; ", ") ; ",
@ -84,9 +90,11 @@ fn test_get_expanded_registration_with_unit_and_description() {
); );
let expected = concat!( let expected = concat!(
"{ if let Some (recorder) = metrics :: try_recorder () { ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . register_mytype (", "recorder . register_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ", "metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"Some (metrics :: Unit :: Nanoseconds) , ", "Some (metrics :: Unit :: Nanoseconds) , ",
"Some (\"flerkin\")", "Some (\"flerkin\")",
") ; ", ") ; ",
@ -108,7 +116,8 @@ fn test_get_expanded_callsite_fast_path_no_labels() {
let expected = concat!( let expected = concat!(
"{ ", "{ ",
"static METRIC_KEY : metrics :: KeyData = metrics :: KeyData :: from_static_name (\"mykeyname\") ; ", "static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"static METRIC_KEY : metrics :: KeyData = metrics :: KeyData :: from_static_name (& METRIC_NAME) ; ",
"if let Some (recorder) = metrics :: try_recorder () { ", "if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (metrics :: Key :: Borrowed (& METRIC_KEY) , 1) ; ", "recorder . myop_mytype (metrics :: Key :: Borrowed (& METRIC_KEY) , 1) ; ",
"} }", "} }",
@ -130,8 +139,9 @@ fn test_get_expanded_callsite_fast_path_static_labels() {
let expected = concat!( let expected = concat!(
"{ ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"static METRIC_LABELS : [metrics :: Label ; 1usize] = [metrics :: Label :: from_static_parts (\"key1\" , \"value1\")] ; ", "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) ; ", "static METRIC_KEY : metrics :: KeyData = metrics :: KeyData :: from_static_parts (& METRIC_NAME , & METRIC_LABELS) ; ",
"if let Some (recorder) = metrics :: try_recorder () { ", "if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (metrics :: Key :: Borrowed (& METRIC_KEY) , 1) ; ", "recorder . myop_mytype (metrics :: Key :: Borrowed (& METRIC_KEY) , 1) ; ",
"} ", "} ",
@ -154,9 +164,10 @@ fn test_get_expanded_callsite_fast_path_dynamic_labels() {
let expected = concat!( let expected = concat!(
"{ ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ", "if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (metrics :: Key :: Owned (", "recorder . myop_mytype (metrics :: Key :: Owned (",
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [metrics :: Label :: new (\"key1\" , & value1)])", "metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , vec ! [metrics :: Label :: new (\"key1\" , & value1)])",
") , 1) ; ", ") , 1) ; ",
"} ", "} ",
"}", "}",
@ -178,9 +189,10 @@ fn test_get_expanded_callsite_regular_path() {
let expected = concat!( let expected = concat!(
"{ ", "{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ", "if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (", "recorder . myop_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_parts (\"mykeyname\" , mylabels)) , ", "metrics :: Key :: Owned (metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , mylabels)) , ",
"1", "1",
") ; ", ") ; ",
"} }", "} }",
@ -191,18 +203,17 @@ fn test_get_expanded_callsite_regular_path() {
#[test] #[test]
fn test_key_to_quoted_no_labels() { fn test_key_to_quoted_no_labels() {
let stream = key_to_quoted(parse_quote! {"mykeyname"}, None); let stream = key_to_quoted(None);
let expected = "metrics :: KeyData :: from_name (\"mykeyname\")"; let expected = "metrics :: KeyData :: from_static_name (& METRIC_NAME)";
assert_eq!(stream.to_string(), expected); assert_eq!(stream.to_string(), expected);
} }
#[test] #[test]
fn test_key_to_quoted_existing_labels() { fn test_key_to_quoted_existing_labels() {
let stream = key_to_quoted( let stream = key_to_quoted(Some(Labels::Existing(Expr::Path(
parse_quote! {"mykeyname"}, parse_quote! { mylabels },
Some(Labels::Existing(Expr::Path(parse_quote! { mylabels }))), ))));
); let expected = "metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , mylabels)";
let expected = "metrics :: KeyData :: from_parts (\"mykeyname\" , mylabels)";
assert_eq!(stream.to_string(), expected); assert_eq!(stream.to_string(), expected);
} }
@ -210,15 +221,12 @@ fn test_key_to_quoted_existing_labels() {
/// Key). /// Key).
#[test] #[test]
fn test_key_to_quoted_inline_labels() { fn test_key_to_quoted_inline_labels() {
let stream = key_to_quoted( let stream = key_to_quoted(Some(Labels::Inline(vec![
parse_quote! {"mykeyname"}, (parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }),
Some(Labels::Inline(vec![ (parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }),
(parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }), ])));
(parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }),
])),
);
let expected = concat!( let expected = concat!(
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [", "metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , vec ! [",
"metrics :: Label :: new (\"mylabel1\" , mylabel1) , ", "metrics :: Label :: new (\"mylabel1\" , mylabel1) , ",
"metrics :: Label :: new (\"mylabel2\" , \"mylabel2\")", "metrics :: Label :: new (\"mylabel2\" , \"mylabel2\")",
"])" "])"
@ -228,10 +236,7 @@ fn test_key_to_quoted_inline_labels() {
#[test] #[test]
fn test_key_to_quoted_inline_labels_empty() { fn test_key_to_quoted_inline_labels_empty() {
let stream = key_to_quoted(parse_quote! {"mykeyname"}, Some(Labels::Inline(vec![]))); let stream = key_to_quoted(Some(Labels::Inline(vec![])));
let expected = concat!( let expected = concat!("metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , vec ! [])");
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [",
"])"
);
assert_eq!(stream.to_string(), expected); assert_eq!(stream.to_string(), expected);
} }

View File

@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, Benchmark, Criterion}; use criterion::{criterion_group, criterion_main, Benchmark, Criterion};
use metrics::{Key, KeyData, Label, NoopRecorder, Recorder}; use metrics::{Key, KeyData, Label, NoopRecorder, Recorder, SharedString};
use metrics_tracing_context::{MetricsLayer, TracingContextLayer}; use metrics_tracing_context::{MetricsLayer, TracingContextLayer};
use metrics_util::layers::Layer; use metrics_util::layers::Layer;
use tracing::{ use tracing::{
@ -22,8 +22,9 @@ fn layer_benchmark(c: &mut Criterion) {
let tracing_layer = TracingContextLayer::all(); let tracing_layer = TracingContextLayer::all();
let recorder = tracing_layer.layer(NoopRecorder); let recorder = tracing_layer.layer(NoopRecorder);
static LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")];
static KEY_DATA: KeyData = KeyData::from_static_parts("key", &LABELS); 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(|| { b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1); recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
@ -32,8 +33,9 @@ fn layer_benchmark(c: &mut Criterion) {
}) })
.with_function("noop recorder overhead (increment_counter)", |b| { .with_function("noop recorder overhead (increment_counter)", |b| {
let recorder = NoopRecorder; let recorder = NoopRecorder;
static LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")]; static KEY_NAME: [SharedString; 1] = [SharedString::const_str("key")];
static KEY_DATA: KeyData = KeyData::from_static_parts("key", &LABELS); 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(|| { b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1); recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);

View File

@ -101,7 +101,7 @@ where
} }
} }
/// [`TracingContext`] is a [`metrics::Recorder`] that injects labels from [`tracing::Span`]s. /// [`TracingContext`] is a [`metrics::Recorder`] that injects labels from[`tracing::Span`]s.
pub struct TracingContext<R, F> { pub struct TracingContext<R, F> {
inner: R, inner: R,
label_filter: F, label_filter: F,
@ -126,7 +126,7 @@ where
fn enhance_key(&self, key: Key) -> Key { fn enhance_key(&self, key: Key) -> Key {
let (name, mut labels) = key.into_owned().into_parts(); let (name, mut labels) = key.into_owned().into_parts();
self.enhance_labels(&mut labels); self.enhance_labels(&mut labels);
KeyData::from_parts(name, labels).into() KeyData::from_owned_parts(name, labels).into()
} }
} }

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, BatchSize, Benchmark, Criterion}; use criterion::{criterion_group, criterion_main, BatchSize, Benchmark, Criterion};
use metrics::{Key, KeyData, Label, NameParts}; use metrics::{Key, KeyData, Label, SharedString};
use metrics_util::Registry; use metrics_util::Registry;
fn registry_benchmark(c: &mut Criterion) { fn registry_benchmark(c: &mut Criterion) {
@ -7,7 +7,7 @@ fn registry_benchmark(c: &mut Criterion) {
"registry", "registry",
Benchmark::new("cached op (basic)", |b| { Benchmark::new("cached op (basic)", |b| {
let registry: Registry<Key, ()> = Registry::new(); let registry: Registry<Key, ()> = Registry::new();
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key"); static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME); static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME);
b.iter(|| { b.iter(|| {
@ -17,7 +17,7 @@ fn registry_benchmark(c: &mut Criterion) {
}) })
.with_function("cached op (labels)", |b| { .with_function("cached op (labels)", |b| {
let registry: Registry<Key, ()> = Registry::new(); let registry: Registry<Key, ()> = Registry::new();
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key"); static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")]; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS); static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
@ -64,18 +64,18 @@ fn registry_benchmark(c: &mut Criterion) {
b.iter(|| { b.iter(|| {
let key = "simple_key"; let key = "simple_key";
let labels = vec![Label::new("type", "http")]; let labels = vec![Label::new("type", "http")];
KeyData::from_parts(key, labels) KeyData::from_owned_parts(key, labels)
}) })
}) })
.with_function("const key data overhead (basic)", |b| { .with_function("const key data overhead (basic)", |b| {
b.iter(|| { b.iter(|| {
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key"); static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
KeyData::from_static_name(&KEY_NAME) KeyData::from_static_name(&KEY_NAME)
}) })
}) })
.with_function("const key data overhead (labels)", |b| { .with_function("const key data overhead (labels)", |b| {
b.iter(|| { b.iter(|| {
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key"); static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static LABELS: [Label; 1] = [Label::from_static_parts("type", "http")]; static LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
KeyData::from_static_parts(&KEY_NAME, &LABELS) KeyData::from_static_parts(&KEY_NAME, &LABELS)
}) })
@ -90,16 +90,16 @@ fn registry_benchmark(c: &mut Criterion) {
b.iter(|| { b.iter(|| {
let key = "simple_key"; let key = "simple_key";
let labels = vec![Label::new("type", "http")]; let labels = vec![Label::new("type", "http")];
Key::Owned(KeyData::from_parts(key, labels)) Key::Owned(KeyData::from_owned_parts(key, labels))
}) })
}) })
.with_function("cached key overhead (basic)", |b| { .with_function("cached key overhead (basic)", |b| {
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key"); static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME); static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME);
b.iter(|| Key::Borrowed(&KEY_DATA)) b.iter(|| Key::Borrowed(&KEY_DATA))
}) })
.with_function("cached key overhead (labels)", |b| { .with_function("cached key overhead (labels)", |b| {
static KEY_NAME: NameParts = NameParts::from_static_name("simple_key"); static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")]; static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS); static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| Key::Borrowed(&KEY_DATA)) b.iter(|| Key::Borrowed(&KEY_DATA))

View File

@ -1,19 +1,17 @@
use crate::layers::Layer; use crate::layers::Layer;
use metrics::{Key, Recorder, Unit}; use metrics::{Key, Recorder, SharedString, Unit};
/// Applies a prefix to every metric key. /// Applies a prefix to every metric key.
/// ///
/// Keys will be prefixed in the format of `<prefix>.<remaining>`. /// Keys will be prefixed in the format of `<prefix>.<remaining>`.
pub struct Prefix<R> { pub struct Prefix<R> {
prefix: &'static str, prefix: SharedString,
inner: R, inner: R,
} }
impl<R> Prefix<R> { impl<R> Prefix<R> {
fn prefix_key(&self, key: Key) -> Key { fn prefix_key(&self, key: Key) -> Key {
let mut owned = key.into_owned(); key.into_owned().prepend_name(self.prefix.clone()).into()
owned.prepend_name(self.prefix);
owned.into()
} }
} }
@ -66,7 +64,7 @@ impl<R> Layer<R> for PrefixLayer {
fn layer(&self, inner: R) -> Self::Output { fn layer(&self, inner: R) -> Self::Output {
Prefix { Prefix {
prefix: self.0, prefix: self.0.into(),
inner, inner,
} }
} }

View File

@ -1,13 +1,22 @@
//! This example is purely for development. //! This example is purely for development.
use metrics::{Key, KeyData, Label, NameParts, SharedString};
use std::borrow::Cow; use std::borrow::Cow;
use metrics::{Key, KeyData, NameParts, Label, SharedString};
fn main() { fn main() {
println!("KeyData: {} bytes", std::mem::size_of::<KeyData>()); println!("KeyData: {} bytes", std::mem::size_of::<KeyData>());
println!("Key: {} bytes", std::mem::size_of::<Key>()); println!("Key: {} bytes", std::mem::size_of::<Key>());
println!("NameParts: {} bytes", std::mem::size_of::<NameParts>()); println!("NameParts: {} bytes", std::mem::size_of::<NameParts>());
println!("Label: {} bytes", std::mem::size_of::<Label>()); println!("Label: {} bytes", std::mem::size_of::<Label>());
println!("Cow<'static, [Label]>: {} bytes", std::mem::size_of::<Cow<'static, [Label]>>()); println!(
println!("Vec<SharedString>: {} bytes", std::mem::size_of::<Vec<SharedString>>()); "Cow<'static, [Label]>: {} bytes",
println!("[Option<SharedString>; 2]: {} bytes", std::mem::size_of::<[Option<SharedString>; 2]>()); std::mem::size_of::<Cow<'static, [Label]>>()
);
println!(
"Vec<SharedString>: {} bytes",
std::mem::size_of::<Vec<SharedString>>()
);
println!(
"[Option<SharedString>; 2]: {} bytes",
std::mem::size_of::<[Option<SharedString>; 2]>()
);
} }

View File

@ -1,7 +1,4 @@
#[cfg(target_pointer_width = "64")] use crate::cow::Cow;
use beef::lean::Cow;
#[cfg(not(target_pointer_width = "64"))]
use beef::Cow;
/// An allocation-optimized string. /// An allocation-optimized string.
/// ///

View File

@ -1,5 +1,5 @@
use crate::{IntoLabels, Label, SharedString}; use crate::{cow::Cow, IntoLabels, Label, SharedString};
use alloc::{borrow::Cow, string::String, vec::Vec}; use alloc::{string::String, vec::Vec};
use core::{ use core::{
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -7,147 +7,81 @@ use core::{
slice::Iter, slice::Iter,
}; };
const NO_LABELS: [Label; 0] = [];
/// Parts compromising a metric name. /// Parts compromising a metric name.
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum NameParts { pub struct NameParts(Cow<'static, [SharedString]>);
/// 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 { impl NameParts {
/// Creates a [`NameParts`] from the given name. /// Creates a [`NameParts`] from the given name.
pub fn from_name<N: Into<SharedString>>(name: N) -> Self { pub fn from_name<N: Into<SharedString>>(name: N) -> Self {
NameParts::Inline([Some(name.into()), None]) NameParts(Cow::owned(vec![name.into()]))
} }
/// Creates a [`NameParts`] from the given static name. /// Creates a [`NameParts`] from the given static name.
pub const fn from_static_name(name: &'static str) -> Self { pub const fn from_static_names(names: &'static [SharedString]) -> Self {
NameParts::Inline([Some(SharedString::const_str(name)), None]) NameParts(Cow::<'static, [SharedString]>::const_slice(names))
}
/// 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. /// Appends a name part.
pub fn append<S: Into<SharedString>>(&mut self, part: S) { pub fn append<S: Into<SharedString>>(self, part: S) -> Self {
match *self { let mut parts = self.0.into_owned();
NameParts::Inline(ref mut inner) => { parts.push(part.into());
if inner[1].is_none() { NameParts(Cow::owned(parts))
// 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. /// Prepends a name part.
pub fn prepend<S: Into<SharedString>>(&mut self, part: S) { pub fn prepend<S: Into<SharedString>>(self, part: S) -> Self {
match *self { let mut parts = self.0.into_owned();
NameParts::Inline(ref mut inner) => { parts.insert(0, part.into());
if inner[1].is_none() { NameParts(Cow::owned(parts))
// 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. /// Gets a reference to the parts for this name.
pub fn parts(&self) -> PartsIter { pub fn parts(&self) -> Iter<'_, SharedString> {
PartsIter::from(self) self.0.iter()
}
/// Renders the name parts as a dot-delimited string.
pub fn to_string(&self) -> String {
// This is suboptimal since we're allocating in a bunch of ways.
//
// Might be faster to figure out the string length and then allocate a single string with
// the required capacity, and write into it, potentially pooling them? Dunno, we should
// actually benchmark this. :P
self.0
.iter()
.map(|s| s.as_ref())
.collect::<Vec<_>>()
.join(".")
} }
} }
enum PartsIterInner<'a> { impl From<String> for NameParts {
Inline(&'a [Option<SharedString>; 2]), fn from(name: String) -> NameParts {
Dynamic(Iter<'a, SharedString>), NameParts::from_name(name)
}
/// 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> { impl From<&'static str> for NameParts {
type Item = &'a SharedString; fn from(name: &'static str) -> NameParts {
NameParts::from_name(name)
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 { impl fmt::Display for NameParts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { let mut first = true;
NameParts::Inline(inner) => if inner[1].is_none() { for s in self.parts() {
write!(f, "{}", inner[0].as_ref().unwrap()) if !first {
write!(f, ".{}", s)?;
first = false;
} else { } else {
write!(f, "{}.{}", inner[0].as_ref().unwrap(), inner[1].as_ref().unwrap()) write!(f, "{}", s)?;
}, }
NameParts::Dynamic(parts) => {
for (i, s) in parts.iter().enumerate() {
if i != parts.len() - 1 {
write!(f, "{}.", s)?;
} else {
write!(f, "{}", s)?;
}
}
Ok(())
},
} }
Ok(())
} }
} }
@ -158,7 +92,7 @@ impl fmt::Display for NameParts {
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyData { pub struct KeyData {
// TODO: once const slicing is possible on stable, we could likely use `beef` for both of these // TODO: once const slicing is possible on stable, we could likely use `beef` for both of these
name_parts: Cow<'static, NameParts>, name_parts: NameParts,
labels: Cow<'static, [Label]>, labels: Cow<'static, [Label]>,
} }
@ -168,38 +102,52 @@ impl KeyData {
where where
N: Into<SharedString>, N: Into<SharedString>,
{ {
Self::from_parts(name, Vec::new()) Self {
name_parts: NameParts::from_name(name),
labels: Cow::owned(Vec::new()),
}
} }
/// Creates a [`KeyData`] from a name and vector of [`Label`]s. /// Creates a [`KeyData`] from a name.
pub fn from_parts<N, L>(name: N, labels: L) -> Self pub fn from_owned_parts<N, L>(name: N, labels: L) -> Self
where where
N: Into<SharedString>, N: Into<NameParts>,
L: IntoLabels, L: IntoLabels,
{ {
Self { Self {
name_parts: Cow::Owned(NameParts::from_name(name)), name_parts: name.into(),
labels: labels.into_labels().into(), labels: Cow::owned(labels.into_labels()),
}
}
/// Creates a [`KeyData`] from a name and vector of [`Label`]s.
pub fn from_hybrid_parts<L>(name_parts: &'static [SharedString], labels: L) -> Self
where
L: IntoLabels,
{
Self {
name_parts: NameParts::from_static_names(name_parts),
labels: Cow::owned(labels.into_labels()),
} }
} }
/// Creates a [`KeyData`] from a static name. /// Creates a [`KeyData`] from a static name.
/// ///
/// This function is `const`, so it can be used in a static context. /// This function is `const`, so it can be used in a static context.
pub const fn from_static_name(name_parts: &'static NameParts) -> Self { pub const fn from_static_name(name_parts: &'static [SharedString]) -> Self {
Self { Self::from_static_parts(name_parts, &NO_LABELS)
name_parts: Cow::Borrowed(name_parts),
labels: Cow::Owned(Vec::new()),
}
} }
/// Creates a [`KeyData`] from a static name and static set of labels. /// 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. /// This function is `const`, so it can be used in a static context.
pub const fn from_static_parts(name_parts: &'static NameParts, labels: &'static [Label]) -> Self { pub const fn from_static_parts(
name_parts: &'static [SharedString],
labels: &'static [Label],
) -> Self {
Self { Self {
name_parts: Cow::Borrowed(name_parts), name_parts: NameParts::from_static_names(name_parts),
labels: Cow::Borrowed(labels), labels: Cow::<[Label]>::const_slice(labels),
} }
} }
@ -214,18 +162,26 @@ impl KeyData {
} }
/// Appends a part to the name, /// Appends a part to the name,
pub fn append_name<S: Into<SharedString>>(&mut self, part: S) { pub fn append_name<S: Into<SharedString>>(self, part: S) -> Self {
self.name_parts.to_mut().append(part) let name_parts = self.name_parts.append(part);
Self {
name_parts,
labels: self.labels,
}
} }
/// Prepends a part to the name. /// Prepends a part to the name.
pub fn prepend_name<S: Into<SharedString>>(&mut self, part: S) { pub fn prepend_name<S: Into<SharedString>>(self, part: S) -> Self {
self.name_parts.to_mut().prepend(part) let name_parts = self.name_parts.prepend(part);
Self {
name_parts,
labels: self.labels,
}
} }
/// Consumes this [`Key`], returning the name parts and any labels. /// Consumes this [`Key`], returning the name parts and any labels.
pub fn into_parts(self) -> (NameParts, Vec<Label>) { pub fn into_parts(self) -> (NameParts, Vec<Label>) {
(self.name_parts.into_owned(), self.labels.into_owned()) (self.name_parts.clone(), self.labels.into_owned())
} }
/// Clones this [`Key`], and expands the existing set of labels. /// Clones this [`Key`], and expands the existing set of labels.
@ -283,7 +239,10 @@ where
L: IntoLabels, L: IntoLabels,
{ {
fn from(parts: (N, L)) -> Self { fn from(parts: (N, L)) -> Self {
Self::from_parts(parts.0, parts.1) Self {
name_parts: NameParts::from_name(parts.0),
labels: Cow::owned(parts.1.into_labels()),
}
} }
} }
@ -383,11 +342,12 @@ impl From<&'static KeyData> for Key {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Key, KeyData, NameParts}; use super::{Key, KeyData};
use crate::Label; use crate::{Label, SharedString};
use std::collections::HashMap; use std::collections::HashMap;
static BORROWED_NAME: NameParts = NameParts::from_static_name("name"); static BORROWED_NAME: [SharedString; 1] = [SharedString::const_str("name")];
static FOOBAR_NAME: [SharedString; 1] = [SharedString::const_str("foobar")];
static BORROWED_BASIC: KeyData = KeyData::from_static_name(&BORROWED_NAME); static BORROWED_BASIC: KeyData = KeyData::from_static_name(&BORROWED_NAME);
static LABELS: [Label; 1] = [Label::from_static_parts("key", "value")]; static LABELS: [Label; 1] = [Label::from_static_parts("key", "value")];
static BORROWED_LABELS: KeyData = KeyData::from_static_parts(&BORROWED_NAME, &LABELS); static BORROWED_LABELS: KeyData = KeyData::from_static_parts(&BORROWED_NAME, &LABELS);
@ -406,7 +366,7 @@ mod tests {
assert_eq!(previous, Some(&42)); assert_eq!(previous, Some(&42));
let labels = LABELS.to_vec(); let labels = LABELS.to_vec();
let owned_labels = KeyData::from_parts("name", labels); let owned_labels = KeyData::from_hybrid_parts(&BORROWED_NAME, labels);
assert_eq!(&owned_labels, &BORROWED_LABELS); assert_eq!(&owned_labels, &BORROWED_LABELS);
let previous = keys.insert(owned_labels, 43); let previous = keys.insert(owned_labels, 43);
@ -431,7 +391,7 @@ mod tests {
assert_eq!(previous, Some(&42)); assert_eq!(previous, Some(&42));
let labels = LABELS.to_vec(); let labels = LABELS.to_vec();
let owned_labels = Key::from(KeyData::from_parts("name", labels)); let owned_labels = Key::from(KeyData::from_hybrid_parts(&BORROWED_NAME, labels));
let borrowed_labels = Key::from(&BORROWED_LABELS); let borrowed_labels = Key::from(&BORROWED_LABELS);
assert_eq!(owned_labels, borrowed_labels); assert_eq!(owned_labels, borrowed_labels);
@ -448,19 +408,19 @@ mod tests {
let result1 = key1.to_string(); let result1 = key1.to_string();
assert_eq!(result1, "KeyData(foobar)"); assert_eq!(result1, "KeyData(foobar)");
let key2 = KeyData::from_parts("foobar", vec![Label::new("system", "http")]); let key2 = KeyData::from_hybrid_parts(&FOOBAR_NAME, vec![Label::new("system", "http")]);
let result2 = key2.to_string(); let result2 = key2.to_string();
assert_eq!(result2, "KeyData(foobar, [system = http])"); assert_eq!(result2, "KeyData(foobar, [system = http])");
let key3 = KeyData::from_parts( let key3 = KeyData::from_hybrid_parts(
"foobar", &FOOBAR_NAME,
vec![Label::new("system", "http"), Label::new("user", "joe")], vec![Label::new("system", "http"), Label::new("user", "joe")],
); );
let result3 = key3.to_string(); let result3 = key3.to_string();
assert_eq!(result3, "KeyData(foobar, [system = http, user = joe])"); assert_eq!(result3, "KeyData(foobar, [system = http, user = joe])");
let key4 = KeyData::from_parts( let key4 = KeyData::from_hybrid_parts(
"foobar", &FOOBAR_NAME,
vec![ vec![
Label::new("black", "black"), Label::new("black", "black"),
Label::new("lives", "lives"), Label::new("lives", "lives"),
@ -479,10 +439,9 @@ mod tests {
let owned_a = KeyData::from_name("a"); let owned_a = KeyData::from_name("a");
let owned_b = KeyData::from_name("b"); let owned_b = KeyData::from_name("b");
static A_NAME: [SharedString; 1] = [SharedString::const_str("a")];
static A_NAME: NameParts = NameParts::from_static_name("a");
static STATIC_A: KeyData = KeyData::from_static_name(&A_NAME); static STATIC_A: KeyData = KeyData::from_static_name(&A_NAME);
static B_NAME: NameParts = NameParts::from_static_name("b"); static B_NAME: [SharedString; 1] = [SharedString::const_str("b")];
static STATIC_B: KeyData = KeyData::from_static_name(&B_NAME); 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_a.clone()), Key::Owned(owned_a.clone()));

View File

@ -223,6 +223,8 @@ use proc_macro_hack::proc_macro_hack;
mod common; mod common;
pub use self::common::*; pub use self::common::*;
mod cow;
mod key; mod key;
pub use self::key::*; pub use self::key::*;