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:
metrics is MIT licensed.
`metrics` is MIT licensed.
Longer version:
Copyrights in the metrics project are retained by their contributors. No
copyright assignment is required to contribute to the metrics project.
Copyrights in the `metrics` project are retained by their contributors. No copyright assignment is
required to contribute to the `metrics` project.
Some files include explicit copyright notices and/or license notices.
For full authorship information, see the version control history.
Some files include explicit copyright notices and/or license notices. For full authorship
information, see the version control history.
Except as otherwise noted (below and/or in individual files), metrics
is licensed under the MIT license <LICENSE> or
<http://opensource.org/licenses/MIT>.
Except as otherwise noted (below and/or in individual files), `metrics` is licensed under the MIT
license <LICENSE> or <http://opensource.org/licenses/MIT>.
metrics includes packages written by third parties.
The following third party packages are included, and carry
their own copyright notices and license terms:
`metrics` includes packages written by third parties. The following third party packages are
included, and carry their own copyright notices and license terms:
* Portions of the API design are derived from tic
<https://github.com/brayniac/tic>, which carries the following
license:
* Portions of the API design are derived from the `tic` crate which carries the following license:
Copyright (c) 2016 Brian Martin
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:
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 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.
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.
* metrics is a fork of rust-lang-nursery/log which carries the following
license:
* metrics is a fork of `rust-lang-nursery/log` which carries the following license:
Copyright (c) 2014 The Rust Project Developers
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:
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 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.
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.
* metrics-observer reuses code from `std::time::Duration` which carries
the following license:
* metrics-observer reuses code from `std::time::Duration` which carries the following license:
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:
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 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.
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.
* metrics includes code from the `beef` crate which carries the following license:
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 {
fn drop(&mut self) {
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.value_at_percentile(50.0)),
nanos_to_readable(self.hist.value_at_percentile(95.0)),
@ -196,7 +196,7 @@ fn main() {
info!("--------------------------------------------------------------------------------");
info!(" ingested samples total: {}", total);
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.value_at_percentile(50.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>) {
if let Some(description) = description {
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);
}
}
@ -552,7 +552,11 @@ fn key_to_parts(key: Key) -> (String, Vec<String>) {
let name = key.name();
let labels = key.labels();
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
.into_iter()
.map(|label| {

View File

@ -224,13 +224,13 @@ pub fn histogram(input: TokenStream) -> TokenStream {
fn get_expanded_registration(
metric_type: &str,
key: LitStr,
name: LitStr,
unit: Option<Expr>,
description: Option<LitStr>,
labels: Option<Labels>,
) -> proc_macro2::TokenStream {
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 {
Some(e) => quote! { Some(#e) },
@ -244,6 +244,7 @@ fn get_expanded_registration(
quote! {
{
static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
// Only do this work if there's a recorder installed.
if let Some(recorder) = metrics::try_recorder() {
// Registrations are fairly rare, don't attempt to cache here
@ -257,7 +258,7 @@ fn get_expanded_registration(
fn get_expanded_callsite<V>(
metric_type: &str,
op_type: &str,
key: LitStr,
name: LitStr,
labels: Option<Labels>,
op_values: V,
) -> proc_macro2::TokenStream
@ -288,7 +289,7 @@ where
let labels_len = quote! { #labels_len };
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_KEY: metrics::KeyData =
metrics::KeyData::from_static_parts(&METRIC_NAME, &METRIC_LABELS);
@ -296,7 +297,7 @@ where
}
None => {
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 =
metrics::KeyData::from_static_name(&METRIC_NAME);
}
@ -316,9 +317,11 @@ where
}
} else {
// We're on the slow path, so we allocate, womp.
let key = key_to_quoted(key, labels);
let key = key_to_quoted(labels);
quote! {
{
static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
// Only do this work if there's a recorder installed.
if let Some(recorder) = metrics::try_recorder() {
recorder.#op_ident(metrics::Key::Owned(#key), #op_values);
@ -355,17 +358,19 @@ fn read_key(input: &mut ParseStream) -> Result<LitStr> {
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 {
None => quote! { metrics::KeyData::from_name(#name) },
None => quote! { metrics::KeyData::from_static_name(&METRIC_NAME) },
Some(labels) => match labels {
Labels::Inline(pairs) => {
let labels = pairs
.into_iter()
.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);
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 (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ",
"metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"None , ",
"None",
") ; ",
@ -35,9 +37,11 @@ fn test_get_expanded_registration_with_unit() {
);
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 (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ",
"metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"Some (metrics :: Unit :: Nanoseconds) , ",
"None",
") ; ",
@ -59,9 +63,11 @@ fn test_get_expanded_registration_with_description() {
);
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 (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ",
"metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"None , ",
"Some (\"flerkin\")",
") ; ",
@ -84,9 +90,11 @@ fn test_get_expanded_registration_with_unit_and_description() {
);
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 (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_name (\"mykeyname\")) , ",
"metrics :: Key :: Owned (metrics :: KeyData :: from_static_name (& METRIC_NAME)) , ",
"Some (metrics :: Unit :: Nanoseconds) , ",
"Some (\"flerkin\")",
") ; ",
@ -108,7 +116,8 @@ fn test_get_expanded_callsite_fast_path_no_labels() {
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 () { ",
"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!(
"{ ",
"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_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 () { ",
"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!(
"{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"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) ; ",
"} ",
"}",
@ -178,9 +189,10 @@ fn test_get_expanded_callsite_regular_path() {
let expected = concat!(
"{ ",
"static METRIC_NAME : [metrics :: SharedString ; 1] = [metrics :: SharedString :: const_str (\"mykeyname\")] ; ",
"if let Some (recorder) = metrics :: try_recorder () { ",
"recorder . myop_mytype (",
"metrics :: Key :: Owned (metrics :: KeyData :: from_parts (\"mykeyname\" , mylabels)) , ",
"metrics :: Key :: Owned (metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , mylabels)) , ",
"1",
") ; ",
"} }",
@ -191,18 +203,17 @@ fn test_get_expanded_callsite_regular_path() {
#[test]
fn test_key_to_quoted_no_labels() {
let stream = key_to_quoted(parse_quote! {"mykeyname"}, None);
let expected = "metrics :: KeyData :: from_name (\"mykeyname\")";
let stream = key_to_quoted(None);
let expected = "metrics :: KeyData :: from_static_name (& METRIC_NAME)";
assert_eq!(stream.to_string(), expected);
}
#[test]
fn test_key_to_quoted_existing_labels() {
let stream = key_to_quoted(
parse_quote! {"mykeyname"},
Some(Labels::Existing(Expr::Path(parse_quote! { mylabels }))),
);
let expected = "metrics :: KeyData :: from_parts (\"mykeyname\" , mylabels)";
let stream = key_to_quoted(Some(Labels::Existing(Expr::Path(
parse_quote! { mylabels },
))));
let expected = "metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , mylabels)";
assert_eq!(stream.to_string(), expected);
}
@ -210,15 +221,12 @@ fn test_key_to_quoted_existing_labels() {
/// Key).
#[test]
fn test_key_to_quoted_inline_labels() {
let stream = key_to_quoted(
parse_quote! {"mykeyname"},
Some(Labels::Inline(vec![
(parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }),
(parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }),
])),
);
let stream = key_to_quoted(Some(Labels::Inline(vec![
(parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }),
(parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }),
])));
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 (\"mylabel2\" , \"mylabel2\")",
"])"
@ -228,10 +236,7 @@ fn test_key_to_quoted_inline_labels() {
#[test]
fn test_key_to_quoted_inline_labels_empty() {
let stream = key_to_quoted(parse_quote! {"mykeyname"}, Some(Labels::Inline(vec![])));
let expected = concat!(
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [",
"])"
);
let stream = key_to_quoted(Some(Labels::Inline(vec![])));
let expected = concat!("metrics :: KeyData :: from_hybrid_parts (& METRIC_NAME , vec ! [])");
assert_eq!(stream.to_string(), expected);
}

View File

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

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> {
inner: R,
label_filter: F,
@ -126,7 +126,7 @@ where
fn enhance_key(&self, key: Key) -> Key {
let (name, mut labels) = key.into_owned().into_parts();
self.enhance_labels(&mut labels);
KeyData::from_parts(name, labels).into()
KeyData::from_owned_parts(name, labels).into()
}
}

View File

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

View File

@ -1,5 +1,5 @@
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};
fn layer_benchmark(c: &mut Criterion) {
@ -8,9 +8,9 @@ fn layer_benchmark(c: &mut Criterion) {
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);
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
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);
@ -18,9 +18,9 @@ fn layer_benchmark(c: &mut Criterion) {
})
.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);
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
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);

View File

@ -1,5 +1,5 @@
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;
fn registry_benchmark(c: &mut Criterion) {
@ -7,7 +7,7 @@ fn registry_benchmark(c: &mut Criterion) {
"registry",
Benchmark::new("cached op (basic)", |b| {
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);
b.iter(|| {
@ -17,7 +17,7 @@ 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_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
@ -64,18 +64,18 @@ fn registry_benchmark(c: &mut Criterion) {
b.iter(|| {
let key = "simple_key";
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| {
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)
})
})
.with_function("const key data overhead (labels)", |b| {
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")];
KeyData::from_static_parts(&KEY_NAME, &LABELS)
})
@ -90,16 +90,16 @@ fn registry_benchmark(c: &mut Criterion) {
b.iter(|| {
let key = "simple_key";
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| {
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);
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_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| Key::Borrowed(&KEY_DATA))

View File

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

View File

@ -1,13 +1,22 @@
//! This example is purely for development.
use metrics::{Key, KeyData, Label, NameParts, SharedString};
use std::borrow::Cow;
use metrics::{Key, KeyData, NameParts, Label, SharedString};
fn main() {
println!("KeyData: {} bytes", std::mem::size_of::<KeyData>());
println!("Key: {} bytes", std::mem::size_of::<Key>());
println!("NameParts: {} bytes", std::mem::size_of::<NameParts>());
println!("Label: {} bytes", std::mem::size_of::<Label>());
println!("Cow<'static, [Label]>: {} bytes", 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]>());
println!(
"Cow<'static, [Label]>: {} bytes",
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 beef::lean::Cow;
#[cfg(not(target_pointer_width = "64"))]
use beef::Cow;
use crate::cow::Cow;
/// An allocation-optimized string.
///

View File

@ -1,5 +1,5 @@
use crate::{IntoLabels, Label, SharedString};
use alloc::{borrow::Cow, string::String, vec::Vec};
use crate::{cow::Cow, IntoLabels, Label, SharedString};
use alloc::{string::String, vec::Vec};
use core::{
fmt,
hash::{Hash, Hasher},
@ -7,147 +7,81 @@ use core::{
slice::Iter,
};
const NO_LABELS: [Label; 0] = [];
/// 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>),
}
pub struct NameParts(Cow<'static, [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])
NameParts(Cow::owned(vec![name.into()]))
}
/// 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)),
])
pub const fn from_static_names(names: &'static [SharedString]) -> Self {
NameParts(Cow::<'static, [SharedString]>::const_slice(names))
}
/// 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())
},
}
pub fn append<S: Into<SharedString>>(self, part: S) -> Self {
let mut parts = self.0.into_owned();
parts.push(part.into());
NameParts(Cow::owned(parts))
}
/// 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())
},
}
pub fn prepend<S: Into<SharedString>>(self, part: S) -> Self {
let mut parts = self.0.into_owned();
parts.insert(0, part.into());
NameParts(Cow::owned(parts))
}
/// Gets a reference to the parts for this name.
pub fn parts(&self) -> PartsIter {
PartsIter::from(self)
pub fn parts(&self) -> Iter<'_, SharedString> {
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> {
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 From<String> for NameParts {
fn from(name: String) -> NameParts {
NameParts::from_name(name)
}
}
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 From<&'static str> for NameParts {
fn from(name: &'static str) -> NameParts {
NameParts::from_name(name)
}
}
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())
let mut first = true;
for s in self.parts() {
if !first {
write!(f, ".{}", s)?;
first = false;
} 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(())
},
write!(f, "{}", s)?;
}
}
Ok(())
}
}
@ -158,7 +92,7 @@ impl fmt::Display for NameParts {
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyData {
// 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]>,
}
@ -168,38 +102,52 @@ impl KeyData {
where
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.
pub fn from_parts<N, L>(name: N, labels: L) -> Self
/// Creates a [`KeyData`] from a name.
pub fn from_owned_parts<N, L>(name: N, labels: L) -> Self
where
N: Into<SharedString>,
N: Into<NameParts>,
L: IntoLabels,
{
Self {
name_parts: Cow::Owned(NameParts::from_name(name)),
labels: labels.into_labels().into(),
name_parts: name.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.
///
/// This function is `const`, so it can be used in a static context.
pub const fn from_static_name(name_parts: &'static NameParts) -> Self {
Self {
name_parts: Cow::Borrowed(name_parts),
labels: Cow::Owned(Vec::new()),
}
pub const fn from_static_name(name_parts: &'static [SharedString]) -> Self {
Self::from_static_parts(name_parts, &NO_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.
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 {
name_parts: Cow::Borrowed(name_parts),
labels: Cow::Borrowed(labels),
name_parts: NameParts::from_static_names(name_parts),
labels: Cow::<[Label]>::const_slice(labels),
}
}
@ -214,18 +162,26 @@ impl KeyData {
}
/// Appends a part to the name,
pub fn append_name<S: Into<SharedString>>(&mut self, part: S) {
self.name_parts.to_mut().append(part)
pub fn append_name<S: Into<SharedString>>(self, part: S) -> Self {
let name_parts = self.name_parts.append(part);
Self {
name_parts,
labels: self.labels,
}
}
/// Prepends a part to the name.
pub fn prepend_name<S: Into<SharedString>>(&mut self, part: S) {
self.name_parts.to_mut().prepend(part)
pub fn prepend_name<S: Into<SharedString>>(self, part: S) -> Self {
let name_parts = self.name_parts.prepend(part);
Self {
name_parts,
labels: self.labels,
}
}
/// 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())
(self.name_parts.clone(), self.labels.into_owned())
}
/// Clones this [`Key`], and expands the existing set of labels.
@ -283,7 +239,10 @@ where
L: IntoLabels,
{
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)]
mod tests {
use super::{Key, KeyData, NameParts};
use crate::Label;
use super::{Key, KeyData};
use crate::{Label, SharedString};
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 LABELS: [Label; 1] = [Label::from_static_parts("key", "value")];
static BORROWED_LABELS: KeyData = KeyData::from_static_parts(&BORROWED_NAME, &LABELS);
@ -406,7 +366,7 @@ mod tests {
assert_eq!(previous, Some(&42));
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);
let previous = keys.insert(owned_labels, 43);
@ -431,7 +391,7 @@ mod tests {
assert_eq!(previous, Some(&42));
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);
assert_eq!(owned_labels, borrowed_labels);
@ -448,19 +408,19 @@ mod tests {
let result1 = key1.to_string();
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();
assert_eq!(result2, "KeyData(foobar, [system = http])");
let key3 = KeyData::from_parts(
"foobar",
let key3 = KeyData::from_hybrid_parts(
&FOOBAR_NAME,
vec![Label::new("system", "http"), Label::new("user", "joe")],
);
let result3 = key3.to_string();
assert_eq!(result3, "KeyData(foobar, [system = http, user = joe])");
let key4 = KeyData::from_parts(
"foobar",
let key4 = KeyData::from_hybrid_parts(
&FOOBAR_NAME,
vec![
Label::new("black", "black"),
Label::new("lives", "lives"),
@ -479,10 +439,9 @@ mod tests {
let owned_a = KeyData::from_name("a");
let owned_b = KeyData::from_name("b");
static A_NAME: NameParts = NameParts::from_static_name("a");
static A_NAME: [SharedString; 1] = [SharedString::const_str("a")];
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);
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;
pub use self::common::*;
mod cow;
mod key;
pub use self::key::*;