diff --git a/.changesets/feat_jr_add_fleet_awareness_plugin.md b/.changesets/feat_jr_add_fleet_awareness_plugin.md new file mode 100644 index 0000000000..da6bc19375 --- /dev/null +++ b/.changesets/feat_jr_add_fleet_awareness_plugin.md @@ -0,0 +1,13 @@ +### Adds Fleet Awareness Plugin + +Adds a new plugin that reports telemetry to Apollo on the configuration and deployment of the Router. Initially this +covers CPU & Memory usage, CPU Frequency, and other deployment characteristics such as Operating System, and Cloud +Provider. For more details, along with a full list of data captured and how to opt out, please see our guidance +[here](https://www.apollographql.com/docs/graphos/reference/data-privacy). + +As part of the above PluginPrivate has been extended with a new `activate` hook which is guaranteed to be called once +the OTEL meter has been refreshed. This ensures that code, particularly that concerned with gauges, will survive a hot +reload of the router. + +By [@jonathanrainer](https://github.com/jonathanrainer), [@nmoutschen](https://github.com/nmoutschen), [@loshz](https://github.com/loshz) +in https://github.com/apollographql/router/pull/6151 diff --git a/Cargo.lock b/Cargo.lock index d90809fbaf..a91864cf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -176,6 +191,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "apollo-environment-detector" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c628346f10c7615f1dd9e3f486d55bcad9edb667f4444dcbcb9cb5943815583a" +dependencies = [ + "libc", + "serde", + "wmi", +] + [[package]] name = "apollo-federation" version = "1.58.0" @@ -237,6 +263,7 @@ dependencies = [ "ahash", "anyhow", "apollo-compiler", + "apollo-environment-detector", "apollo-federation", "arc-swap", "async-channel 1.9.0", @@ -362,6 +389,7 @@ dependencies = [ "static_assertions", "strum_macros 0.26.4", "sys-info", + "sysinfo", "tempfile", "test-log", "thiserror", @@ -651,7 +679,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.25.0", - "syn 2.0.76", + "syn 2.0.90", "thiserror", ] @@ -819,7 +847,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -836,7 +864,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -1410,7 +1438,7 @@ dependencies = [ "proc-macro2", "quote", "str_inflector", - "syn 2.0.76", + "syn 2.0.90", "thiserror", "try_match", ] @@ -1517,6 +1545,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + [[package]] name = "ci_info" version = "0.14.14" @@ -1586,7 +1627,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -1926,7 +1967,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -1937,7 +1978,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -1997,7 +2038,7 @@ checksum = "3c65c2ffdafc1564565200967edc4851c7b55422d3913466688907efd05ea26f" dependencies = [ "deno-proc-macro-rules-macros", "proc-macro2", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2009,7 +2050,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2064,7 +2105,7 @@ dependencies = [ "strum 0.25.0", "strum_macros 0.25.3", "syn 1.0.109", - "syn 2.0.76", + "syn 2.0.90", "thiserror", ] @@ -2145,7 +2186,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2158,7 +2199,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2249,7 +2290,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2355,7 +2396,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2629,7 +2670,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2797,7 +2838,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -2896,7 +2937,7 @@ checksum = "b0e085ded9f1267c32176b40921b9754c474f7dd96f7e808d4a982e48aa1e854" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -3446,6 +3487,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3823,9 +3887,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libfuzzer-sys" @@ -3933,7 +3997,7 @@ checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -4122,7 +4186,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -4194,6 +4258,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -4783,7 +4856,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -4826,7 +4899,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -4904,7 +4977,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -5013,9 +5086,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -5115,7 +5188,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -5513,7 +5586,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -5645,7 +5718,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.76", + "syn 2.0.90", "walkdir", ] @@ -5821,7 +5894,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -5906,9 +5979,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -5924,13 +5997,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -5941,7 +6014,7 @@ checksum = "afb2522c2a87137bf6c2b3493127fed12877ef1b9476f074d6664edc98acd8a7" dependencies = [ "quote", "regex", - "syn 2.0.76", + "syn 2.0.90", "thiserror", ] @@ -5953,7 +6026,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6075,7 +6148,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6302,7 +6375,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6315,7 +6388,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6349,9 +6422,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -6374,6 +6447,20 @@ dependencies = [ "libc", ] +[[package]] +name = "sysinfo" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -6450,7 +6537,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6485,7 +6572,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6663,7 +6750,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -6932,7 +7019,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -7044,7 +7131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -7080,7 +7167,7 @@ checksum = "b0a91713132798caecb23c977488945566875e7b61b902fb111979871cbff34e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] @@ -7423,7 +7510,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -7457,7 +7544,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7546,6 +7633,132 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -7801,6 +8014,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "wmi" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70df482bbec7017ce4132154233642de658000b24b805345572036782a66ad55" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror", + "windows 0.58.0", + "windows-core 0.58.0", +] + [[package]] name = "wsl" version = "0.1.0" @@ -7845,7 +8073,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.90", ] [[package]] diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index f2cedca917..5f1991f5be 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -212,6 +212,7 @@ serde_yaml = "0.8.26" static_assertions = "1.1.0" strum_macros = "0.26.0" sys-info = "0.9.1" +sysinfo = { version = "0.32.0", features = ["windows"] } thiserror = "1.0.61" tokio.workspace = true tokio-stream = { version = "0.1.15", features = ["sync", "net"] } @@ -276,6 +277,7 @@ bytesize = { version = "1.3.0", features = ["serde"] } ahash = "0.8.11" itoa = "1.0.9" ryu = "1.0.15" +apollo-environment-detector = "0.1.0" [target.'cfg(macos)'.dependencies] uname = "0.1.1" diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 0dc3c12eab..a7a0bc8d08 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -1267,6 +1267,9 @@ snapshot_kind: text "type": "object" }, "Conf5": { + "type": "object" + }, + "Conf6": { "anyOf": [ { "additionalProperties": { @@ -1278,7 +1281,7 @@ snapshot_kind: text ], "description": "Subgraph URL mappings" }, - "Conf6": { + "Conf7": { "additionalProperties": false, "description": "Configuration for the Rhai Plugin", "properties": { @@ -1295,7 +1298,7 @@ snapshot_kind: text }, "type": "object" }, - "Conf7": { + "Conf8": { "additionalProperties": false, "description": "Telemetry configuration", "properties": { @@ -8343,6 +8346,10 @@ snapshot_kind: text "description": "Type conditioned fetching configuration.", "type": "boolean" }, + "fleet_detector": { + "$ref": "#/definitions/Conf5", + "description": "#/definitions/Conf5" + }, "forbid_mutations": { "$ref": "#/definitions/ForbidMutationsConfig", "description": "#/definitions/ForbidMutationsConfig" @@ -8368,8 +8375,8 @@ snapshot_kind: text "description": "#/definitions/Config" }, "override_subgraph_url": { - "$ref": "#/definitions/Conf5", - "description": "#/definitions/Conf5" + "$ref": "#/definitions/Conf6", + "description": "#/definitions/Conf6" }, "persisted_queries": { "$ref": "#/definitions/PersistedQueries", @@ -8392,8 +8399,8 @@ snapshot_kind: text "description": "#/definitions/Config8" }, "rhai": { - "$ref": "#/definitions/Conf6", - "description": "#/definitions/Conf6" + "$ref": "#/definitions/Conf7", + "description": "#/definitions/Conf7" }, "sandbox": { "$ref": "#/definitions/Sandbox", @@ -8408,8 +8415,8 @@ snapshot_kind: text "description": "#/definitions/Supergraph" }, "telemetry": { - "$ref": "#/definitions/Conf7", - "description": "#/definitions/Conf7" + "$ref": "#/definitions/Conf8", + "description": "#/definitions/Conf8" }, "tls": { "$ref": "#/definitions/Tls", diff --git a/apollo-router/src/executable.rs b/apollo-router/src/executable.rs index 86bdee162f..a67e294a7f 100644 --- a/apollo-router/src/executable.rs +++ b/apollo-router/src/executable.rs @@ -62,6 +62,7 @@ pub(crate) static mut DHAT_HEAP_PROFILER: OnceCell = OnceCell::n pub(crate) static mut DHAT_AD_HOC_PROFILER: OnceCell = OnceCell::new(); pub(crate) const APOLLO_ROUTER_DEV_ENV: &str = "APOLLO_ROUTER_DEV"; +pub(crate) const APOLLO_TELEMETRY_DISABLED: &str = "APOLLO_TELEMETRY_DISABLED"; // Note: Constructor/Destructor functions may not play nicely with tracing, since they run after // main completes, so don't use tracing, use println!() and eprintln!().. @@ -240,7 +241,7 @@ pub struct Opt { apollo_uplink_poll_interval: Duration, /// Disable sending anonymous usage information to Apollo. - #[clap(long, env = "APOLLO_TELEMETRY_DISABLED", value_parser = FalseyValueParser::new())] + #[clap(long, env = APOLLO_TELEMETRY_DISABLED, value_parser = FalseyValueParser::new())] anonymous_telemetry_disabled: bool, /// The timeout for an http call to Apollo uplink. Defaults to 30s. diff --git a/apollo-router/src/metrics/filter.rs b/apollo-router/src/metrics/filter.rs index fa6dad38df..59bd991966 100644 --- a/apollo-router/src/metrics/filter.rs +++ b/apollo-router/src/metrics/filter.rs @@ -94,7 +94,7 @@ impl FilterMeterProvider { .delegate(delegate) .allow( Regex::new( - r"apollo\.(graphos\.cloud|router\.(operations?|lifecycle|config|schema|query|query_planning|telemetry))(\..*|$)|apollo_router_uplink_fetch_count_total|apollo_router_uplink_fetch_duration_seconds", + r"apollo\.(graphos\.cloud|router\.(operations?|lifecycle|config|schema|query|query_planning|telemetry|instance))(\..*|$)|apollo_router_uplink_fetch_count_total|apollo_router_uplink_fetch_duration_seconds", ) .expect("regex should have been valid"), ) @@ -105,7 +105,7 @@ impl FilterMeterProvider { FilterMeterProvider::builder() .delegate(delegate) .deny( - Regex::new(r"apollo\.router\.(config|entities)(\..*|$)") + Regex::new(r"apollo\.router\.(config|entities|instance)(\..*|$)") .expect("regex should have been valid"), ) .build() @@ -244,7 +244,6 @@ impl opentelemetry::metrics::MeterProvider for FilterMeterProvider { #[cfg(test)] mod test { - use opentelemetry::metrics::MeterProvider; use opentelemetry::metrics::Unit; use opentelemetry::runtime; diff --git a/apollo-router/src/plugin/mod.rs b/apollo-router/src/plugin/mod.rs index 3354868491..bfdaa631e3 100644 --- a/apollo-router/src/plugin/mod.rs +++ b/apollo-router/src/plugin/mod.rs @@ -630,6 +630,9 @@ pub(crate) trait PluginPrivate: Send + Sync + 'static { fn web_endpoints(&self) -> MultiMap { MultiMap::new() } + + /// The point of no return this plugin is about to go live + fn activate(&self) {} } #[async_trait] @@ -677,6 +680,8 @@ where fn web_endpoints(&self) -> MultiMap { PluginUnstable::web_endpoints(self) } + + fn activate(&self) {} } fn get_type_of(_: &T) -> &'static str { @@ -733,6 +738,9 @@ pub(crate) trait DynPlugin: Send + Sync + 'static { /// Support downcasting #[cfg(test)] fn as_any_mut(&mut self) -> &mut dyn std::any::Any; + + /// The point of no return, this plugin is about to go live + fn activate(&self) {} } #[async_trait] @@ -783,6 +791,19 @@ where fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } + + fn activate(&self) { + self.activate() + } +} + +impl From for Box +where + T: PluginPrivate, +{ + fn from(value: T) -> Self { + Box::new(value) + } } /// Register a plugin with a group and a name diff --git a/apollo-router/src/plugins/fleet_detector.rs b/apollo-router/src/plugins/fleet_detector.rs new file mode 100644 index 0000000000..5e3d9c7513 --- /dev/null +++ b/apollo-router/src/plugins/fleet_detector.rs @@ -0,0 +1,294 @@ +use std::env; +use std::env::consts::ARCH; +use std::env::consts::OS; +use std::sync::Arc; +use std::sync::Mutex; +use std::time::Duration; +use std::time::Instant; + +use opentelemetry::metrics::MeterProvider; +use opentelemetry_api::metrics::ObservableGauge; +use opentelemetry_api::metrics::Unit; +use opentelemetry_api::KeyValue; +use schemars::JsonSchema; +use serde::Deserialize; +use sysinfo::System; +use tower::BoxError; +use tracing::debug; + +use crate::executable::APOLLO_TELEMETRY_DISABLED; +use crate::metrics::meter_provider; +use crate::plugin::PluginInit; +use crate::plugin::PluginPrivate; + +const REFRESH_INTERVAL: Duration = Duration::from_secs(60); +const COMPUTE_DETECTOR_THRESHOLD: u16 = 24576; + +#[derive(Debug, Default, Deserialize, JsonSchema)] +struct Conf {} + +#[derive(Debug)] +struct SystemGetter { + system: System, + start: Instant, +} + +impl SystemGetter { + fn new() -> Self { + let mut system = System::new(); + system.refresh_all(); + Self { + system, + start: Instant::now(), + } + } + + fn get_system(&mut self) -> &System { + if self.start.elapsed() < REFRESH_INTERVAL { + &self.system + } else { + self.start = Instant::now(); + self.system.refresh_cpu_all(); + self.system.refresh_memory(); + &self.system + } + } +} + +#[derive(Default)] +enum GaugeStore { + #[default] + Disabled, + Pending, + Active(Vec>), +} + +impl GaugeStore { + fn active() -> GaugeStore { + let system_getter = Arc::new(Mutex::new(SystemGetter::new())); + let meter = meter_provider().meter("apollo/router"); + + let mut gauges = Vec::new(); + { + let mut attributes = Vec::new(); + // CPU architecture + attributes.push(KeyValue::new("host.arch", get_otel_arch())); + // Operating System + attributes.push(KeyValue::new("os.type", get_otel_os())); + if OS == "linux" { + attributes.push(KeyValue::new( + "linux.distribution", + System::distribution_id(), + )); + } + // Compute Environment + if let Some(env) = apollo_environment_detector::detect_one(COMPUTE_DETECTOR_THRESHOLD) { + attributes.push(KeyValue::new("cloud.platform", env.platform_code())); + if let Some(cloud_provider) = env.cloud_provider() { + attributes.push(KeyValue::new("cloud.provider", cloud_provider.code())); + } + } + gauges.push( + meter + .u64_observable_gauge("apollo.router.instance") + .with_description("The number of instances the router is running on") + .with_callback(move |i| { + i.observe(1, &attributes); + }) + .init(), + ); + } + { + let system_getter = system_getter.clone(); + gauges.push( + meter + .u64_observable_gauge("apollo.router.instance.cpu_freq") + .with_description( + "The CPU frequency of the underlying instance the router is deployed to", + ) + .with_unit(Unit::new("Mhz")) + .with_callback(move |i| { + let local_system_getter = system_getter.clone(); + let mut system_getter = local_system_getter.lock().unwrap(); + let system = system_getter.get_system(); + let cpus = system.cpus(); + let cpu_freq = + cpus.iter().map(|cpu| cpu.frequency()).sum::() / cpus.len() as u64; + i.observe(cpu_freq, &[]) + }) + .init(), + ); + } + { + let system_getter = system_getter.clone(); + gauges.push( + meter + .u64_observable_gauge("apollo.router.instance.cpu_count") + .with_description( + "The number of CPUs reported by the instance the router is running on", + ) + .with_callback(move |i| { + let local_system_getter = system_getter.clone(); + let mut system_getter = local_system_getter.lock().unwrap(); + let system = system_getter.get_system(); + let cpu_count = detect_cpu_count(system); + i.observe(cpu_count, &[KeyValue::new("host.arch", get_otel_arch())]) + }) + .init(), + ); + } + { + let system_getter = system_getter.clone(); + gauges.push( + meter + .u64_observable_gauge("apollo.router.instance.total_memory") + .with_description( + "The amount of memory reported by the instance the router is running on", + ) + .with_callback(move |i| { + let local_system_getter = system_getter.clone(); + let mut system_getter = local_system_getter.lock().unwrap(); + let system = system_getter.get_system(); + i.observe( + system.total_memory(), + &[KeyValue::new("host.arch", get_otel_arch())], + ) + }) + .with_unit(Unit::new("bytes")) + .init(), + ); + } + GaugeStore::Active(gauges) + } +} + +#[derive(Default)] +struct FleetDetector { + gauge_store: Mutex, +} +#[async_trait::async_trait] +impl PluginPrivate for FleetDetector { + type Config = Conf; + + async fn new(_: PluginInit) -> Result { + debug!("initialising fleet detection plugin"); + if let Ok(val) = env::var(APOLLO_TELEMETRY_DISABLED) { + if val == "true" { + debug!("fleet detection disabled, no telemetry will be sent"); + return Ok(FleetDetector::default()); + } + } + + Ok(FleetDetector { + gauge_store: Mutex::new(GaugeStore::Pending), + }) + } + + fn activate(&self) { + let mut store = self.gauge_store.lock().expect("lock poisoned"); + if matches!(*store, GaugeStore::Pending) { + *store = GaugeStore::active(); + } + } +} + +#[cfg(not(target_os = "linux"))] +fn detect_cpu_count(system: &System) -> u64 { + system.cpus().len() as u64 +} + +// Because Linux provides CGroups as a way of controlling the proportion of CPU time each +// process gets we can perform slightly more introspection here than simply appealing to the +// raw number of processors. Hence, the extra logic including below. +#[cfg(target_os = "linux")] +fn detect_cpu_count(system: &System) -> u64 { + use std::collections::HashSet; + use std::fs; + + let system_cpus = system.cpus().len() as u64; + // Grab the contents of /proc/filesystems + let fses: HashSet = match fs::read_to_string("/proc/filesystems") { + Ok(content) => content + .lines() + .map(|x| x.split_whitespace().next().unwrap_or("").to_string()) + .filter(|x| x.contains("cgroup")) + .collect(), + Err(_) => return system_cpus, + }; + + if fses.contains("cgroup2") { + // If we're looking at cgroup2 then we need to look in `cpu.max` + match fs::read_to_string("/sys/fs/cgroup/cpu.max") { + Ok(readings) => { + // The format of the file lists the quota first, followed by the period, + // but the quota could also be max which would mean there are no restrictions. + if readings.starts_with("max") { + system_cpus + } else { + // If it's not max then divide the two to get an integer answer + match readings.split_once(' ') { + None => system_cpus, + Some((quota, period)) => { + calculate_cpu_count_with_default(system_cpus, quota, period) + } + } + } + } + Err(_) => system_cpus, + } + } else if fses.contains("cgroup") { + // If we're in cgroup v1 then we need to read from two separate files + let quota = fs::read_to_string("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + .map(|s| String::from(s.trim())) + .ok(); + let period = fs::read_to_string("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + .map(|s| String::from(s.trim())) + .ok(); + match (quota, period) { + (Some(quota), Some(period)) => { + // In v1 quota being -1 indicates no restrictions so return the maximum (all + // system CPUs) otherwise divide the two. + if quota == "-1" { + system_cpus + } else { + calculate_cpu_count_with_default(system_cpus, "a, &period) + } + } + _ => system_cpus, + } + } else { + system_cpus + } +} + +#[cfg(target_os = "linux")] +fn calculate_cpu_count_with_default(default: u64, quota: &str, period: &str) -> u64 { + if let (Ok(q), Ok(p)) = (quota.parse::(), period.parse::()) { + q / p + } else { + default + } +} + +fn get_otel_arch() -> &'static str { + match ARCH { + "x86_64" => "amd64", + "aarch64" => "arm64", + "arm" => "arm32", + "powerpc" => "ppc32", + "powerpc64" => "ppc64", + a => a, + } +} + +fn get_otel_os() -> &'static str { + match OS { + "apple" => "darwin", + "dragonfly" => "dragonflybsd", + "macos" => "darwin", + "ios" => "darwin", + a => a, + } +} + +register_private_plugin!("apollo", "fleet_detector", FleetDetector); diff --git a/apollo-router/src/plugins/mod.rs b/apollo-router/src/plugins/mod.rs index beac8037b9..5684d0c3d3 100644 --- a/apollo-router/src/plugins/mod.rs +++ b/apollo-router/src/plugins/mod.rs @@ -28,6 +28,7 @@ pub(crate) mod csrf; mod demand_control; mod expose_query_plan; pub(crate) mod file_uploads; +mod fleet_detector; mod forbid_mutations; mod headers; mod include_subgraph_errors; diff --git a/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs b/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs index 47a13762d0..d721bad5a6 100644 --- a/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs +++ b/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs @@ -190,8 +190,8 @@ mod test { use super::studio::SingleStatsReport; use super::*; use crate::context::OPERATION_KIND; - use crate::plugin::Plugin; use crate::plugin::PluginInit; + use crate::plugin::PluginPrivate; use crate::plugins::subscription; use crate::plugins::telemetry::apollo; use crate::plugins::telemetry::apollo::default_buffer_size; @@ -364,7 +364,7 @@ mod test { request_builder.header("accept", "multipart/mixed;subscriptionSpec=1.0"); } TestHarness::builder() - .extra_plugin(plugin) + .extra_private_plugin(plugin) .extra_plugin(create_subscription_plugin().await?) .build_router() .await? diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index b6a0e7a1a2..bea99b93b7 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -93,8 +93,8 @@ use crate::layers::ServiceBuilderExt; use crate::metrics::aggregation::MeterProviderType; use crate::metrics::filter::FilterMeterProvider; use crate::metrics::meter_provider; -use crate::plugin::Plugin; use crate::plugin::PluginInit; +use crate::plugin::PluginPrivate; use crate::plugins::telemetry::apollo::ForwardHeaders; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::node::Id::ResponseName; use crate::plugins::telemetry::apollo_exporter::proto::reports::StatsContext; @@ -133,7 +133,6 @@ use crate::plugins::telemetry::tracing::apollo_telemetry::APOLLO_PRIVATE_OPERATI use crate::plugins::telemetry::tracing::TracingConfigurator; use crate::plugins::telemetry::utils::TracingUtils; use crate::query_planner::OperationKind; -use crate::register_plugin; use crate::router_factory::Endpoint; use crate::services::execution; use crate::services::router; @@ -280,7 +279,7 @@ fn create_builtin_instruments(config: &InstrumentsConfig) -> BuiltinInstruments } #[async_trait::async_trait] -impl Plugin for Telemetry { +impl PluginPrivate for Telemetry { type Config = config::Conf; async fn new(init: PluginInit) -> Result { @@ -853,10 +852,8 @@ impl Plugin for Telemetry { fn web_endpoints(&self) -> MultiMap { self.custom_endpoints.clone() } -} -impl Telemetry { - pub(crate) fn activate(&self) { + fn activate(&self) { let mut activation = self.activation.lock(); if activation.is_active { return; @@ -910,7 +907,9 @@ impl Telemetry { reload_fmt(create_fmt_layer(&self.config)); activation.is_active = true; } +} +impl Telemetry { fn create_propagator(config: &config::Conf) -> TextMapCompositePropagator { let propagation = &config.exporters.tracing.propagation; @@ -1979,7 +1978,7 @@ fn handle_error_internal>( } } -register_plugin!("apollo", "telemetry", Telemetry); +register_private_plugin!("apollo", "telemetry", Telemetry); fn request_ftv1(mut req: SubgraphRequest) -> SubgraphRequest { if req diff --git a/apollo-router/src/plugins/test.rs b/apollo-router/src/plugins/test.rs index ec1d9c509e..c8924f0ef0 100644 --- a/apollo-router/src/plugins/test.rs +++ b/apollo-router/src/plugins/test.rs @@ -65,12 +65,12 @@ use crate::Notify; /// You can pass in a configuration and a schema to the test harness. If you pass in a schema, the test harness will create a query planner and use the schema to extract subgraph schemas. /// /// -pub(crate) struct PluginTestHarness { +pub(crate) struct PluginTestHarness>> { plugin: Box, phantom: std::marker::PhantomData, } #[buildstructor::buildstructor] -impl PluginTestHarness { +impl> + 'static> PluginTestHarness { #[builder] pub(crate) async fn new<'a, 'b>(config: Option<&'a str>, schema: Option<&'b str>) -> Self { let factory = crate::plugin::plugins() diff --git a/apollo-router/src/router_factory.rs b/apollo-router/src/router_factory.rs index ca40bd0a86..b3a4bb27b6 100644 --- a/apollo-router/src/router_factory.rs +++ b/apollo-router/src/router_factory.rs @@ -678,6 +678,7 @@ pub(crate) async fn create_plugins( } add_mandatory_apollo_plugin!("limits"); add_mandatory_apollo_plugin!("traffic_shaping"); + add_mandatory_apollo_plugin!("fleet_detector"); add_optional_apollo_plugin!("forbid_mutations"); add_optional_apollo_plugin!("subscription"); add_optional_apollo_plugin!("override_subgraph_url"); diff --git a/apollo-router/src/services/supergraph/service.rs b/apollo-router/src/services/supergraph/service.rs index f87c81727f..3f80ecf6f6 100644 --- a/apollo-router/src/services/supergraph/service.rs +++ b/apollo-router/src/services/supergraph/service.rs @@ -40,7 +40,6 @@ use crate::plugins::telemetry::config_new::events::log_event; use crate::plugins::telemetry::config_new::events::SupergraphEventResponse; use crate::plugins::telemetry::consts::QUERY_PLANNING_SPAN_NAME; use crate::plugins::telemetry::tracing::apollo_telemetry::APOLLO_PRIVATE_DURATION_NS; -use crate::plugins::telemetry::Telemetry; use crate::plugins::telemetry::LOGGING_DISPLAY_BODY; use crate::plugins::traffic_shaping::TrafficShaping; use crate::plugins::traffic_shaping::APOLLO_TRAFFIC_SHAPING; @@ -806,9 +805,7 @@ impl PluggableSupergraphServiceBuilder { // Activate the telemetry plugin. // We must NOT fail to go live with the new router from this point as the telemetry plugin activate interacts with globals. for (_, plugin) in self.plugins.iter() { - if let Some(telemetry) = plugin.as_any().downcast_ref::() { - telemetry.activate(); - } + plugin.activate(); } // We need a non-fallible hook so that once we know we are going live with a pipeline we do final initialization. diff --git a/apollo-router/src/test_harness.rs b/apollo-router/src/test_harness.rs index 516048e3d7..2eb49be5f2 100644 --- a/apollo-router/src/test_harness.rs +++ b/apollo-router/src/test_harness.rs @@ -176,7 +176,7 @@ impl<'a> TestHarness<'a> { ), }; - self.extra_plugins.push((name, Box::new(plugin))); + self.extra_plugins.push((name, plugin.into())); self }