diff --git a/Cargo.lock b/Cargo.lock index b66266f..9b88b54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,18 +31,27 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "alloc" version = "0.1.0" dependencies = [ "dhat", - "metrics 0.23.0", "serde", "serde_json", "thiserror", "tikv-jemalloc-ctl", "tikv-jemallocator", "tokio", + "wc_metrics", ] [[package]] @@ -90,6 +99,67 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "async-lock" version = "3.4.0" @@ -101,6 +171,61 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-signal" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.80" @@ -136,6 +261,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aws-lc-rs" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "aws-runtime" version = "1.2.2" @@ -314,11 +466,11 @@ dependencies = [ "http-body 0.4.6", "http-body 1.0.0", "hyper 0.14.29", - "hyper-rustls", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", - "rustls", + "rustls 0.21.12", "tokio", "tracing", ] @@ -483,6 +635,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64-simd" version = "0.8.0" @@ -499,6 +657,29 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.66", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -520,6 +701,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -553,6 +747,20 @@ name = "cc" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -575,6 +783,17 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.34.0" @@ -586,6 +805,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "collections" version = "0.1.0" @@ -835,6 +1063,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "ecdsa" version = "0.14.8" @@ -873,12 +1107,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -951,25 +1215,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "future" version = "0.1.0" dependencies = [ - "metrics 0.1.0", "pin-project", "thiserror", "tokio", "tokio-util", ] -[[package]] -name = "future_metrics" -version = "0.1.0" -dependencies = [ - "metrics 0.23.0", - "pin-project", -] - [[package]] name = "futures" version = "0.3.30" @@ -1018,6 +1279,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1186,6 +1460,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -1201,6 +1481,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -1324,10 +1613,29 @@ dependencies = [ "http 0.2.12", "hyper 0.14.29", "log", - "rustls", - "rustls-native-certs", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "log", + "rustls 0.23.10", + "rustls-native-certs 0.7.0", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", ] [[package]] @@ -1337,12 +1645,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", + "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1514,6 +1827,12 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "ipnetwork" version = "0.18.0" @@ -1523,12 +1842,30 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1544,12 +1881,34 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.3" @@ -1615,19 +1974,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "metrics" -version = "0.1.0" -dependencies = [ - "once_cell", - "opentelemetry", - "opentelemetry-prometheus", - "opentelemetry_sdk", - "pin-project", - "prometheus", - "smallvec", -] - [[package]] name = "metrics" version = "0.23.0" @@ -1638,12 +1984,54 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "metrics-exporter-prometheus" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0af7a0d7ced10c0151f870e5e3f3f8bc9ffc5992d32873566ca1f9169ae776" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.27.2", + "hyper-util", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -1670,6 +2058,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "moka" version = "0.12.7" @@ -1694,6 +2088,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -1783,7 +2187,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -1808,56 +2212,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "opentelemetry" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", -] - -[[package]] -name = "opentelemetry-prometheus" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bbcf6341cab7e2193e5843f0ac36c446a5b3fccb28747afaeda17996dcd02e" -dependencies = [ - "once_cell", - "opentelemetry", - "opentelemetry_sdk", - "prometheus", - "protobuf", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" -dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "once_cell", - "opentelemetry", - "ordered-float 4.2.0", - "percent-encoding", - "rand", - "thiserror", - "tokio", - "tokio-stream", -] - [[package]] name = "ordered-float" version = "2.10.1" @@ -1867,15 +2221,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ordered-float" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" -dependencies = [ - "num-traits", -] - [[package]] name = "outref" version = "0.5.1" @@ -1995,6 +2340,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -2005,6 +2361,21 @@ dependencies = [ "spki", ] +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "portable-atomic" version = "1.6.0" @@ -2018,10 +2389,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "prettyplease" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.66", +] [[package]] name = "proc-macro-error" @@ -2057,26 +2432,17 @@ dependencies = [ ] [[package]] -name = "prometheus" -version = "0.13.4" +name = "prometheus-parse" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +checksum = "811031bea65e5a401fb2e1f37d802cca6601e204ac463809a3189352d13b78a5" dependencies = [ - "cfg-if", - "fnv", - "lazy_static", - "memchr", - "parking_lot", - "protobuf", - "thiserror", + "chrono", + "itertools", + "once_cell", + "regex", ] -[[package]] -name = "protobuf" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" - [[package]] name = "quanta" version = "0.12.3" @@ -2101,27 +2467,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -2186,12 +2531,41 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-lite" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "rfc6979" version = "0.3.1" @@ -2239,6 +2613,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -2247,10 +2634,25 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -2258,7 +2660,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "schannel", "security-framework", ] @@ -2269,9 +2684,25 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2282,6 +2713,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -2449,6 +2892,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2468,6 +2917,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + [[package]] name = "slab" version = "0.4.9" @@ -2483,6 +2938,23 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smol" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e635339259e51ef85ac7aa29a1cd991b957047507288697a690e80ab97d07cad" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + [[package]] name = "socket2" version = "0.5.7" @@ -2651,7 +3123,7 @@ checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" dependencies = [ "byteorder", "integer-encoding", - "ordered-float 2.10.1", + "ordered-float", ] [[package]] @@ -2770,18 +3242,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", "tokio", ] [[package]] -name = "tokio-stream" -version = "0.1.15" +name = "tokio-rustls" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "futures-core", - "pin-project-lite", + "rustls 0.23.10", + "rustls-pki-types", "tokio", ] @@ -2921,12 +3393,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf16_iter" version = "1.0.5" @@ -3039,14 +3505,33 @@ dependencies = [ "axum", "collections", "future", - "future_metrics", "geoip", "hyper 1.3.1", - "metrics 0.1.0", + "metrics-exporter-prometheus", "rate_limit", "structopt", "tokio", "tower", + "wc_metrics", +] + +[[package]] +name = "wc_metrics" +version = "0.1.0" +dependencies = [ + "arc-swap", + "enum-ordinalize", + "futures", + "metrics", + "metrics-exporter-prometheus", + "parking_lot", + "pin-project", + "prometheus-parse", + "smallvec", + "smol", + "tikv-jemalloc-ctl", + "tikv-jemallocator", + "wc_metrics", ] [[package]] @@ -3059,6 +3544,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3317,6 +3814,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index aa5e6ca..34d7801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,7 @@ collections = ["dep:collections"] future = ["dep:future"] geoblock = ["geoip/middleware"] geoip = ["dep:geoip"] -metrics = ["dep:metrics", "future/metrics", "alloc/metrics"] -future_metrics = ["dep:future_metrics"] +metrics = ["dep:metrics", "alloc/metrics"] alloc_metrics = ["alloc/metrics"] profiler = ["alloc/profiler"] rate_limit = ["dep:rate_limit"] @@ -45,8 +44,7 @@ analytics = { path = "./crates/analytics", optional = true } collections = { path = "./crates/collections", optional = true } future = { path = "./crates/future", optional = true } geoip = { path = "./crates/geoip", optional = true } -metrics = { path = "./crates/metrics", optional = true } -future_metrics = { path = "./crates/future_metrics", optional = true } +metrics = { package = "wc_metrics", path = "./crates/metrics", optional = true } rate_limit = { path = "./crates/rate_limit", optional = true } [dev-dependencies] @@ -56,6 +54,7 @@ tokio = { version = "1", features = ["full"] } hyper = { version = "1.2.0", features = ["full"] } tower = { version = "0.4", features = ["util", "filter"] } axum = "0.7.5" +metrics-exporter-prometheus = { version = "0.15", default-features = false } [[example]] name = "alloc_profiler" @@ -65,10 +64,6 @@ required-features = ["alloc", "profiler"] name = "alloc_stats" required-features = ["alloc", "metrics"] -[[example]] -name = "metrics" -required-features = ["metrics", "future"] - [[example]] name = "geoblock" required-features = ["geoblock"] diff --git a/crates/alloc/Cargo.toml b/crates/alloc/Cargo.toml index 8d14f5a..bc46b7c 100644 --- a/crates/alloc/Cargo.toml +++ b/crates/alloc/Cargo.toml @@ -10,7 +10,7 @@ profiler = ["dep:dhat", "dep:tokio"] metrics = ["dep:metrics"] [dependencies] -metrics = { version = "0.23", optional = true } +metrics = { package = "wc_metrics", path = "../metrics", optional = true } tikv-jemallocator = { version = "0.5", features = ["stats"] } tikv-jemalloc-ctl = { version = "0.5", features = ["use_std"] } serde = { version = "1", features = ["derive"] } diff --git a/crates/alloc/src/stats.rs b/crates/alloc/src/stats.rs index 0803d14..4710c68 100644 --- a/crates/alloc/src/stats.rs +++ b/crates/alloc/src/stats.rs @@ -87,7 +87,7 @@ pub fn collect_jemalloc_stats() -> Result { #[cfg(feature = "metrics")] pub fn update_jemalloc_metrics() -> Result<(), Error> { - use metrics::gauge; + use metrics::backend::gauge; let stats = collect_jemalloc_stats()?; let total = &stats.total; diff --git a/crates/future/Cargo.toml b/crates/future/Cargo.toml index ae5ee5b..0cd4fa4 100644 --- a/crates/future/Cargo.toml +++ b/crates/future/Cargo.toml @@ -5,11 +5,8 @@ edition = "2021" [features] default = [] -full = ["metrics"] -metrics = ["dep:metrics"] [dependencies] -metrics = { path = "../metrics", optional = true } tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "time", "macros"] } tokio-util = { version = "0.7", default-features = false } pin-project = "1" diff --git a/crates/future/src/lib.rs b/crates/future/src/lib.rs index 1ab40ac..030b814 100644 --- a/crates/future/src/lib.rs +++ b/crates/future/src/lib.rs @@ -202,34 +202,6 @@ pub trait FutureExt { self, token: CancellationToken, ) -> CancellationFuture>; - - /// Consumes the future, returning a new future that records the metrics of - /// the inner future's async task execution. - /// - /// # Example - /// - /// ```rust - /// use {future::FutureExt, metrics::OtelTaskMetricsRecorder, std::time::Duration}; - /// - /// # async fn example() { - /// let recorder = OtelTaskMetricsRecorder::new("custom_task").with_name("specific_task_name"); - /// - /// async { - /// tokio::time::sleep(Duration::from_millis(500)).await; - /// } - /// .with_metrics(recorder) - /// .await - /// # } - /// - /// # #[tokio::main] - /// # async fn main() { - /// # example().await; - /// # } - /// ``` - #[cfg(feature = "metrics")] - fn with_metrics(self, recorder: R) -> metrics::TaskMetricsFuture - where - R: metrics::TaskMetricsRecorder; } pub trait StaticFutureExt { @@ -248,7 +220,7 @@ pub trait StaticFutureExt { /// tokio::time::sleep(Duration::from_millis(500)).await; /// 42 /// } - /// .spawn(""); + /// .spawn(); /// /// assert!(matches!(join_handle.await, Ok(42))); /// # } @@ -258,15 +230,7 @@ pub trait StaticFutureExt { /// # example().await; /// # } /// ``` - #[cfg(feature = "metrics")] - fn spawn(self, name: &'static str) -> JoinHandle<::Output>; - - /// Same as [`StaticFutureExt::spawn`], but it won't monitor long running - /// futures. - /// - /// Use this only if your future is expected to be long running (ex. - /// singleton). - fn spawn_and_forget(self) -> JoinHandle<::Output>; + fn spawn(self) -> JoinHandle<::Output>; } impl FutureExt for T @@ -292,14 +256,6 @@ where on_cancel: ready(()), } } - - #[cfg(feature = "metrics")] - fn with_metrics(self, recorder: R) -> metrics::TaskMetricsFuture - where - R: metrics::TaskMetricsRecorder, - { - metrics::TaskMetricsFuture::new(self, recorder) - } } impl StaticFutureExt for T @@ -309,19 +265,12 @@ where { type Future = T; - #[cfg(feature = "metrics")] - fn spawn(self, name: &'static str) -> JoinHandle<::Output> { - static METRICS: metrics::TaskMetrics = metrics::TaskMetrics::new("spawned_task"); - - tokio::spawn(self.with_metrics(METRICS.with_name(name))) - } - - fn spawn_and_forget(self) -> JoinHandle<::Output> { + fn spawn(self) -> JoinHandle<::Output> { tokio::spawn(self) } } -#[cfg(all(test, feature = "metrics"))] +#[cfg(test)] mod test { use { super::*, @@ -356,7 +305,7 @@ mod test { tokio::time::sleep(Duration::from_millis(100)).await; b.fetch_add(1, Ordering::SeqCst); }) - .spawn("") + .spawn() }; tokio::time::sleep(Duration::from_millis(200)).await; @@ -385,7 +334,7 @@ mod test { tokio::time::sleep(Duration::from_millis(100)).await; b.fetch_add(1, Ordering::Relaxed); }) - .spawn("") + .spawn() }; tokio::time::sleep(Duration::from_millis(200)).await; @@ -416,7 +365,7 @@ mod test { tokio::time::sleep(Duration::from_millis(100)).await; b.fetch_add(1, Ordering::Relaxed); }) - .spawn("") + .spawn() }; assert_eq!(handle.await.unwrap(), Err(Error::Timeout)); @@ -441,7 +390,7 @@ mod test { tokio::time::sleep(Duration::from_millis(100)).await; b.fetch_add(1, Ordering::Relaxed); }) - .spawn("") + .spawn() }; assert_eq!(handle.await.unwrap(), Ok(42)); diff --git a/crates/future_metrics/Cargo.toml b/crates/future_metrics/Cargo.toml deleted file mode 100644 index 710a22b..0000000 --- a/crates/future_metrics/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "future_metrics" -version = "0.1.0" -edition = "2021" - -[dependencies] -pin-project = "1" -metrics = "0.23" diff --git a/crates/future_metrics/src/lib.rs b/crates/future_metrics/src/lib.rs deleted file mode 100644 index a2db759..0000000 --- a/crates/future_metrics/src/lib.rs +++ /dev/null @@ -1,215 +0,0 @@ -use { - metrics::{Counter, Gauge, Histogram, Key, Label, Level, Metadata}, - std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, - time::{Duration, Instant}, - }, -}; - -/// Target specified in [`metrics::Metadata`] for all metrics produced by this -/// crate. -pub const METADATA_TARGET: &str = "future_metrics"; - -/// Metric names used by this crate. -pub mod metric_name { - pub const FUTURE_DURATION: &str = "future_duration"; - pub const FUTURE_CANCELLED_DURATION: &str = "future_cancelled_duration"; - - pub const FUTURES_CREATED: &str = "futures_created_count"; - pub const FUTURES_STARTED: &str = "futures_started_count"; - pub const FUTURES_FINISHED: &str = "futures_finished_count"; - pub const FUTURES_CANCELLED: &str = "futures_cancelled_count"; - - pub const FUTURE_POLL_DURATION: &str = "future_poll_duration"; - pub const FUTURE_POLL_DURATION_MAX: &str = "future_poll_duration_max"; - pub const FUTURE_POLLS: &str = "future_polls_count"; -} - -/// Creates a new label identifying a future by its name. -pub const fn future_name(s: &'static str) -> Label { - Label::from_static_parts("future_name", s) -} - -pub trait FutureExt: Sized { - /// Consumes the future, returning a new future that records the executiion - /// metrics of the inner future. - /// - /// It is expected that you provide at least one label identifying the - /// future being metered. - /// Consider using [`future_name`] label, or the [`FutureExt::with_metrics`] - /// shortcut. - fn with_labeled_metrics(self, labels: &'static [Label]) -> Metered { - Metered::new(self, labels) - } - - /// A shortcut for [`FutureExt::with_labeled_metrics`] using a single label - /// only (presumably [`future_name`]). - fn with_metrics(self, label: &'static Label) -> Metered { - self.with_labeled_metrics(std::slice::from_ref(label)) - } -} - -impl FutureExt for F where F: Future {} - -#[pin_project::pin_project] -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct Metered { - #[pin] - future: F, - state: State, -} - -struct State { - started_at: Option, - is_finished: bool, - - poll_duration_sum: Duration, - poll_duration_max: Duration, - polls_count: usize, - - metrics: Metrics, -} - -impl Metered { - fn new(future: F, metric_labels: &'static [Label]) -> Self { - let metrics = Metrics::new(metric_labels); - - metrics.created.increment(1); - - Self { - future, - state: State { - started_at: None, - is_finished: false, - poll_duration_sum: Duration::from_secs(0), - poll_duration_max: Duration::from_secs(0), - polls_count: 0, - metrics, - }, - } - } -} - -impl Future for Metered { - type Output = F::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let mut this = self.project(); - let state = &mut this.state; - - if state.started_at.is_none() { - state.started_at = Some(Instant::now()); - state.metrics.started.increment(1); - } - - let poll_started_at = Instant::now(); - let result = this.future.poll(cx); - let poll_duration = poll_started_at.elapsed(); - - state.poll_duration_sum += poll_duration; - state.poll_duration_max = state.poll_duration_max.max(poll_duration); - state.polls_count += 1; - - if result.is_ready() && !state.is_finished { - state.is_finished = true; - - state.metrics.finished.increment(1); - - if let Some(started_at) = state.started_at { - state.metrics.duration.record(started_at.elapsed()) - } - } - - result - } -} - -impl Drop for State { - fn drop(&mut self) { - if !self.is_finished { - self.metrics.cancelled.increment(1); - - if let Some(started_at) = self.started_at { - self.metrics.cancelled_duration.record(started_at.elapsed()) - } - } - - self.metrics - .poll_duration - .record(duration_as_millis_f64(self.poll_duration_sum)); - - self.metrics - .poll_duration_max - .set(duration_as_millis_f64(self.poll_duration_max)); - - self.metrics.polls.increment(self.polls_count as u64); - } -} - -struct Metrics { - duration: Histogram, - cancelled_duration: Histogram, - - created: Counter, - started: Counter, - finished: Counter, - cancelled: Counter, - - poll_duration: Histogram, - poll_duration_max: Gauge, - polls: Counter, -} - -impl Metrics { - fn new(labels: &'static [Label]) -> Self { - metrics::with_recorder(|r| { - let metadata = Metadata::new(METADATA_TARGET, Level::INFO, None); - - Self { - duration: r.register_histogram( - &Key::from_static_parts(metric_name::FUTURE_DURATION, labels), - &metadata, - ), - cancelled_duration: r.register_histogram( - &Key::from_static_parts(metric_name::FUTURE_CANCELLED_DURATION, labels), - &metadata, - ), - created: r.register_counter( - &Key::from_static_parts(metric_name::FUTURES_CREATED, labels), - &metadata, - ), - started: r.register_counter( - &Key::from_static_parts(metric_name::FUTURES_STARTED, labels), - &metadata, - ), - finished: r.register_counter( - &Key::from_static_parts(metric_name::FUTURES_FINISHED, labels), - &metadata, - ), - cancelled: r.register_counter( - &Key::from_static_parts(metric_name::FUTURES_CANCELLED, labels), - &metadata, - ), - poll_duration: r.register_histogram( - &Key::from_static_parts(metric_name::FUTURE_POLL_DURATION, labels), - &metadata, - ), - poll_duration_max: r.register_gauge( - &Key::from_static_parts(metric_name::FUTURE_POLL_DURATION_MAX, labels), - &metadata, - ), - polls: r.register_counter( - &Key::from_static_parts(metric_name::FUTURE_POLLS, labels), - &metadata, - ), - } - }) - } -} - -#[inline] -pub fn duration_as_millis_f64(val: Duration) -> f64 { - val.as_secs_f64() * 1000.0 -} diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index 3c4b83a..72ca108 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -1,13 +1,25 @@ [package] -name = "metrics" +name = "wc_metrics" version = "0.1.0" edition = "2021" +[features] +default = ["future"] +future = ["dep:pin-project"] + [dependencies] -pin-project = "1" -prometheus = "0.13" -opentelemetry = { version = "0.22.0", features = ["metrics"] } -opentelemetry_sdk = { version = "0.22.1", features = ["metrics", "rt-tokio"] } -opentelemetry-prometheus = "0.15" -once_cell = "1.17" -smallvec = "1.11" +metrics = "0.23" +smallvec = "1" +parking_lot = "0.12" +enum-ordinalize = "4.3" +arc-swap = "1.7" +pin-project = { version = "1", optional = true } +futures = "0.3" + +[dev-dependencies] +wc_metrics = { path = "./" } +metrics-exporter-prometheus = "0.15" +prometheus-parse = "0.2" +tikv-jemalloc-ctl = { version = "0.5", features = ["use_std"] } +tikv-jemallocator = "0.5" +smol = "2" diff --git a/crates/metrics/src/examples/macros_counter.rs b/crates/metrics/src/examples/macros_counter.rs new file mode 100644 index 0000000..3047162 --- /dev/null +++ b/crates/metrics/src/examples/macros_counter.rs @@ -0,0 +1,104 @@ +use wc_metrics::{ + counter, + enum_ordinalize::Ordinalize, + BoolLabel, + EnumLabel, + OptionalBoolLabel, + OptionalEnumLabel, + OptionalStringLabel, + StringLabel, +}; + +#[derive(Clone, Copy, Debug, Ordinalize)] +enum MyEnum { + A, + B, +} + +impl wc_metrics::Enum for MyEnum { + fn as_str(&self) -> &'static str { + match self { + Self::A => "a", + Self::B => "b", + } + } +} + +pub fn counters(v: u64) { + let s = "a"; + let b = true; + let u = 42; + let e = MyEnum::A; + + counter!("counter1").increment(v); + + counter!("counter2", EnumLabel<"e", MyEnum> => e).increment(v); + + counter!("counter3", BoolLabel<"b"> => b).increment(v); + + counter!("counter4", StringLabel<"s"> => s).increment(v); + + counter!("counter5", StringLabel<"s", u8> => &u).increment(v); + + counter!("counter6", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + ) + .increment(v); + + counter!("counter7", "st" => "1").increment(v); + + counter!("counter8", "st1" => "1", "st2" => "2").increment(v); + + counter!("counter9", StringLabel<"s", u8> => &u, "st" => "2").increment(v); + + counter!("counter10", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + "st1" => "1", + "st2" => "2" + ) + .increment(v); + + counter!("counter11", "description11").increment(v); + + counter!("counter12", "description12", EnumLabel<"e", MyEnum> => e).increment(v); + + counter!("counter13", "description13", BoolLabel<"b"> => b).increment(v); + + counter!("counter14", "description14", StringLabel<"s"> => s).increment(v); + + counter!("counter15", "description15", StringLabel<"s", u8> => &u).increment(v); + + counter!("counter16", "description16", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + ) + .increment(v); + + counter!("counter17", "description17", "st" => "1").increment(v); + + counter!("counter18", "description18", "st1" => "1", "st2" => "2").increment(v); + + counter!("counter19", "description19", StringLabel<"s", u8> => &u, "st" => "2").increment(v); + + counter!("counter20", "description20", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + OptionalEnumLabel<"oe", MyEnum> => Some(e), + OptionalStringLabel<"os1"> => Some(s), + OptionalStringLabel<"os2", u8> => Some(&u), + OptionalBoolLabel<"ob"> => Some(b), + "st1" => "1", + "st2" => "2" + ) + .increment(v); +} diff --git a/crates/metrics/src/examples/macros_future_metrics.rs b/crates/metrics/src/examples/macros_future_metrics.rs new file mode 100644 index 0000000..448b539 --- /dev/null +++ b/crates/metrics/src/examples/macros_future_metrics.rs @@ -0,0 +1,89 @@ +use wc_metrics::{ + enum_ordinalize::Ordinalize, + future_metrics, + BoolLabel, + EnumLabel, + FutureExt, + OptionalBoolLabel, + OptionalEnumLabel, + OptionalStringLabel, + StringLabel, +}; + +#[derive(Clone, Copy, Debug, Ordinalize)] +enum MyEnum { + A, + B, +} + +impl wc_metrics::Enum for MyEnum { + fn as_str(&self) -> &'static str { + match self { + Self::A => "a", + Self::B => "b", + } + } +} + +pub async fn future_metrics() { + let s = "a"; + let b = true; + let u = 42; + let e = MyEnum::A; + + async {} + .with_metrics(future_metrics!("future_metrics1")) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics2", EnumLabel<"e", MyEnum> => e)) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics3", BoolLabel<"b"> => b)) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics4", StringLabel<"s"> => s)) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics5", StringLabel<"s", u8> => &u)) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics6", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + )) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics7", "st" => "1")) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics8", "st1" => "1", "st2" => "2")) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics9", StringLabel<"s", u8> => &u, "st" => "2")) + .await; + + async {} + .with_metrics(future_metrics!("future_metrics10", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + OptionalEnumLabel<"oe", MyEnum> => Some(e), + OptionalStringLabel<"os1"> => Some(s), + OptionalStringLabel<"os2", u8> => Some(&u), + OptionalBoolLabel<"ob"> => Some(b), + "st1" => "1", + "st2" => "2" + )) + .await; +} diff --git a/crates/metrics/src/examples/macros_gauge.rs b/crates/metrics/src/examples/macros_gauge.rs new file mode 100644 index 0000000..8b28275 --- /dev/null +++ b/crates/metrics/src/examples/macros_gauge.rs @@ -0,0 +1,104 @@ +use wc_metrics::{ + enum_ordinalize::Ordinalize, + gauge, + BoolLabel, + EnumLabel, + OptionalBoolLabel, + OptionalEnumLabel, + OptionalStringLabel, + StringLabel, +}; + +#[derive(Clone, Copy, Debug, Ordinalize)] +enum MyEnum { + A, + B, +} + +impl wc_metrics::Enum for MyEnum { + fn as_str(&self) -> &'static str { + match self { + Self::A => "a", + Self::B => "b", + } + } +} + +pub fn gauges(v: f64) { + let s = "a"; + let b = true; + let u = 42; + let e = MyEnum::A; + + gauge!("gauge1").set(v); + + gauge!("gauge2", EnumLabel<"e", MyEnum> => e).set(v); + + gauge!("gauge3", BoolLabel<"b"> => b).set(v); + + gauge!("gauge4", StringLabel<"s"> => s).set(v); + + gauge!("gauge5", StringLabel<"s", u8> => &u).set(v); + + gauge!("gauge6", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + ) + .set(v); + + gauge!("gauge7", "st" => "1").set(v); + + gauge!("gauge8", "st1" => "1", "st2" => "2").set(v); + + gauge!("gauge9", StringLabel<"s", u8> => &u, "st" => "2").set(v); + + gauge!("gauge10", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + "st1" => "1", + "st2" => "2" + ) + .set(v); + + gauge!("gauge11", "description11").set(v); + + gauge!("gauge12", "description12", EnumLabel<"e", MyEnum> => e).set(v); + + gauge!("gauge13", "description13", BoolLabel<"b"> => b).set(v); + + gauge!("gauge14", "description14", StringLabel<"s"> => s).set(v); + + gauge!("gauge15", "description15", StringLabel<"s", u8> => &u).set(v); + + gauge!("gauge16", "description16", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + ) + .set(v); + + gauge!("gauge17", "description17", "st" => "1").set(v); + + gauge!("gauge18", "description18", "st1" => "1", "st2" => "2").set(v); + + gauge!("gauge19", "description19", StringLabel<"s", u8> => &u, "st" => "2").set(v); + + gauge!("gauge20", "description20", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + OptionalEnumLabel<"oe", MyEnum> => Some(e), + OptionalStringLabel<"os1"> => Some(s), + OptionalStringLabel<"os2", u8> => Some(&u), + OptionalBoolLabel<"ob"> => Some(b), + "st1" => "1", + "st2" => "2" + ) + .set(v); +} diff --git a/crates/metrics/src/examples/macros_histogram.rs b/crates/metrics/src/examples/macros_histogram.rs new file mode 100644 index 0000000..5374827 --- /dev/null +++ b/crates/metrics/src/examples/macros_histogram.rs @@ -0,0 +1,104 @@ +use wc_metrics::{ + enum_ordinalize::Ordinalize, + histogram, + BoolLabel, + EnumLabel, + OptionalBoolLabel, + OptionalEnumLabel, + OptionalStringLabel, + StringLabel, +}; + +#[derive(Clone, Copy, Debug, Ordinalize)] +enum MyEnum { + A, + B, +} + +impl wc_metrics::Enum for MyEnum { + fn as_str(&self) -> &'static str { + match self { + Self::A => "a", + Self::B => "b", + } + } +} + +pub fn histograms(v: f64) { + let s = "a"; + let b = true; + let u = 42; + let e = MyEnum::A; + + histogram!("histogram1").record(v); + + histogram!("histogram2", EnumLabel<"e", MyEnum> => e).record(v); + + histogram!("histogram3", BoolLabel<"b"> => b).record(v); + + histogram!("histogram4", StringLabel<"s"> => s).record(v); + + histogram!("histogram5", StringLabel<"s", u8> => &u).record(v); + + histogram!("histogram6", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + ) + .record(v); + + histogram!("histogram7", "st" => "1").record(v); + + histogram!("histogram8", "st1" => "1", "st2" => "2").record(v); + + histogram!("histogram9", StringLabel<"s", u8> => &u, "st" => "2").record(v); + + histogram!("histogram10", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + "st1" => "1", + "st2" => "2" + ) + .record(v); + + histogram!("histogram11", "description11").record(v); + + histogram!("histogram12", "description12", EnumLabel<"e", MyEnum> => e).record(v); + + histogram!("histogram13", "description13", BoolLabel<"b"> => b).record(v); + + histogram!("histogram14", "description14", StringLabel<"s"> => s).record(v); + + histogram!("histogram15", "description15", StringLabel<"s", u8> => &u).record(v); + + histogram!("histogram16", "description16", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b + ) + .record(v); + + histogram!("histogram17", "description17", "st" => "1").record(v); + + histogram!("histogram18", "description18", "st1" => "1", "st2" => "2").record(v); + + histogram!("histogram19", "description19", StringLabel<"s", u8> => &u, "st" => "2").record(v); + + histogram!("histogram20", "description20", + EnumLabel<"e", MyEnum> => e, + StringLabel<"s1"> => s, + StringLabel<"s2", u8> => &u, + BoolLabel<"b"> => b, + OptionalEnumLabel<"oe", MyEnum> => Some(e), + OptionalStringLabel<"os1"> => Some(s), + OptionalStringLabel<"os2", u8> => Some(&u), + OptionalBoolLabel<"ob"> => Some(b), + "st1" => "1", + "st2" => "2" + ) + .record(v); +} diff --git a/crates/metrics/src/examples/mod.rs b/crates/metrics/src/examples/mod.rs new file mode 100644 index 0000000..d74e2a6 --- /dev/null +++ b/crates/metrics/src/examples/mod.rs @@ -0,0 +1,4 @@ +pub mod macros_counter; +pub mod macros_future_metrics; +pub mod macros_gauge; +pub mod macros_histogram; diff --git a/crates/metrics/src/future.rs b/crates/metrics/src/future.rs index 4053f3d..f9b5e71 100644 --- a/crates/metrics/src/future.rs +++ b/crates/metrics/src/future.rs @@ -1,129 +1,207 @@ -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, - time::{Duration, Instant}, +//! Instrumentation machinery for collecting [`Future`] metrics. +//! +//! Usage: +//! +//! ``` +//! use wc_metrics::{ +//! self as metrics, +//! label_name, +//! BoolLabel, +//! FutureExt, +//! FutureMetrics, +//! LabeledFutureMetrics2, +//! Lazy, +//! }; +//! +//! type MyBoolLabelA = BoolLabel<{ label_name("my_bool_label_a") }>; +//! type MyBoolLabelB = BoolLabel<{ label_name("my_bool_label_b") }>; +//! +//! static FUTURE_METRICS_A: Lazy = metrics::new("my_future_a"); +//! static FUTURE_METRICS_B: Lazy> = +//! metrics::builder("my_future_b") +//! .with_static_labels(&[("labelA", "valueA"), ("labelA", "valueA")]) +//! .build(); +//! +//! let fut_a = async {}.with_metrics(&FUTURE_METRICS_A); +//! let fut_b = async {}.with_metrics( +//! FUTURE_METRICS_B.resolve_labels((MyBoolLabelA::new(false), MyBoolLabelB::new(true))), +//! ); +//! ``` + +use { + crate::{ + sealed::{Attrs, Metric}, + Lazy, + }, + futures::future::FusedFuture, + metrics::{counter, gauge, histogram, Counter, Gauge, Histogram, Label}, + std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + time::{Duration, Instant}, + }, }; -/// Trait for tracking task execution related metrics with -/// [`TaskMetricsFuture`]. -/// -/// Most of the time [`OtelTaskMetricsRecorder`] should be used instead of -/// manual implementations of this trait, unless we want to support multiple -/// metrics tracking APIs. -pub trait TaskMetricsRecorder: Send + Sync + 'static { - fn record_task_started(&self) {} - - fn record_task_finished( - &self, - _total_duration: Duration, - _poll_duration: Duration, - _poll_entries: u64, - _completed: bool, - ) { - } -} +/// Metric names used by this module. +pub mod name { + pub const FUTURE_DURATION: &str = "future_duration"; + pub const FUTURE_CANCELLED_DURATION: &str = "future_cancelled_duration"; -/// Trait that implements task name tagging using a static string. -pub trait AsTaskName: Send + Sync + 'static { - fn as_task_name(&self) -> &'static str; -} + pub const FUTURES_CREATED: &str = "futures_created_count"; + pub const FUTURES_STARTED: &str = "futures_started_count"; + pub const FUTURES_FINISHED: &str = "futures_finished_count"; + pub const FUTURES_CANCELLED: &str = "futures_cancelled_count"; -impl AsTaskName for () { - fn as_task_name(&self) -> &'static str { - "" - } + pub const FUTURE_POLL_DURATION: &str = "future_poll_duration"; + pub const FUTURE_POLL_DURATION_MAX: &str = "future_poll_duration_max"; + pub const FUTURE_POLLS: &str = "future_polls_count"; } -impl AsTaskName for &'static str { - fn as_task_name(&self) -> &'static str { - self - } -} +/// Metrics collected during a [`Future`] execution. +pub struct Metrics { + duration: Histogram, + cancelled_duration: Histogram, + + created: Counter, + started: Counter, + finished: Counter, + cancelled: Counter, -struct Stats { - started: Instant, - completed: bool, - poll_duration: Duration, - poll_entries: u64, - recorder: R, + poll_duration: Histogram, + poll_duration_max: Gauge, + polls: Counter, } -impl Stats -where - R: TaskMetricsRecorder, -{ - fn new(recorder: R) -> Self { - recorder.record_task_started(); +impl Metric for Metrics { + fn register(attrs: &Attrs) -> Self { + let mut labels = attrs.labels(); + let name = Label::from_static_parts("future_name", attrs.name()); + labels.push(name); Self { - started: Instant::now(), - completed: false, - poll_duration: Duration::from_secs(0), - poll_entries: 0, - recorder, + duration: histogram!(name::FUTURE_DURATION, labels.iter()), + cancelled_duration: histogram!(name::FUTURE_CANCELLED_DURATION, labels.iter()), + created: counter!(name::FUTURES_CREATED, labels.iter()), + started: counter!(name::FUTURES_STARTED, labels.iter()), + finished: counter!(name::FUTURES_FINISHED, labels.iter()), + cancelled: counter!(name::FUTURES_CANCELLED, labels.iter()), + poll_duration: histogram!(name::FUTURE_POLL_DURATION, labels.iter()), + poll_duration_max: gauge!(name::FUTURE_POLL_DURATION_MAX, labels.iter()), + polls: counter!(name::FUTURE_POLLS, labels.iter()), } } } -impl Drop for Stats -where - R: TaskMetricsRecorder, -{ - fn drop(&mut self) { - self.recorder.record_task_finished( - self.started.elapsed(), - self.poll_duration, - self.poll_entries, - self.completed, - ); +/// Convienience extension `trait` for creating [`Metered`] [`Future`]s. +pub trait FutureExt: Sized { + /// Consumes the future, returning a new future that records the executiion + /// metrics of the inner future. + fn with_metrics(self, metrics: impl Into<&'static Metrics>) -> Metered { + Metered::new(self, metrics) } } +impl FutureExt for F where F: Future {} + +/// [`Future`] wrapper collecting [`Metrics`] of inner [`Future`] `F`. #[pin_project::pin_project] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct TaskMetricsFuture -where - F: Future, - R: TaskMetricsRecorder, -{ +pub struct Metered { #[pin] - inner: F, - stats: Stats, + future: F, + state: State, } -impl TaskMetricsFuture -where - F: Future, - R: TaskMetricsRecorder, -{ - pub fn new(inner: F, recorder: R) -> Self { +struct State { + started_at: Option, + is_finished: bool, + + poll_duration_sum: Duration, + poll_duration_max: Duration, + polls_count: usize, + + metrics: &'static Metrics, +} + +impl Metered { + fn new(future: F, metrics: impl Into<&'static Metrics>) -> Self { + let metrics = metrics.into(); + + metrics.created.increment(1); + Self { - inner, - stats: Stats::new(recorder), + future, + state: State { + started_at: None, + is_finished: false, + poll_duration_sum: Duration::from_secs(0), + poll_duration_max: Duration::from_secs(0), + polls_count: 0, + metrics, + }, } } } -impl Future for TaskMetricsFuture -where - F: Future, - R: TaskMetricsRecorder, -{ +impl From<&'static Lazy> for &'static Metrics { + fn from(lazy: &'static Lazy) -> Self { + lazy.get_or_register() + } +} + +impl Future for Metered { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let poll_start = Instant::now(); - let this = self.project(); - let result = this.inner.poll(cx); + let mut this = self.project(); + let state = &mut this.state; - if result.is_ready() { - this.stats.completed = true; + if state.started_at.is_none() { + state.started_at = Some(Instant::now()); + state.metrics.started.increment(1); } - this.stats.poll_entries += 1; - this.stats.poll_duration += poll_start.elapsed(); + let poll_started_at = Instant::now(); + let result = this.future.poll(cx); + let poll_duration = poll_started_at.elapsed(); + + state.poll_duration_sum += poll_duration; + state.poll_duration_max = state.poll_duration_max.max(poll_duration); + state.polls_count += 1; + + if result.is_ready() { + state.is_finished = true; + + state.metrics.finished.increment(1); + + if let Some(started_at) = state.started_at { + state.metrics.duration.record(started_at.elapsed()) + } + } result } } + +impl Drop for State { + fn drop(&mut self) { + if !self.is_finished { + self.metrics.cancelled.increment(1); + + if let Some(started_at) = self.started_at { + self.metrics.cancelled_duration.record(started_at.elapsed()) + } + } + + self.metrics.poll_duration.record(self.poll_duration_sum); + self.metrics.poll_duration_max.set(self.poll_duration_max); + self.metrics.polls.increment(self.polls_count as u64); + } +} + +impl FusedFuture for Metered { + fn is_terminated(&self) -> bool { + self.state.is_finished + } +} diff --git a/crates/metrics/src/label.rs b/crates/metrics/src/label.rs new file mode 100644 index 0000000..a5c4d15 --- /dev/null +++ b/crates/metrics/src/label.rs @@ -0,0 +1,545 @@ +use { + crate::{ + sealed::{Attrs, Execute}, + Metric, + }, + arc_swap::ArcSwap, + enum_ordinalize::Ordinalize, + metrics::Label, + parking_lot::Mutex, + smallvec::SmallVec, + std::{borrow::Borrow, collections::HashMap, sync::Arc}, +}; + +pub type DynamicLabels = SmallVec<[Label; 4]>; +pub type StaticLabels = &'static [(&'static str, &'static str)]; + +pub type Labeled = WithLabel; +pub type Labeled2 = WithLabel>; +pub type Labeled3 = WithLabel>>; +pub type Labeled4 = WithLabel>>>; + +pub trait DynamicLabel { + type MetricCollection; +} + +pub trait ResolveLabels { + type Target; + + fn resolve_labels(&self, labels: L) -> &Self::Target; +} + +/// Adds dynamically resolved (in runtime) label to `M`. +pub struct WithLabel, M> { + collection: L::MetricCollection, +} + +impl WithLabel +where + L: DynamicLabel, +{ + /// Resolves a single dynamic label and finds the underlying metric. + pub fn resolve_label(&self, label: T) -> &M + where + Self: ResolveLabels<(T,), Target = M>, + { + self.resolve_labels((label,)) + } + + /// Resolves multilpe dynamic labels and finds the underlying metric. + /// + /// Expects a tuple of labels as `LS` argument. + pub fn resolve_labels(&self, labels: LS) -> &>::Target + where + Self: ResolveLabels, + { + ResolveLabels::resolve_labels(self, labels) + } +} + +/// Metric label using an `enum` as its value. +/// +/// The most efficient way to specify metric labels in runtime, metric lookups +/// using enum labels are as fast as array indexing. Prefer using this when all +/// of your possible label values are known at the compile time. +#[derive(Clone, Copy, Debug)] +pub struct EnumLabel(T); + +impl EnumLabel { + /// Creates a new [`EnumLabel`]. + pub fn new(e: impl Into) -> Self { + Self(e.into()) + } + + /// Convert this [`EnumLabel`] into the inner [`Enum`]. + pub fn into_inner(self) -> T { + self.0 + } +} + +/// `enum` used as the value in [`EnumLabel`]s. +/// +/// To implement this `trait` you also need to derive [`Ordinalize`] for your +/// `enum`. +/// SAFETY: DO NOT use custom discriminant values (eg. `enum MyEnum { MyVariant +/// = -1 }`), this will lead to either: +/// - `panic` in runtime (only for builds with `debug_assertions`) +/// - incorrect label resolution +pub trait Enum: Copy + Ordinalize { + /// String representation of this enum. + fn as_str(&self) -> &'static str; +} + +impl DynamicLabel for EnumLabel +where + T: Enum, +{ + // TODO: Switch to `[(T, M); T::VARIANT_COUNT]` once generic parameters are + // allowed to be used in const expressions. + type MetricCollection = Vec<(T, M)>; +} + +impl Metric for WithLabel, M> +where + T: Enum, + M: Metric, +{ + fn register(attrs: &Attrs) -> Self { + let name = const { resolve_label_name::() }; + + let metrics = T::VARIANTS.iter().map(|variant| { + let label = Label::from_static_parts(name, variant.as_str()); + (*variant, M::register(&attrs.with_label(label))) + }); + + Self { + collection: metrics.collect(), + } + } +} + +impl ResolveLabels<(EnumLabel,)> + for WithLabel, M> +where + T: Enum, + M: Metric, +{ + type Target = M; + + fn resolve_labels(&self, (label,): (EnumLabel,)) -> &M { + let debug_panic = || { + if cfg!(debug_assertions) { + panic!("Invalid enum usage, custom discriminants must not be used") + } + }; + + let idx = label.0.ordinal(); + let mut idx = if idx < 0 { + debug_panic(); + 0 + } else { + idx as usize + }; + + if idx >= self.collection.len() { + debug_panic(); + idx = 0; + }; + + &self.collection[idx].1 + } +} + +/// Label with the only possible values being `true` and `false`. A special +/// case of `EnumLabel` having the same peformance characteristics. +/// +/// Due to the lack of `&'static str` const generics at the moment the label +/// name should be specified using the following hack: +/// +/// ``` +/// use wc_metrics::{label_name, BoolLabel}; +/// +/// type MyLabel = BoolLabel<{ label_name("my_label") }>; +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct BoolLabel(bool); + +impl BoolLabel { + /// Creates a new [`BoolLabel`]. + pub fn new(b: bool) -> Self { + Self(b) + } +} + +impl From> for bool { + fn from(label: BoolLabel) -> Self { + label.0 + } +} + +impl DynamicLabel for BoolLabel { + type MetricCollection = (M, M); +} + +impl Metric for WithLabel, M> +where + M: Metric, +{ + fn register(attrs: &Attrs) -> Self { + let name = const { resolve_label_name::() }; + + let f = Label::from_static_parts(name, "false"); + let t = Label::from_static_parts(name, "true"); + + Self { + collection: ( + M::register(&attrs.with_label(f)), + M::register(&attrs.with_label(t)), + ), + } + } +} + +impl ResolveLabels<(BoolLabel,)> for WithLabel, M> +where + M: Metric, +{ + type Target = M; + + fn resolve_labels(&self, (label,): (BoolLabel,)) -> &M { + if label.0 { + &self.collection.1 + } else { + &self.collection.0 + } + } +} + +/// Label with values which are unknown at the complite time. +/// +/// Metric lookups using these labels will be slower compared to [`EnumLabel`]s +/// and [`BoolLabel`]s. Prefer not to use these unless you really don't know +/// your label values beforehand. +/// +/// Despite its name the label can accept any type that implements [`ToString`], +/// not just [`String`]s. The most frequent use-case (aside from the actual +/// strings) - numbers. +/// +/// Due to the lack of `&'static str` const generics at the moment the label +/// name should be specified using the following hack: +/// +/// ``` +/// use wc_metrics::{label_name, StringLabel}; +/// +/// type MyLabel = StringLabel<{ label_name("my_label") }>; +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct StringLabel(pub T); + +impl StringLabel { + /// Creates a new [`StringLabel`]. + /// + /// Expects a [`Borrow`]ed version of your type as the owned version is not + /// needed for the label resolution. + pub fn new(ref_: &U) -> StringLabel + where + T: Borrow, + { + StringLabel(ref_) + } + + /// Converts this [`StringLabel`] into the inner `T`. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl AsRef for StringLabel { + fn as_ref(&self) -> &T { + &self.0 + } +} + +pub struct StringCollection { + inner: ArcSwap>, + mutex: Mutex<()>, + attrs: Attrs, +} + +impl DynamicLabel for StringLabel +where + M: 'static, +{ + type MetricCollection = StringCollection; +} + +impl Metric for WithLabel, M> +where + M: Metric + 'static, +{ + fn register(attrs: &Attrs) -> Self { + Self { + collection: StringCollection { + inner: ArcSwap::new(Arc::new(HashMap::new())), + mutex: Mutex::new(()), + attrs: attrs.clone(), + }, + } + } +} + +impl ResolveLabels<(StringLabel,)> + for WithLabel, M> +where + T: std::hash::Hash + Eq + Borrow + ToString + Clone, + U: std::hash::Hash + Eq + ToOwned + ?Sized, + M: Metric + 'static, +{ + type Target = M; + + fn resolve_labels(&self, (label,): (StringLabel,)) -> &M { + let label = label.0; + let col = &self.collection; + + if let Some(metric) = col.inner.load().get(label) { + return metric; + }; + + let _guard = col.mutex.lock(); + + let inner = col.inner.load(); + + // In case if another thread has already initialized the metric while we were + // waiting on the lock + if let Some(m) = inner.get(label) { + return m; + }; + + // Copy-on-write + let m: &'static M = { + // Make a deep copy of the `HashMap`. + let mut inner_clone: HashMap<_, _> = (**inner).clone(); + + let name = const { resolve_label_name::() }; + let label_ = Label::new(name, label.to_owned().to_string()); + + // Insert the new `Metric`. + // + // We are still holding the lock here and by doing so guaranteeing that + // the static value is only being initialized once per key. + // + // Leaking is fine here as this collection can only be used inside + // `static` variables and there should be limited amount of label + // values defined in runtime. + let m = Box::leak(Box::new(M::register(&col.attrs.with_label(label_)))); + inner_clone.insert(label.to_owned(), m); + + // Write the updated `HashMap` into `ArcSwap`. + col.inner.store(Arc::new(inner_clone)); + m + }; + + m + } +} + +/// Makes any other label optional by accepting [`Option`] instead of the actual +/// label value during the label resolution. +pub struct Optional(pub Option); + +impl Optional> { + /// Creates a new [`Optional`] [`EnumLabel`]. + pub fn new(v: Option>) -> Self { + Self(v.map(EnumLabel::new)) + } +} + +impl Optional> { + /// Creates a new [`Optional`] [`BoolLabel`]. + pub fn new(v: Option) -> Self { + Self(v.map(BoolLabel::new)) + } +} + +impl Optional> { + /// Creates a new [`Optional`] [`StringLabel`]. + pub fn new(v: Option<&U>) -> Optional> + where + T: Borrow, + { + Optional(v.map(StringLabel::::new)) + } +} + +impl DynamicLabel for Optional +where + T: DynamicLabel, +{ + type MetricCollection = (M, WithLabel); +} + +impl Metric for WithLabel, M> +where + T: DynamicLabel, + M: Metric + 'static, + WithLabel: Metric, +{ + fn register(attrs: &Attrs) -> Self { + Self { + collection: ( + M::register(attrs), + as Metric>::register(attrs), + ), + } + } +} + +impl ResolveLabels<(Option,)> for WithLabel, M> +where + T: DynamicLabel, + M: Metric, + WithLabel: ResolveLabels<(U,), Target = M>, +{ + type Target = M; + + fn resolve_labels(&self, (label,): (Option,)) -> &M { + match label { + Some(l) => self.collection.1.resolve_labels((l,)), + None => &self.collection.0, + } + } +} + +impl ResolveLabels<(Optional,)> for WithLabel, M> +where + T: DynamicLabel, + M: Metric, + WithLabel: ResolveLabels<(U,), Target = M>, +{ + type Target = M; + + fn resolve_labels(&self, (label,): (Optional,)) -> &M { + self.resolve_labels((label.0,)) + } +} + +// TODO: macro to autogenerate these + +impl ResolveLabels<(A, B)> for WithLabel +where + L: DynamicLabel, + M: ResolveLabels<(B,)>, + Self: ResolveLabels<(A,), Target = M>, +{ + type Target = M::Target; + + fn resolve_labels(&self, (a, b): (A, B)) -> &Self::Target { + self.resolve_label(a).resolve_labels((b,)) + } +} + +impl ResolveLabels<(A, B, C)> for WithLabel +where + L: DynamicLabel, + M: ResolveLabels<(B, C)>, + Self: ResolveLabels<(A,), Target = M>, +{ + type Target = M::Target; + + fn resolve_labels(&self, (a, b, c): (A, B, C)) -> &Self::Target { + self.resolve_label(a).resolve_labels((b, c)) + } +} + +impl ResolveLabels<(A, B, C, D)> for WithLabel +where + L: DynamicLabel, + M: ResolveLabels<(B, C, D)>, + Self: ResolveLabels<(A,), Target = M>, +{ + type Target = M::Target; + + fn resolve_labels(&self, (a, b, c, d): (A, B, C, D)) -> &Self::Target { + self.resolve_label(a).resolve_labels((b, c, d)) + } +} + +impl Execute for WithLabel +where + L: DynamicLabel, + Self: ResolveLabels>, +{ + fn execute(&self, op: Op, labels: LS) { + self.resolve_labels(labels).execute(op, ()) + } +} + +/// `u128` representation of `&'static str` label name. +pub type LabelName = u128; + +/// Converts a `&'static str` into a byte-wise equivalent `u128`. +/// +/// Required to hack around the lack of const `&'static str` generics in stable +/// Rust. +pub const fn label_name(s: &'static str) -> LabelName { + let bytes = s.as_bytes(); + + assert!( + bytes.len() <= 16, + "`LabelName` should be no longer than 16 bytes" + ); + + // loops aren't supported in const fns + const fn copy(idx: usize, src: &[u8], mut dst: [u8; 16]) -> [u8; 16] { + if idx == src.len() { + return dst; + } + + dst[idx] = src[idx]; + copy(idx + 1, src, dst) + } + + u128::from_be_bytes(copy(0, bytes, [0u8; 16])) +} + +const fn resolve_label_name() -> &'static str { + let bytes = Const::::BYTES; + + // Find the index of the first null byte + const fn null_byte_idx(b: &[u8], idx: usize) -> usize { + if idx == b.len() { + return idx; + } + + if b[idx] == 0 { + return idx; + } + + null_byte_idx(b, idx + 1) + } + + // truncate null bytes + let (bytes, _) = bytes.split_at(null_byte_idx(bytes, 0)); + + match std::str::from_utf8(bytes) { + Ok(s) => s, + Err(_) => panic!("Invalid utf8"), + } +} + +trait ConstByteSlice { + const BYTES: &'static [u8]; +} + +struct Const; + +impl ConstByteSlice for Const { + const BYTES: &'static [u8] = &U.to_be_bytes(); +} + +#[test] +fn test_label_name() { + const A: LabelName = label_name("test"); + let name = const { resolve_label_name::() }; + assert_eq!(name, "test"); +} diff --git a/crates/metrics/src/lazy.rs b/crates/metrics/src/lazy.rs new file mode 100644 index 0000000..04b264a --- /dev/null +++ b/crates/metrics/src/lazy.rs @@ -0,0 +1,134 @@ +use { + crate::{ + label::{DynamicLabel, ResolveLabels, WithLabel}, + sealed::{Decrement, Execute, Increment, Record, Set}, + Attrs, + Metric, + StaticAttrs, + }, + metrics::{Counter, Gauge, Histogram, IntoF64}, + std::sync::OnceLock, +}; + +/// Lazily initialized metric. +/// +/// Can only be used if assigned to a `static` variable. +/// +/// Use [`Builder`](crate::Builder) to specify metric attributes known at the +/// complile time and to build [`Lazy`] metrics. +pub struct Lazy { + metric: OnceLock, + attrs: StaticAttrs, +} + +impl Lazy { + pub(super) const fn new(attrs: StaticAttrs) -> Self { + Self { + metric: OnceLock::new(), + attrs, + } + } + + pub(crate) fn get_or_register(&self) -> &M { + if let Some(m) = self.metric.get() { + return m; + }; + + let attrs = Attrs { + static_: self.attrs, + dynamic: Default::default(), + }; + + self.metric.get_or_init(|| M::register(&attrs)) + } +} + +impl Lazy { + /// See [`Counter::increment`]. + pub fn increment(&'static self, value: u64) { + self.get_or_register().increment(value) + } +} + +impl Lazy { + /// See [`Gauge::increment`]. + pub fn increment(&'static self, value: T) { + self.get_or_register().increment(value) + } + + /// See [`Gauge::decrement`]. + pub fn decrement(&'static self, value: T) { + self.get_or_register().decrement(value) + } + + /// See [`Gauge::set`]. + pub fn set(&'static self, value: T) { + self.get_or_register().set(value) + } +} + +impl Lazy { + /// See [`Histogram::record`]. + pub fn record(&'static self, value: T) { + self.get_or_register().record(value) + } +} + +impl Lazy> +where + L: DynamicLabel, +{ + /// See [`WithLabel::resolve_label`]. + pub fn resolve_label(&'static self, label: T) -> &M + where + WithLabel: Metric + ResolveLabels<(T,), Target = M>, + { + self.get_or_register().resolve_label(label) + } + + /// See [`WithLabel::resolve_labels`]. + pub fn resolve_labels( + &'static self, + labels: LS, + ) -> & as ResolveLabels>::Target + where + WithLabel: Metric + ResolveLabels, + { + self.get_or_register().resolve_labels(labels) + } + + /// Calls [`Counter::increment`] or [`Gauge::increment`] on the metric built + /// using the provided labels. + pub fn increment(&'static self, value: T, labels: Labels) + where + WithLabel: Metric + Execute, Labels>, + { + self.get_or_register().execute(Increment(value), labels); + } + + /// Calls [`Gauge::decrement`] on the metric built using the provided + /// labels. + pub fn decrement(&'static self, value: T, labels: Labels) + where + WithLabel: Metric + Execute, Labels>, + { + self.get_or_register().execute(Decrement(value), labels); + } + + /// Calls [`Gauge::set`] on the metric built using the provided labels. + pub fn set(&'static self, value: T, labels: Labels) + where + WithLabel: Metric + Execute, Labels>, + { + self.get_or_register().execute(Set(value), labels); + } + + /// Calls [`Histogram::record`] on the metric built using the provided + /// labels. + pub fn record(&'static self, value: T, labels: Labels) + where + WithLabel: Metric + Execute, Labels>, + { + self.get_or_register().execute(Record(value), labels); + } +} diff --git a/crates/metrics/src/lib.rs b/crates/metrics/src/lib.rs index 154fdc6..cf6fe50 100644 --- a/crates/metrics/src/lib.rs +++ b/crates/metrics/src/lib.rs @@ -1,151 +1,325 @@ +//! Alternative API facade to the [`metrics`] backend. +//! +//! Priorities: performance, ergonomics (in that order). +//! +//! Metrics reporting should be as fast as possible, without substantially +//! hurting the code ergonomics. +//! +//! A trivial atomic counter increment MUST NOT allocate stuff on the heap +//! (looking at you [`metrics::counter`] and it SHOULD NOT acquire locks or do +//! [`HashMap`](std::collections::HashMap) lookups unless absolutely necessary. +//! +//! If your metric is only being used once, or you can cache it somewhere +//! consider using [`counter`], [`gauge`] or [`histogram`] convinience macros. +//! The macros are completely optional and the machinery can be used +//! as is without them. +//! +//! # Usage +//! +//! ``` +//! use wc_metrics::{ +//! self as metrics, +//! enum_ordinalize::Ordinalize, +//! label_name, +//! BoolLabel, +//! Counter, +//! Enum, +//! EnumLabel, +//! Gauge, +//! Histogram, +//! LabeledCounter2, +//! LabeledGauge3, +//! LabeledHistogram, +//! Lazy, +//! Optional, +//! StringLabel, +//! }; +//! +//! #[derive(Clone, Copy, Ordinalize)] +//! enum MyEnum { +//! A, +//! B, +//! } +//! +//! type MyEnumLabel = EnumLabel<{ label_name("my_enum_label") }, MyEnum>; +//! +//! impl Enum for MyEnum { +//! fn as_str(&self) -> &'static str { +//! match self { +//! Self::A => "a", +//! Self::B => "b", +//! } +//! } +//! } +//! +//! type MyBoolLabel = BoolLabel<{ label_name("my_bool_label") }>; +//! type MyStringLabel = StringLabel<{ label_name("my_string_label") }>; +//! type MyU8StringLabel = StringLabel<{ label_name("my_u8_label") }, u8>; +//! +//! static COUNTER_A: Lazy = metrics::new("counter_a"); +//! +//! static GAUGE_A: Lazy = metrics::builder("gauge_a") +//! .with_description("My gauge") +//! .with_static_labels(&[("labelA", "valueA"), ("labelA", "valueA")]) +//! .build(); +//! +//! static HISTOGRAM_A: Lazy> = metrics::new("histogram_a"); +//! +//! static COUNTER_B: Lazy> = +//! metrics::builder("counter_b") +//! .with_description("My labeled counter") +//! .build(); +//! +//! static GAUGE_B: Lazy>> = +//! metrics::new("gauge_b"); +//! +//! COUNTER_A.increment(1); +//! GAUGE_A.set(42); +//! HISTOGRAM_A.record(1000, (MyEnumLabel::new(MyEnum::A),)); +//! COUNTER_B.increment(2u64, (MyStringLabel::new("test"), MyBoolLabel::new(false))); +//! +//! let labels = (MyU8StringLabel::new(&42), MyEnumLabel::new(MyEnum::B), None); +//! GAUGE_B.decrement(2, labels); +//! ``` + pub use { - future::*, - once_cell::sync::Lazy, - opentelemetry as otel, - opentelemetry_sdk as otel_sdk, - task::*, + enum_ordinalize, + label::{label_name, BoolLabel, Enum, EnumLabel, LabelName, Optional, StringLabel, WithLabel}, + lazy::Lazy, + metrics::{self as backend, Counter, Gauge, Histogram}, }; use { - opentelemetry_sdk::metrics::{MeterProviderBuilder, SdkMeterProvider}, - otel::{ - global, - metrics::{Meter, MeterProvider}, - }, - prometheus::{Error as PrometheusError, Registry, TextEncoder}, - std::{ - sync::{Arc, Mutex}, - time::Duration, - }, + label::{DynamicLabels, Labeled, Labeled2, Labeled3, Labeled4, StaticLabels}, + metrics::{IntoF64, Label}, + sealed::{Attrs, Decrement, Execute, Increment, Metric, Record, Set}, }; +mod label; +mod lazy; +mod macros; + +#[cfg(test)] +mod examples; +#[cfg(test)] +mod test; + +#[cfg(feature = "future")] pub mod future; -pub mod macros; -pub mod task; +#[cfg(feature = "future")] +pub use future::{FutureExt, Metrics as FutureMetrics}; -const DEFAULT_SERVICE_NAME: &str = "unknown_service"; +/// Builder of [`Lazy`] metrics. +/// +/// Intended to be used exclusively in const contexts to specify metric +/// attributes known at the compile time and to assign [`Lazy`] metrics to +/// `static` variables. +pub struct Builder { + attrs: StaticAttrs, +} + +/// Creates a new [`Builder`] with the specified metric `name`. +/// +/// For `future` metrics `name` is going to be used as `future_name` label +/// value instead. +pub const fn builder(name: &'static str) -> Builder { + Builder { + attrs: StaticAttrs { + name, + description: None, + labels: &[], + }, + } +} + +/// Creates a new [`Lazy`] metric with the specified `name`. +/// +/// For `future` metrics `name` is going to be used as `future_name` label +/// value instead. +pub const fn new(name: &'static str) -> Lazy { + builder(name).build() +} -static SERVICE_NAME: Mutex> = Mutex::new(None); +impl Builder { + /// Specifies description of the metric. + /// + /// No-op for `future` metrics. + pub const fn with_description(mut self, description: &'static str) -> Self { + self.attrs.description = Some(description); + self + } -pub type MeterProviderBuilderFn = fn(MeterProviderBuilder) -> MeterProviderBuilder; -static METER_PROVIDER_BUILDER_FN: Mutex> = Mutex::new(None); + /// Specifies statically known metric labels. + pub const fn with_static_labels( + mut self, + labels: &'static [(&'static str, &'static str)], + ) -> Self { + self.attrs.labels = labels; + self + } -static METRICS_CORE: Lazy> = Lazy::new(|| { - let service_name = SERVICE_NAME.lock().unwrap().unwrap_or(DEFAULT_SERVICE_NAME); - let meter_provider_builder = *METER_PROVIDER_BUILDER_FN.lock().unwrap(); + /// Builds the [`Lazy`] metric. + pub const fn build(self) -> Lazy { + Lazy::new(self.attrs) + } +} - let registry = Registry::new(); - let prometheus_exporter = opentelemetry_prometheus::exporter() - .with_registry(registry.clone()) - .build() - .unwrap(); - let mut builder = SdkMeterProvider::builder().with_reader(prometheus_exporter); - if let Some(buidler_fn) = meter_provider_builder { - builder = buidler_fn(builder); - }; - let provider = builder.build(); - let meter = provider.meter(service_name); +impl Attrs { + fn name(&self) -> &'static str { + self.static_.name + } - global::set_meter_provider(provider); + fn description(&self) -> Option<&'static str> { + self.static_.description + } - Arc::new(ServiceMetrics { registry, meter }) -}); + fn labels(&self) -> DynamicLabels { + let mut labels = self.dynamic.labels.clone(); + let static_ = self.static_.labels.iter(); + labels.extend(static_.map(|(k, v)| Label::from_static_parts(k, v))); + labels + } -/// Global application metrics access. -/// -/// The main functionality is to provide global access to opentelemetry's -/// [`Meter`]. -pub struct ServiceMetrics { - registry: Registry, - meter: Meter, + fn with_label(&self, label: Label) -> Self { + let mut this = self.clone(); + this.dynamic.labels.push(label); + this + } } -impl ServiceMetrics { - /// Initializes service metrics with the default name. - /// - /// # Panics - /// - /// Panics if either prometheus exporter or opentelemetry meter fails to - /// initialize. - pub fn init() { - Lazy::force(&METRICS_CORE); +#[derive(Clone, Copy, Debug)] +struct StaticAttrs { + name: &'static str, + description: Option<&'static str>, + labels: StaticLabels, +} + +#[derive(Clone, Debug, Default)] +struct DynamicAttrs { + labels: DynamicLabels, +} + +mod sealed { + use crate::{DynamicAttrs, StaticAttrs}; + + #[derive(Clone, Debug)] + pub struct Attrs { + pub(super) static_: StaticAttrs, + pub(super) dynamic: DynamicAttrs, } - /// Initializes service metrics with the specified name. - /// - /// # Panics - /// - /// Panics if either prometheus exporter or opentelemetry meter fails to - /// initialize. - pub fn init_with_name(name: &'static str) { - *SERVICE_NAME.lock().unwrap() = Some(name); - Lazy::force(&METRICS_CORE); + pub trait Metric { + fn register(attrs: &Attrs) -> Self; } - /// Initializes service metrics with the specified name and meter provider - /// builder. - /// - /// # Panics - /// - /// Panics if either prometheus exporter or opentelemetry meter fails to - /// initialize. - pub fn init_with_meter_builder( - name: &'static str, - meter_provider_builder: MeterProviderBuilderFn, - ) { - *SERVICE_NAME.lock().unwrap() = Some(name); - *METER_PROVIDER_BUILDER_FN.lock().unwrap() = Some(meter_provider_builder); - Lazy::force(&METRICS_CORE); + pub trait Execute { + fn execute(&self, op: Op, labels: L); } - /// Generates export data in Prometheus format, serialized into string. - pub fn export() -> Result { - let data = Self::get().registry.gather(); - TextEncoder::new().encode_to_string(&data) + pub struct Increment(pub T); + pub struct Decrement(pub T); + pub struct Set(pub T); + pub struct Record(pub T); +} + +pub type LabeledCounter = Labeled; +pub type LabeledCounter2 = Labeled2; +pub type LabeledCounter3 = Labeled3; +pub type LabeledCounter4 = Labeled4; + +impl Metric for Counter { + fn register(attrs: &Attrs) -> Self { + let counter = backend::counter!(attrs.name(), attrs.labels().iter()); + if let Some(desc) = attrs.description() { + backend::describe_counter!(attrs.name(), desc); + } + counter } +} - /// Returns a static reference to [`Meter`] which can be used to set up - /// global static counters. See [`crate::counter`] macro for an example. - #[inline] - pub fn meter() -> &'static Meter { - &Self::get().meter +impl Execute, ()> for Counter +where + T: Into, +{ + fn execute(&self, op: Increment, _labels: ()) { + self.increment(op.0.into()) } +} + +pub type LabeledGauge = Labeled; +pub type LabeledGauge2 = Labeled2; +pub type LabeledGauge3 = Labeled3; +pub type LabeledGauge4 = Labeled4; - /// Global access to the application metrics singleton. - #[inline] - fn get() -> &'static Self { - METRICS_CORE.as_ref() +impl Metric for Gauge { + fn register(attrs: &Attrs) -> Self { + let gauge = backend::gauge!(attrs.name(), attrs.labels().iter()); + if let Some(desc) = attrs.description() { + backend::describe_gauge!(attrs.name(), desc); + } + gauge } } -#[inline] -pub fn duration_as_millis_f64(val: Duration) -> f64 { - val.as_secs_f64() * 1000.0 +impl Execute, ()> for Gauge +where + T: IntoF64, +{ + fn execute(&self, op: Increment, _labels: ()) { + self.increment(op.0) + } } -#[inline] -pub fn value_bucket( - size: usize, - buckets: &'static [usize; NUM_BUCKETS], -) -> usize { - *buckets - .iter() - .find(|&bucket| size <= *bucket) - .or_else(|| buckets.last()) - .unwrap_or(&0) +impl Execute, ()> for Gauge +where + T: IntoF64, +{ + fn execute(&self, op: Decrement, _labels: ()) { + self.decrement(op.0) + } } -#[cfg(test)] -mod tests { - use super::*; +impl Execute, ()> for Gauge +where + T: IntoF64, +{ + fn execute(&self, op: Set, _labels: ()) { + self.set(op.0) + } +} + +pub type LabeledHistogram = Labeled; +pub type LabeledHistogram2 = Labeled2; +pub type LabeledHistogram3 = Labeled3; +pub type LabeledHistogram4 = Labeled4; - #[test] - fn test_value_buckets() { - const BUCKETS: [usize; 8] = [128, 256, 512, 2048, 4096, 65535, 131070, 262140]; +impl Metric for Histogram { + fn register(attrs: &Attrs) -> Self { + let histogram = backend::histogram!(attrs.name(), attrs.labels().iter()); + if let Some(desc) = attrs.description() { + backend::describe_histogram!(attrs.name(), desc); + } + histogram + } +} - assert_eq!(value_bucket(0, &BUCKETS), 128); - assert_eq!(value_bucket(65536, &BUCKETS), 131070); - assert_eq!(value_bucket(131070, &BUCKETS), 131070); - assert_eq!(value_bucket(131071, &BUCKETS), 262140); - assert_eq!(value_bucket(usize::MAX, &BUCKETS), 262140); +impl Execute, ()> for Histogram +where + T: IntoF64, +{ + fn execute(&self, op: Record, _labels: ()) { + self.record(op.0) } } + +#[cfg(feature = "future")] +pub type LabeledFutureMetrics = Labeled; +#[cfg(feature = "future")] +pub type LabeledFutureMetrics2 = Labeled2; +#[cfg(feature = "future")] +pub type LabeledFutureMetrics3 = Labeled3; +#[cfg(feature = "future")] +pub type LabeledFutureMetrics4 = Labeled4; + +pub type OptionalEnumLabel = Optional>; +pub type OptionalBoolLabel = Optional>; +pub type OptionalStringLabel = Optional>; diff --git a/crates/metrics/src/macros.rs b/crates/metrics/src/macros.rs index d9a67d2..09473e8 100644 --- a/crates/metrics/src/macros.rs +++ b/crates/metrics/src/macros.rs @@ -1,64 +1,174 @@ -/// Define a local static -/// [`ObservableGauge`](opentelemetry::metrics::ObservableGauge) and return a -/// reference to it, or immediately observe a value. +/// Similar to [`metrics::counter`](crate::backend::counter), but expects +/// dynamic labels to be type-annotated and provides an option to specify +/// metric descriptions. +/// +/// Uses the machinery of this crate to create appropriately-typed `static` +/// metric and to resolve dynamic labels. +/// +/// Using this macro with the same arguments multilpe times is not recommended +/// as each time it creates a separate `static` variable. +/// If your metric needs to be modified from multiple places either store it +/// inside your own types, or use the vanilla machinery of this crate to define +/// your own `static` metric and use it instead. +/// +/// Usage: +/// ``` +#[doc = include_str!("examples/macros_counter.rs")] +/// ``` #[macro_export] -macro_rules! gauge { - ($name:expr) => {{ - static METRIC: $crate::Lazy<$crate::otel::metrics::ObservableGauge> = - $crate::Lazy::new(|| { - $crate::ServiceMetrics::meter() - .u64_observable_gauge($name) - .init() - }); +macro_rules! counter { + ($($tail:tt)*) => { + $crate::metric!($crate::backend::Counter, $($tail)*) + }; +} - &METRIC - }}; +/// Similar to [`metrics::gauge`](crate::backend::gauge), but expects +/// dynamic labels to be type-annotated and provides an option to specify +/// metric descriptions. +/// +/// Uses the machinery of this crate to create appropriately-typed `static` +/// metric and to resolve dynamic labels. +/// +/// Using this macro with the same arguments multilpe times is not recommended +/// as each time it creates a separate `static` variable. +/// If your metric needs to be modified from multiple places either store it +/// inside your own types, or use the vanilla machinery of this crate to define +/// your own `static` metric and use it instead. +/// +/// Usage: +/// ``` +#[doc = include_str!("examples/macros_gauge.rs")] +/// ``` +#[macro_export] +macro_rules! gauge { + ($($tail:tt)*) => { + $crate::metric!($crate::backend::Gauge, $($tail)*) + }; +} - ($name:expr, $value:expr) => {{ - $crate::gauge!($name, $value, &[]); - }}; +/// Similar to [`metrics::histogram`](crate::backend::histogram), but expects +/// dynamic labels to be type-annotated and provides an option to specify +/// metric descriptions. +/// +/// Uses the machinery of this crate to create appropriately-typed `static` +/// metric and to resolve dynamic labels. +/// +/// Using this macro with the same arguments multilpe times is not recommended +/// as each time it creates a separate `static` variable. +/// If your metric needs to be modified from multiple places either store it +/// inside your own types, or use the vanilla machinery of this crate to define +/// your own `static` metric and use it instead. +/// +/// Usage: +/// ``` +#[doc = include_str!("examples/macros_histogram.rs")] +/// ``` +#[macro_export] +macro_rules! histogram { + ($($tail:tt)*) => { + $crate::metric!($crate::backend::Histogram, $($tail)*) + }; +} - ($name:expr, $value:expr, $tags:expr) => {{ - $crate::gauge!($name).observe($value as u64, $tags); - }}; +/// Similar to [`counter`], [`gauge`] and [`histogram`], but operates with +/// [`FutureMetrics`](crate::FutureMetrics) instead. +/// +/// Usage: +/// ``` +#[doc = include_str!("examples/macros_future_metrics.rs")] +/// ``` +#[cfg(feature = "future")] +#[macro_export] +macro_rules! future_metrics { + ($($tail:tt)*) => { + $crate::metric!($crate::FutureMetrics, $($tail)*) + }; } -/// Define a local static [`Histogram`](opentelemetry::metrics::Histogram) and -/// return a reference to it, or immediately record a value. +#[doc(hidden)] #[macro_export] -macro_rules! histogram { - ($name:expr) => {{ - static METRIC: $crate::Lazy<$crate::otel::metrics::Histogram> = - $crate::Lazy::new(|| $crate::ServiceMetrics::meter().f64_histogram($name).init()); +macro_rules! metric { + ( $type:ty, $name:literal) => { + { + static METRIC: $crate::Lazy<$type> = $crate::new($name); + &METRIC + } + }; + + ( $type:ty, $name:literal, $description:literal) => { + { + static METRIC: $crate::Lazy<$type> = $crate::builder($name) + .with_description($description) + .build(); + &METRIC + } + }; - &METRIC - }}; + ( $type:ty, $name:literal, $description:literal, $($tail:tt)*) => { + { + static METRIC: $crate::Lazy<$crate::metric_type!($type, $($tail)*)> = $crate::builder($name) + .with_description($description) + .with_static_labels($crate::static_labels!($($tail)*)) + .build(); - ($name:expr, $value:expr) => {{ - $crate::histogram!($name, $value, &[]); - }}; + let m = &METRIC; + $crate::resolve_labels!(m, $($tail)*); + m + } + }; + ( $type:ty, $name:literal, $($tail:tt)*) => { + { + static METRIC: $crate::Lazy<$crate::metric_type!($type, $($tail)*)> = $crate::builder($name) + .with_static_labels($crate::static_labels!($($tail)*)) + .build(); - ($name:expr, $value:expr, $tags:expr) => {{ - $crate::histogram!($name).record($value as f64, $tags); - }}; + let m = &METRIC; + $crate::resolve_labels!(m, $($tail)*); + m + } + }; } -/// Define a local static [`Counter`](opentelemetry::metrics::Counter) and -/// return a reference to it, or immediately add a value. +#[doc(hidden)] #[macro_export] -macro_rules! counter { - ($name:expr) => {{ - static METRIC: $crate::Lazy<$crate::otel::metrics::Counter> = - $crate::Lazy::new(|| $crate::ServiceMetrics::meter().u64_counter($name).init()); - - &METRIC - }}; +macro_rules! metric_type { + ( $type:ty, $( $_:literal => $__:literal ),+ )=> { + $type + }; + ( $type:ty, $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr, $( $_:literal => $__:literal ),+ )=> { + $crate::WithLabel<$label_type_name<{ $crate::label_name($label_name) }$(,$inner_ty)?>, $type> + }; + ( $type:ty, $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr, $($tail:tt)*) => { + $crate::WithLabel<$label_type_name<{ $crate::label_name($label_name) }$(,$inner_ty)?>, $crate::metric_type!($type, $($tail)*)> + }; + ( $type:ty, $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr )=> { + $crate::WithLabel<$label_type_name<{ $crate::label_name($label_name) }$(,$inner_ty)?>, $type> + }; +} - ($name:expr, $value:expr) => {{ - $crate::counter!($name, $value, &[]); - }}; +#[doc(hidden)] +#[macro_export] +macro_rules! static_labels { + ( $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr) => { + &[] + }; + ( $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr, $($tail:tt)*) => { + $crate::static_labels!($($tail)*) + }; + ( $( $label_name:literal => $label_value:literal ),+ ) => { + &[$(($label_name, $label_value),)*] + }; +} - ($name:expr, $value:expr, $tags:expr) => {{ - $crate::counter!($name).add($value as u64, $tags); - }}; +#[doc(hidden)] +#[macro_export] +macro_rules! resolve_labels { + ( $var:ident, $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr, $($tail:tt)*) => { + let $var = $var.resolve_label($label_type_name::<{ $crate::label_name($label_name) }$(,$inner_ty)?>::new($label_value)); + $crate::resolve_labels!($var, $($tail)*) + }; + ( $var:ident, $label_type_name:ident<$label_name:literal$(,$inner_ty:ty)?> => $label_value:expr )=> { + let $var = $var.resolve_label($label_type_name::<{ $crate::label_name($label_name) }$(,$inner_ty)?>::new($label_value)); + }; + ( $var:ident, $( $label_name:literal => $label_value:literal ),+ ) => {}; } diff --git a/crates/metrics/src/task.rs b/crates/metrics/src/task.rs deleted file mode 100644 index 525620c..0000000 --- a/crates/metrics/src/task.rs +++ /dev/null @@ -1,149 +0,0 @@ -use { - super::{ - duration_as_millis_f64, - future::{AsTaskName, TaskMetricsRecorder}, - otel, - ServiceMetrics, - }, - once_cell::sync::OnceCell, - opentelemetry::metrics::{Counter, Histogram}, - smallvec::SmallVec, - std::{ops::Deref, sync::Arc, time::Duration}, -}; - -/// Wrapper for [`OtelTaskMetricsRecorder`], which can be statically -/// initialized. -pub struct TaskMetrics { - prefix: &'static str, - inner: OnceCell, -} - -impl TaskMetrics { - pub const fn new(prefix: &'static str) -> Self { - Self { - prefix, - inner: OnceCell::new(), - } - } - - pub fn recorder(&self) -> &OtelTaskMetricsRecorder { - self.inner - .get_or_init(|| OtelTaskMetricsRecorder::new(self.prefix)) - } -} - -impl Deref for TaskMetrics { - type Target = OtelTaskMetricsRecorder; - - fn deref(&self) -> &Self::Target { - self.recorder() - } -} - -/// Async task metrics recorder, which records the following data: -/// - `duration`: Total task duration, in milliseconds; -/// - `poll_duration`: Time spent in task `poll()` method, in milliseconds; -/// - `poll_entries`: Number of task `poll()` method entries; -/// - `started`: Number of tasks that were polled at least once; -/// - `finished`: Number of tasks that finished, either by polling to -/// completion or being dropped. -/// -/// The above metrics are tracked using [`opentelemetry`] metrics API and are -/// prefixed according to the constructor arguments. -#[derive(Clone)] -pub struct OtelTaskMetricsRecorder { - inner: Arc, - name: &'static str, - attributes: SmallVec<[otel::KeyValue; 2]>, -} - -impl OtelTaskMetricsRecorder { - pub fn new(prefix: &str) -> Self { - Self { - inner: Arc::new(OtelRecorderInner::new(prefix)), - name: "unknown", - attributes: SmallVec::new(), - } - } - - /// Clones the current recording context with a new task name. - pub fn with_name(&self, name: N) -> Self - where - N: AsTaskName, - { - Self { - inner: self.inner.clone(), - name: name.as_task_name(), - attributes: self.attributes.clone(), - } - } - - /// Clones the current recording context with a new set of attributes. - pub fn with_attributes( - &self, - attributes: impl IntoIterator, - ) -> OtelTaskMetricsRecorder { - Self { - inner: self.inner.clone(), - name: self.name, - attributes: attributes.into_iter().collect(), - } - } - - fn combine_attributes(&self) -> SmallVec<[otel::KeyValue; 4]> { - let name = [otel::KeyValue::new("task_name", self.name)]; - let extra = self.attributes.iter().cloned(); - name.into_iter().chain(extra).collect() - } -} - -impl TaskMetricsRecorder for OtelTaskMetricsRecorder { - fn record_task_started(&self) { - self.inner.tasks_started.add(1, &self.combine_attributes()); - } - - fn record_task_finished( - &self, - total_duration: Duration, - poll_duration: Duration, - poll_entries: u64, - completed: bool, - ) { - let total_duration_ms = duration_as_millis_f64(total_duration); - let poll_duration_ms = duration_as_millis_f64(poll_duration); - - let mut attrs = self.combine_attributes(); - attrs.push(otel::KeyValue::new("completed", completed)); - - self.inner.total_duration.record(total_duration_ms, &attrs); - - self.inner.poll_duration.record(poll_duration_ms, &attrs); - - self.inner.poll_entries.add(poll_entries, &attrs); - self.inner.tasks_finished.add(1, &attrs); - } -} - -struct OtelRecorderInner { - total_duration: Histogram, - poll_duration: Histogram, - poll_entries: Counter, - tasks_started: Counter, - tasks_finished: Counter, -} - -impl OtelRecorderInner { - fn new(prefix: &str) -> Self { - let meter = ServiceMetrics::meter(); - - Self { - total_duration: meter.f64_histogram(format!("{prefix}_duration")).init(), - poll_duration: meter - .f64_histogram(format!("{prefix}_poll_duration")) - .init(), - poll_entries: meter.u64_counter(format!("{prefix}_poll_entries")).init(), - tasks_started: meter.u64_counter(format!("{prefix}_started")).init(), - tasks_finished: meter.u64_counter(format!("{prefix}_finished")).init(), - } - } -} diff --git a/crates/metrics/src/test.rs b/crates/metrics/src/test.rs new file mode 100644 index 0000000..ae4c80c --- /dev/null +++ b/crates/metrics/src/test.rs @@ -0,0 +1,273 @@ +use { + metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle}, + prometheus_parse::{HistogramCount, Value}, + std::collections::HashMap, + tikv_jemalloc_ctl as alloc, +}; + +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +#[test] +fn suite() { + use crate::examples::{ + macros_counter::counters, + macros_future_metrics::future_metrics, + macros_gauge::gauges, + macros_histogram::histograms, + }; + + let mut metrics = Metrics::new(); + + let allocated1 = measure_heap_allocation(); + + counters(1); + gauges(1.0); + histograms(1.0); + #[cfg(feature = "future")] + smol::block_on(future_metrics()); + + let allocated2 = measure_heap_allocation(); + + assert!(allocated2 > allocated1); + assert!( + allocated2 - allocated1 < 1024 * 1024, + "before: {allocated1}, after: {allocated2}" + ); + + metrics.scrape(); + + metrics.assert_counters(1); + metrics.assert_gauges(1.0); + metrics.assert_histograms(1.0); + #[cfg(feature = "future")] + metrics.assert_future_metrics(1.0); + + const ITERATIONS: usize = 1_000_000; + + // Histograms are not included here, because their current implementation is + // leaky in `metrics` itself. It needs to be consumed consistently by the + // `prometheus` exporter to not leak. + // + // This crate adds identical abstractions on top of every `metrics` metric, so + // we can be pretty sure that at least our own code doesn't leak by just + // checking the counters. + let allocated1 = measure_heap_allocation(); + for n in 0..ITERATIONS { + counters(1); + gauges(n as f64); + } + let allocated2 = measure_heap_allocation(); + + assert_eq!(allocated1, allocated2,); + + metrics.scrape(); + + metrics.assert_counters(ITERATIONS as u64 + 1); + metrics.assert_gauges(ITERATIONS as f64 - 1.0); +} + +struct Metrics { + prometheus: PrometheusHandle, + scrape: Option, +} + +impl Metrics { + fn new() -> Self { + Self { + prometheus: PrometheusBuilder::new() + .set_buckets_for_metric(Matcher::Prefix("histogram".into()), &[0.0]) + .unwrap() + .set_buckets_for_metric(Matcher::Prefix("future".into()), &[0.0]) + .unwrap() + .install_recorder() + .unwrap(), + scrape: None, + } + } + + fn scrape(&mut self) { + let rendered = self.prometheus.render(); + print!("{rendered}"); + + self.scrape = Some( + prometheus_parse::Scrape::parse(rendered.lines().map(ToString::to_string).map(Ok)) + .unwrap(), + ); + } + + fn assert_counters(&mut self, value: u64) { + self.assert_metrics("counter", Value::Counter(value as f64)) + } + + fn assert_gauges(&mut self, value: f64) { + self.assert_metrics("gauge", Value::Gauge(value)) + } + + fn assert_histograms(&mut self, count: f64) { + self.assert_metrics("histogram", expected_histogram(count)) + } + + fn assert_metrics(&mut self, ty: &'static str, value: Value) { + let name = |n| format!("{ty}{n}"); + + self.assert_metric(&name(1), None, &[], &value); + self.assert_metric(&name(2), None, &[("e", "a")], &value); + self.assert_metric(&name(3), None, &[("b", "true")], &value); + self.assert_metric(&name(4), None, &[("s", "a")], &value); + self.assert_metric(&name(5), None, &[("s", "42")], &value); + + let labels = &[("e", "a"), ("s1", "a"), ("s2", "42"), ("b", "true")]; + self.assert_metric(&name(6), None, labels, &value); + + self.assert_metric(&name(7), None, &[("st", "1")], &value); + + let labels = &[("st1", "1"), ("st2", "2")]; + self.assert_metric(&name(8), None, labels, &value); + + let labels = &[("s", "42"), ("st", "2")]; + self.assert_metric(&name(9), None, labels, &value); + + let labels = &[ + ("e", "a"), + ("s1", "a"), + ("s2", "42"), + ("b", "true"), + ("st1", "1"), + ("st2", "2"), + ]; + self.assert_metric(&name(10), None, labels, &value); + + self.assert_metric(&name(11), Some("description11"), &[], &value); + self.assert_metric(&name(12), Some("description12"), &[("e", "a")], &value); + self.assert_metric(&name(13), Some("description13"), &[("b", "true")], &value); + self.assert_metric(&name(14), Some("description14"), &[("s", "a")], &value); + self.assert_metric(&name(15), Some("description15"), &[("s", "42")], &value); + + let labels = &[("e", "a"), ("s1", "a"), ("s2", "42"), ("b", "true")]; + self.assert_metric(&name(16), Some("description16"), labels, &value); + + self.assert_metric(&name(17), Some("description17"), &[("st", "1")], &value); + + let labels = &[("st1", "1"), ("st2", "2")]; + self.assert_metric(&name(18), Some("description18"), labels, &value); + + let labels = &[("s", "42"), ("st", "2")]; + self.assert_metric(&name(19), Some("description19"), labels, &value); + + let labels = &[ + ("e", "a"), + ("s1", "a"), + ("s2", "42"), + ("b", "true"), + ("oe", "a"), + ("os1", "a"), + ("os2", "42"), + ("ob", "true"), + ("st1", "1"), + ("st2", "2"), + ]; + self.assert_metric(&name(20), Some("description20"), labels, &value); + } + + fn assert_metric( + &mut self, + name: &str, + description: Option<&str>, + labels: &[(&str, &str)], + value: &Value, + ) { + let labels: HashMap<_, _> = labels + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + + let scrape = self.scrape.as_mut().unwrap(); + + let samples = &mut scrape.samples; + let idx = samples + .iter() + .position(|m| m.metric == name && *m.labels == labels) + .unwrap_or_else(|| panic!("{name} {labels:?} missing")); + let metric = samples.remove(idx); + assert_eq!(&metric.value, value); + + let doc = scrape.docs.remove(name); + assert_eq!(doc, description.map(ToString::to_string)) + } + + #[cfg(feature = "future")] + fn assert_future_metrics(&mut self, count: f64) { + use crate::future::name; + + self.assert_future_metrics_(name::FUTURES_CREATED, Value::Counter(count)); + self.assert_future_metrics_(name::FUTURES_STARTED, Value::Counter(count)); + self.assert_future_metrics_(name::FUTURES_FINISHED, Value::Counter(count)); + self.assert_future_metrics_(name::FUTURE_POLLS, Value::Counter(count)); + + self.assert_future_metrics_(name::FUTURE_DURATION, expected_histogram(count)); + self.assert_future_metrics_(name::FUTURE_POLL_DURATION, expected_histogram(count)); + } + + #[cfg(feature = "future")] + fn assert_future_metrics_(&mut self, name: &str, value: Value) { + let future_names: Vec = (1..=10).map(|n| format!("future_metrics{n}")).collect(); + let future_name = |n: usize| ("future_name", future_names[n].as_str()); + + self.assert_metric(name, None, &[future_name(0)], &value); + self.assert_metric(name, None, &[future_name(1), ("e", "a")], &value); + self.assert_metric(name, None, &[future_name(2), ("b", "true")], &value); + self.assert_metric(name, None, &[future_name(3), ("s", "a")], &value); + self.assert_metric(name, None, &[future_name(4), ("s", "42")], &value); + + let labels = &[ + future_name(5), + ("e", "a"), + ("s1", "a"), + ("s2", "42"), + ("b", "true"), + ]; + self.assert_metric(name, None, labels, &value); + + self.assert_metric(name, None, &[future_name(6), ("st", "1")], &value); + + let labels = &[future_name(7), ("st1", "1"), ("st2", "2")]; + self.assert_metric(name, None, labels, &value); + + let labels = &[future_name(8), ("s", "42"), ("st", "2")]; + self.assert_metric(name, None, labels, &value); + + let labels = &[ + future_name(9), + ("e", "a"), + ("s1", "a"), + ("s2", "42"), + ("b", "true"), + ("oe", "a"), + ("os1", "a"), + ("os2", "42"), + ("ob", "true"), + ("st1", "1"), + ("st2", "2"), + ]; + self.assert_metric(name, None, labels, &value); + } +} + +fn measure_heap_allocation() -> usize { + alloc::epoch::advance().unwrap(); + alloc::stats::allocated::read().unwrap() +} + +fn expected_histogram(count: f64) -> Value { + Value::Histogram(vec![ + HistogramCount { + less_than: 0.0, + count: 0.0, + }, + HistogramCount { + less_than: f64::INFINITY, + count, + }, + ]) +} diff --git a/examples/alloc_stats.rs b/examples/alloc_stats.rs index 4b2a7c3..460130e 100644 --- a/examples/alloc_stats.rs +++ b/examples/alloc_stats.rs @@ -1,16 +1,18 @@ -use wc::metrics::ServiceMetrics; +use metrics_exporter_prometheus::PrometheusBuilder; #[global_allocator] static ALLOCATOR: wc::alloc::Jemalloc = wc::alloc::Jemalloc; #[tokio::main] async fn main() -> anyhow::Result<()> { - ServiceMetrics::init_with_name("metrics_example"); + let prometheus = PrometheusBuilder::new() + .install_recorder() + .expect("install prometheus recorder"); // Collect allocation stats from Jemalloc and update the metrics. wc::alloc::stats::update_jemalloc_metrics().unwrap(); - println!("{}", ServiceMetrics::export().unwrap()); + println!("{}", prometheus.render()); Ok(()) } diff --git a/examples/metrics.rs b/examples/metrics.rs deleted file mode 100644 index d757cd1..0000000 --- a/examples/metrics.rs +++ /dev/null @@ -1,35 +0,0 @@ -use { - std::time::Duration, - wc::{ - future::{FutureExt, StaticFutureExt}, - metrics::{counter, gauge, histogram, ServiceMetrics, TaskMetrics}, - }, -}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - ServiceMetrics::init_with_name("metrics_example"); - - gauge!("example_gauge", 150u64); - histogram!("example_histogram", 150.0); - counter!("example_counter", 150u64); - - static CORE_TASK_METRICS: TaskMetrics = TaskMetrics::new("core_task"); - - async { - tokio::time::sleep(Duration::from_millis(300)).await; - } - .with_metrics(CORE_TASK_METRICS.with_name("sleeper")) - .await; - - async { - tokio::time::sleep(Duration::from_millis(300)).await; - } - .spawn("spawned_sleeper") - .await - .unwrap(); - - println!("{}", ServiceMetrics::export().unwrap()); - - Ok(()) -} diff --git a/src/lib.rs b/src/lib.rs index a9204a9..e823040 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,8 @@ pub use analytics; pub use collections; #[cfg(feature = "future")] pub use future; -#[cfg(feature = "future_metrics")] -pub use future_metrics; #[cfg(feature = "geoip")] pub use geoip; -#[cfg(feature = "http")] -pub use http; #[cfg(feature = "metrics")] pub use metrics; #[cfg(feature = "rate_limit")]