From 9f7e7ac2f55b6bee7fe324a9f6fd91e86a546c41 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 16 Nov 2023 13:09:43 -0800 Subject: [PATCH] admin: Add optional /debug/pprof/profile endpoint (#2516) This change introduces a new, optional /debug/pprof/profile.pb.gz endpoint to the proxy's admin server. This endpoint is feature-gated and, initially, it will not be included in release builds. It replicates Go's /debug/pprof/profile, but it always returns gzipped protobuf (i.e. as can be read by pprof pprofutils). When the feature is enabled, profiling requests are only permitted over the loopback interface. The development Dockerfile is updated to prevent stripping debug symbols when pprof is enabled so that the pprof data has useful names. Co-authored-by: Eliza Weisman --- .github/workflows/check-all.yml | 1 + .vscode/settings.json | 6 +- Cargo.lock | 398 ++++++++++++++++++++++++++++---- Dockerfile | 29 +-- deny.toml | 9 + justfile | 7 +- linkerd/app/Cargo.toml | 1 + linkerd/app/admin/Cargo.toml | 4 + linkerd/app/admin/src/lib.rs | 2 + linkerd/app/admin/src/pprof.rs | 62 +++++ linkerd/app/admin/src/server.rs | 36 ++- linkerd/app/admin/src/stack.rs | 8 + linkerd/app/src/env.rs | 6 + linkerd/tracing/Cargo.toml | 12 +- linkerd2-proxy/Cargo.toml | 1 + 15 files changed, 518 insertions(+), 64 deletions(-) create mode 100644 linkerd/app/admin/src/pprof.rs diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index 3ab742361..5051aa1bf 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -28,3 +28,4 @@ jobs: - run: git config --global --add safe.directory "$PWD" # actions/runner#2033 - run: just fetch - run: just check --exclude=linkerd-meshtls-boring + - run: just check --exclude=linkerd-meshtls-boring --features=pprof diff --git a/.vscode/settings.json b/.vscode/settings.json index be2b3c246..7e2f86ed0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { - "rust-analyzer.cargo.features": [], + "rust-analyzer.cargo.features": [ + "pprof" + ], "files.insertFinalNewline": true -} \ No newline at end of file +} diff --git a/Cargo.lock b/Cargo.lock index b4b26c4d1..8900920cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -29,9 +38,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -81,7 +90,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.39", ] [[package]] @@ -135,6 +144,21 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.1", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.1" @@ -164,7 +188,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.12", + "syn 2.0.39", ] [[package]] @@ -179,6 +203,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "boring" version = "3.0.4" @@ -266,6 +299,24 @@ dependencies = [ "cc", ] +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -275,12 +326,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "data-encoding" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "deflate" version = "1.0.0" @@ -311,6 +381,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "drain" version = "0.1.1" @@ -369,6 +449,18 @@ dependencies = [ "instant", ] +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -382,7 +474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] @@ -499,7 +591,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.39", ] [[package]] @@ -532,6 +624,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -543,6 +645,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "glob" version = "0.3.1" @@ -846,9 +954,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libfuzzer-sys" @@ -913,12 +1021,14 @@ dependencies = [ name = "linkerd-app-admin" version = "0.1.0" dependencies = [ + "deflate", "futures", "http", "hyper", "linkerd-app-core", "linkerd-app-inbound", "linkerd-tracing", + "pprof", "serde", "serde_json", "thiserror", @@ -1493,7 +1603,7 @@ dependencies = [ "linkerd-tls", "linkerd2-proxy-api", "pin-project", - "prost", + "prost 0.11.8", "tonic", "tower", "tracing", @@ -1535,7 +1645,7 @@ dependencies = [ "linkerd2-proxy-api", "maplit", "once_cell", - "prost-types", + "prost-types 0.11.8", "quickcheck", "thiserror", "tonic", @@ -1642,7 +1752,7 @@ dependencies = [ "linkerd-http-route", "linkerd2-proxy-api", "maplit", - "prost-types", + "prost-types 0.11.8", "quickcheck", "thiserror", ] @@ -1665,7 +1775,7 @@ dependencies = [ "linkerd2-proxy-api", "parking_lot", "pin-project", - "prost-types", + "prost-types 0.11.8", "quickcheck", "rand", "thiserror", @@ -1764,7 +1874,7 @@ dependencies = [ "linkerd-tonic-watch", "linkerd2-proxy-api", "once_cell", - "prost-types", + "prost-types 0.11.8", "quickcheck", "regex", "thiserror", @@ -1916,8 +2026,8 @@ dependencies = [ "linkerd-error", "linkerd-io", "linkerd-stack", - "prost", - "prost-build", + "prost 0.11.8", + "prost-build 0.11.8", "tokio", "tokio-test", "tracing", @@ -1961,8 +2071,8 @@ dependencies = [ "h2", "http", "ipnet", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "quickcheck", "thiserror", "tonic", @@ -2026,7 +2136,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -2047,6 +2157,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.16" @@ -2068,6 +2187,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.6" @@ -2086,6 +2214,17 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.0" @@ -2126,6 +2265,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.17.1" @@ -2137,8 +2285,8 @@ name = "opencensus-proto" version = "0.1.0" dependencies = [ "bytes", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "tonic", "tonic-build", ] @@ -2242,6 +2390,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "pprof" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb" +dependencies = [ + "backtrace", + "cfg-if", + "findshlibs", + "libc", + "log", + "nix", + "once_cell", + "parking_lot", + "prost 0.12.1", + "prost-build 0.12.1", + "prost-derive 0.12.1", + "sha2", + "smallvec", + "symbolic-demangle", + "tempfile", + "thiserror", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2258,6 +2430,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.39", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -2287,7 +2469,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.8", +] + +[[package]] +name = "prost" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +dependencies = [ + "bytes", + "prost-derive 0.12.1", ] [[package]] @@ -2303,15 +2495,37 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", - "prost", - "prost-types", + "prettyplease 0.1.25", + "prost 0.11.8", + "prost-types 0.11.8", "regex", "syn 1.0.109", "tempfile", "which", ] +[[package]] +name = "prost-build" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease 0.2.15", + "prost 0.12.1", + "prost-types 0.12.1", + "regex", + "syn 2.0.39", + "tempfile", + "which", +] + [[package]] name = "prost-derive" version = "0.11.8" @@ -2325,13 +2539,35 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "prost-types" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" dependencies = [ - "prost", + "prost 0.11.8", +] + +[[package]] +name = "prost-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +dependencies = [ + "prost 0.12.1", ] [[package]] @@ -2351,9 +2587,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -2426,13 +2662,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-automata 0.3.7", + "regex-syntax 0.7.5", ] [[package]] @@ -2441,7 +2678,18 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", ] [[package]] @@ -2450,6 +2698,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "resolv-conf" version = "0.7.0" @@ -2475,6 +2729,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2591,6 +2851,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2646,6 +2917,35 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "symbolic-common" +version = "12.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "405af7bd5edd866cef462e22ef73f11cf9bf506c9d62824fef8364eb69d4d4ad" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bcd041ccfb77d9c70639efcd5b804b508ac7a273e9224d227379e225625daf9" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] + [[package]] name = "syn" version = "1.0.109" @@ -2659,9 +2959,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.12" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -2881,8 +3181,8 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost", - "prost-derive", + "prost 0.11.8", + "prost-derive 0.11.8", "tokio", "tokio-stream", "tokio-util", @@ -2899,9 +3199,9 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ - "prettyplease", + "prettyplease 0.1.25", "proc-macro2", - "prost-build", + "prost-build 0.11.8", "quote", "syn 1.0.109", ] @@ -3005,12 +3305,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -3026,9 +3326,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -3097,6 +3397,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.11" @@ -3135,6 +3441,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" + [[package]] name = "valuable" version = "0.1.0" @@ -3448,5 +3760,5 @@ checksum = "9731702e2f0617ad526794ae28fbc6f6ca8849b5ba729666c2a5bc4b6ddee2cd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.39", ] diff --git a/Dockerfile b/Dockerfile index e5f7d2a03..ee515af5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,11 +7,11 @@ ARG RUST_IMAGE=ghcr.io/linkerd/dev:v42-rust # Use an arbitrary ~recent edge release image to get the proxy # identity-initializing and linkerd-await wrappers. -# Currently pinned to a build off of edge-23.11.1 + dev:v42 -ARG LINKERD2_IMAGE=ghcr.io/olix0r/l2-proxy:git-04283611 +ARG LINKERD2_IMAGE=ghcr.io/linkerd/proxy:edge-23.11.2 -# Build the proxy. -FROM --platform=$BUILDPLATFORM $RUST_IMAGE as build +FROM $LINKERD2_IMAGE as linkerd2 + +FROM --platform=$BUILDPLATFORM $RUST_IMAGE as fetch ARG PROXY_FEATURES="" RUN apt-get update && \ @@ -28,24 +28,27 @@ WORKDIR /src COPY . . RUN --mount=type=cache,id=cargo,target=/usr/local/cargo/registry \ just fetch + +# Build the proxy. +FROM fetch as build ENV CARGO_INCREMENTAL=0 ENV RUSTFLAGS="-D warnings -A deprecated" ARG TARGETARCH="amd64" ARG PROFILE="release" ARG LINKERD2_PROXY_VERSION="" ARG LINKERD2_PROXY_VENDOR="" +SHELL ["/bin/bash", "-c"] RUN --mount=type=cache,id=cargo,target=/usr/local/cargo/registry \ - /usr/bin/time -v just arch="$TARGETARCH" features="$PROXY_FEATURES" profile="$PROFILE" build && \ - bin=$(just --evaluate profile="$PROFILE" _target_bin) ; \ - du -sh "$bin" "$bin".dbg && \ - mkdir -p /out && mv "$bin" /out/linkerd2-proxy + if [[ "$PROXY_FEATURES" =~ .*pprof.* ]] ; then cmd=build-debug ; else cmd=build ; fi ; \ + /usr/bin/time -v just arch="$TARGETARCH" features="$PROXY_FEATURES" profile="$PROFILE" "$cmd" && \ + ( mkdir -p /out ; \ + mv $(just --evaluate profile="$PROFILE" _target_bin) /out/ ; \ + du -sh /out/* ) -FROM $LINKERD2_IMAGE as linkerd2 - -# Install the proxy binary into a base image that we can at least get a shell to -# debug on. +# Install the proxy binary into a base image that we can at least get a shell +# for debugging. FROM docker.io/library/debian:bookworm-slim as runtime WORKDIR /linkerd COPY --from=linkerd2 /usr/lib/linkerd/* /usr/lib/linkerd/ -COPY --from=build /out/linkerd2-proxy /usr/lib/linkerd/linkerd2-proxy +COPY --from=build /out/* /usr/lib/linkerd/ ENTRYPOINT ["/usr/lib/linkerd/linkerd2-proxy-identity"] diff --git a/deny.toml b/deny.toml index 441e22fbf..f059ea1a6 100644 --- a/deny.toml +++ b/deny.toml @@ -76,6 +76,15 @@ skip = [ # `linkerd-trace-context`, `rustls-pemfile` and `tonic` depend on `base64` # v0.13.1 while `rcgen` depends on v0.21.5 { name = "base64" }, + # https://github.com/hawkw/matchers/pull/4 + { name = "regex-automata", version = "0.1" }, + { name = "regex-syntax", version = "0.6" }, + # linkerd2-proxy-api needs to upgrade tonic to upgrade prost... + { name = "prost", version = "0.11" }, + { name = "prost-build", version = "0.11" }, + { name = "prost-derive", version = "0.11" }, + { name = "prost-types", version = "0.11" }, + { name = "prettyplease", version = "0.1" }, ] skip-tree = [ # right now we have a mix of versions of this crate in the ecosystem diff --git a/justfile b/justfile index f8c985d3a..592bb5a53 100644 --- a/justfile +++ b/justfile @@ -116,7 +116,12 @@ test-dir dir *flags: cd {{ dir }} && {{ _cargo }} test --frozen {{ _features }} {{ flags }} # Build the proxy -build: && checksec _strip +build: _build checksec _strip + +# Build the proxy without stripping debug symbols +build-debug: _build + +_build: @rm -f {{ _target_bin }} {{ _target_bin }}.dbg @{{ _cargo }} build --frozen --package=linkerd2-proxy {{ _features }} diff --git a/linkerd/app/Cargo.toml b/linkerd/app/Cargo.toml index eb19e1d92..2221e5a1c 100644 --- a/linkerd/app/Cargo.toml +++ b/linkerd/app/Cargo.toml @@ -14,6 +14,7 @@ This is used by tests and the executable. [features] allow-loopback = ["linkerd-app-outbound/allow-loopback"] log-streaming = ["linkerd-app-admin/log-streaming"] +pprof = ["linkerd-app-admin/pprof"] [dependencies] futures = { version = "0.3", default-features = false } diff --git a/linkerd/app/admin/Cargo.toml b/linkerd/app/admin/Cargo.toml index 7f2208329..ce61556ea 100644 --- a/linkerd/app/admin/Cargo.toml +++ b/linkerd/app/admin/Cargo.toml @@ -10,15 +10,19 @@ The linkerd proxy's admin server. """ [features] +default = [] +pprof = ["deflate", "dep:pprof"] log-streaming = ["linkerd-tracing/stream"] [dependencies] +deflate = { version = "1", optional = true, features = ["gzip"] } http = "0.2" hyper = { version = "0.14", features = ["http1", "http2"] } futures = { version = "0.3", default-features = false } linkerd-app-core = { path = "../core" } linkerd-app-inbound = { path = "../inbound" } linkerd-tracing = { path = "../../tracing" } +pprof = { version = "0.13", optional = true, features = ["prost-codec"] } serde = "1" serde_json = "1" thiserror = "1" diff --git a/linkerd/app/admin/src/lib.rs b/linkerd/app/admin/src/lib.rs index 97c66dc51..e1e1264b1 100644 --- a/linkerd/app/admin/src/lib.rs +++ b/linkerd/app/admin/src/lib.rs @@ -1,6 +1,8 @@ #![deny(rust_2018_idioms, clippy::disallowed_methods, clippy::disallowed_types)] #![forbid(unsafe_code)] +#[cfg(feature = "pprof")] +mod pprof; mod server; mod stack; diff --git a/linkerd/app/admin/src/pprof.rs b/linkerd/app/admin/src/pprof.rs new file mode 100644 index 000000000..ad8c6aac0 --- /dev/null +++ b/linkerd/app/admin/src/pprof.rs @@ -0,0 +1,62 @@ +use linkerd_app_core::Result; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct Pprof; + +impl Pprof { + pub async fn profile(self, req: http::Request) -> Result> { + use pprof::protos::Message; + + fn query_param<'r, B>(req: &'r http::Request, name: &'static str) -> Option<&'r str> { + let params = req.uri().path_and_query()?.query()?.split('&'); + params + .filter_map(|p| p.strip_prefix(name)?.strip_prefix('=')) + .next() + } + + // TODO(ver) Pretty-up error handling if we ever expose this outside of + // development. + let duration = std::time::Duration::from_secs_f64( + query_param(&req, "seconds") + .map(|s| s.parse::()) + .transpose()? + .unwrap_or(30.0), + ); + let frequency = query_param(&req, "frequency") + .map(|s| s.parse::()) + .transpose()? + // Go's default. + .unwrap_or(100); + tracing::info!(?duration, frequency, "Collecting"); + + let report = { + let guard = pprof::ProfilerGuard::new(frequency)?; + tokio::time::sleep(duration).await; + guard.report().build()? + }; + tracing::info!( + ?duration, + frequency, + frames = report.data.len(), + "Collected" + ); + + let pb_gz = { + let mut gz = deflate::write::GzEncoder::new( + Vec::::new(), + deflate::CompressionOptions::fast(), + ); + std::io::Write::write_all(&mut gz, &{ + let mut buf = Vec::new(); + report.pprof()?.encode(&mut buf)?; + buf + })?; + gz.finish()? + }; + + Ok(http::Response::builder() + .header(http::header::CONTENT_TYPE, "application/octet-stream") + .body(hyper::Body::from(pb_gz)) + .expect("response must be valid")) + } +} diff --git a/linkerd/app/admin/src/server.rs b/linkerd/app/admin/src/server.rs index d70b53c5a..eb1c0cfdd 100644 --- a/linkerd/app/admin/src/server.rs +++ b/linkerd/app/admin/src/server.rs @@ -19,7 +19,7 @@ use hyper::{ use linkerd_app_core::{ metrics::{self as metrics, FmtMetrics}, proxy::http::ClientHandle, - trace, Error, + trace, Error, Result, }; use std::{ future::Future, @@ -40,10 +40,11 @@ pub struct Admin { tracing: trace::Handle, ready: Readiness, shutdown_tx: mpsc::UnboundedSender<()>, + #[cfg(feature = "pprof")] + pprof: Option, } -pub type ResponseFuture = - Pin, Error>> + Send + 'static>>; +pub type ResponseFuture = Pin>> + Send + 'static>>; impl Admin { pub fn new( @@ -57,9 +58,18 @@ impl Admin { ready, shutdown_tx, tracing, + + #[cfg(feature = "pprof")] + pprof: None, } } + #[cfg(feature = "pprof")] + pub fn with_profiling(mut self, enabled: bool) -> Self { + self.pprof = enabled.then_some(crate::pprof::Pprof); + self + } + fn ready_rsp(&self) -> Response { if self.ready.is_ready() { Response::builder() @@ -254,6 +264,26 @@ where } } + #[cfg(feature = "pprof")] + "/debug/pprof/profile.pb.gz" if self.pprof.is_some() => { + let pprof = self.pprof.expect("unreachable"); + + if !Self::client_is_localhost(&req) { + return Box::pin(future::ok(Self::forbidden_not_localhost())); + } + + if req.method() != http::Method::GET { + return Box::pin(future::ok(Self::method_not_allowed())); + } + + Box::pin(async move { + Ok(pprof + .profile(req) + .await + .unwrap_or_else(Self::internal_error_rsp)) + }) + } + _ => Box::pin(future::ok(Self::not_found())), } } diff --git a/linkerd/app/admin/src/stack.rs b/linkerd/app/admin/src/stack.rs index 016c46711..97514ae40 100644 --- a/linkerd/app/admin/src/stack.rs +++ b/linkerd/app/admin/src/stack.rs @@ -22,6 +22,8 @@ use tracing::debug; pub struct Config { pub server: ServerConfig, pub metrics_retain_idle: Duration, + #[cfg(feature = "pprof")] + pub enable_profiling: bool, } pub struct Task { @@ -96,7 +98,13 @@ impl Config { let policy = policy.get_policy(OrigDstAddr(listen_addr.into())); let (ready, latch) = crate::server::Readiness::new(); + + #[cfg_attr(not(feature = "pprof"), allow(unused_mut))] let admin = crate::server::Admin::new(report, ready, shutdown, trace); + + #[cfg(feature = "pprof")] + let admin = admin.with_profiling(self.enable_profiling); + let http = svc::stack(move |_| admin.clone()) .push( metrics diff --git a/linkerd/app/src/env.rs b/linkerd/app/src/env.rs index 2892e10f0..a1673b5d4 100644 --- a/linkerd/app/src/env.rs +++ b/linkerd/app/src/env.rs @@ -717,6 +717,12 @@ pub fn parse_config(strings: &S) -> Result keepalive: inbound.proxy.server.keepalive, h2_settings, }, + + // TODO(ver) Currently we always enable profiling when the pprof feature + // is enabled. In the future, this should be driven by runtime + // configuration. + #[cfg(feature = "pprof")] + enable_profiling: true, }; let dns = dns::Config { diff --git a/linkerd/tracing/Cargo.toml b/linkerd/tracing/Cargo.toml index 7bd44147a..c00beec26 100644 --- a/linkerd/tracing/Cargo.toml +++ b/linkerd/tracing/Cargo.toml @@ -17,9 +17,17 @@ slab = { version = "0.4", optional = true } thingbuf = { version = "0.1.2", features = ["std"], optional = true } tokio = { version = "1", features = ["time"] } tracing = "0.1" -tracing-log = "0.1" +tracing-log = "0.2" [dependencies.tracing-subscriber] version = "0.3.16" default-features = false -features = ["env-filter", "fmt", "smallvec", "tracing-log", "json", "parking_lot", "registry"] +features = [ + "env-filter", + "fmt", + "smallvec", + "tracing-log", + "json", + "parking_lot", + "registry", +] diff --git a/linkerd2-proxy/Cargo.toml b/linkerd2-proxy/Cargo.toml index 3197f20ae..8b3af75ab 100644 --- a/linkerd2-proxy/Cargo.toml +++ b/linkerd2-proxy/Cargo.toml @@ -14,6 +14,7 @@ meshtls-boring = ["linkerd-meshtls/boring"] meshtls-boring-fips = ["linkerd-meshtls/boring-fips"] meshtls-rustls = ["linkerd-meshtls/rustls"] log-streaming = ["linkerd-app/log-streaming"] +pprof = ["linkerd-app/pprof"] [dependencies] futures = { version = "0.3", default-features = false }