From 5d38f2cf7f312de69a3106556fc43219ca1473c3 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Mon, 5 Dec 2022 18:43:48 +0100 Subject: [PATCH] Add basic support for metrics --- Cargo.lock | 172 ++++++++++++++++++++++++++++ Cargo.nix | 268 +++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 + src/https.rs | 72 +++++++++++- src/main.rs | 15 +++ src/metrics.rs | 83 ++++++++++++++ src/proxy_config.rs | 70 ++++++++++-- 7 files changed, 655 insertions(+), 28 deletions(-) create mode 100644 src/metrics.rs diff --git a/Cargo.lock b/Cargo.lock index f3acee4..e2acbd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,17 @@ dependencies = [ "zstd-safe", ] +[[package]] +name = "async-trait" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -311,6 +322,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "cxx" version = "1.0.83" @@ -355,6 +385,16 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", +] + [[package]] name = "dhat" version = "0.3.2" @@ -979,6 +1019,38 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "dashmap", + "fnv", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand", + "thiserror", +] + +[[package]] +name = "opentelemetry-prometheus" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9328977e479cebe12ce0d3fcecdaea4721d234895a9440c5b5dfd113f0594ac6" +dependencies = [ + "opentelemetry", + "prometheus", + "protobuf", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1017,6 +1089,26 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1035,6 +1127,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "pretty_env_logger" version = "0.4.0" @@ -1084,6 +1182,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "publicsuffix" version = "1.5.6" @@ -1118,6 +1237,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rcgen" version = "0.10.0" @@ -1599,6 +1748,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thousands" version = "0.2.0" @@ -1785,7 +1954,10 @@ dependencies = [ "hyper", "hyper-rustls", "log", + "opentelemetry", + "opentelemetry-prometheus", "pretty_env_logger", + "prometheus", "rcgen", "regex", "reqwest", diff --git a/Cargo.nix b/Cargo.nix index ca15b13..514b19c 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -23,7 +23,7 @@ args@{ ignoreLockHash, }: let - nixifiedLockHash = "8b9764c8874557f30d0d72cb4e6453209d5ab90c71cfe15c3367a366506066d2"; + nixifiedLockHash = "ba43baa059b58c2c0ae0bf86c8d8dc3663b7bd3ecb95274246a3d7123238271a"; workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc; currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock); lockHashIgnored = if ignoreLockHash @@ -184,6 +184,18 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.59" = overridableMkRustCrate (profileName: rec { + name = "async-trait"; + version = "0.1.59"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"; }; + dependencies = { + proc_macro2 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.47" { inherit profileName; }).out; + quote = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.21" { inherit profileName; }).out; + syn = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.105" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".atty."0.2.14" = overridableMkRustCrate (profileName: rec { name = "atty"; version = "0.2.14"; @@ -480,6 +492,35 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".crossbeam-channel."0.5.6" = overridableMkRustCrate (profileName: rec { + name = "crossbeam-channel"; + version = "0.5.6"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"; }; + features = builtins.concatLists [ + [ "crossbeam-utils" ] + [ "default" ] + [ "std" ] + ]; + dependencies = { + cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; + crossbeam_utils = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.14" { inherit profileName; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.14" = overridableMkRustCrate (profileName: rec { + name = "crossbeam-utils"; + version = "0.8.14"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"; }; + features = builtins.concatLists [ + [ "std" ] + ]; + dependencies = { + cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".cxx."1.0.83" = overridableMkRustCrate (profileName: rec { name = "cxx"; version = "1.0.83"; @@ -538,6 +579,20 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".dashmap."4.0.2" = overridableMkRustCrate (profileName: rec { + name = "dashmap"; + version = "4.0.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"; }; + features = builtins.concatLists [ + [ "default" ] + ]; + dependencies = { + cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; + num_cpus = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num_cpus."1.14.0" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".dhat."0.3.2" = overridableMkRustCrate (profileName: rec { name = "dhat"; version = "0.3.2"; @@ -752,6 +807,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"; }; features = builtins.concatLists [ + [ "default" ] [ "std" ] ]; dependencies = { @@ -846,6 +902,9 @@ in version = "0.2.8"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"; }; + features = builtins.concatLists [ + [ "std" ] + ]; dependencies = { cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; ${ if hostPlatform.isUnix then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.138" { inherit profileName; }).out; @@ -1184,10 +1243,10 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"; }; dependencies = { - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "scopeguard" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".scopeguard."1.1.0" { inherit profileName; }).out; + scopeguard = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".scopeguard."1.1.0" { inherit profileName; }).out; }; buildDependencies = { - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "autocfg" else null } = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out; + autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out; }; }); @@ -1402,17 +1461,63 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" = overridableMkRustCrate (profileName: rec { + name = "opentelemetry"; + version = "0.17.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8"; }; + features = builtins.concatLists [ + [ "async-trait" ] + [ "crossbeam-channel" ] + [ "dashmap" ] + [ "default" ] + [ "fnv" ] + [ "metrics" ] + [ "percent-encoding" ] + [ "pin-project" ] + [ "rand" ] + [ "trace" ] + ]; + dependencies = { + async_trait = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.59" { profileName = "__noProfile"; }).out; + crossbeam_channel = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-channel."0.5.6" { inherit profileName; }).out; + dashmap = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".dashmap."4.0.2" { inherit profileName; }).out; + fnv = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".fnv."1.0.7" { inherit profileName; }).out; + futures_channel = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-channel."0.3.25" { inherit profileName; }).out; + futures_executor = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-executor."0.3.25" { inherit profileName; }).out; + futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.25" { inherit profileName; }).out; + ${ if hostPlatform.parsed.cpu.name == "wasm32" then "js_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".js-sys."0.3.60" { inherit profileName; }).out; + lazy_static = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }).out; + percent_encoding = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.2.0" { inherit profileName; }).out; + pin_project = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.12" { inherit profileName; }).out; + rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out; + thiserror = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.37" { inherit profileName; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" = overridableMkRustCrate (profileName: rec { + name = "opentelemetry-prometheus"; + version = "0.10.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "9328977e479cebe12ce0d3fcecdaea4721d234895a9440c5b5dfd113f0594ac6"; }; + dependencies = { + opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out; + prometheus = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.3" { inherit profileName; }).out; + protobuf = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".protobuf."2.28.0" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.12.1" = overridableMkRustCrate (profileName: rec { name = "parking_lot"; version = "0.12.1"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap") "default") + [ "default" ] ]; dependencies = { - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "lock_api" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lock_api."0.4.9" { inherit profileName; }).out; - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "parking_lot_core" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot_core."0.9.5" { inherit profileName; }).out; + lock_api = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lock_api."0.4.9" { inherit profileName; }).out; + parking_lot_core = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot_core."0.9.5" { inherit profileName; }).out; }; }); @@ -1422,11 +1527,11 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"; }; dependencies = { - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "cfg_if" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; - ${ if (rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap") && hostPlatform.isUnix then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.138" { inherit profileName; }).out; - ${ if (rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap") && hostPlatform.parsed.kernel.name == "redox" then "syscall" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".redox_syscall."0.2.16" { inherit profileName; }).out; - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "smallvec" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".smallvec."1.10.0" { inherit profileName; }).out; - ${ if (rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap") && hostPlatform.isWindows then "windows_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows-sys."0.42.0" { inherit profileName; }).out; + cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; + ${ if hostPlatform.isUnix then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.138" { inherit profileName; }).out; + ${ if hostPlatform.parsed.kernel.name == "redox" then "syscall" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".redox_syscall."0.2.16" { inherit profileName; }).out; + smallvec = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".smallvec."1.10.0" { inherit profileName; }).out; + ${ if hostPlatform.isWindows then "windows_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows-sys."0.42.0" { inherit profileName; }).out; }; }); @@ -1451,6 +1556,28 @@ in ]; }); + "registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.12" = overridableMkRustCrate (profileName: rec { + name = "pin-project"; + version = "1.0.12"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"; }; + dependencies = { + pin_project_internal = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-internal."1.0.12" { profileName = "__noProfile"; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".pin-project-internal."1.0.12" = overridableMkRustCrate (profileName: rec { + name = "pin-project-internal"; + version = "1.0.12"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"; }; + dependencies = { + proc_macro2 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.47" { inherit profileName; }).out; + quote = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.21" { inherit profileName; }).out; + syn = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.105" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.9" = overridableMkRustCrate (profileName: rec { name = "pin-project-lite"; version = "0.2.9"; @@ -1472,6 +1599,17 @@ in src = fetchCratesIo { inherit name version; sha256 = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"; }; }); + "registry+https://github.com/rust-lang/crates.io-index".ppv-lite86."0.2.17" = overridableMkRustCrate (profileName: rec { + name = "ppv-lite86"; + version = "0.2.17"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"; }; + features = builtins.concatLists [ + [ "simd" ] + [ "std" ] + ]; + }); + "registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" = overridableMkRustCrate (profileName: rec { name = "pretty_env_logger"; version = "0.4.0"; @@ -1540,6 +1678,33 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.3" = overridableMkRustCrate (profileName: rec { + name = "prometheus"; + version = "0.13.3"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"; }; + features = builtins.concatLists [ + [ "default" ] + [ "protobuf" ] + ]; + dependencies = { + cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; + fnv = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".fnv."1.0.7" { inherit profileName; }).out; + lazy_static = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }).out; + memchr = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.5.0" { inherit profileName; }).out; + parking_lot = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.12.1" { inherit profileName; }).out; + protobuf = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".protobuf."2.28.0" { inherit profileName; }).out; + thiserror = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.37" { inherit profileName; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".protobuf."2.28.0" = overridableMkRustCrate (profileName: rec { + name = "protobuf"; + version = "2.28.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"; }; + }); + "registry+https://github.com/rust-lang/crates.io-index".publicsuffix."1.5.6" = overridableMkRustCrate (profileName: rec { name = "publicsuffix"; version = "1.5.6"; @@ -1582,6 +1747,55 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" = overridableMkRustCrate (profileName: rec { + name = "rand"; + version = "0.8.5"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"; }; + features = builtins.concatLists [ + [ "alloc" ] + [ "getrandom" ] + [ "libc" ] + [ "rand_chacha" ] + [ "std" ] + [ "std_rng" ] + ]; + dependencies = { + ${ if hostPlatform.isUnix then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.138" { inherit profileName; }).out; + rand_chacha = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_chacha."0.3.1" { inherit profileName; }).out; + rand_core = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.6.4" { inherit profileName; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_chacha."0.3.1" = overridableMkRustCrate (profileName: rec { + name = "rand_chacha"; + version = "0.3.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"; }; + features = builtins.concatLists [ + [ "std" ] + ]; + dependencies = { + ppv_lite86 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".ppv-lite86."0.2.17" { inherit profileName; }).out; + rand_core = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.6.4" { inherit profileName; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_core."0.6.4" = overridableMkRustCrate (profileName: rec { + name = "rand_core"; + version = "0.6.4"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"; }; + features = builtins.concatLists [ + [ "alloc" ] + [ "getrandom" ] + [ "std" ] + ]; + dependencies = { + getrandom = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".getrandom."0.2.8" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".rcgen."0.10.0" = overridableMkRustCrate (profileName: rec { name = "rcgen"; version = "0.10.0"; @@ -1605,7 +1819,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"; }; dependencies = { - ${ if rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap" then "bitflags" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }).out; + bitflags = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }).out; }; }); @@ -2181,6 +2395,7 @@ in [ "proc-macro" ] [ "quote" ] [ "visit" ] + [ "visit-mut" ] ]; dependencies = { proc_macro2 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.47" { inherit profileName; }).out; @@ -2239,6 +2454,28 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.37" = overridableMkRustCrate (profileName: rec { + name = "thiserror"; + version = "1.0.37"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"; }; + dependencies = { + thiserror_impl = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror-impl."1.0.37" { profileName = "__noProfile"; }).out; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".thiserror-impl."1.0.37" = overridableMkRustCrate (profileName: rec { + name = "thiserror-impl"; + version = "1.0.37"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"; }; + dependencies = { + proc_macro2 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.47" { inherit profileName; }).out; + quote = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.21" { inherit profileName; }).out; + syn = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.105" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".thousands."0.2.0" = overridableMkRustCrate (profileName: rec { name = "thousands"; version = "0.2.0"; @@ -2507,7 +2744,10 @@ in hyper = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.23" { inherit profileName; }).out; hyper_rustls = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper-rustls."0.23.1" { inherit profileName; }).out; log = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.17" { inherit profileName; }).out; + opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out; + opentelemetry_prometheus = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" { inherit profileName; }).out; pretty_env_logger = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" { inherit profileName; }).out; + prometheus = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.3" { inherit profileName; }).out; rcgen = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rcgen."0.10.0" { inherit profileName; }).out; regex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".regex."1.7.0" { inherit profileName; }).out; reqwest = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".reqwest."0.11.13" { inherit profileName; }).out; @@ -2980,9 +3220,9 @@ in [ "Win32_Storage_FileSystem" ] [ "Win32_System" ] [ "Win32_System_IO" ] - (lib.optional (rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap") "Win32_System_LibraryLoader") + [ "Win32_System_LibraryLoader" ] [ "Win32_System_Pipes" ] - (lib.optional (rootFeatures' ? "tricot/dhat" || rootFeatures' ? "tricot/dhat-heap") "Win32_System_SystemServices") + [ "Win32_System_SystemServices" ] [ "Win32_System_WindowsProgramming" ] [ "default" ] ]; diff --git a/Cargo.toml b/Cargo.toml index a41b3c3..416038f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,9 @@ accept-encoding-fork = "0.2.0-alpha.3" async-compression = { version = "0.3", features = ["tokio", "gzip", "zstd", "deflate", "brotli"] } tokio-util = { version = "0.7", features = ["io"] } uuid = { version = "1.2", features = ["v4"] } +opentelemetry = "0.17" +opentelemetry-prometheus = "0.10" +prometheus = "0.13" dhat = { version = "0.3", optional = true } diff --git a/src/https.rs b/src/https.rs index ba4365d..cb5e1c8 100644 --- a/src/https.rs +++ b/src/https.rs @@ -21,6 +21,8 @@ use tokio::sync::watch; use tokio_rustls::TlsAcceptor; use tokio_util::io::{ReaderStream, StreamReader}; +use opentelemetry::{metrics, KeyValue}; + use crate::cert_store::{CertStore, StoreResolver}; use crate::proxy_config::ProxyConfig; use crate::reverse_proxy; @@ -33,6 +35,11 @@ pub struct HttpsConfig { pub compress_mime_types: Vec, } +struct HttpsMetrics { + requests_received: metrics::Counter, + requests_served: metrics::Counter, +} + pub async fn serve_https( config: HttpsConfig, cert_store: Arc, @@ -41,6 +48,18 @@ pub async fn serve_https( ) -> Result<()> { let config = Arc::new(config); + let meter = opentelemetry::global::meter("tricot"); + let metrics = Arc::new(HttpsMetrics { + requests_received: meter + .u64_counter("https_requests_received") + .with_description("Total number of requests received over HTTPS") + .init(), + requests_served: meter + .u64_counter("https_requests_served") + .with_description("Total number of requests served over HTTPS") + .init(), + }); + let mut tls_cfg = rustls::ServerConfig::builder() .with_safe_defaults() .with_no_client_auth() @@ -71,6 +90,7 @@ pub async fn serve_https( let rx_proxy_config = rx_proxy_config.clone(); let tls_acceptor = tls_acceptor.clone(); let config = config.clone(); + let metrics = metrics.clone(); let mut must_exit_2 = must_exit.clone(); let conn = tokio::spawn(async move { @@ -84,7 +104,8 @@ pub async fn serve_https( let https_config = config.clone(); let proxy_config: Arc = rx_proxy_config.borrow().clone(); - handle_outer(remote_addr, req, https_config, proxy_config) + let metrics = metrics.clone(); + handle_outer(remote_addr, req, https_config, proxy_config, metrics) }), ) .with_upgrades(); @@ -124,17 +145,43 @@ async fn handle_outer( req: Request, https_config: Arc, proxy_config: Arc, + metrics: Arc, ) -> Result, Infallible> { - match handle(remote_addr, req, https_config, proxy_config).await { + let mut tags = vec![ + KeyValue::new("method", req.method().to_string()), + KeyValue::new( + "host", + req.uri() + .authority() + .map(|auth| auth.to_string()) + .or_else(|| { + req.headers() + .get("host") + .map(|host| host.to_str().unwrap_or_default().to_string()) + }) + .unwrap_or_default(), + ), + ]; + metrics.requests_received.add(1, &tags); + + let resp = match handle(remote_addr, req, https_config, proxy_config, &mut tags).await { Err(e) => { warn!("Handler error: {}", e); - Ok(Response::builder() + Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(Body::from(format!("{}", e))) - .unwrap()) + .unwrap() } - Ok(r) => Ok(r), - } + Ok(r) => r, + }; + + tags.push(KeyValue::new( + "response_code", + resp.status().as_u16().to_string(), + )); + metrics.requests_served.add(1, &tags); + + Ok(resp) } // Custom echo service, handling two different routes and a @@ -144,6 +191,7 @@ async fn handle( req: Request, https_config: Arc, proxy_config: Arc, + tags: &mut Vec, ) -> Result, anyhow::Error> { let method = req.method().clone(); let uri = req.uri().to_string(); @@ -184,6 +232,18 @@ async fn handle( }); if let Some(proxy_to) = best_match { + tags.push(KeyValue::new("service_name", proxy_to.service_name.clone())); + tags.push(KeyValue::new( + "target_addr", + proxy_to.target_addr.to_string(), + )); + tags.push(KeyValue::new( + "https_target", + proxy_to.https_target.to_string(), + )); + tags.push(KeyValue::new("same_node", proxy_to.same_node.to_string())); + tags.push(KeyValue::new("same_site", proxy_to.same_site.to_string())); + proxy_to.calls.fetch_add(1, Ordering::SeqCst); debug!("{}{} -> {}", host, path, proxy_to); diff --git a/src/main.rs b/src/main.rs index edc79b4..cb39c49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ mod cert_store; mod consul; mod http; mod https; +mod metrics; mod proxy_config; mod reverse_proxy; mod tls_util; @@ -80,6 +81,10 @@ struct Opt { )] pub https_bind_addr: SocketAddr, + /// Bind address for metrics server (Prometheus format over HTTP) + #[structopt(long = "metrics-bind-addr", env = "TRICOT_METRICS_BIND_ADDR")] + pub metrics_bind_addr: Option, + /// E-mail address for Let's Encrypt certificate requests #[structopt(long = "letsencrypt-email", env = "TRICOT_LETSENCRYPT_EMAIL")] pub letsencrypt_email: String, @@ -123,6 +128,8 @@ async fn main() { let _ = provoke_exit.send(true); }; + let metrics_server = metrics::MetricsServer::init(opt.metrics_bind_addr); + let consul_config = consul::ConsulConfig { addr: opt.consul_addr.clone(), ca_cert: opt.consul_ca_cert.clone(), @@ -143,6 +150,13 @@ async fn main() { exit_on_err.clone(), ); + let metrics_task = tokio::spawn( + metrics_server + .run(wait_from(exit_signal.clone())) + .map_err(exit_on_err.clone()) + .then(|_| async { info!("Metrics server exited") }), + ); + let http_task = tokio::spawn( http::serve_http( opt.http_bind_addr, @@ -176,6 +190,7 @@ async fn main() { let dump_task = tokio::spawn(dump_config_on_change(rx_proxy_config, exit_signal.clone())); + let _ = metrics_task.await.expect("Tokio task await failure"); let _ = http_task.await.expect("Tokio task await failure"); let _ = https_task.await.expect("Tokio task await failure"); let _ = dump_task.await.expect("Tokio task await failure"); diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..c635269 --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,83 @@ +use std::convert::Infallible; +use std::net::SocketAddr; +use std::sync::Arc; + +use anyhow::Result; +use futures::future::*; +use log::*; + +use hyper::{ + header::CONTENT_TYPE, + service::{make_service_fn, service_fn}, + Body, Method, Request, Response, Server, +}; +use opentelemetry_prometheus::PrometheusExporter; +use prometheus::{Encoder, TextEncoder}; + +pub struct MetricsServer { + bind_addr: Option, + exporter: PrometheusExporter, +} + +impl MetricsServer { + pub fn init(bind_addr: Option) -> MetricsServer { + let exporter = opentelemetry_prometheus::exporter().init(); + Self { + bind_addr, + exporter, + } + } + + pub async fn run(self, shutdown_signal: impl Future) -> Result<()> { + if let Some(addr) = self.bind_addr { + let metrics_server = Arc::new(self); + + let make_svc = make_service_fn(move |_conn| { + let metrics_server = metrics_server.clone(); + async move { + Ok::<_, Infallible>(service_fn(move |req| { + metrics_server.clone().serve_req(req) + })) + } + }); + + let server = Server::bind(&addr).serve(make_svc); + let graceful = server.with_graceful_shutdown(shutdown_signal); + info!("Metrics server listening on http://{}", addr); + + graceful.await?; + } else { + info!("Metrics server is disabled"); + } + + Ok(()) + } + + async fn serve_req( + self: Arc, + req: Request, + ) -> Result, hyper::Error> { + debug!("{} {}", req.method(), req.uri()); + + let response = match (req.method(), req.uri().path()) { + (&Method::GET, "/metrics") => { + let mut buffer = vec![]; + let encoder = TextEncoder::new(); + let metric_families = self.exporter.registry().gather(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + + Response::builder() + .status(200) + .header(CONTENT_TYPE, encoder.format_type()) + .body(Body::from(buffer)) + .unwrap() + } + _ => Response::builder() + .status(404) + .body(Body::from("Not implemented")) + .unwrap(), + }; + + Ok(response) + } +} diff --git a/src/proxy_config.rs b/src/proxy_config.rs index e45cc7b..24ade8b 100644 --- a/src/proxy_config.rs +++ b/src/proxy_config.rs @@ -4,6 +4,7 @@ use std::sync::{atomic, Arc}; use std::{cmp, time::Duration}; use anyhow::Result; +use opentelemetry::{metrics, KeyValue}; use futures::future::BoxFuture; use futures::stream::{FuturesUnordered, StreamExt}; @@ -38,6 +39,15 @@ impl HostDescription { } } +impl std::fmt::Display for HostDescription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HostDescription::Hostname(h) => write!(f, "{}", h), + HostDescription::Pattern(p) => write!(f, "Pattern('{}')", p.as_str()), + } + } +} + #[derive(Debug)] pub struct ProxyEntry { /// Publicly exposed TLS hostnames for matching this rule @@ -47,6 +57,8 @@ pub struct ProxyEntry { /// Priority with which this rule is considered (highest first) pub priority: u32, + /// Consul service name + pub service_name: String, /// Node address (ip+port) to handle requests that match this entry pub target_addr: SocketAddr, /// Is the target serving HTTPS instead of HTTP? @@ -75,14 +87,11 @@ impl std::fmt::Display for ProxyEntry { write!(f, "https://")?; } write!(f, "{} ", self.target_addr)?; - match &self.host { - HostDescription::Hostname(h) => write!(f, "{}", h)?, - HostDescription::Pattern(p) => write!(f, "Pattern('{}')", p.as_str())?, - } write!( f, - "{} {}", - self.path_prefix.as_ref().unwrap_or(&String::new()), + "{}{} {}", + self.host, + self.path_prefix.as_deref().unwrap_or_default(), self.priority )?; if self.same_node { @@ -113,6 +122,7 @@ fn retry_to_time(retries: u32, max_time: Duration) -> Duration { } fn parse_tricot_tag( + service_name: String, tag: &str, target_addr: SocketAddr, add_headers: &[(String, String)], @@ -148,6 +158,7 @@ fn parse_tricot_tag( }; Some(ProxyEntry { + service_name, target_addr, https_target: (splits[0] == "tricot-https"), host, @@ -178,7 +189,7 @@ fn parse_consul_catalog( let mut entries = vec![]; - for (_, svc) in catalog.services.iter() { + for (service_name, svc) in catalog.services.iter() { let ip_addr = match svc.address.parse() { Ok(ip) => ip, _ => match catalog.node.address.parse() { @@ -210,7 +221,14 @@ fn parse_consul_catalog( } for tag in svc.tags.iter() { - if let Some(ent) = parse_tricot_tag(tag, addr, &add_headers[..], same_node, same_site) { + if let Some(ent) = parse_tricot_tag( + service_name.clone(), + tag, + addr, + &add_headers[..], + same_node, + same_site, + ) { entries.push(ent); } } @@ -239,6 +257,7 @@ pub fn spawn_proxy_config_task( entries: Vec::new(), })); + let metrics = ProxyConfigMetrics::new(rx.clone()); let consul = Arc::new(consul); tokio::spawn(async move { @@ -348,11 +367,46 @@ pub fn spawn_proxy_config_task( tx.send(Arc::new(config)).expect("Internal error"); } + + drop(metrics); // ensure Metrics lives up to here }); rx } +// ---- + +struct ProxyConfigMetrics { + _proxy_config_entries: metrics::ValueObserver, +} + +impl ProxyConfigMetrics { + fn new(rx: watch::Receiver>) -> Self { + let meter = opentelemetry::global::meter("tricot"); + Self { + _proxy_config_entries: meter + .u64_value_observer("proxy_config_entries", move |observer| { + let mut patterns = HashMap::new(); + for ent in rx.borrow().entries.iter() { + let pat = format!( + "{}{}", + ent.host, + ent.path_prefix.as_deref().unwrap_or_default() + ); + *patterns.entry(pat).or_default() += 1; + } + for (pat, num) in patterns { + observer.observe(num, &[KeyValue::new("host", pat)]); + } + }) + .with_description("Number of proxy entries (back-ends) configured in Tricot") + .init(), + } + } +} + +// ---- + #[cfg(test)] mod tests { use super::*;