From 6dba8b117c9f45d2fece59ba780a668f744d1147 Mon Sep 17 00:00:00 2001 From: Fredrik Enestad Date: Tue, 26 Aug 2025 13:40:26 +0200 Subject: [PATCH 01/17] runtimes/core: Add requests counter metric --- Cargo.lock | 708 +++++++++++++++++++--- runtimes/core/Cargo.toml | 7 + runtimes/core/src/api/auth/local.rs | 6 + runtimes/core/src/api/endpoint.rs | 19 +- runtimes/core/src/api/manager.rs | 3 + runtimes/core/src/api/server.rs | 7 + runtimes/core/src/lib.rs | 19 +- runtimes/core/src/metadata/gce.rs | 176 ++++++ runtimes/core/src/metadata/mod.rs | 95 +++ runtimes/core/src/metrics/counter.rs | 80 +++ runtimes/core/src/metrics/exporter/gcp.rs | 210 +++++++ runtimes/core/src/metrics/exporter/mod.rs | 2 + runtimes/core/src/metrics/manager.rs | 185 ++++++ runtimes/core/src/metrics/mod.rs | 18 + runtimes/core/src/metrics/registry.rs | 154 +++++ runtimes/core/src/metrics/test.rs | 354 +++++++++++ runtimes/core/src/pubsub/manager.rs | 13 + 17 files changed, 1948 insertions(+), 108 deletions(-) create mode 100644 runtimes/core/src/metadata/gce.rs create mode 100644 runtimes/core/src/metadata/mod.rs create mode 100644 runtimes/core/src/metrics/counter.rs create mode 100644 runtimes/core/src/metrics/exporter/gcp.rs create mode 100644 runtimes/core/src/metrics/exporter/mod.rs create mode 100644 runtimes/core/src/metrics/manager.rs create mode 100644 runtimes/core/src/metrics/mod.rs create mode 100644 runtimes/core/src/metrics/registry.rs create mode 100644 runtimes/core/src/metrics/test.rs diff --git a/Cargo.lock b/Cargo.lock index 462758d190..e6e8892e73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,7 +744,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.7.0", "hyper-util", "itoa", "matchit", @@ -925,7 +925,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.95", "which", @@ -976,6 +976,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bon" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44aa969f86ffb99e5c2d51f393ec9ed6e9fe2f47b609c917b0071f129854d29" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e78cd86b6a6515d87392332fd63c4950ed3e50eab54275259a5f59f3666f90" +dependencies = [ + "darling 0.21.3", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.95", +] + [[package]] name = "brotli" version = "3.5.0" @@ -1037,9 +1062,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] [[package]] name = "bytes-utils" @@ -1089,6 +1117,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.39" @@ -1395,8 +1429,18 @@ version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -1413,13 +1457,38 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.95", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", + "quote", + "syn 2.0.95", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.95", ] @@ -1437,6 +1506,20 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -1466,12 +1549,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -1631,6 +1714,7 @@ dependencies = [ "anyhow", "assert_matches", "async-stream", + "async-trait", "aws-config", "aws-credential-types", "aws-sdk-s3", @@ -1649,6 +1733,7 @@ dependencies = [ "cidr", "colored", "cookie", + "dashmap 6.1.0", "duct", "email_address", "env_logger 0.10.2", @@ -1658,16 +1743,19 @@ dependencies = [ "futures-core", "futures-util", "gjson", - "google-cloud-gax", + "google-cloud-api", + "google-cloud-gax 0.17.0", "google-cloud-googleapis", + "google-cloud-monitoring-v3", "google-cloud-pubsub", "google-cloud-storage", + "google-cloud-wkt", "hex", "hmac", "http 1.2.0", "http-body-util", "httpdate", - "hyper 1.5.2", + "hyper 1.7.0", "indexmap 2.7.0", "insta", "jsonwebtoken", @@ -1675,6 +1763,8 @@ dependencies = [ "malachite", "matchit", "md5", + "metrics", + "metrics-util", "mime", "native-tls", "once_cell", @@ -1693,7 +1783,7 @@ dependencies = [ "radix_fmt", "rand 0.8.5", "regex", - "reqwest 0.12.12", + "reqwest 0.12.23", "rsa", "serde", "serde_json", @@ -1711,7 +1801,7 @@ dependencies = [ "tokio-stream", "tokio-tungstenite 0.21.0", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tower-service", "tracing", "tracing-subscriber", @@ -1733,7 +1823,7 @@ dependencies = [ "flate2", "futures", "http 1.2.0", - "hyper 1.5.2", + "hyper 1.7.0", "libc", "log", "openssl", @@ -1741,7 +1831,7 @@ dependencies = [ "prost 0.12.6", "prost-build 0.12.6", "prost-types 0.12.6", - "reqwest 0.12.12", + "reqwest 0.12.23", "serde", "serde_json", "tokio", @@ -1797,6 +1887,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "env_filter" version = "0.1.3" @@ -2133,9 +2229,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -2180,6 +2278,19 @@ dependencies = [ "walkdir", ] +[[package]] +name = "google-cloud-api" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a67dd9819a0fed2c9251b3455e76149e0ebd30f5ad96ff9ff2cec2e2a5037a5" +dependencies = [ + "bytes", + "google-cloud-wkt", + "serde", + "serde_json", + "serde_with", +] + [[package]] name = "google-cloud-auth" version = "0.13.2" @@ -2214,7 +2325,7 @@ dependencies = [ "google-cloud-token", "home", "jsonwebtoken", - "reqwest 0.12.12", + "reqwest 0.12.23", "serde", "serde_json", "thiserror 1.0.69", @@ -2224,6 +2335,28 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "google-cloud-auth" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d8757f37377c6436b94c6efaf7f9c3c6e48a213bb0790f7a639a9ba0183764" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bon", + "google-cloud-gax 1.0.0", + "http 1.2.0", + "reqwest 0.12.23", + "rustc_version", + "rustls 0.23.20", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "thiserror 2.0.10", + "time", + "tokio", +] + [[package]] name = "google-cloud-gax" version = "0.17.0" @@ -2240,6 +2373,47 @@ dependencies = [ "tracing", ] +[[package]] +name = "google-cloud-gax" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6b1718e2a6965a7fbb282f23645e7adb8e0a012848258bbd6f580307b684f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures", + "google-cloud-rpc", + "google-cloud-wkt", + "http 1.2.0", + "pin-project", + "rand 0.9.2", + "serde", + "serde_json", + "thiserror 2.0.10", + "tokio", +] + +[[package]] +name = "google-cloud-gax-internal" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7ec794189133a50fd3901af3cd7faa37a0a6bb331e49e58df652a37f035552" +dependencies = [ + "bytes", + "google-cloud-auth 1.0.0", + "google-cloud-gax 1.0.0", + "google-cloud-rpc", + "http 1.2.0", + "http-body-util", + "percent-encoding", + "reqwest 0.12.23", + "rustc_version", + "serde", + "serde_json", + "thiserror 2.0.10", + "tokio", +] + [[package]] name = "google-cloud-googleapis" version = "0.12.0" @@ -2268,11 +2442,33 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f945a208886a13d07636f38fb978da371d0abc3e34bad338124b9f8c135a8f" dependencies = [ - "reqwest 0.12.12", + "reqwest 0.12.23", "thiserror 1.0.69", "tokio", ] +[[package]] +name = "google-cloud-monitoring-v3" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5fa201d8a275c591bdc79a2c04cc758ff220f5587c748787b182c9049c918a7" +dependencies = [ + "async-trait", + "bytes", + "google-cloud-api", + "google-cloud-gax 1.0.0", + "google-cloud-gax-internal", + "google-cloud-rpc", + "google-cloud-type", + "google-cloud-wkt", + "lazy_static", + "reqwest 0.12.23", + "serde", + "serde_json", + "serde_with", + "tracing", +] + [[package]] name = "google-cloud-pubsub" version = "0.22.1" @@ -2282,7 +2478,7 @@ dependencies = [ "async-channel", "async-stream", "google-cloud-auth 0.13.2", - "google-cloud-gax", + "google-cloud-gax 0.17.0", "google-cloud-googleapis", "google-cloud-token", "prost-types 0.12.6", @@ -2292,6 +2488,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "google-cloud-rpc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa87d841e35c69efa61786e7e7462b4ebdb96df7be79775aeb698b4c0cc52e" +dependencies = [ + "bytes", + "google-cloud-wkt", + "serde", + "serde_json", + "serde_with", +] + [[package]] name = "google-cloud-storage" version = "0.22.1" @@ -2312,7 +2521,7 @@ dependencies = [ "percent-encoding", "pkcs8 0.10.2", "regex", - "reqwest 0.12.12", + "reqwest 0.12.23", "reqwest-middleware", "ring 0.17.8", "serde", @@ -2334,6 +2543,35 @@ dependencies = [ "async-trait", ] +[[package]] +name = "google-cloud-type" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7190c5c073b273d952e6a01913302ce54f050861ead96d607089d380afdd8a" +dependencies = [ + "bytes", + "google-cloud-wkt", + "serde", + "serde_json", + "serde_with", +] + +[[package]] +name = "google-cloud-wkt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccfa292c272695eb966fe8b08c78cabb35e7775771a1c7f97b58f7dcb7bfb26" +dependencies = [ + "base64 0.22.1", + "bytes", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.10", + "time", + "url", +] + [[package]] name = "group" version = "0.12.1" @@ -2498,7 +2736,7 @@ dependencies = [ "new_debug_unreachable", "once_cell", "phf 0.11.3", - "rustc-hash", + "rustc-hash 1.1.0", "triomphe", ] @@ -2547,12 +2785,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http 1.2.0", "http-body 1.0.1", "pin-project-lite", @@ -2599,7 +2837,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -2608,13 +2846,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2 0.4.7", "http 1.2.0", "http-body 1.0.1", @@ -2622,6 +2861,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -2651,13 +2891,14 @@ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.5.2", + "hyper 1.7.0", "hyper-util", "rustls 0.23.20", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", "tower-service", + "webpki-roots 0.26.11", ] [[package]] @@ -2693,7 +2934,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.2", + "hyper 1.7.0", "hyper-util", "native-tls", "tokio", @@ -2703,21 +2944,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.2", + "hyper 1.7.0", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2956,6 +3204,16 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-macro" version = "0.3.7" @@ -3038,9 +3296,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -3097,9 +3355,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libloading" @@ -3203,6 +3461,12 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "malachite" version = "0.6.1" @@ -3313,6 +3577,36 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metrics" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986" +dependencies = [ + "aho-corasick", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.2", + "indexmap 2.7.0", + "metrics", + "ordered-float", + "quanta", + "radix_trie", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -3445,6 +3739,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.24.3" @@ -3635,6 +3938,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "os_pipe" version = "1.2.1" @@ -4036,7 +4348,7 @@ dependencies = [ "serde", "serde_yaml 0.8.26", "sfv", - "socket2", + "socket2 0.6.0", "strum", "strum_macros", "thread_local", @@ -4107,7 +4419,7 @@ dependencies = [ "pingora-http", "pingora-ketama", "pingora-runtime", - "rand 0.9.1", + "rand 0.9.2", "tokio", ] @@ -4120,7 +4432,7 @@ dependencies = [ "arrayvec", "hashbrown 0.15.2", "parking_lot", - "rand 0.9.1", + "rand 0.9.2", ] [[package]] @@ -4248,6 +4560,12 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "postgres-native-tls" version = "0.5.0" @@ -4274,7 +4592,7 @@ dependencies = [ "log", "md-5", "memchr", - "rand 0.9.1", + "rand 0.9.2", "sha2", "stringprep", ] @@ -4433,7 +4751,7 @@ dependencies = [ "bitflags 2.9.4", "lazy_static", "num-traits", - "rand 0.9.1", + "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", @@ -4553,6 +4871,21 @@ version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +[[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 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -4570,6 +4903,61 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.20", + "socket2 0.6.0", + "thiserror 2.0.10", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring 0.17.8", + "rustc-hash 2.1.1", + "rustls 0.23.20", + "rustls-pki-types", + "slab", + "thiserror 2.0.10", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.0", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.38" @@ -4591,6 +4979,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.4.6" @@ -4630,9 +5028,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -4728,6 +5126,24 @@ dependencies = [ "rand_core 0.9.3", ] +[[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.3", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.9.4", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -4847,9 +5263,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", @@ -4861,36 +5277,37 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.7.0", "hyper-rustls 0.27.5", "hyper-tls 0.6.0", "hyper-util", - "ipnet", "js-sys", "log", "mime", "mime_guess", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.2.0", + "quinn", + "rustls 0.23.20", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", - "system-configuration 0.6.1", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.1", "tokio-util", "tower 0.5.2", + "tower-http 0.6.6", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", + "webpki-roots 1.0.2", ] [[package]] @@ -4902,7 +5319,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.2.0", - "reqwest 0.12.12", + "reqwest 0.12.23", "serde", "thiserror 1.0.69", "tower-service", @@ -5013,6 +5430,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -5080,7 +5503,9 @@ version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ + "log", "once_cell", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -5135,6 +5560,9 @@ name = "rustls-pki-types" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -5315,18 +5743,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -5410,7 +5848,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2", "quote", "syn 2.0.95", @@ -5582,6 +6020,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[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.9" @@ -5624,6 +6068,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spin" version = "0.5.2" @@ -5830,7 +6284,7 @@ source = "git+https://github.com/encoredev/swc?branch=node-resolve-exports#3ccdd dependencies = [ "hstr", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "serde", ] @@ -5841,7 +6295,7 @@ source = "git+https://github.com/encoredev/swc?branch=node-resolve-exports#3ccdd dependencies = [ "ahash", "anyhow", - "dashmap", + "dashmap 5.5.3", "once_cell", "regex", "serde", @@ -5861,7 +6315,7 @@ dependencies = [ "new_debug_unreachable", "num-bigint", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "siphasher 0.3.11", "string_cache", @@ -5896,7 +6350,7 @@ version = "0.45.9" source = "git+https://github.com/encoredev/swc?branch=node-resolve-exports#3ccddcb7d70380b6952296717b2d9f2056f4c2ac" dependencies = [ "anyhow", - "dashmap", + "dashmap 5.5.3", "indexmap 1.9.3", "normpath", "once_cell", @@ -5939,7 +6393,7 @@ dependencies = [ "indexmap 1.9.3", "once_cell", "phf 0.10.1", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "smallvec", "swc_atoms", @@ -5959,7 +6413,7 @@ dependencies = [ "indexmap 1.9.3", "num_cpus", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "swc_atoms", "swc_common", "swc_ecma_ast", @@ -6241,9 +6695,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -6256,15 +6710,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -6308,7 +6762,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] @@ -6403,8 +6857,8 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.9.1", - "socket2", + "rand 0.9.2", + "socket2 0.5.8", "tokio", "tokio-util", "whoami", @@ -6593,7 +7047,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -6657,6 +7111,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -7089,20 +7561,22 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -7114,9 +7588,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -7127,9 +7601,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7137,9 +7611,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -7150,9 +7624,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -7169,9 +7646,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -7193,6 +7680,24 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -7266,34 +7771,39 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ + "windows-link", "windows-result", "windows-strings", - "windows-targets 0.52.6", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows-link", ] [[package]] diff --git a/runtimes/core/Cargo.toml b/runtimes/core/Cargo.toml index e1695dd0c1..94cd250044 100644 --- a/runtimes/core/Cargo.toml +++ b/runtimes/core/Cargo.toml @@ -10,6 +10,7 @@ rttrace = [] [dependencies] pingora = { version = "0.4", features = ["lb", "openssl"] } anyhow = "1.0.76" +async-trait = "0.1" base64 = "0.21.5" gjson = "0.8.1" prost = "0.12.3" @@ -123,6 +124,12 @@ email_address = "0.2.9" cookie = "0.18.1" malachite = "0.6.1" byteorder = "1.5.0" +metrics = "0.24.2" +metrics-util = { version = "0.20.0", features = ["registry", "storage"] } +dashmap = "6.1.0" +google-cloud-monitoring-v3 = "1.0.0" +google-cloud-api = "1.0.0" +google-cloud-wkt = "1.0.0" [build-dependencies] prost-build = "0.12.3" diff --git a/runtimes/core/src/api/auth/local.rs b/runtimes/core/src/api/auth/local.rs index a9c88fad64..3cfaf6e519 100644 --- a/runtimes/core/src/api/auth/local.rs +++ b/runtimes/core/src/api/auth/local.rs @@ -2,6 +2,7 @@ use crate::api::auth::{AuthHandler, AuthPayload, AuthRequest, AuthResponse}; use crate::api::schema::encoding::Schema; use crate::api::{APIResult, HandlerResponse, HandlerResponseInner, PValues}; use crate::log::LogFromRust; +use crate::metrics::Counter; use crate::model::{AuthRequestData, RequestData}; use crate::trace::Tracer; use crate::{api, model, EndpointName}; @@ -14,6 +15,7 @@ pub struct LocalAuthHandler { pub schema: Schema, pub handler: RwLock>>, pub tracer: Tracer, + pub requests_total: Counter, } impl LocalAuthHandler { @@ -152,7 +154,9 @@ impl AuthHandler for LocalAuthHandler { user_id: auth_uid.clone(), })), }; + self.tracer.request_span_end(&model_resp, false); + self.requests_total.increment_with([("code", "ok")]); Ok(AuthResponse::Authenticated { auth_uid, auth_data, @@ -165,6 +169,8 @@ impl AuthHandler for LocalAuthHandler { data: model::ResponseData::Auth(Err(e.clone())), }; self.tracer.request_span_end(&model_resp, false); + self.requests_total + .increment_with([("code", e.code.to_string())]); Err(e) } } diff --git a/runtimes/core/src/api/endpoint.rs b/runtimes/core/src/api/endpoint.rs index 83f8697f03..8cbb8c74d4 100644 --- a/runtimes/core/src/api/endpoint.rs +++ b/runtimes/core/src/api/endpoint.rs @@ -23,6 +23,7 @@ use crate::api::{jsonschema, schema, ErrCode, Error}; use crate::encore::parser::meta::v1::rpc; use crate::encore::parser::meta::v1::{self as meta, selector}; use crate::log::LogFromRust; +use crate::metrics::Counter; use crate::model::StreamDirection; use crate::names::EndpointName; use crate::trace; @@ -406,6 +407,7 @@ pub(super) struct EndpointHandler { pub endpoint: Arc, pub handler: Arc, pub shared: Arc, + pub requests_total: Counter, } #[derive(Debug)] @@ -427,6 +429,7 @@ impl Clone for EndpointHandler { endpoint: self.endpoint.clone(), handler: self.handler.clone(), shared: self.shared.clone(), + requests_total: self.requests_total.clone(), } } } @@ -601,7 +604,6 @@ impl EndpointHandler { let duration = tokio::time::Instant::now().duration_since(request.start); // If we had a request failure, log that separately. - if let ResponseData::Typed(Err(err)) = &resp { logger.error(Some(&request), "request failed", Some(err), { let mut fields = crate::log::Fields::new(); @@ -629,6 +631,12 @@ impl EndpointHandler { }); } + let code = match &resp { + ResponseData::Typed(Ok(_)) => "ok".to_string(), + ResponseData::Typed(Err(err)) => err.code.to_string(), + ResponseData::Raw(resp) => ErrCode::from(resp.status()).to_string(), + }; + logger.info(Some(&request), "request completed", { let mut fields = crate::log::Fields::new(); let dur_ms = (duration.as_secs() as f64 * 1000f64) @@ -644,13 +652,7 @@ impl EndpointHandler { )), ); - let code = match &resp { - ResponseData::Typed(Ok(_)) => "ok".to_string(), - ResponseData::Typed(Err(err)) => err.code.to_string(), - ResponseData::Raw(resp) => ErrCode::from(resp.status()).to_string(), - }; - - fields.insert("code".into(), serde_json::Value::String(code)); + fields.insert("code".into(), serde_json::Value::String(code.clone())); Some(fields) }); @@ -685,6 +687,7 @@ impl EndpointHandler { }), }; self.shared.tracer.request_span_end(&model_resp, sensitive); + self.requests_total.increment_with([("code", code)]); } if let Ok(val) = HeaderValue::from_str(request.span.0.serialize_encore().as_str()) { diff --git a/runtimes/core/src/api/manager.rs b/runtimes/core/src/api/manager.rs index f4f2fe2fb4..14b5a9e79a 100644 --- a/runtimes/core/src/api/manager.rs +++ b/runtimes/core/src/api/manager.rs @@ -18,6 +18,7 @@ use crate::api::{ }; use crate::encore::parser::meta::v1 as meta; use crate::encore::runtime::v1 as runtime; +use crate::metrics::requests_total_counter; use crate::trace::Tracer; use crate::{api, model, pubsub, secrets, EncoreName, EndpointName, Hosted}; @@ -283,6 +284,7 @@ fn build_auth_handler( // let is_local = hosted_services.contains(&explicit.service_name); let is_local = true; let name = EndpointName::new(explicit.service_name.clone(), auth.name.clone()); + let requests_total = requests_total_counter(&explicit.service_name, &auth.name); let auth_data = registry.schema(auth_data_schema_idx); let auth_handler = if is_local { @@ -294,6 +296,7 @@ fn build_auth_handler( schema, handler: Default::default(), tracer, + requests_total, }, )? } else { diff --git a/runtimes/core/src/api/server.rs b/runtimes/core/src/api/server.rs index 3c922fbf49..02c9a74040 100644 --- a/runtimes/core/src/api/server.rs +++ b/runtimes/core/src/api/server.rs @@ -12,6 +12,7 @@ use crate::api::static_assets::StaticAssetsHandler; use crate::api::{self, ToResponse}; use crate::api::{paths, reqauth, schema, BoxedHandler, EndpointMap}; use crate::encore::parser::meta::v1 as meta; +use crate::metrics::requests_total_counter; use crate::names::EndpointName; use crate::trace; @@ -92,11 +93,14 @@ impl Server { // For static asset routes, configure the static asset handler directly. // There's no need to defer it for dynamic runtime registration. let static_handler = StaticAssetsHandler::new(assets); + let requests_total = + requests_total_counter(&ep.name.service(), &ep.name.endpoint()); let handler = EndpointHandler { endpoint: ep.clone(), handler: Arc::new(static_handler), shared: shared.clone(), + requests_total, }; server_handler.set(handler); } @@ -153,11 +157,14 @@ impl Server { None => Ok(()), // anyhow::bail!("no handler found for endpoint: {}", endpoint_name), Some(h) => { let endpoint = self.endpoints.get(&endpoint_name).unwrap().to_owned(); + let requests_total = + requests_total_counter(&endpoint.name.service(), &endpoint.name.endpoint()); let handler = EndpointHandler { endpoint, handler, shared: self.shared.clone(), + requests_total, }; h.add(handler); diff --git a/runtimes/core/src/lib.rs b/runtimes/core/src/lib.rs index 150cd6e126..d587eafe80 100644 --- a/runtimes/core/src/lib.rs +++ b/runtimes/core/src/lib.rs @@ -24,6 +24,8 @@ pub mod error; pub mod infracfg; pub mod log; pub mod meta; +pub mod metadata; +pub mod metrics; pub mod model; mod names; pub mod objects; @@ -209,6 +211,7 @@ pub struct Runtime { app_meta: meta::AppMeta, compute: ComputeConfig, runtime: tokio::runtime::Runtime, + metrics: metrics::Manager, } impl Runtime { @@ -238,6 +241,7 @@ impl Runtime { let mut deployment = cfg.deployment.take().unwrap_or_default(); let service_discovery = deployment.service_discovery.take().unwrap_or_default(); + let observability = deployment.observability.take().unwrap_or_default(); let http_client = reqwest::Client::builder() .build() @@ -250,11 +254,18 @@ impl Runtime { ); let platform_validator = Arc::new(platform_validator); + // Initialize metrics manager from runtime config + let metrics_manager = metrics::Manager::from_runtime_config( + &observability, + &environment, + &http_client, + tokio_rt.handle().clone(), + ); + // Set up observability. let disable_tracing = testing || std::env::var("ENCORE_NOTRACE").is_ok_and(|v| !v.is_empty()); let tracer = if !disable_tracing { - let observability = deployment.observability.take().unwrap_or_default(); let trace_endpoint = observability .tracing .into_iter() @@ -411,6 +422,7 @@ impl Runtime { app_meta, compute, runtime: tokio_rt, + metrics: metrics_manager, }) } @@ -444,6 +456,11 @@ impl Runtime { &self.api } + #[inline] + pub fn metrics(&self) -> &metrics::Manager { + &self.metrics + } + #[inline] pub fn endpoints(&self) -> &api::EndpointMap { self.api.endpoints() diff --git a/runtimes/core/src/metadata/gce.rs b/runtimes/core/src/metadata/gce.rs new file mode 100644 index 0000000000..1ae4ebcd21 --- /dev/null +++ b/runtimes/core/src/metadata/gce.rs @@ -0,0 +1,176 @@ +//! GCE metadata client implementation based on golangs "cloud.google.com/go/compute/metadata" + +use std::time::Duration; +use tokio::sync::OnceCell; + +#[derive(Debug, thiserror::Error)] +pub enum GceMetadataError { + #[error("HTTP request failed: {0}")] + HttpRequest(#[from] reqwest::Error), + #[error("GCE metadata not defined (404)")] + NotDefined, + #[error("GCE metadata server temporarily unavailable (503)")] + ServiceUnavailable, + #[error("GCE metadata server returned error status: {status}")] + HttpStatus { status: reqwest::StatusCode }, + #[error("Failed to read response body: {0}")] + ResponseBody(reqwest::Error), +} + +// Default metadata server IP as documented by Google +const METADATA_IP: &str = "169.254.169.254"; +// Env to override metadata server host, if not set the METADATA_IP will be used +const METADATA_HOST_ENV: &str = "GCE_METADATA_HOST"; +const METADATA_FLAVOR_HEADER: &str = "Metadata-Flavor"; +const GOOGLE_HEADER_VALUE: &str = "Google"; +const USER_AGENT: &str = "encore-runtime/0.1.0"; +// Timeout and retry configuration +const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); +const MAX_RETRIES: usize = 3; + +// Global cache for instance ID to ensure it's shared across all calls +static INSTANCE_ID_CACHE: OnceCell = OnceCell::const_new(); + +#[derive(Debug)] +pub struct GceMetadataClient { + http_client: reqwest::Client, +} + +impl GceMetadataClient { + pub fn new(http_client: reqwest::Client) -> Self { + Self { http_client } + } + + /// Build metadata URL, checking GCE_METADATA_HOST environment variable first + fn build_metadata_url(path: &str) -> String { + let host = std::env::var(METADATA_HOST_ENV).unwrap_or_else(|_| METADATA_IP.to_string()); + let path = path.trim_start_matches('/'); // Remove leading slashes like Go does + format!("http://{}/computeMetadata/v1/{}", host, path) + } + + /// Get the instance ID from GCE metadata server, with global caching + /// This ensures only one HTTP request is made even with concurrent calls + pub async fn instance_id(&self) -> Result { + let instance_id = INSTANCE_ID_CACHE + .get_or_try_init(|| async { self.fetch_metadata("instance/id").await }) + .await?; + + Ok(instance_id.clone()) + } + + /// Fetch metadata from the GCE metadata server + pub async fn fetch_metadata(&self, path: &str) -> Result { + let url = Self::build_metadata_url(path); + log::debug!("Fetching GCE metadata from: {}", url); + + for attempt in 1..=MAX_RETRIES { + let result = self.try_fetch(&url).await; + + match &result { + Ok(_) => { + log::debug!("Successfully fetched GCE metadata from {}", path); + return result; + } + Err(e) if attempt == MAX_RETRIES => { + log::error!( + "Failed to fetch GCE metadata after {} attempts: {}", + MAX_RETRIES, + e + ); + return result; + } + Err(e) => { + log::warn!("Attempt {} failed: {}, retrying...", attempt, e); + tokio::time::sleep(Duration::from_millis(100 * attempt as u64)).await; + } + } + } + + unreachable!("unexpected failure fetching metadata") + } + + async fn try_fetch(&self, url: &str) -> Result { + let response = self + .http_client + .get(url) + .header(METADATA_FLAVOR_HEADER, GOOGLE_HEADER_VALUE) + .header(http::header::USER_AGENT, USER_AGENT) + .timeout(REQUEST_TIMEOUT) + .send() + .await?; + + let status = response.status(); + if status.is_success() { + let text = response + .text() + .await + .map_err(GceMetadataError::ResponseBody)?; + Ok(text.trim().to_string()) + } else if status == reqwest::StatusCode::NOT_FOUND { + Err(GceMetadataError::NotDefined) + } else if status == reqwest::StatusCode::SERVICE_UNAVAILABLE { + Err(GceMetadataError::ServiceUnavailable) + } else { + Err(GceMetadataError::HttpStatus { status }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + #[tokio::test] + async fn test_gce_metadata_client_creation() { + let http_client = reqwest::Client::builder() + .timeout(Duration::from_secs(1)) + .build() + .unwrap(); + + let client = GceMetadataClient::new(http_client); + + // Test that the client can be created without panic + assert!(format!("{:?}", client).contains("GceMetadataClient")); + } + + #[tokio::test] + async fn test_singleton_cache_behavior() { + // Verify the cache is initially empty + assert!(INSTANCE_ID_CACHE.get().is_none()); + + // Test that we can set a value (simulating successful fetch) + let test_id = "test-instance-123".to_string(); + let result = INSTANCE_ID_CACHE.set(test_id.clone()); + assert!(result.is_ok()); + + // Verify the cached value can be retrieved + assert_eq!(INSTANCE_ID_CACHE.get(), Some(&test_id)); + } + + #[test] + fn test_metadata_url_construction() { + // Test default URL construction (no environment variable) + std::env::remove_var(METADATA_HOST_ENV); + let url = GceMetadataClient::build_metadata_url("instance/id"); + assert_eq!(url, "http://169.254.169.254/computeMetadata/v1/instance/id"); + + // Test with leading slash removal + let url = GceMetadataClient::build_metadata_url("/instance/id"); + assert_eq!(url, "http://169.254.169.254/computeMetadata/v1/instance/id"); + } + + #[test] + fn test_metadata_host_env_var() { + // Test custom host via environment variable + std::env::set_var(METADATA_HOST_ENV, "custom.metadata.host"); + let url = GceMetadataClient::build_metadata_url("instance/id"); + assert_eq!( + url, + "http://custom.metadata.host/computeMetadata/v1/instance/id" + ); + + // Clean up + std::env::remove_var(METADATA_HOST_ENV); + } +} diff --git a/runtimes/core/src/metadata/mod.rs b/runtimes/core/src/metadata/mod.rs new file mode 100644 index 0000000000..b84faebb6d --- /dev/null +++ b/runtimes/core/src/metadata/mod.rs @@ -0,0 +1,95 @@ +use std::collections::HashMap; + +use crate::encore::runtime::v1::{environment::Cloud, Environment}; +use anyhow::Context; + +mod gce; + +#[derive(Debug, Clone, Default)] +pub struct ContainerMetadata { + pub service_id: String, + pub revision_id: String, + pub instance_id: String, + pub env_name: String, +} + +impl ContainerMetadata { + pub fn labels(&self) -> Vec<(String, String)> { + vec![ + ("service_id".to_string(), self.service_id.clone()), + ("revision_id".to_string(), self.revision_id.clone()), + ("instance_id".to_string(), self.instance_id.clone()), + ("env_name".to_string(), self.env_name.clone()), + ] + } + + pub async fn collect(env: &Environment, http_client: &reqwest::Client) -> anyhow::Result { + match env.cloud() { + Cloud::Gcp | Cloud::Encore => Self::collect_gcp(env, http_client).await, + Cloud::Aws | Cloud::Azure | Cloud::Unspecified | Cloud::Local => anyhow::bail!( + "can't collect container meta in {}", + env.cloud().as_str_name() + ), + } + } + + async fn collect_gcp(env: &Environment, http_client: &reqwest::Client) -> anyhow::Result { + let service = std::env::var("K_SERVICE").map_err(|_| { + anyhow::anyhow!("unable to get service ID: env variable 'K_SERVICE' unset") + })?; + + let revision = std::env::var("K_REVISION").map_err(|_| { + anyhow::anyhow!("unable to get revision ID: env variable 'K_REVISION' unset") + })?; + + let revision = revision + .strip_prefix(&format!("{}-", service)) + .unwrap_or(&revision) + .to_string(); + + let instance_id = match std::env::var("K_POD") { + Ok(pod_id) => { + // If we have a K8s POD name, take the last part of it which is the random pod ID + // On GKE, the InstanceID appears to be the Node, so if the multiple replicas are running + // on the same InstanceID then we'd have a collision. This is unlikely, but possible - + // hence why we use the pod ID instead. + pod_id + .rsplit('-') + .next() + .ok_or_else(|| anyhow::anyhow!("invalid instance ID '{}'", pod_id))? + .to_string() + } + Err(_) => { + // If we don't have a K8s POD name, we're running on Cloud Run and can get the instance ID from the metadata server + let metadata_client = gce::GceMetadataClient::new(http_client.clone()); + + metadata_client + .instance_id() + .await + .context("failed to get instance ID from GCE metadata server")? + } + }; + + Ok(Self { + service_id: service, + revision_id: revision, + instance_id, + env_name: env.env_name.clone(), + }) + } + + // TODO(fredr): implement collect for aws +} + +/// Process environment variable substitution in labels +/// Replaces $ENV:VARIABLE_NAME with the actual environment variable value +pub fn process_env_substitution(labels: &mut HashMap) { + for (_, value) in labels.iter_mut() { + if value.starts_with("$ENV:") { + let env_var = &value[5..]; + if let Ok(env_value) = std::env::var(env_var) { + *value = env_value; + } + } + } +} diff --git a/runtimes/core/src/metrics/counter.rs b/runtimes/core/src/metrics/counter.rs new file mode 100644 index 0000000000..8658cb0961 --- /dev/null +++ b/runtimes/core/src/metrics/counter.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +/// A counter with base labels that can be incremented with additional labels +#[derive(Debug, Clone)] +pub struct Counter { + metric_name: &'static str, + base_labels: HashMap, +} + +impl Counter { + /// Create a new counter with the given metric name + pub fn new(metric_name: &'static str) -> Self { + Self { + metric_name, + base_labels: HashMap::new(), + } + } + + /// Add multiple labels to the counter's base labels + pub fn with_labels( + mut self, + new_labels: impl IntoIterator, + ) -> Self + where + K: Into, + V: Into, + { + for (key, value) in new_labels { + self.base_labels.insert(key.into(), value.into()); + } + self + } + + /// Increment the counter + pub fn increment(&self) { + use metrics::{Key, Label, Metadata}; + + metrics::with_recorder(|recorder| { + let labels: Vec