Compare commits

...

26 Commits

Author SHA1 Message Date
Deirdre Connolly bde1a50315 Remove extra new lines 2021-10-08 23:56:20 -04:00
Deirdre Connolly d10286ff1e Do not start output with a '# metrics snapshot' etc prelude 2021-10-08 20:52:14 -04:00
Henry de Valence 971133128e deps: update to tokio 0.3
This uses a git dependency on Hyper for now.
2020-11-19 14:22:16 -08:00
Toby Lawrence 7ef47304ed
Merge pull request #130 from metrics-rs/fix_atomic_bucket
fix concurrent writer/uninitialized memory bug with AtomicBucket
2020-11-16 20:10:08 -05:00
Toby Lawrence 8b57975110 fix concurrent writer/uninitialized memory bug with AtomicBucket 2020-11-16 18:45:15 -05:00
Toby Lawrence 507493a59a
Merge pull request #129 from metrics-rs/unit_tweaks
core: fix unit binary vs decimal wonkiness
2020-11-16 18:23:19 -05:00
Toby Lawrence 027cde096a core: fix unit binary vs decimal wonkiness 2020-11-16 17:53:36 -05:00
Toby Lawrence ec1faac74c
Merge pull request #127 from metrics-rs/dependabot/cargo/arc-swap-1.0
Update arc-swap requirement from 0.4 to 1.0
2020-11-16 15:36:34 -05:00
Toby Lawrence 576a7e42b6
Merge pull request #125 from metrics-rs/experiment/name-parts
Switch to collecting metric names by part.
2020-11-16 15:29:11 -05:00
dependabot[bot] 1cdae103b9
Update arc-swap requirement from 0.4 to 1.0
Updates the requirements on [arc-swap](https://github.com/vorner/arc-swap) to permit the latest version.
- [Release notes](https://github.com/vorner/arc-swap/releases)
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v0.4.0...v1.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-16 07:18:14 +00:00
Toby Lawrence ba0530e2cd small docs tweak 2020-11-15 16:42:43 -05:00
Toby Lawrence ee379362f5 collapse from_owned_parts/from_hybrid_parts into from_parts 2020-11-15 15:49:05 -05:00
Toby Lawrence 2cd4e9e100 optimize NameParts::to_string 2020-11-15 15:13:47 -05:00
Toby Lawrence db02cd80da
Merge branch 'main' into experiment/name-parts 2020-11-13 13:26:42 -05:00
Toby Lawrence 55f289261c forgot dat flag 2020-11-13 12:56:48 -05:00
Toby Lawrence 2d807af5b0 gotta bump MSRV to 1.46 for coercing to unsized slices in const fns 2020-11-13 12:30:22 -05:00
Toby Lawrence 30c7ff1a98 a lot of tweaks 2020-11-13 11:06:53 -05:00
Toby Lawrence 7a8f3da859 wip 2020-11-12 23:23:32 -05:00
Toby Lawrence 8ebe921ef7
Merge pull request #123 from str4d/patch-1
PrometheusBuilder::install: Build exporter in runtime context
2020-11-11 14:46:52 -05:00
Toby Lawrence 4312b9f205
Merge pull request #121 from flub/doc-typo 2020-11-11 12:50:42 -05:00
str4d 01fcc020c7
PrometheusBuilder::install: Build exporter in runtime context
Closes https://github.com/metrics-rs/metrics/issues/122
2020-11-11 15:34:30 +00:00
Floris Bruynooghe 4fafc34869 Add docs build to CI
This should avoid accidentally breaking docs.
2020-11-10 21:06:25 +01:00
Floris Bruynooghe 1b1c271531 Trival docs typo 2020-11-10 21:00:05 +01:00
Toby Lawrence d72a744a76 dummy app for getting the sizes of various types related to keys 2020-11-02 19:57:37 -05:00
Toby Lawrence e926a6b6c6 commit notes + sizes example 2020-11-01 11:29:31 -05:00
Toby Lawrence 5fa7e8fd1d wip: inline vs dynamic custom enum approach 2020-11-01 10:44:44 -05:00
26 changed files with 1136 additions and 325 deletions

View File

@ -24,7 +24,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
rust_version: ['1.43.0', 'stable', 'nightly'] rust_version: ['1.46.0', 'stable', 'nightly']
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
@ -35,6 +35,23 @@ jobs:
override: true override: true
- name: Run Tests - name: Run Tests
run: cargo test run: cargo test
docs:
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v2
- name: Install Rust Nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rust-docs
- name: Check docs
uses: actions-rs/cargo@v1
with:
command: doc
args: --workspace --no-deps
bench: bench:
name: Bench ${{ matrix.os }} name: Bench ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -49,4 +66,7 @@ jobs:
toolchain: 'stable' toolchain: 'stable'
override: true override: true
- name: Run Benchmarks - name: Run Benchmarks
run: cargo bench uses: actions-rs/cargo@v1
with:
command: bench
args: --all-features

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

@ -9,3 +9,7 @@ members = [
"metrics-tracing-context", "metrics-tracing-context",
] ]
exclude = ["metrics-observer"] exclude = ["metrics-observer"]
[patch.crates-io]
hyper = { git = "https://github.com/hyperium/hyper/", rev = "ed2b22a7f66899d338691552fbcb6c0f2f4e06b9" }

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

@ -27,8 +27,8 @@ parking_lot = "0.11"
thiserror = "1.0" thiserror = "1.0"
# Optional # Optional
hyper = { version = "0.13", default-features = false, features = ["tcp"], optional = true } hyper = { version = "0.14.0-dev", default-features = false, features = ["tcp", "server", "http1", "http2"], optional = true }
tokio = { version = "0.2", features = ["rt-core", "tcp", "time", "macros"], optional = true } tokio = { version = "0.3", features = ["rt", "net", "time", "macros"], optional = true }
[dev-dependencies] [dev-dependencies]
quanta = "0.6" quanta = "0.6"

View File

@ -13,13 +13,14 @@ use metrics_util::{
parse_quantiles, CompositeKey, Handle, Histogram, MetricKind, Quantile, Registry, parse_quantiles, CompositeKey, Handle, Histogram, MetricKind, Quantile, Registry,
}; };
use parking_lot::RwLock; use parking_lot::RwLock;
use std::collections::HashMap;
use std::io; use std::io;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "tokio-exporter")] #[cfg(feature = "tokio-exporter")]
use std::thread; use std::thread;
use std::{collections::HashMap, time::SystemTime}; use std::time::SystemTime;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
#[cfg(feature = "tokio-exporter")] #[cfg(feature = "tokio-exporter")]
use tokio::{pin, runtime, select}; use tokio::{pin, runtime, select};
@ -211,7 +212,6 @@ impl Inner {
output.push_str(value.to_string().as_str()); output.push_str(value.to_string().as_str());
output.push_str("\n"); output.push_str("\n");
} }
output.push_str("\n");
} }
for (name, mut by_labels) in gauges.drain() { for (name, mut by_labels) in gauges.drain() {
@ -233,7 +233,6 @@ impl Inner {
output.push_str(value.to_string().as_str()); output.push_str(value.to_string().as_str());
output.push_str("\n"); output.push_str("\n");
} }
output.push_str("\n");
} }
let mut sorted_overrides = self let mut sorted_overrides = self
@ -316,8 +315,6 @@ impl Inner {
output.push_str(count.to_string().as_str()); output.push_str(count.to_string().as_str());
output.push_str("\n"); output.push_str("\n");
} }
output.push_str("\n");
} }
output output
@ -345,7 +342,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);
} }
} }
@ -443,14 +440,16 @@ impl PrometheusBuilder {
/// installing the recorder as the global recorder. /// installing the recorder as the global recorder.
#[cfg(feature = "tokio-exporter")] #[cfg(feature = "tokio-exporter")]
pub fn install(self) -> Result<(), Error> { pub fn install(self) -> Result<(), Error> {
let (recorder, exporter) = self.build_with_exporter()?; let runtime = runtime::Builder::new_current_thread()
metrics::set_boxed_recorder(Box::new(recorder))?;
let mut runtime = runtime::Builder::new()
.basic_scheduler()
.enable_all() .enable_all()
.build()?; .build()?;
let (recorder, exporter) = {
let _guard = runtime.enter();
self.build_with_exporter()
}?;
metrics::set_boxed_recorder(Box::new(recorder))?;
thread::Builder::new() thread::Builder::new()
.name("metrics-exporter-prometheus-http".to_string()) .name("metrics-exporter-prometheus-http".to_string())
.spawn(move || { .spawn(move || {
@ -596,7 +595,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,15 +289,17 @@ where
let labels_len = quote! { #labels_len }; let labels_len = quote! { #labels_len };
quote! { quote! {
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(#key, &METRIC_LABELS); metrics::KeyData::from_static_parts(&METRIC_NAME, &METRIC_LABELS);
} }
} }
None => { None => {
quote! { quote! {
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(#key); metrics::KeyData::from_static_name(&METRIC_NAME);
} }
} }
_ => unreachable!("use_fast_path == true, but found expression-based labels"), _ => unreachable!("use_fast_path == true, but found expression-based labels"),
@ -314,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);
@ -353,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_parts(&METRIC_NAME[..], vec![#(#labels),*])
} }
Labels::Existing(e) => quote! { metrics::KeyData::from_parts(#name, #e) }, }
Labels::Existing(e) => quote! { metrics::KeyData::from_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_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_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_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"},
Some(Labels::Inline(vec![
(parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }), (parse_quote! {"mylabel1"}, parse_quote! { mylabel1 }),
(parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }), (parse_quote! {"mylabel2"}, parse_quote! { "mylabel2" }),
])), ])));
);
let expected = concat!( let expected = concat!(
"metrics :: KeyData :: from_parts (\"mykeyname\" , vec ! [", "metrics :: KeyData :: from_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_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,

View File

@ -20,7 +20,7 @@ impl Visit for Labels {
} }
fn record_bool(&mut self, field: &Field, value: bool) { fn record_bool(&mut self, field: &Field, value: bool) {
let label = Label::new(field.name(), if value { "true" } else { "false" }); let label = Label::from_static_parts(field.name(), if value { "true" } else { "false" });
self.0.push(label); self.0.push(label);
} }

View File

@ -26,11 +26,19 @@ harness = false
name = "registry" name = "registry"
harness = false harness = false
[[bench]]
name = "prefix"
harness = false
[[bench]]
name = "filter"
harness = false
[dependencies] [dependencies]
metrics = { version = "0.13.0-alpha.1", path = "../metrics", features = ["std"] } metrics = { version = "0.13.0-alpha.1", path = "../metrics", features = ["std"] }
crossbeam-epoch = { version = "0.9", optional = true } crossbeam-epoch = { version = "0.9", optional = true }
crossbeam-utils = { version = "0.8", default-features = false } crossbeam-utils = { version = "0.8", default-features = false }
arc-swap = { version = "0.4", optional = true } arc-swap = { version = "1.0", optional = true }
atomic-shim = { version = "0.1", optional = true } atomic-shim = { version = "0.1", optional = true }
aho-corasick = { version = "0.7", optional = true } aho-corasick = { version = "0.7", optional = true }
dashmap = { version = "3", optional = true } dashmap = { version = "3", optional = true }

View File

@ -0,0 +1,61 @@
use criterion::{criterion_group, criterion_main, Benchmark, Criterion};
use metrics::{Key, KeyData, Label, NoopRecorder, Recorder, SharedString};
use metrics_util::layers::{FilterLayer, Layer};
fn layer_benchmark(c: &mut Criterion) {
c.bench(
"filter",
Benchmark::new("match", |b| {
let patterns = vec!["tokio"];
let filter_layer = FilterLayer::from_patterns(patterns);
let recorder = filter_layer.layer(NoopRecorder);
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("tokio.foo")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
})
})
.with_function("no match", |b| {
let patterns = vec!["tokio"];
let filter_layer = FilterLayer::from_patterns(patterns);
let recorder = filter_layer.layer(NoopRecorder);
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("hyper.foo")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
})
})
.with_function("deep match", |b| {
let patterns = vec!["tokio"];
let filter_layer = FilterLayer::from_patterns(patterns);
let recorder = filter_layer.layer(NoopRecorder);
static KEY_NAME: [SharedString; 2] = [
SharedString::const_str("prefix"),
SharedString::const_str("tokio.foo"),
];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
})
})
.with_function("noop recorder overhead (increment_counter)", |b| {
let recorder = NoopRecorder;
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("tokio.foo")];
static KEY_LABELS: [Label; 1] = [Label::from_static_parts("foo", "bar")];
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| {
recorder.increment_counter(Key::Borrowed(&KEY_DATA), 1);
})
}),
);
}
criterion_group!(benches, layer_benchmark);
criterion_main!(benches);

View File

@ -0,0 +1,33 @@
use criterion::{criterion_group, criterion_main, Benchmark, Criterion};
use metrics::{Key, KeyData, Label, NoopRecorder, Recorder, SharedString};
use metrics_util::layers::{Layer, PrefixLayer};
fn layer_benchmark(c: &mut Criterion) {
c.bench(
"prefix",
Benchmark::new("basic", |b| {
let prefix_layer = PrefixLayer::new("prefix");
let recorder = prefix_layer.layer(NoopRecorder);
static 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);
})
})
.with_function("noop recorder overhead (increment_counter)", |b| {
let recorder = NoopRecorder;
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);
})
}),
);
}
criterion_group!(benches, layer_benchmark);
criterion_main!(benches);

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}; 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,8 @@ 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_DATA: KeyData = KeyData::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(|| { b.iter(|| {
let key = Key::Borrowed(&KEY_DATA); let key = Key::Borrowed(&KEY_DATA);
@ -16,8 +17,9 @@ 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: [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("simple_key", &KEY_LABELS); static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| { b.iter(|| {
let key = Key::Borrowed(&KEY_DATA); let key = Key::Borrowed(&KEY_DATA);
@ -67,15 +69,15 @@ fn registry_benchmark(c: &mut Criterion) {
}) })
.with_function("const key data overhead (basic)", |b| { .with_function("const key data overhead (basic)", |b| {
b.iter(|| { b.iter(|| {
let key = "simple_key"; static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
KeyData::from_static_name(key) 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(|| {
let key = "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, &LABELS) KeyData::from_static_parts(&KEY_NAME, &LABELS)
}) })
}) })
.with_function("owned key overhead (basic)", |b| { .with_function("owned key overhead (basic)", |b| {
@ -92,12 +94,14 @@ fn registry_benchmark(c: &mut Criterion) {
}) })
}) })
.with_function("cached key overhead (basic)", |b| { .with_function("cached key overhead (basic)", |b| {
static KEY_DATA: KeyData = KeyData::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)) b.iter(|| Key::Borrowed(&KEY_DATA))
}) })
.with_function("cached key overhead (labels)", |b| { .with_function("cached key overhead (labels)", |b| {
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("simple_key", &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

@ -5,14 +5,19 @@ use std::{
sync::atomic::{AtomicUsize, Ordering}, sync::atomic::{AtomicUsize, Ordering},
}; };
const BLOCK_SIZE: usize = 512; #[cfg(target_pointer_width = "16")]
const BLOCK_SIZE: usize = 16;
#[cfg(target_pointer_width = "32")]
const BLOCK_SIZE: usize = 32;
#[cfg(target_pointer_width = "64")]
const BLOCK_SIZE: usize = 64;
/// Discrete chunk of values with atomic read/write access. /// Discrete chunk of values with atomic read/write access.
struct Block<T> { struct Block<T> {
// Write index. // Write index.
write: AtomicUsize, write: AtomicUsize,
// Read index. // Read bitmap.
read: AtomicUsize, read: AtomicUsize,
// The individual slots. // The individual slots.
@ -35,7 +40,7 @@ impl<T> Block<T> {
/// Gets the current length of this block. /// Gets the current length of this block.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.read.load(Ordering::Acquire) self.read.load(Ordering::Acquire).trailing_ones() as usize
} }
/// Gets a slice of the data written to this block. /// Gets a slice of the data written to this block.
@ -71,7 +76,7 @@ impl<T> Block<T> {
} }
// Scoot our read index forward. // Scoot our read index forward.
self.read.fetch_add(1, Ordering::AcqRel); self.read.fetch_or(1 << index, Ordering::AcqRel);
Ok(()) Ok(())
} }
@ -324,6 +329,7 @@ mod tests {
let result = block.push(42); let result = block.push(42);
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(block.len(), 1);
let data = block.data(); let data = block.data();
assert_eq!(data.len(), 1); assert_eq!(data.len(), 1);

View File

@ -13,7 +13,9 @@ pub struct Filter<R> {
impl<R> Filter<R> { impl<R> Filter<R> {
fn should_filter(&self, key: &Key) -> bool { fn should_filter(&self, key: &Key) -> bool {
self.automaton.is_match(key.name().as_ref()) key.name()
.parts()
.any(|s| self.automaton.is_match(s.as_ref()))
} }
} }
@ -75,11 +77,14 @@ impl FilterLayer {
/// Creates a `FilterLayer` from an existing set of patterns. /// Creates a `FilterLayer` from an existing set of patterns.
pub fn from_patterns<P, I>(patterns: P) -> Self pub fn from_patterns<P, I>(patterns: P) -> Self
where where
P: Iterator<Item = I>, P: IntoIterator<Item = I>,
I: AsRef<str>, I: AsRef<str>,
{ {
FilterLayer { FilterLayer {
patterns: patterns.map(|s| s.as_ref().to_string()).collect(), patterns: patterns
.into_iter()
.map(|s| s.as_ref().to_string())
.collect(),
case_insensitive: false, case_insensitive: false,
use_dfa: true, use_dfa: true,
} }
@ -142,7 +147,7 @@ mod tests {
let patterns = &["tokio", "bb8"]; let patterns = &["tokio", "bb8"];
let recorder = DebuggingRecorder::new(); let recorder = DebuggingRecorder::new();
let snapshotter = recorder.snapshotter(); let snapshotter = recorder.snapshotter();
let filter = FilterLayer::from_patterns(patterns.iter()); let filter = FilterLayer::from_patterns(patterns);
let layered = filter.layer(recorder); let layered = filter.layer(recorder);
let before = snapshotter.snapshot(); let before = snapshotter.snapshot();
@ -186,7 +191,10 @@ mod tests {
assert_eq!(after.len(), 2); assert_eq!(after.len(), 2);
for (_kind, key, unit, desc, _value) in after { for (_kind, key, unit, desc, _value) in after {
assert!(!key.name().contains("tokio") && !key.name().contains("bb8")); assert!(
!key.name().to_string().contains("tokio")
&& !key.name().to_string().contains("bb8")
);
// We cheat here since we're not comparing one-to-one with the source data, // We cheat here since we're not comparing one-to-one with the source data,
// but we know which metrics are going to make it through so we can hard code. // but we know which metrics are going to make it through so we can hard code.
assert_eq!(Some(Unit::Bytes), unit); assert_eq!(Some(Unit::Bytes), unit);
@ -217,8 +225,8 @@ mod tests {
for (_kind, key, _unit, _desc, _value) in &after { for (_kind, key, _unit, _desc, _value) in &after {
assert!( assert!(
!key.name().to_lowercase().contains("tokio") !key.name().to_string().to_lowercase().contains("tokio")
&& !key.name().to_lowercase().contains("bb8") && !key.name().to_string().to_lowercase().contains("bb8")
); );
} }
} }

View File

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

View File

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

View File

@ -27,10 +27,15 @@ bench = false
name = "macros" name = "macros"
harness = false harness = false
[[bench]]
name = "key"
harness = false
[dependencies] [dependencies]
beef = "0.4" beef = "0.4"
metrics-macros = { version = "0.1.0-alpha.1", path = "../metrics-macros" } metrics-macros = { version = "0.1.0-alpha.1", path = "../metrics-macros" }
proc-macro-hack = "0.5" proc-macro-hack = "0.5"
sharded-slab = "0.1"
[dev-dependencies] [dev-dependencies]
log = "0.4" log = "0.4"

28
metrics/benches/key.rs Normal file
View File

@ -0,0 +1,28 @@
use criterion::{criterion_group, criterion_main, Benchmark, Criterion};
use metrics::{NameParts, SharedString};
fn key_benchmark(c: &mut Criterion) {
c.bench(
"key",
Benchmark::new("name_parts/to_string", |b| {
static NAME_PARTS: [SharedString; 2] = [
SharedString::const_str("part1"),
SharedString::const_str("part2"),
];
let name = NameParts::from_static_names(&NAME_PARTS);
b.iter(|| name.to_string())
})
.with_function("name_parts/Display::to_string", |b| {
static NAME_PARTS: [SharedString; 2] = [
SharedString::const_str("part1"),
SharedString::const_str("part2"),
];
let name = NameParts::from_static_names(&NAME_PARTS);
b.iter(|| std::fmt::Display::to_string(&name))
}),
);
}
criterion_group!(benches, key_benchmark);
criterion_main!(benches);

22
metrics/examples/sizes.rs Normal file
View File

@ -0,0 +1,22 @@
//! This example is purely for development.
use metrics::{Key, KeyData, Label, NameParts, SharedString};
use std::borrow::Cow;
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]>()
);
}

View File

@ -1,13 +1,13 @@
#[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.
/// ///
/// We specify `SharedString` to attempt to get the best of both worlds: flexibility to provide a /// We specify `SharedString` to attempt to get the best of both worlds: flexibility to provide a
/// static or dynamic (owned) string, while retaining the performance benefits of being able to /// static or dynamic (owned) string, while retaining the performance benefits of being able to
/// take ownership of owned strings and borrows of completely static strings. /// take ownership of owned strings and borrows of completely static strings.
///
/// `SharedString` can be converted to from either `&'static str` or `String`, with a method,
/// `const_str`, from constructing `SharedString` from `&'static str` in a `const` fashion.
pub type SharedString = Cow<'static, str>; pub type SharedString = Cow<'static, str>;
/// Units for a given metric. /// Units for a given metric.
@ -16,61 +16,57 @@ pub type SharedString = Cow<'static, str>;
/// downstream systems natively support defining units and so they can be specified during registration. /// downstream systems natively support defining units and so they can be specified during registration.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Unit { pub enum Unit {
// Dimensionless measurements.
/// Count. /// Count.
Count, Count,
/// Percentage. /// Percentage.
Percent, Percent,
// Time measurements.
/// Seconds. /// Seconds.
///
/// One second is equal to 1000 milliseconds.
Seconds, Seconds,
/// Milliseconds. /// Milliseconds.
///
/// One millisecond is equal to 1000 microseconds.
Milliseconds, Milliseconds,
/// Microseconds. /// Microseconds.
///
/// One microsecond is equal to 1000 nanoseconds.
Microseconds, Microseconds,
/// Nanoseconds. /// Nanoseconds.
Nanoseconds, Nanoseconds,
/// Tebibytes.
// Data measurement. ///
/// Terabytes. /// One tebibyte is equal to 1024 gigibytes.
Terabytes, Tebibytes,
/// Gigabytes. /// Gigibytes.
Gigabytes, ///
/// Megabytes. /// One gigibyte is equal to 1024 mebibytes.
Megabytes, Gigibytes,
/// Kilobytes. /// Mebibytes.
Kilobytes, ///
/// One mebibyte is equal to 1024 kibibytes.
Mebibytes,
/// Kibibytes.
///
/// One kibibyte is equal to 1024 bytes.
Kibibytes,
/// Bytes. /// Bytes.
Bytes, Bytes,
/// Terabits.
Terabits,
/// Gigabits.
Gigabits,
/// Megabits.
Megabits,
/// Kilobits.
Kilobits,
/// Bits.
Bits,
// Rate measurements.
/// Terabytes per second.
TerabytesPerSecond,
/// Gigabytes per second.
GigabytesPerSecond,
/// Megabytes per second.
MegabytesPerSecond,
/// Kilobytes per second.
KilobytesPerSecond,
/// Bytes per second.
BytesPerSecond,
/// Terabits per second. /// Terabits per second.
///
/// One terabit is equal to 1000 gigabits.
TerabitsPerSecond, TerabitsPerSecond,
/// Gigabits per second. /// Gigabits per second.
///
/// One gigabit is equal to 1000 megabits.
GigabitsPerSecond, GigabitsPerSecond,
/// Megabits per second. /// Megabits per second.
///
/// One megabit is equal to 1000 kilobits.
MegabitsPerSecond, MegabitsPerSecond,
/// Kilobits per second. /// Kilobits per second.
///
/// One kilobit is equal to 1000 bits.
KilobitsPerSecond, KilobitsPerSecond,
/// Bits per second. /// Bits per second.
BitsPerSecond, BitsPerSecond,
@ -88,21 +84,11 @@ impl Unit {
Unit::Milliseconds => "milliseconds", Unit::Milliseconds => "milliseconds",
Unit::Microseconds => "microseconds", Unit::Microseconds => "microseconds",
Unit::Nanoseconds => "nanoseconds", Unit::Nanoseconds => "nanoseconds",
Unit::Terabytes => "terabytes", Unit::Tebibytes => "tebibytes",
Unit::Gigabytes => "gigabytes", Unit::Gigibytes => "gigibytes",
Unit::Megabytes => "megabytes", Unit::Mebibytes => "mebibytes",
Unit::Kilobytes => "kilobytes", Unit::Kibibytes => "kibibytes",
Unit::Bytes => "bytes", Unit::Bytes => "bytes",
Unit::Terabits => "terabits",
Unit::Gigabits => "gigabits",
Unit::Megabits => "megabits",
Unit::Kilobits => "kilobits",
Unit::Bits => "bits",
Unit::TerabytesPerSecond => "terabytes_per_second",
Unit::GigabytesPerSecond => "gigabytes_per_second",
Unit::MegabytesPerSecond => "megabytes_per_second",
Unit::KilobytesPerSecond => "kilobytes_per_second",
Unit::BytesPerSecond => "bytes_per_second",
Unit::TerabitsPerSecond => "terabits_per_second", Unit::TerabitsPerSecond => "terabits_per_second",
Unit::GigabitsPerSecond => "gigabits_per_second", Unit::GigabitsPerSecond => "gigabits_per_second",
Unit::MegabitsPerSecond => "megabits_per_second", Unit::MegabitsPerSecond => "megabits_per_second",
@ -126,25 +112,15 @@ impl Unit {
Unit::Milliseconds => "ms", Unit::Milliseconds => "ms",
Unit::Microseconds => "us", Unit::Microseconds => "us",
Unit::Nanoseconds => "ns", Unit::Nanoseconds => "ns",
Unit::Terabytes => "TB", Unit::Tebibytes => "TiB",
Unit::Gigabytes => "Gb", Unit::Gigibytes => "GiB",
Unit::Megabytes => "MB", Unit::Mebibytes => "MiB",
Unit::Kilobytes => "KB", Unit::Kibibytes => "KiB",
Unit::Bytes => "B", Unit::Bytes => "B",
Unit::Terabits => "Tb",
Unit::Gigabits => "Gb",
Unit::Megabits => "Mb",
Unit::Kilobits => "Kb",
Unit::Bits => "b",
Unit::TerabytesPerSecond => "TBps",
Unit::GigabytesPerSecond => "GBps",
Unit::MegabytesPerSecond => "MBps",
Unit::KilobytesPerSecond => "KBps",
Unit::BytesPerSecond => "Bps",
Unit::TerabitsPerSecond => "Tbps", Unit::TerabitsPerSecond => "Tbps",
Unit::GigabitsPerSecond => "Gbps", Unit::GigabitsPerSecond => "Gbps",
Unit::MegabitsPerSecond => "Mbps", Unit::MegabitsPerSecond => "Mbps",
Unit::KilobitsPerSecond => "Kbps", Unit::KilobitsPerSecond => "kbps",
Unit::BitsPerSecond => "bps", Unit::BitsPerSecond => "bps",
Unit::CountPerSecond => "/s", Unit::CountPerSecond => "/s",
} }
@ -161,21 +137,11 @@ impl Unit {
"milliseconds" => Some(Unit::Milliseconds), "milliseconds" => Some(Unit::Milliseconds),
"microseconds" => Some(Unit::Microseconds), "microseconds" => Some(Unit::Microseconds),
"nanoseconds" => Some(Unit::Nanoseconds), "nanoseconds" => Some(Unit::Nanoseconds),
"terabytes" => Some(Unit::Terabytes), "tebibytes" => Some(Unit::Tebibytes),
"gigabytes" => Some(Unit::Gigabytes), "gigibytes" => Some(Unit::Gigibytes),
"megabytes" => Some(Unit::Megabytes), "mebibytes" => Some(Unit::Mebibytes),
"kilobytes" => Some(Unit::Kilobytes), "kibibytes" => Some(Unit::Kibibytes),
"bytes" => Some(Unit::Bytes), "bytes" => Some(Unit::Bytes),
"terabits" => Some(Unit::Terabits),
"gigabits" => Some(Unit::Gigabits),
"megabits" => Some(Unit::Megabits),
"kilobits" => Some(Unit::Kilobits),
"bits" => Some(Unit::Bits),
"terabytes_per_second" => Some(Unit::TerabytesPerSecond),
"gigabytes_per_second" => Some(Unit::GigabytesPerSecond),
"megabytes_per_second" => Some(Unit::MegabytesPerSecond),
"kilobytes_per_second" => Some(Unit::KilobytesPerSecond),
"bytes_per_second" => Some(Unit::BytesPerSecond),
"terabits_per_second" => Some(Unit::TerabitsPerSecond), "terabits_per_second" => Some(Unit::TerabitsPerSecond),
"gigabits_per_second" => Some(Unit::GigabitsPerSecond), "gigabits_per_second" => Some(Unit::GigabitsPerSecond),
"megabits_per_second" => Some(Unit::MegabitsPerSecond), "megabits_per_second" => Some(Unit::MegabitsPerSecond),
@ -197,21 +163,11 @@ impl Unit {
/// Whether or not this unit relates to the measurement of data. /// Whether or not this unit relates to the measurement of data.
pub fn is_data_based(&self) -> bool { pub fn is_data_based(&self) -> bool {
match self { match self {
Unit::Terabytes Unit::Tebibytes
| Unit::Gigabytes | Unit::Gigibytes
| Unit::Megabytes | Unit::Mebibytes
| Unit::Kilobytes | Unit::Kibibytes
| Unit::Bytes | Unit::Bytes
| Unit::Terabits
| Unit::Gigabits
| Unit::Megabits
| Unit::Kilobits
| Unit::Bits
| Unit::TerabytesPerSecond
| Unit::GigabytesPerSecond
| Unit::MegabytesPerSecond
| Unit::KilobytesPerSecond
| Unit::BytesPerSecond
| Unit::TerabitsPerSecond | Unit::TerabitsPerSecond
| Unit::GigabitsPerSecond | Unit::GigabitsPerSecond
| Unit::MegabitsPerSecond | Unit::MegabitsPerSecond
@ -224,13 +180,8 @@ impl Unit {
/// Whether or not this unit relates to the measurement of data rates. /// Whether or not this unit relates to the measurement of data rates.
pub fn is_data_rate_based(&self) -> bool { pub fn is_data_rate_based(&self) -> bool {
match self { match self {
Unit::TerabytesPerSecond Unit::TerabitsPerSecond
| Unit::GigabytesPerSecond | Unit::GigabitsPerSecond
| Unit::MegabytesPerSecond
| Unit::KilobytesPerSecond
| Unit::BytesPerSecond
| Unit::TerabitsPerSecond
| Unit::Gigabits
| Unit::MegabitsPerSecond | Unit::MegabitsPerSecond
| Unit::KilobitsPerSecond | Unit::KilobitsPerSecond
| Unit::BitsPerSecond => true, | Unit::BitsPerSecond => true,
@ -265,3 +216,37 @@ impl IntoU64 for core::time::Duration {
pub fn __into_u64<V: IntoU64>(value: V) -> u64 { pub fn __into_u64<V: IntoU64>(value: V) -> u64 {
value.into_u64() value.into_u64()
} }
#[cfg(test)]
mod tests {
use super::Unit;
#[test]
fn test_unit_conversions() {
let all_variants = vec![
Unit::Count,
Unit::Percent,
Unit::Seconds,
Unit::Milliseconds,
Unit::Microseconds,
Unit::Nanoseconds,
Unit::Tebibytes,
Unit::Gigibytes,
Unit::Mebibytes,
Unit::Kibibytes,
Unit::Bytes,
Unit::TerabitsPerSecond,
Unit::GigabitsPerSecond,
Unit::MegabitsPerSecond,
Unit::KilobitsPerSecond,
Unit::BitsPerSecond,
Unit::CountPerSecond,
];
for variant in all_variants {
let s = variant.as_str();
let parsed = Unit::from_str(s);
assert_eq!(Some(variant), parsed);
}
}
}

515
metrics/src/cow.rs Normal file
View File

@ -0,0 +1,515 @@
use crate::label::Label;
use alloc::borrow::Borrow;
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::ptr::{slice_from_raw_parts, NonNull};
/// A clone-on-write smart pointer with an optimized memory layout.
pub struct Cow<'a, T: Cowable + ?Sized + 'a> {
/// Pointer to data.
ptr: NonNull<T::Pointer>,
/// Pointer metadata: length and capacity.
meta: Metadata,
/// Lifetime marker.
marker: PhantomData<&'a T>,
}
impl<T> Cow<'_, T>
where
T: Cowable + ?Sized,
{
#[inline]
pub fn owned(val: T::Owned) -> Self {
let (ptr, meta) = T::owned_into_parts(val);
Cow {
ptr,
meta,
marker: PhantomData,
}
}
}
impl<'a, T> Cow<'a, T>
where
T: Cowable + ?Sized,
{
#[inline]
pub fn borrowed(val: &'a T) -> Self {
let (ptr, meta) = T::ref_into_parts(val);
Cow {
ptr,
meta,
marker: PhantomData,
}
}
#[inline]
pub fn into_owned(self) -> T::Owned {
let cow = ManuallyDrop::new(self);
if cow.is_borrowed() {
unsafe { T::clone_from_parts(cow.ptr, &cow.meta) }
} else {
unsafe { T::owned_from_parts(cow.ptr, &cow.meta) }
}
}
#[inline]
pub fn is_borrowed(&self) -> bool {
self.meta.capacity() == 0
}
#[inline]
pub fn is_owned(&self) -> bool {
self.meta.capacity() != 0
}
#[inline]
fn borrow(&self) -> &T {
unsafe { &*T::ref_from_parts(self.ptr, &self.meta) }
}
}
// Implementations of constant functions for creating `Cow` via static strings, static string
// slices, and static label slices.
impl<'a> Cow<'a, str> {
pub const fn const_str(val: &'a str) -> Self {
Cow {
// We are casting *const T to *mut T, however for all borrowed values
// this raw pointer is only ever dereferenced back to &T.
ptr: unsafe { NonNull::new_unchecked(val.as_ptr() as *mut u8) },
meta: Metadata::from_ref(val.len()),
marker: PhantomData,
}
}
}
impl<'a> Cow<'a, [Cow<'static, str>]> {
pub const fn const_slice(val: &'a [Cow<'static, str>]) -> Self {
Cow {
ptr: unsafe { NonNull::new_unchecked(val.as_ptr() as *mut Cow<'static, str>) },
meta: Metadata::from_ref(val.len()),
marker: PhantomData,
}
}
}
impl<'a> Cow<'a, [Label]> {
pub const fn const_slice(val: &'a [Label]) -> Self {
Cow {
ptr: unsafe { NonNull::new_unchecked(val.as_ptr() as *mut Label) },
meta: Metadata::from_ref(val.len()),
marker: PhantomData,
}
}
}
impl<T> Hash for Cow<'_, T>
where
T: Hash + Cowable + ?Sized,
{
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.borrow().hash(state)
}
}
impl<'a, T> Default for Cow<'a, T>
where
T: Cowable + ?Sized,
&'a T: Default,
{
#[inline]
fn default() -> Self {
Cow::borrowed(Default::default())
}
}
impl<T> Eq for Cow<'_, T> where T: Eq + Cowable + ?Sized {}
impl<A, B> PartialOrd<Cow<'_, B>> for Cow<'_, A>
where
A: Cowable + ?Sized + PartialOrd<B>,
B: Cowable + ?Sized,
{
#[inline]
fn partial_cmp(&self, other: &Cow<'_, B>) -> Option<Ordering> {
PartialOrd::partial_cmp(self.borrow(), other.borrow())
}
}
impl<T> Ord for Cow<'_, T>
where
T: Ord + Cowable + ?Sized,
{
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self.borrow(), other.borrow())
}
}
impl<'a, T> From<&'a T> for Cow<'a, T>
where
T: Cowable + ?Sized,
{
#[inline]
fn from(val: &'a T) -> Self {
Cow::borrowed(val)
}
}
impl From<String> for Cow<'_, str> {
#[inline]
fn from(s: String) -> Self {
Cow::owned(s)
}
}
impl From<Vec<Label>> for Cow<'_, [Label]> {
#[inline]
fn from(v: Vec<Label>) -> Self {
Cow::owned(v)
}
}
impl From<Vec<Cow<'static, str>>> for Cow<'_, [Cow<'static, str>]> {
#[inline]
fn from(v: Vec<Cow<'static, str>>) -> Self {
Cow::owned(v)
}
}
impl<T> Drop for Cow<'_, T>
where
T: Cowable + ?Sized,
{
#[inline]
fn drop(&mut self) {
if self.is_owned() {
unsafe { T::owned_from_parts(self.ptr, &self.meta) };
}
}
}
impl<'a, T> Clone for Cow<'a, T>
where
T: Cowable + ?Sized,
{
#[inline]
fn clone(&self) -> Self {
if self.is_owned() {
// Gotta clone the actual inner value.
Cow::owned(unsafe { T::clone_from_parts(self.ptr, &self.meta) })
} else {
Cow { ..*self }
}
}
}
impl<T> core::ops::Deref for Cow<'_, T>
where
T: Cowable + ?Sized,
{
type Target = T;
#[inline]
fn deref(&self) -> &T {
self.borrow()
}
}
impl<T> AsRef<T> for Cow<'_, T>
where
T: Cowable + ?Sized,
{
#[inline]
fn as_ref(&self) -> &T {
self.borrow()
}
}
impl<T> Borrow<T> for Cow<'_, T>
where
T: Cowable + ?Sized,
{
#[inline]
fn borrow(&self) -> &T {
self.borrow()
}
}
impl<A, B> PartialEq<Cow<'_, B>> for Cow<'_, A>
where
A: Cowable + ?Sized,
B: Cowable + ?Sized,
A: PartialEq<B>,
{
fn eq(&self, other: &Cow<B>) -> bool {
self.borrow() == other.borrow()
}
}
impl<T> fmt::Debug for Cow<'_, T>
where
T: Cowable + fmt::Debug + ?Sized,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.borrow().fmt(f)
}
}
impl<T> fmt::Display for Cow<'_, T>
where
T: Cowable + fmt::Display + ?Sized,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.borrow().fmt(f)
}
}
unsafe impl<T: Cowable + Sync + ?Sized> Sync for Cow<'_, T> {}
unsafe impl<T: Cowable + Send + ?Sized> Send for Cow<'_, T> {}
/// Helper trait required by `Cow<T>` to extract capacity of owned
/// variant of `T`, and manage conversions.
///
/// This can be only implemented on types that match requirements:
///
/// + `T::Owned` has a `capacity`, which is an extra word that is absent in `T`.
/// + `T::Owned` with `capacity` of `0` does not allocate memory.
/// + `T::Owned` can be reconstructed from `*mut T` borrowed out of it, plus capacity.
pub unsafe trait Cowable {
type Pointer;
type Owned;
fn ref_into_parts(&self) -> (NonNull<Self::Pointer>, Metadata);
fn owned_into_parts(owned: Self::Owned) -> (NonNull<Self::Pointer>, Metadata);
unsafe fn ref_from_parts(ptr: NonNull<Self::Pointer>, metadata: &Metadata) -> *const Self;
unsafe fn owned_from_parts(ptr: NonNull<Self::Pointer>, metadata: &Metadata) -> Self::Owned;
unsafe fn clone_from_parts(ptr: NonNull<Self::Pointer>, metadata: &Metadata) -> Self::Owned;
}
unsafe impl Cowable for str {
type Pointer = u8;
type Owned = String;
#[inline]
fn ref_into_parts(&self) -> (NonNull<u8>, Metadata) {
// A note on soundness:
//
// We are casting *const T to *mut T, however for all borrowed values
// this raw pointer is only ever dereferenced back to &T.
let ptr = unsafe { NonNull::new_unchecked(self.as_ptr() as *mut _) };
let metadata = Metadata::from_ref(self.len());
(ptr, metadata)
}
#[inline]
unsafe fn ref_from_parts(ptr: NonNull<u8>, metadata: &Metadata) -> *const str {
slice_from_raw_parts(ptr.as_ptr(), metadata.length()) as *const _
}
#[inline]
fn owned_into_parts(owned: String) -> (NonNull<u8>, Metadata) {
let mut owned = ManuallyDrop::new(owned);
let ptr = unsafe { NonNull::new_unchecked(owned.as_mut_ptr()) };
let metadata = Metadata::from_owned(owned.len(), owned.capacity());
(ptr, metadata)
}
#[inline]
unsafe fn owned_from_parts(ptr: NonNull<u8>, metadata: &Metadata) -> String {
String::from_utf8_unchecked(Vec::from_raw_parts(
ptr.as_ptr(),
metadata.length(),
metadata.capacity(),
))
}
#[inline]
unsafe fn clone_from_parts(ptr: NonNull<u8>, metadata: &Metadata) -> Self::Owned {
let str = Self::ref_from_parts(ptr, metadata);
str.as_ref().unwrap().to_string()
}
}
unsafe impl<'a> Cowable for [Cow<'a, str>] {
type Pointer = Cow<'a, str>;
type Owned = Vec<Cow<'a, str>>;
#[inline]
fn ref_into_parts(&self) -> (NonNull<Cow<'a, str>>, Metadata) {
// A note on soundness:
//
// We are casting *const T to *mut T, however for all borrowed values
// this raw pointer is only ever dereferenced back to &T.
let ptr = unsafe { NonNull::new_unchecked(self.as_ptr() as *mut _) };
let metadata = Metadata::from_ref(self.len());
(ptr, metadata)
}
#[inline]
unsafe fn ref_from_parts(
ptr: NonNull<Cow<'a, str>>,
metadata: &Metadata,
) -> *const [Cow<'a, str>] {
slice_from_raw_parts(ptr.as_ptr(), metadata.length())
}
#[inline]
fn owned_into_parts(owned: Vec<Cow<'a, str>>) -> (NonNull<Cow<'a, str>>, Metadata) {
let mut owned = ManuallyDrop::new(owned);
let ptr = unsafe { NonNull::new_unchecked(owned.as_mut_ptr()) };
let metadata = Metadata::from_owned(owned.len(), owned.capacity());
(ptr, metadata)
}
#[inline]
unsafe fn owned_from_parts(
ptr: NonNull<Cow<'a, str>>,
metadata: &Metadata,
) -> Vec<Cow<'a, str>> {
Vec::from_raw_parts(ptr.as_ptr(), metadata.length(), metadata.capacity())
}
#[inline]
unsafe fn clone_from_parts(ptr: NonNull<Cow<'a, str>>, metadata: &Metadata) -> Self::Owned {
let ptr = Self::ref_from_parts(ptr, metadata);
let xs = ptr.as_ref().unwrap();
let mut owned = Vec::with_capacity(xs.len() + 1);
owned.extend_from_slice(xs);
owned
}
}
unsafe impl Cowable for [Label] {
type Pointer = Label;
type Owned = Vec<Label>;
#[inline]
fn ref_into_parts(&self) -> (NonNull<Label>, Metadata) {
// A note on soundness:
//
// We are casting *const T to *mut T, however for all borrowed values
// this raw pointer is only ever dereferenced back to &T.
let ptr = unsafe { NonNull::new_unchecked(self.as_ptr() as *mut _) };
let metadata = Metadata::from_ref(self.len());
(ptr, metadata)
}
#[inline]
unsafe fn ref_from_parts(ptr: NonNull<Label>, metadata: &Metadata) -> *const [Label] {
slice_from_raw_parts(ptr.as_ptr(), metadata.length())
}
#[inline]
fn owned_into_parts(owned: Vec<Label>) -> (NonNull<Label>, Metadata) {
let mut owned = ManuallyDrop::new(owned);
let ptr = unsafe { NonNull::new_unchecked(owned.as_mut_ptr()) };
let metadata = Metadata::from_owned(owned.len(), owned.capacity());
(ptr, metadata)
}
#[inline]
unsafe fn owned_from_parts(ptr: NonNull<Label>, metadata: &Metadata) -> Vec<Label> {
Vec::from_raw_parts(ptr.as_ptr(), metadata.length(), metadata.capacity())
}
#[inline]
unsafe fn clone_from_parts(ptr: NonNull<Label>, metadata: &Metadata) -> Self::Owned {
let xs = Self::ref_from_parts(ptr, metadata);
xs.as_ref().unwrap().to_vec()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Metadata(usize, usize);
impl Metadata {
#[inline]
fn length(&self) -> usize {
self.0
}
#[inline]
fn capacity(&self) -> usize {
self.1
}
pub const fn from_ref(len: usize) -> Metadata {
Metadata(len, 0)
}
pub const fn from_owned(len: usize, capacity: usize) -> Metadata {
Metadata(len, capacity)
}
pub const fn borrowed() -> Metadata {
Metadata(0, 0)
}
pub const fn owned() -> Metadata {
Metadata(0, 1)
}
}
/*
This can be enabled again when we have a way to do panics/asserts in stable Rust,
since const panicking is behind a feature flag at the moment.
const MASK_LO: usize = u32::MAX as usize;
const MASK_HI: usize = !MASK_LO;
#[cfg(target_pointer_width = "64")]
impl Metadata {
#[inline]
fn length(&self) -> usize {
self.0 & MASK_LO
}
#[inline]
fn capacity(&self) -> usize {
self.0 & MASK_HI
}
pub const fn from_ref(len: usize) -> Metadata {
if len & MASK_HI != 0 {
panic!("Cow: length out of bounds for referenced value");
}
Metadata(len)
}
pub const fn from_owned(len: usize, capacity: usize) -> Metadata {
if len & MASK_HI != 0 {
panic!("Cow: length out of bounds for owned value");
}
if capacity & MASK_HI != 0 {
panic!("Cow: capacity out of bounds for owned value");
}
Metadata((capacity & MASK_LO) << 32 | len & MASK_LO)
}
pub const fn borrowed() -> Metadata {
Metadata(0)
}
pub const fn owned() -> Metadata {
Metadata(1 << 32)
}
}*/

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,13 +7,95 @@ use core::{
slice::Iter, slice::Iter,
}; };
const NO_LABELS: [Label; 0] = [];
/// Parts compromising a metric name.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
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(Cow::owned(vec![name.into()]))
}
/// Creates a [`NameParts`] from the given static name.
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>>(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>>(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) -> Iter<'_, SharedString> {
self.0.iter()
}
/// Renders the name parts as a dot-delimited string.
pub fn to_string(&self) -> String {
// It's faster to allocate the string by hand instead of collecting the parts and joining
// them, or deferring to Dsiplay::to_string, or anything else. This may change in the
// future, or benefit from some sort of string pooling, but otherwise, this seemingly
// suboptimal approach -- oh no, a single allocation! :P -- works pretty well overall.
let mut first = false;
let mut s = String::with_capacity(16);
for p in self.0.iter() {
if first {
s.push_str(".");
first = false;
}
s.push_str(p.as_ref());
}
s
}
}
impl From<String> for NameParts {
fn from(name: String) -> NameParts {
NameParts::from_name(name)
}
}
impl From<&'static str> for NameParts {
fn from(name: &'static str) -> NameParts {
NameParts::from_name(name)
}
}
impl From<&'static [SharedString]> for NameParts {
fn from(names: &'static [SharedString]) -> NameParts {
NameParts::from_static_names(names)
}
}
impl fmt::Display for NameParts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = self.to_string();
f.write_str(s.as_str())?;
Ok(())
}
}
/// Inner representation of [`Key`]. /// Inner representation of [`Key`].
/// ///
/// While [`Key`] is the type that users will interact with via [`Recorder`][crate::Recorder`, /// While [`Key`] is the type that users will interact with via [`Recorder`][crate::Recorder],
/// [`KeyData`] is responsible for the actual storage of the name and label data. /// [`KeyData`] is responsible for the actual storage of the name and label data.
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyData { pub struct KeyData {
name: SharedString, // TODO: once const slicing is possible on stable, we could likely use `beef` for both of these
name_parts: NameParts,
labels: Cow<'static, [Label]>, labels: Cow<'static, [Label]>,
} }
@ -23,44 +105,47 @@ 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 and set of labels.
pub fn from_parts<N, L>(name: N, labels: L) -> Self pub fn from_parts<N, L>(name: N, labels: L) -> Self
where where
N: Into<SharedString>, N: Into<NameParts>,
L: IntoLabels, L: IntoLabels,
{ {
Self { Self {
name: name.into(), name_parts: name.into(),
labels: labels.into_labels().into(), 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: &'static str) -> Self { pub const fn from_static_name(name_parts: &'static [SharedString]) -> Self {
Self { Self::from_static_parts(name_parts, &NO_LABELS)
name: SharedString::const_str(name),
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: &'static str, labels: &'static [Label]) -> Self { pub const fn from_static_parts(
name_parts: &'static [SharedString],
labels: &'static [Label],
) -> Self {
Self { Self {
name: SharedString::const_str(name), name_parts: NameParts::from_static_names(name_parts),
labels: Cow::Borrowed(labels), labels: Cow::<[Label]>::const_slice(labels),
} }
} }
/// Name of this key. /// Name parts of this key.
pub fn name(&self) -> &SharedString { pub fn name(&self) -> &NameParts {
&self.name &self.name_parts
} }
/// Labels of this key, if they exist. /// Labels of this key, if they exist.
@ -68,21 +153,27 @@ impl KeyData {
self.labels.iter() self.labels.iter()
} }
/// Map the name of this key to a new name, based on `f`. /// Appends a part to the name,
/// pub fn append_name<S: Into<SharedString>>(self, part: S) -> Self {
/// The value returned by `f` becomes the new name of the key. let name_parts = self.name_parts.append(part);
pub fn map_name<F>(mut self, f: F) -> Self Self {
where name_parts,
F: Fn(SharedString) -> String, labels: self.labels,
{ }
let new_name = f(self.name);
self.name = new_name.into();
self
} }
/// Consumes this [`Key`], returning the name and any labels. /// Prepends a part to the name.
pub fn into_parts(self) -> (SharedString, Vec<Label>) { pub fn prepend_name<S: Into<SharedString>>(self, part: S) -> Self {
(self.name, self.labels.into_owned()) 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.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.
@ -91,12 +182,12 @@ impl KeyData {
return self.clone(); return self.clone();
} }
let name = self.name.clone(); let name_parts = self.name_parts.clone();
let mut labels = self.labels.clone().into_owned(); let mut labels = self.labels.clone().into_owned();
labels.extend(extra_labels); labels.extend(extra_labels);
Self { Self {
name, name_parts,
labels: labels.into(), labels: labels.into(),
} }
} }
@ -105,9 +196,9 @@ impl KeyData {
impl fmt::Display for KeyData { impl fmt::Display for KeyData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.labels.is_empty() { if self.labels.is_empty() {
write!(f, "KeyData({})", self.name) write!(f, "KeyData({})", self.name_parts)
} else { } else {
write!(f, "KeyData({}, [", self.name)?; write!(f, "KeyData({}, [", self.name_parts)?;
let mut first = true; let mut first = true;
for label in self.labels.as_ref() { for label in self.labels.as_ref() {
if first { if first {
@ -140,7 +231,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()),
}
} }
} }
@ -241,12 +335,14 @@ impl From<&'static KeyData> for Key {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Key, KeyData}; use super::{Key, KeyData};
use crate::Label; use crate::{Label, SharedString};
use std::collections::HashMap; use std::collections::HashMap;
static BORROWED_BASIC: KeyData = KeyData::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 LABELS: [Label; 1] = [Label::from_static_parts("key", "value")];
static BORROWED_LABELS: KeyData = KeyData::from_static_parts("name", &LABELS); static BORROWED_LABELS: KeyData = KeyData::from_static_parts(&BORROWED_NAME, &LABELS);
#[test] #[test]
fn test_keydata_eq_and_hash() { fn test_keydata_eq_and_hash() {
@ -262,7 +358,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_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);
@ -287,7 +383,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_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);
@ -304,19 +400,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_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_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_parts(
"foobar", &FOOBAR_NAME[..],
vec![ vec![
Label::new("black", "black"), Label::new("black", "black"),
Label::new("lives", "lives"), Label::new("lives", "lives"),
@ -335,8 +431,10 @@ 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 STATIC_A: KeyData = KeyData::from_static_name("a"); static A_NAME: [SharedString; 1] = [SharedString::const_str("a")];
static STATIC_B: KeyData = KeyData::from_static_name("b"); static STATIC_A: KeyData = KeyData::from_static_name(&A_NAME);
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())); assert_eq!(Key::Owned(owned_a.clone()), Key::Owned(owned_a.clone()));
assert_eq!(Key::Owned(owned_b.clone()), Key::Owned(owned_b.clone())); assert_eq!(Key::Owned(owned_b.clone()), Key::Owned(owned_b.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::*;