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 }}
strategy:
matrix:
rust_version: ['1.43.0', 'stable', 'nightly']
rust_version: ['1.46.0', 'stable', 'nightly']
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
@ -35,6 +35,23 @@ jobs:
override: true
- name: Run Tests
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:
name: Bench ${{ matrix.os }}
runs-on: ${{ matrix.os }}
@ -49,4 +66,7 @@ jobs:
toolchain: 'stable'
override: true
- 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:
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

@ -9,3 +9,7 @@ members = [
"metrics-tracing-context",
]
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 {
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

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

View File

@ -13,13 +13,14 @@ use metrics_util::{
parse_quantiles, CompositeKey, Handle, Histogram, MetricKind, Quantile, Registry,
};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::io;
use std::iter::FromIterator;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
#[cfg(feature = "tokio-exporter")]
use std::thread;
use std::{collections::HashMap, time::SystemTime};
use std::time::SystemTime;
use thiserror::Error as ThisError;
#[cfg(feature = "tokio-exporter")]
use tokio::{pin, runtime, select};
@ -211,7 +212,6 @@ impl Inner {
output.push_str(value.to_string().as_str());
output.push_str("\n");
}
output.push_str("\n");
}
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("\n");
}
output.push_str("\n");
}
let mut sorted_overrides = self
@ -316,8 +315,6 @@ impl Inner {
output.push_str(count.to_string().as_str());
output.push_str("\n");
}
output.push_str("\n");
}
output
@ -345,7 +342,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);
}
}
@ -443,14 +440,16 @@ impl PrometheusBuilder {
/// installing the recorder as the global recorder.
#[cfg(feature = "tokio-exporter")]
pub fn install(self) -> Result<(), Error> {
let (recorder, exporter) = self.build_with_exporter()?;
metrics::set_boxed_recorder(Box::new(recorder))?;
let mut runtime = runtime::Builder::new()
.basic_scheduler()
let runtime = runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let (recorder, exporter) = {
let _guard = runtime.enter();
self.build_with_exporter()
}?;
metrics::set_boxed_recorder(Box::new(recorder))?;
thread::Builder::new()
.name("metrics-exporter-prometheus-http".to_string())
.spawn(move || {
@ -596,7 +595,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,15 +289,17 @@ where
let labels_len = quote! { #labels_len };
quote! {
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(#key, &METRIC_LABELS);
metrics::KeyData::from_static_parts(&METRIC_NAME, &METRIC_LABELS);
}
}
None => {
quote! {
static METRIC_NAME: [metrics::SharedString; 1] = [metrics::SharedString::const_str(#name)];
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"),
@ -314,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);
@ -353,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_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);
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_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_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_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_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_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,

View File

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

View File

@ -26,11 +26,19 @@ harness = false
name = "registry"
harness = false
[[bench]]
name = "prefix"
harness = false
[[bench]]
name = "filter"
harness = false
[dependencies]
metrics = { version = "0.13.0-alpha.1", path = "../metrics", features = ["std"] }
crossbeam-epoch = { version = "0.9", optional = true }
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 }
aho-corasick = { version = "0.7", 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 metrics::{Key, KeyData, Label};
use metrics::{Key, KeyData, Label, SharedString};
use metrics_util::Registry;
fn registry_benchmark(c: &mut Criterion) {
@ -7,7 +7,8 @@ fn registry_benchmark(c: &mut Criterion) {
"registry",
Benchmark::new("cached op (basic)", |b| {
let registry: Registry<Key, ()> = Registry::new();
static KEY_DATA: KeyData = KeyData::from_static_name("simple_key");
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static KEY_DATA: KeyData = KeyData::from_static_name(&KEY_NAME);
b.iter(|| {
let key = Key::Borrowed(&KEY_DATA);
@ -16,8 +17,9 @@ fn registry_benchmark(c: &mut Criterion) {
})
.with_function("cached op (labels)", |b| {
let registry: Registry<Key, ()> = Registry::new();
static KEY_NAME: [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("simple_key", &KEY_LABELS);
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| {
let key = Key::Borrowed(&KEY_DATA);
@ -67,15 +69,15 @@ fn registry_benchmark(c: &mut Criterion) {
})
.with_function("const key data overhead (basic)", |b| {
b.iter(|| {
let key = "simple_key";
KeyData::from_static_name(key)
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
KeyData::from_static_name(&KEY_NAME)
})
})
.with_function("const key data overhead (labels)", |b| {
b.iter(|| {
let key = "simple_key";
static KEY_NAME: [SharedString; 1] = [SharedString::const_str("simple_key")];
static LABELS: [Label; 1] = [Label::from_static_parts("type", "http")];
KeyData::from_static_parts(key, &LABELS)
KeyData::from_static_parts(&KEY_NAME, &LABELS)
})
})
.with_function("owned key overhead (basic)", |b| {
@ -92,12 +94,14 @@ fn registry_benchmark(c: &mut Criterion) {
})
})
.with_function("cached key overhead (basic)", |b| {
static KEY_DATA: KeyData = KeyData::from_static_name("simple_key");
static KEY_NAME: [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: [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("simple_key", &KEY_LABELS);
static KEY_DATA: KeyData = KeyData::from_static_parts(&KEY_NAME, &KEY_LABELS);
b.iter(|| Key::Borrowed(&KEY_DATA))
}),
);

View File

@ -5,14 +5,19 @@ use std::{
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.
struct Block<T> {
// Write index.
write: AtomicUsize,
// Read index.
// Read bitmap.
read: AtomicUsize,
// The individual slots.
@ -35,7 +40,7 @@ impl<T> Block<T> {
/// Gets the current length of this block.
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.
@ -71,7 +76,7 @@ impl<T> Block<T> {
}
// Scoot our read index forward.
self.read.fetch_add(1, Ordering::AcqRel);
self.read.fetch_or(1 << index, Ordering::AcqRel);
Ok(())
}
@ -324,6 +329,7 @@ mod tests {
let result = block.push(42);
assert!(result.is_ok());
assert_eq!(block.len(), 1);
let data = block.data();
assert_eq!(data.len(), 1);

View File

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

View File

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

View File

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

View File

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

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 beef::lean::Cow;
#[cfg(not(target_pointer_width = "64"))]
use beef::Cow;
use crate::cow::Cow;
/// An allocation-optimized string.
///
/// 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
/// 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>;
/// 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.
#[derive(Clone, Debug, PartialEq)]
pub enum Unit {
// Dimensionless measurements.
/// Count.
Count,
/// Percentage.
Percent,
// Time measurements.
/// Seconds.
///
/// One second is equal to 1000 milliseconds.
Seconds,
/// Milliseconds.
///
/// One millisecond is equal to 1000 microseconds.
Milliseconds,
/// Microseconds.
///
/// One microsecond is equal to 1000 nanoseconds.
Microseconds,
/// Nanoseconds.
Nanoseconds,
// Data measurement.
/// Terabytes.
Terabytes,
/// Gigabytes.
Gigabytes,
/// Megabytes.
Megabytes,
/// Kilobytes.
Kilobytes,
/// Tebibytes.
///
/// One tebibyte is equal to 1024 gigibytes.
Tebibytes,
/// Gigibytes.
///
/// One gigibyte is equal to 1024 mebibytes.
Gigibytes,
/// Mebibytes.
///
/// One mebibyte is equal to 1024 kibibytes.
Mebibytes,
/// Kibibytes.
///
/// One kibibyte is equal to 1024 bytes.
Kibibytes,
/// 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.
///
/// One terabit is equal to 1000 gigabits.
TerabitsPerSecond,
/// Gigabits per second.
///
/// One gigabit is equal to 1000 megabits.
GigabitsPerSecond,
/// Megabits per second.
///
/// One megabit is equal to 1000 kilobits.
MegabitsPerSecond,
/// Kilobits per second.
///
/// One kilobit is equal to 1000 bits.
KilobitsPerSecond,
/// Bits per second.
BitsPerSecond,
@ -88,21 +84,11 @@ impl Unit {
Unit::Milliseconds => "milliseconds",
Unit::Microseconds => "microseconds",
Unit::Nanoseconds => "nanoseconds",
Unit::Terabytes => "terabytes",
Unit::Gigabytes => "gigabytes",
Unit::Megabytes => "megabytes",
Unit::Kilobytes => "kilobytes",
Unit::Tebibytes => "tebibytes",
Unit::Gigibytes => "gigibytes",
Unit::Mebibytes => "mebibytes",
Unit::Kibibytes => "kibibytes",
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::GigabitsPerSecond => "gigabits_per_second",
Unit::MegabitsPerSecond => "megabits_per_second",
@ -126,25 +112,15 @@ impl Unit {
Unit::Milliseconds => "ms",
Unit::Microseconds => "us",
Unit::Nanoseconds => "ns",
Unit::Terabytes => "TB",
Unit::Gigabytes => "Gb",
Unit::Megabytes => "MB",
Unit::Kilobytes => "KB",
Unit::Tebibytes => "TiB",
Unit::Gigibytes => "GiB",
Unit::Mebibytes => "MiB",
Unit::Kibibytes => "KiB",
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::GigabitsPerSecond => "Gbps",
Unit::MegabitsPerSecond => "Mbps",
Unit::KilobitsPerSecond => "Kbps",
Unit::KilobitsPerSecond => "kbps",
Unit::BitsPerSecond => "bps",
Unit::CountPerSecond => "/s",
}
@ -161,21 +137,11 @@ impl Unit {
"milliseconds" => Some(Unit::Milliseconds),
"microseconds" => Some(Unit::Microseconds),
"nanoseconds" => Some(Unit::Nanoseconds),
"terabytes" => Some(Unit::Terabytes),
"gigabytes" => Some(Unit::Gigabytes),
"megabytes" => Some(Unit::Megabytes),
"kilobytes" => Some(Unit::Kilobytes),
"tebibytes" => Some(Unit::Tebibytes),
"gigibytes" => Some(Unit::Gigibytes),
"mebibytes" => Some(Unit::Mebibytes),
"kibibytes" => Some(Unit::Kibibytes),
"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),
"gigabits_per_second" => Some(Unit::GigabitsPerSecond),
"megabits_per_second" => Some(Unit::MegabitsPerSecond),
@ -197,21 +163,11 @@ impl Unit {
/// Whether or not this unit relates to the measurement of data.
pub fn is_data_based(&self) -> bool {
match self {
Unit::Terabytes
| Unit::Gigabytes
| Unit::Megabytes
| Unit::Kilobytes
Unit::Tebibytes
| Unit::Gigibytes
| Unit::Mebibytes
| Unit::Kibibytes
| 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::GigabitsPerSecond
| Unit::MegabitsPerSecond
@ -224,13 +180,8 @@ impl Unit {
/// Whether or not this unit relates to the measurement of data rates.
pub fn is_data_rate_based(&self) -> bool {
match self {
Unit::TerabytesPerSecond
| Unit::GigabytesPerSecond
| Unit::MegabytesPerSecond
| Unit::KilobytesPerSecond
| Unit::BytesPerSecond
| Unit::TerabitsPerSecond
| Unit::Gigabits
Unit::TerabitsPerSecond
| Unit::GigabitsPerSecond
| Unit::MegabitsPerSecond
| Unit::KilobitsPerSecond
| Unit::BitsPerSecond => true,
@ -265,3 +216,37 @@ impl IntoU64 for core::time::Duration {
pub fn __into_u64<V: IntoU64>(value: V) -> 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 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,13 +7,95 @@ use core::{
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`].
///
/// 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.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
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]>,
}
@ -23,44 +105,47 @@ 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.
/// Creates a [`KeyData`] from a name and set of labels.
pub fn from_parts<N, L>(name: N, labels: L) -> Self
where
N: Into<SharedString>,
N: Into<NameParts>,
L: IntoLabels,
{
Self {
name: name.into(),
labels: labels.into_labels().into(),
name_parts: name.into(),
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: &'static str) -> Self {
Self {
name: SharedString::const_str(name),
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: &'static str, labels: &'static [Label]) -> Self {
pub const fn from_static_parts(
name_parts: &'static [SharedString],
labels: &'static [Label],
) -> Self {
Self {
name: SharedString::const_str(name),
labels: Cow::Borrowed(labels),
name_parts: NameParts::from_static_names(name_parts),
labels: Cow::<[Label]>::const_slice(labels),
}
}
/// Name of this key.
pub fn name(&self) -> &SharedString {
&self.name
/// Name parts of this key.
pub fn name(&self) -> &NameParts {
&self.name_parts
}
/// Labels of this key, if they exist.
@ -68,21 +153,27 @@ impl KeyData {
self.labels.iter()
}
/// Map the name of this key to a new name, based on `f`.
///
/// The value returned by `f` becomes the new name of the key.
pub fn map_name<F>(mut self, f: F) -> Self
where
F: Fn(SharedString) -> String,
{
let new_name = f(self.name);
self.name = new_name.into();
self
/// Appends a part to the name,
pub fn append_name<S: Into<SharedString>>(self, part: S) -> Self {
let name_parts = self.name_parts.append(part);
Self {
name_parts,
labels: self.labels,
}
}
/// Consumes this [`Key`], returning the name and any labels.
pub fn into_parts(self) -> (SharedString, Vec<Label>) {
(self.name, self.labels.into_owned())
/// Prepends a part to the name.
pub fn prepend_name<S: Into<SharedString>>(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.clone(), self.labels.into_owned())
}
/// Clones this [`Key`], and expands the existing set of labels.
@ -91,12 +182,12 @@ impl KeyData {
return self.clone();
}
let name = self.name.clone();
let name_parts = self.name_parts.clone();
let mut labels = self.labels.clone().into_owned();
labels.extend(extra_labels);
Self {
name,
name_parts,
labels: labels.into(),
}
}
@ -105,9 +196,9 @@ impl KeyData {
impl fmt::Display for KeyData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.labels.is_empty() {
write!(f, "KeyData({})", self.name)
write!(f, "KeyData({})", self.name_parts)
} else {
write!(f, "KeyData({}, [", self.name)?;
write!(f, "KeyData({}, [", self.name_parts)?;
let mut first = true;
for label in self.labels.as_ref() {
if first {
@ -140,7 +231,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()),
}
}
}
@ -241,12 +335,14 @@ impl From<&'static KeyData> for Key {
#[cfg(test)]
mod tests {
use super::{Key, KeyData};
use crate::Label;
use crate::{Label, SharedString};
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 BORROWED_LABELS: KeyData = KeyData::from_static_parts("name", &LABELS);
static BORROWED_LABELS: KeyData = KeyData::from_static_parts(&BORROWED_NAME, &LABELS);
#[test]
fn test_keydata_eq_and_hash() {
@ -262,7 +358,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_parts(&BORROWED_NAME[..], labels);
assert_eq!(&owned_labels, &BORROWED_LABELS);
let previous = keys.insert(owned_labels, 43);
@ -287,7 +383,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_parts(&BORROWED_NAME[..], labels));
let borrowed_labels = Key::from(&BORROWED_LABELS);
assert_eq!(owned_labels, borrowed_labels);
@ -304,19 +400,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_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",
&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",
&FOOBAR_NAME[..],
vec![
Label::new("black", "black"),
Label::new("lives", "lives"),
@ -335,8 +431,10 @@ mod tests {
let owned_a = KeyData::from_name("a");
let owned_b = KeyData::from_name("b");
static STATIC_A: KeyData = KeyData::from_static_name("a");
static STATIC_B: KeyData = KeyData::from_static_name("b");
static A_NAME: [SharedString; 1] = [SharedString::const_str("a")];
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_b.clone()), Key::Owned(owned_b.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::*;