diff --git a/Cargo.lock b/Cargo.lock index 72f754ab..dc8faad4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -96,12 +108,46 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.10.0" @@ -196,6 +242,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -264,12 +312,31 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -285,6 +352,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "derive_more" version = "1.0.0" @@ -306,6 +388,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -357,6 +445,8 @@ dependencies = [ "ember-persistence", "ember-protocol", "emberkv-core", + "metrics", + "metrics-exporter-prometheus", "tokio", "tracing", "tracing-subscriber", @@ -412,6 +502,24 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -530,13 +638,41 @@ dependencies = [ "wasip2", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", ] [[package]] @@ -551,6 +687,111 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -585,6 +826,12 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -597,6 +844,16 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -661,6 +918,53 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash 0.8.12", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mio" version = "1.1.1" @@ -738,6 +1042,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "ordered-float" version = "5.1.0" @@ -802,6 +1112,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -849,6 +1165,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.44" @@ -929,6 +1260,24 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -984,6 +1333,20 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rkyv" version = "0.7.46" @@ -1042,12 +1405,68 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "1.2.1" @@ -1072,6 +1491,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1152,6 +1594,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" + [[package]] name = "slab" version = "0.4.12" @@ -1180,6 +1628,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1313,6 +1767,29 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -1343,6 +1820,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -1414,6 +1897,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -1426,6 +1915,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8-width" version = "0.1.8" @@ -1471,6 +1966,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1531,6 +2035,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -1590,13 +2126,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -1608,6 +2153,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -1615,58 +2176,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -1717,6 +2326,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zmij" version = "1.0.19" diff --git a/Cargo.toml b/Cargo.toml index eab9112e..9ea372e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,10 @@ crc32fast = "1" # float ordering (used for sorted set scores) ordered-float = "5" +# metrics +metrics = "0.24" +metrics-exporter-prometheus = { version = "0.16", features = ["http-listener"] } + # internal crates (version required for crates.io publishing) emberkv-core = { version = "0.2.1", path = "crates/ember-core" } ember-protocol = { version = "0.2.1", path = "crates/ember-protocol" } diff --git a/crates/ember-core/src/keyspace.rs b/crates/ember-core/src/keyspace.rs index 4b3f189e..d80c4217 100644 --- a/crates/ember-core/src/keyspace.rs +++ b/crates/ember-core/src/keyspace.rs @@ -181,6 +181,10 @@ pub struct KeyspaceStats { pub used_bytes: usize, /// Number of keys with an expiration set. pub keys_with_expiry: usize, + /// Cumulative count of keys removed by expiration (lazy + active). + pub keys_expired: u64, + /// Cumulative count of keys removed by eviction. + pub keys_evicted: u64, } /// Number of random keys to sample when looking for an eviction candidate. @@ -205,6 +209,10 @@ pub struct Keyspace { config: ShardConfig, /// Number of entries that currently have an expiration set. expiry_count: usize, + /// Cumulative count of keys removed by expiration (lazy + active). + expired_total: u64, + /// Cumulative count of keys removed by eviction. + evicted_total: u64, } impl Keyspace { @@ -220,6 +228,8 @@ impl Keyspace { memory: MemoryTracker::new(), config, expiry_count: 0, + expired_total: 0, + evicted_total: 0, } } @@ -328,6 +338,7 @@ impl Keyspace { if entry.expires_at.is_some() { self.expiry_count = self.expiry_count.saturating_sub(1); } + self.evicted_total += 1; return true; } } @@ -535,6 +546,8 @@ impl Keyspace { key_count: self.memory.key_count(), used_bytes: self.memory.used_bytes(), keys_with_expiry: self.expiry_count, + keys_expired: self.expired_total, + keys_evicted: self.evicted_total, } } @@ -1565,6 +1578,7 @@ impl Keyspace { if entry.expires_at.is_some() { self.expiry_count = self.expiry_count.saturating_sub(1); } + self.expired_total += 1; } } expired diff --git a/crates/ember-protocol/src/command.rs b/crates/ember-protocol/src/command.rs index c3c1d4b2..b32a926b 100644 --- a/crates/ember-protocol/src/command.rs +++ b/crates/ember-protocol/src/command.rs @@ -285,6 +285,84 @@ pub struct ZAddFlags { impl Eq for ZAddFlags {} impl Command { + /// Returns the lowercase command name as a static string. + /// + /// Used for metrics labels and slow log entries. Zero allocation — + /// returns a `&'static str` for every known variant. + pub fn command_name(&self) -> &'static str { + match self { + Command::Ping(_) => "ping", + Command::Echo(_) => "echo", + Command::Get { .. } => "get", + Command::Set { .. } => "set", + Command::Incr { .. } => "incr", + Command::Decr { .. } => "decr", + Command::Del { .. } => "del", + Command::Exists { .. } => "exists", + Command::MGet { .. } => "mget", + Command::MSet { .. } => "mset", + Command::Expire { .. } => "expire", + Command::Ttl { .. } => "ttl", + Command::Persist { .. } => "persist", + Command::Pttl { .. } => "pttl", + Command::Pexpire { .. } => "pexpire", + Command::DbSize => "dbsize", + Command::Info { .. } => "info", + Command::BgSave => "bgsave", + Command::BgRewriteAof => "bgrewriteaof", + Command::FlushDb => "flushdb", + Command::Scan { .. } => "scan", + Command::LPush { .. } => "lpush", + Command::RPush { .. } => "rpush", + Command::LPop { .. } => "lpop", + Command::RPop { .. } => "rpop", + Command::LRange { .. } => "lrange", + Command::LLen { .. } => "llen", + Command::Type { .. } => "type", + Command::ZAdd { .. } => "zadd", + Command::ZRem { .. } => "zrem", + Command::ZScore { .. } => "zscore", + Command::ZRank { .. } => "zrank", + Command::ZCard { .. } => "zcard", + Command::ZRange { .. } => "zrange", + Command::HSet { .. } => "hset", + Command::HGet { .. } => "hget", + Command::HGetAll { .. } => "hgetall", + Command::HDel { .. } => "hdel", + Command::HExists { .. } => "hexists", + Command::HLen { .. } => "hlen", + Command::HIncrBy { .. } => "hincrby", + Command::HKeys { .. } => "hkeys", + Command::HVals { .. } => "hvals", + Command::HMGet { .. } => "hmget", + Command::SAdd { .. } => "sadd", + Command::SRem { .. } => "srem", + Command::SMembers { .. } => "smembers", + Command::SIsMember { .. } => "sismember", + Command::SCard { .. } => "scard", + Command::ClusterInfo => "cluster_info", + Command::ClusterNodes => "cluster_nodes", + Command::ClusterSlots => "cluster_slots", + Command::ClusterKeySlot { .. } => "cluster_keyslot", + Command::ClusterMyId => "cluster_myid", + Command::ClusterSetSlotImporting { .. } => "cluster_setslot", + Command::ClusterSetSlotMigrating { .. } => "cluster_setslot", + Command::ClusterSetSlotNode { .. } => "cluster_setslot", + Command::ClusterSetSlotStable { .. } => "cluster_setslot", + Command::ClusterMeet { .. } => "cluster_meet", + Command::ClusterAddSlots { .. } => "cluster_addslots", + Command::ClusterDelSlots { .. } => "cluster_delslots", + Command::ClusterForget { .. } => "cluster_forget", + Command::ClusterReplicate { .. } => "cluster_replicate", + Command::ClusterFailover { .. } => "cluster_failover", + Command::ClusterCountKeysInSlot { .. } => "cluster_countkeysinslot", + Command::ClusterGetKeysInSlot { .. } => "cluster_getkeysinslot", + Command::Migrate { .. } => "migrate", + Command::Asking => "asking", + Command::Unknown(_) => "unknown", + } + } + /// Parses a [`Frame`] into a [`Command`]. /// /// Expects an array frame where the first element is the command name diff --git a/crates/ember-server/Cargo.toml b/crates/ember-server/Cargo.toml index f1d32499..69f18bc7 100644 --- a/crates/ember-server/Cargo.toml +++ b/crates/ember-server/Cargo.toml @@ -19,3 +19,5 @@ tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } clap = { workspace = true } +metrics = { workspace = true } +metrics-exporter-prometheus = { workspace = true } diff --git a/crates/ember-server/src/connection.rs b/crates/ember-server/src/connection.rs index 2949650a..66ec4c16 100644 --- a/crates/ember-server/src/connection.rs +++ b/crates/ember-server/src/connection.rs @@ -4,7 +4,7 @@ //! sharded engine, and writes responses back. Supports pipelining //! by processing multiple frames from a single read. -use std::time::Duration; +use std::time::{Duration, Instant}; use bytes::{Bytes, BytesMut}; use ember_core::{Engine, KeyspaceStats, ShardRequest, ShardResponse, TtlResult, Value}; @@ -33,6 +33,7 @@ const IDLE_TIMEOUT: Duration = Duration::from_secs(300); // 5 minutes pub async fn handle( mut stream: TcpStream, engine: Engine, + metrics_enabled: bool, ) -> Result<(), Box> { // disable Nagle's algorithm — cache servers need low-latency writes, // and we already batch responses from pipelining into a single write @@ -67,7 +68,7 @@ pub async fn handle( match parse_frame(&buf) { Ok(Some((frame, consumed))) => { let _ = buf.split_to(consumed); - let response = process(frame, &engine).await; + let response = process(frame, &engine, metrics_enabled).await; response.serialize(&mut out); } Ok(None) => break, // need more data @@ -87,9 +88,28 @@ pub async fn handle( } /// Converts a raw frame into a command and executes it. -async fn process(frame: Frame, engine: &Engine) -> Frame { +/// +/// When `metrics_enabled` is true, records per-command latency and +/// error counters in prometheus. +async fn process(frame: Frame, engine: &Engine, metrics_enabled: bool) -> Frame { match Command::from_frame(frame) { - Ok(cmd) => execute(cmd, engine).await, + Ok(cmd) => { + let cmd_name = cmd.command_name(); + let start = if metrics_enabled { + Some(Instant::now()) + } else { + None + }; + + let response = execute(cmd, engine).await; + + if let Some(start) = start { + let is_error = matches!(&response, Frame::Error(_)); + crate::metrics::record_command(cmd_name, start.elapsed(), is_error); + } + + response + } Err(e) => Frame::Error(format!("ERR {e}")), } } @@ -318,12 +338,16 @@ async fn execute(cmd: Command, engine: &Engine) -> Frame { key_count: 0, used_bytes: 0, keys_with_expiry: 0, + keys_expired: 0, + keys_evicted: 0, }; for r in &responses { if let ShardResponse::Stats(stats) = r { total.key_count += stats.key_count; total.used_bytes += stats.used_bytes; total.keys_with_expiry += stats.keys_with_expiry; + total.keys_expired += stats.keys_expired; + total.keys_evicted += stats.keys_evicted; } } let info = format!( diff --git a/crates/ember-server/src/main.rs b/crates/ember-server/src/main.rs index f5c93bcf..c571e1f2 100644 --- a/crates/ember-server/src/main.rs +++ b/crates/ember-server/src/main.rs @@ -1,5 +1,6 @@ mod config; mod connection; +mod metrics; mod server; use std::net::SocketAddr; @@ -43,6 +44,10 @@ struct Args { /// fsync policy for the AOF: always, everysec, or no #[arg(long, default_value = "everysec")] appendfsync: String, + + /// port for prometheus metrics HTTP endpoint. disabled when not set + #[arg(long)] + metrics_port: Option, } #[tokio::main] @@ -119,9 +124,20 @@ async fn main() { ); } + // install prometheus metrics exporter if --metrics-port is set + if let Some(metrics_port) = args.metrics_port { + let metrics_addr: std::net::SocketAddr = format!("{}:{}", args.host, metrics_port) + .parse() + .expect("invalid metrics bind address"); + if let Err(e) = metrics::install_exporter(metrics_addr) { + eprintln!("failed to start metrics exporter: {e}"); + std::process::exit(1); + } + } + info!("ember server starting..."); - if let Err(e) = server::run(addr, shard_count, engine_config, None).await { + if let Err(e) = server::run(addr, shard_count, engine_config, None, args.metrics_port.is_some()).await { eprintln!("server error: {e}"); std::process::exit(1); } diff --git a/crates/ember-server/src/metrics.rs b/crates/ember-server/src/metrics.rs new file mode 100644 index 00000000..c537c80a --- /dev/null +++ b/crates/ember-server/src/metrics.rs @@ -0,0 +1,127 @@ +//! Prometheus metrics exposition. +//! +//! When `--metrics-port` is set, installs a prometheus exporter that +//! serves `/metrics` on a separate HTTP port. The hot path records +//! per-command counters and latency histograms through the `metrics` +//! crate's global recorder. A background stats poller periodically +//! broadcasts `ShardRequest::Stats` and updates gauge values. + +use std::net::SocketAddr; +use std::time::Duration; + +use ember_core::{Engine, KeyspaceStats, ShardRequest, ShardResponse}; +use metrics::{counter, gauge, histogram}; +use metrics_exporter_prometheus::PrometheusBuilder; +use tracing::{info, warn}; + +/// Histogram buckets tuned for cache latency (10µs to 100ms). +const HISTOGRAM_BUCKETS: &[f64] = &[ + 0.000_01, // 10µs + 0.000_025, // 25µs + 0.000_05, // 50µs + 0.000_1, // 100µs + 0.000_25, // 250µs + 0.000_5, // 500µs + 0.001, // 1ms + 0.002_5, // 2.5ms + 0.005, // 5ms + 0.01, // 10ms + 0.025, // 25ms + 0.05, // 50ms + 0.1, // 100ms +]; + +/// How often the stats poller queries shards and updates gauges. +const STATS_POLL_INTERVAL: Duration = Duration::from_secs(5); + +/// Installs the prometheus exporter and starts the HTTP listener. +/// +/// Returns an error if the listener can't bind to the address. +pub fn install_exporter(addr: SocketAddr) -> Result<(), Box> { + PrometheusBuilder::new() + .with_http_listener(addr) + .set_buckets(HISTOGRAM_BUCKETS) + .map_err(|e| format!("failed to set histogram buckets: {e}"))? + .install() + .map_err(|e| format!("failed to install prometheus exporter: {e}"))?; + + info!("prometheus metrics available on http://{addr}/metrics"); + Ok(()) +} + +/// Spawns a background task that polls shard stats every 5 seconds +/// and publishes them as prometheus gauges. +/// +/// Keeps `ember-core` free of metrics dependencies — the poller +/// pulls stats through the existing `ShardRequest::Stats` broadcast. +pub fn spawn_stats_poller(engine: Engine) { + tokio::spawn(async move { + let mut interval = tokio::time::interval(STATS_POLL_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + interval.tick().await; + + match engine.broadcast(|| ShardRequest::Stats).await { + Ok(responses) => { + let mut total = KeyspaceStats { + key_count: 0, + used_bytes: 0, + keys_with_expiry: 0, + keys_expired: 0, + keys_evicted: 0, + }; + for r in &responses { + if let ShardResponse::Stats(stats) = r { + total.key_count += stats.key_count; + total.used_bytes += stats.used_bytes; + total.keys_with_expiry += stats.keys_with_expiry; + total.keys_expired += stats.keys_expired; + total.keys_evicted += stats.keys_evicted; + } + } + + gauge!("ember_keys_total").set(total.key_count as f64); + gauge!("ember_memory_used_bytes").set(total.used_bytes as f64); + gauge!("ember_keys_expired_total").set(total.keys_expired as f64); + gauge!("ember_keys_evicted_total").set(total.keys_evicted as f64); + } + Err(e) => { + warn!("stats poller broadcast failed: {e}"); + } + } + } + }); +} + +/// Records a command execution in prometheus metrics. +/// +/// Called from the connection handler after each command completes. +#[inline] +pub fn record_command(cmd_name: &'static str, duration: Duration, is_error: bool) { + let labels = [("cmd", cmd_name)]; + counter!("ember_commands_total", &labels).increment(1); + histogram!("ember_commands_duration_seconds", &labels).record(duration.as_secs_f64()); + if is_error { + counter!("ember_commands_errors_total", &labels).increment(1); + } +} + +/// Increments the active connection gauge and total counter. +#[inline] +pub fn on_connection_accepted() { + gauge!("ember_connections_active").increment(1.0); + counter!("ember_connections_total").increment(1); +} + +/// Decrements the active connection gauge. +#[inline] +pub fn on_connection_closed() { + gauge!("ember_connections_active").decrement(1.0); +} + +/// Records a rejected connection (limit reached). +#[inline] +pub fn on_connection_rejected() { + counter!("ember_connections_rejected").increment(1); +} diff --git a/crates/ember-server/src/server.rs b/crates/ember-server/src/server.rs index 42bdee1c..34f5d61e 100644 --- a/crates/ember-server/src/server.rs +++ b/crates/ember-server/src/server.rs @@ -30,6 +30,7 @@ pub async fn run( shard_count: usize, config: EngineConfig, max_connections: Option, + metrics_enabled: bool, ) -> Result<(), Box> { // ensure data directory exists if persistence is configured if let Some(ref pcfg) = config.persistence { @@ -37,6 +38,11 @@ pub async fn run( } let engine = Engine::with_config(shard_count, config); + + if metrics_enabled { + crate::metrics::spawn_stats_poller(engine.clone()); + } + let listener = TcpListener::bind(addr).await?; let max_conn = max_connections.unwrap_or(DEFAULT_MAX_CONNECTIONS); let semaphore = Arc::new(Semaphore::new(max_conn)); @@ -65,17 +71,28 @@ pub async fn run( Ok(permit) => permit, Err(_) => { warn!("connection limit reached, dropping connection from {peer}"); + if metrics_enabled { + crate::metrics::on_connection_rejected(); + } drop(stream); continue; } }; + if metrics_enabled { + crate::metrics::on_connection_accepted(); + } + let engine = engine.clone(); + let metrics = metrics_enabled; tokio::spawn(async move { - if let Err(e) = connection::handle(stream, engine).await { + if let Err(e) = connection::handle(stream, engine, metrics).await { error!("connection error from {peer}: {e}"); } + if metrics { + crate::metrics::on_connection_closed(); + } // permit is dropped here, releasing the slot drop(permit); });