From f108aaf1f3fad7d519353aed0603dd0e90ab8fae Mon Sep 17 00:00:00 2001 From: Javier Honduvilla Coto Date: Tue, 23 Jul 2024 10:40:24 +0100 Subject: [PATCH] Add facilities to produce pprof profiles (#53) This (rather large) commit adds pprof profile generation to lightswitch. They can be dumped to disk and the read with pprof tooling (see test plan) as well as sent to a remote server with a new collector. By default profiles are written to dis as flamegraphs in svg format. Test Plan / Demo ================ build with: `nix develop --command 'cargo build --release'` ``` $ sudo target/release/lightswitch --duration=5 ``` produces a flamegraph in svg format ``` $ sudo target/release/lightswitch --duration=5 --profile-format=pprof ``` produces a pprof file (uncompressed) that can be read with standard tooling ``` $ go tool pprof profile.pb For tag pid used unit task-tgid, also encountered unit(s) task-id File: ruby Build ID: gnu-bae4d9744266cf1f3e27a1ba7512c1c97b5e4a86 lightswitch Type: cpu Time: Jul 23, 2024 at 10:03am (BST) Duration: 5s, Total samples = 1.70s (34.07%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top For tag pid used unit task-tgid, also encountered unit(s) task-id Showing nodes accounting for 703.70ms, 41.30% of 1703.70ms total Showing top 10 nodes out of 140 flat flat% sum% cum cum% 111.11ms 6.52% 6.52% 148.15ms 8.70% BSD_vfprintf 111.11ms 6.52% 13.04% 259.26ms 15.22% lightswitch::unwind_info::UnwindInfoBuilder::process 111.11ms 6.52% 19.57% 222.22ms 13.04% n_tty_inherit_ops 74.07ms 4.35% 23.91% 111.11ms 6.52% __memcpy_avx_unaligned_erms 74.07ms 4.35% 28.26% 407.41ms 23.91% do_syscall_64 74.07ms 4.35% 32.61% 74.07ms 4.35% queue_work_on 37.04ms 2.17% 34.78% 37.04ms 2.17% __GI___pthread_enable_asynccancel 37.04ms 2.17% 36.96% 444.44ms 26.09% __GI___writev 37.04ms 2.17% 39.13% 37.04ms 2.17% __malloc_usable_size 37.04ms 2.17% 41.30% 37.04ms 2.17% __probestub_page_fault_kernel ``` ``` $ sudo target/release/lightswitch --duration=5 --sender=remote ``` Performs an HTTP POST with the pprof as the body to a hardcoded server URL (this will change in the future). --- .gitignore | 1 + Cargo.lock | 1191 +++++++++++++++++--- Cargo.toml | 9 + flake.nix | 4 +- lightswitch-proto/Cargo.toml | 11 + lightswitch-proto/build.rs | 9 + lightswitch-proto/src/lib.rs | 1 + lightswitch-proto/src/profile.rs | 461 ++++++++ lightswitch-proto/src/protos/profile.proto | 227 ++++ src/collector.rs | 131 ++- src/main.rs | 73 +- src/object.rs | 19 + src/profile.rs | 117 +- src/profiler.rs | 30 +- tests/integration_test.rs | 7 +- vm.nix | 12 +- 16 files changed, 2113 insertions(+), 190 deletions(-) create mode 100644 lightswitch-proto/Cargo.toml create mode 100644 lightswitch-proto/build.rs create mode 100644 lightswitch-proto/src/lib.rs create mode 100644 lightswitch-proto/src/profile.rs create mode 100644 lightswitch-proto/src/protos/profile.proto diff --git a/.gitignore b/.gitignore index 1457c3a..4a960d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target flame.svg +profile.pb src/bpf/*_skel.rs .vmtest.log /result \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3020ebf..7924119 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.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli 0.29.0", +] + [[package]] name = "adler" version = "1.0.2" @@ -81,7 +90,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -91,7 +100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -121,22 +130,49 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -146,15 +182,21 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.66", + "syn 2.0.72", "which", ] [[package]] name = "bitflags" -version = "2.5.0" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blazesym" @@ -163,7 +205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519a0f9df086d6c4f44576558523a777c984454daeb124bee79bde69227360c4" dependencies = [ "cpp_demangle", - "gimli", + "gimli 0.30.0", "libc", "miniz_oxide", "rustc-demangle", @@ -188,9 +230,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -198,6 +240,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" + [[package]] name = "camino" version = "1.1.7" @@ -232,9 +280,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cexpr" @@ -274,7 +322,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -290,9 +338,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -300,9 +348,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -312,14 +360,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -343,7 +391,17 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -387,11 +445,12 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "dashmap" -version = "5.5.3" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" dependencies = [ "cfg-if", + "crossbeam-utils", "hashbrown", "lock_api", "once_cell", @@ -404,17 +463,6 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "difflib" version = "0.4.0" @@ -429,9 +477,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" @@ -439,12 +487,31 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", +] + [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "env_filter", "log", ] @@ -461,7 +528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -476,6 +543,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.30" @@ -486,6 +559,36 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -548,7 +651,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -598,6 +701,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "gimli" version = "0.30.0" @@ -615,6 +724,25 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hamming" version = "0.1.3" @@ -657,7 +785,120 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -683,6 +924,16 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -695,9 +946,9 @@ dependencies = [ [[package]] name = "inferno" -version = "0.11.19" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" +checksum = "7c77a3ae7d4761b9c64d2c030f70746ceb8cfba32dce0325a56792e0a4816c31" dependencies = [ "ahash", "clap", @@ -729,6 +980,12 @@ dependencies = [ "similar", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "is-terminal" version = "0.4.12" @@ -737,7 +994,7 @@ checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -755,6 +1012,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -772,9 +1038,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -806,7 +1072,7 @@ version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1159a07fd5b9aca3f86ba5688394bb9bd75142bcec510052d6f765e98d801b09" dependencies = [ - "bitflags", + "bitflags 2.6.0", "libbpf-sys", "libc", "strum_macros", @@ -832,12 +1098,12 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -852,7 +1118,7 @@ dependencies = [ "clap", "data-encoding", "errno", - "gimli", + "gimli 0.30.0", "glob", "inferno", "insta", @@ -860,6 +1126,7 @@ dependencies = [ "libbpf-cargo", "libbpf-rs", "libc", + "lightswitch-proto", "memmap2 0.9.4", "nix 0.29.0", "object", @@ -868,6 +1135,9 @@ dependencies = [ "plain", "primal", "procfs", + "prost 0.12.6", + "rand 0.8.5", + "reqwest", "ring", "rstest", "tempdir", @@ -876,6 +1146,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lightswitch-proto" +version = "0.1.0" +dependencies = [ + "anyhow", + "prost 0.12.6", + "prost-build", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -900,9 +1179,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -928,6 +1207,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -936,21 +1221,55 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "cfg_aliases 0.1.1", "libc", @@ -962,7 +1281,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -1018,9 +1337,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "flate2", "memchr", @@ -1033,6 +1352,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1059,9 +1422,15 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "perf-event-open-sys" version = "4.0.0" @@ -1071,6 +1440,36 @@ dependencies = [ "libc", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1095,6 +1494,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "predicates" version = "3.1.0" @@ -1129,7 +1534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1189,9 +1594,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1202,7 +1607,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags", + "bitflags 2.6.0", "chrono", "flate2", "hex", @@ -1217,18 +1622,94 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "bitflags", + "bitflags 2.6.0", "chrono", "hex", ] [[package]] -name = "quick-xml" -version = "0.26.0" +name = "prost" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ - "memchr", + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +dependencies = [ + "bytes", + "prost-derive 0.13.1", +] + +[[package]] +name = "prost-build" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.1", + "prost-types", + "regex", + "syn 2.0.72", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "prost-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "prost-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" +dependencies = [ + "prost 0.13.1", +] + +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", ] [[package]] @@ -1253,6 +1734,27 @@ dependencies = [ "winapi", ] +[[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 0.6.4", +] + +[[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 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1268,6 +1770,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -1279,11 +1790,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1330,11 +1841,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" dependencies = [ "bytemuck", ] @@ -1351,7 +1906,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1380,7 +1935,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.66", + "syn 2.0.72", "unicode-ident", ] @@ -1411,11 +1966,51 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1426,12 +2021,11 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ruzstd" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" +checksum = "5022b253619b1ba797f243056276bed8ed1a73b0f5a7ce7225d524067644bf8f" dependencies = [ "byteorder", - "derive_more", "twox-hash", ] @@ -1441,12 +2035,44 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.23" @@ -1458,35 +2084,47 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1510,9 +2148,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "similar" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "slab" @@ -1529,6 +2167,16 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -1572,6 +2220,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1585,22 +2239,49 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempdir" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "rand", + "rand 0.4.6", "remove_dir_all", ] @@ -1613,7 +2294,7 @@ dependencies = [ "cfg-if", "fastrand", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1624,22 +2305,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1652,6 +2333,70 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.6" @@ -1669,6 +2414,33 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -1688,7 +2460,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] [[package]] @@ -1726,6 +2498,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "twox-hash" version = "1.6.3" @@ -1736,18 +2514,44 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -1760,6 +2564,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1785,6 +2595,15 @@ dependencies = [ "libc", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1812,10 +2631,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -1834,7 +2665,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1845,6 +2676,16 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "4.4.2" @@ -1885,7 +2726,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -1894,72 +2744,129 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -1970,22 +2877,38 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.72", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 9add155..5dacae1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,11 @@ name = "lightswitch" version = "0.1.0" edition = "2021" +[workspace] +members = [ + "lightswitch-proto", +] + [dependencies] gimli = "0.30.0" object = "0.36.0" @@ -27,12 +32,16 @@ chrono = "0.4.38" inferno = "0.11.19" primal = "0.3.3" nix = { version = "0.29.0", features = ["user"] } +prost = "0.12" # Needed to encode protocol buffers to bytes. +reqwest = { version = "0.12", features = ["blocking"] } +lightswitch-proto = { path = "./lightswitch-proto"} [dev-dependencies] assert_cmd = { version = "2.0.14" } insta = { version = "1.39.0", features = ["yaml"] } rstest = "0.21.0" tempdir = "0.3.7" +rand = "0.8.5" [build-dependencies] bindgen = "0.69.4" diff --git a/flake.nix b/flake.nix index 2127bc9..2b31241 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ elfutils' = (pkgs.elfutils.override { enableDebuginfod = false; }).overrideAttrs (attrs: { configureFlags = attrs.configureFlags ++ [ "--without-zstd" ]; }); + openssl' = (pkgs.openssl.override { static = true; }); buildInputs = with pkgs; [ llvmPackages_16.clang llvmPackages_16.libcxx @@ -36,6 +37,8 @@ zlib.dev glibc glibc.static + protobuf + openssl' ]; nativeBuildInputs = with pkgs; [ pkg-config @@ -73,7 +76,6 @@ # Debugging strace gdb - openssl # Other tools skopeo cargo-edit diff --git a/lightswitch-proto/Cargo.toml b/lightswitch-proto/Cargo.toml new file mode 100644 index 0000000..abbd9b4 --- /dev/null +++ b/lightswitch-proto/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lightswitch-proto" +version = "0.1.0" +edition = "2021" + +[dependencies] +prost = "0.12" +anyhow = "1.0.86" + +[build-dependencies] +prost-build = "0.13.1" \ No newline at end of file diff --git a/lightswitch-proto/build.rs b/lightswitch-proto/build.rs new file mode 100644 index 0000000..ebd2ccd --- /dev/null +++ b/lightswitch-proto/build.rs @@ -0,0 +1,9 @@ +fn main() { + let mut config = prost_build::Config::new(); + config.type_attribute("Sample", "#[derive(Eq, Hash)]"); + config.type_attribute("Label", "#[derive(Eq, Hash)]"); + + config + .compile_protos(&["src/protos/profile.proto"], &["src/protos"]) + .expect("build profile.proto"); +} diff --git a/lightswitch-proto/src/lib.rs b/lightswitch-proto/src/lib.rs new file mode 100644 index 0000000..6b76aba --- /dev/null +++ b/lightswitch-proto/src/lib.rs @@ -0,0 +1 @@ +pub mod profile; diff --git a/lightswitch-proto/src/profile.rs b/lightswitch-proto/src/profile.rs new file mode 100644 index 0000000..2f765c2 --- /dev/null +++ b/lightswitch-proto/src/profile.rs @@ -0,0 +1,461 @@ +#![allow(dead_code)] + +pub mod pprof { + include!(concat!(env!("OUT_DIR"), "/perftools.profiles.rs")); +} + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; + +use anyhow::{anyhow, Result}; + +pub struct PprofBuilder { + duration: Duration, + freq_in_hz: i64, + + known_mappings: HashMap, + mappings: Vec, + + known_strings: HashMap, + string_table: Vec, + + /// (address, mapping_id) => location_id + known_locations: HashMap<(u64, u64), u64>, + locations: Vec, + + known_functions: HashMap, + pub functions: Vec, + + samples: Vec, +} + +pub enum LabelStringOrNumber { + String(String), + /// Value and unit. + Number(i64, String), +} + +impl PprofBuilder { + pub fn new(duration: Duration, freq_in_hz: i64) -> Self { + Self { + duration, + freq_in_hz, + + known_mappings: HashMap::new(), + mappings: Vec::new(), + + known_strings: HashMap::new(), + string_table: Vec::new(), + + known_locations: HashMap::new(), + locations: Vec::new(), + + known_functions: HashMap::new(), + functions: Vec::new(), + + samples: Vec::new(), + } + } + + /// Run some validations to ensure that the profile is semantically correct. + pub fn validate(&self) -> Result<()> { + let validate_line = |line: &pprof::Line| { + let function_id = line.function_id; + if function_id == 0 { + return Err(anyhow!("Found a null function_id (id=0)")); + } + + let maybe_function = self.functions.get(function_id as usize - 1); + match maybe_function { + Some(function) => { + if function.id == 0 { + return Err(anyhow!("Found a null function (id=0)")); + } + + let function_id = function.name; + self.string_table.get(function_id as usize).ok_or(anyhow!( + "Could not find function name with id {}", + function_id + ))?; + } + None => { + return Err(anyhow!("Function with id {} not found", function_id)); + } + } + Ok(()) + }; + + let validate_location = |location: &pprof::Location| { + let mapping_id = location.mapping_id; + if mapping_id == 0 { + return Err(anyhow!("Found a null mapping (id=0)")); + } + let maybe_mapping = self.mappings.get(mapping_id as usize - 1); + match maybe_mapping { + Some(mapping) => { + if mapping.id == 0 { + return Err(anyhow!("Found a null mapping (id=0)")); + } + } + None => { + return Err(anyhow!("Mapping with id {} not found", mapping_id)); + } + } + + for line in &location.line { + validate_line(line)?; + } + + Ok(()) + }; + + for sample in &self.samples { + for location_id in &sample.location_id { + if *location_id == 0 { + return Err(anyhow!("Found a null location (id=0)")); + } + + let maybe_location = self.locations.get(*location_id as usize - 1); + match maybe_location { + Some(location) => validate_location(location)?, + None => { + return Err(anyhow!("Location with id {} not found", location_id)); + } + } + } + } + Ok(()) + } + + /// Returns the id for a string in the string table or None if it's not present. + pub fn string_id(&self, string: &str) -> Option { + self.known_strings.get(string).copied() + } + + /// Inserts a string in the string table and returns its id. + pub fn get_or_insert_string(&mut self, string: &str) -> i64 { + // The first element in the string table must be the empty string. + if self.string_table.is_empty() { + self.known_strings.insert("".to_string(), 0); + self.string_table.push("".to_string()); + } + + match self.known_strings.entry(string.to_string()) { + Entry::Occupied(o) => { + return *o.get(); + } + Entry::Vacant(v) => { + let id = self.string_table.len() as i64; + v.insert(id); + self.string_table.push(string.to_string()); + id + } + } + } + + pub fn add_function(&mut self, func_name: &str) -> u64 { + let id = self.functions.len() as u64 + 1; + let name_idx = self.get_or_insert_string(func_name); + + let function: pprof::Function = pprof::Function { + id, + name: name_idx, + system_name: name_idx, + filename: self.get_or_insert_string("no-filename"), + ..Default::default() + }; + + match self.known_functions.entry(name_idx) { + Entry::Occupied(o) => { + return *o.get(); + } + Entry::Vacant(v) => { + let id = self.functions.len() as u64 + 1; + v.insert(id); + self.functions.push(function); + id + } + } + } + + pub fn add_line(&mut self, func_name: &str) -> (pprof::Line, u64) { + let function_id = self.add_function(func_name); + ( + pprof::Line { + function_id, + ..Default::default() + }, + function_id, + ) + } + + pub fn add_location(&mut self, address: u64, mapping_id: u64, lines: Vec) -> u64 { + let id: u64 = self.locations.len() as u64 + 1; + + let location = pprof::Location { + id, + mapping_id, + address, + line: lines, // only used for local symbolisation. + is_folded: false, // only used for local symbolisation. + }; + + let unique_id = (address, mapping_id); + + match self.known_locations.entry(unique_id) { + Entry::Occupied(o) => { + return *o.get(); + } + Entry::Vacant(v) => { + let id = self.locations.len() as u64 + 1; + v.insert(id); + self.locations.push(location); + id + } + } + } + + /// Adds a memory mapping. The id of the mapping is derived from the hash of the code region and should + /// be unique. + pub fn add_mapping( + &mut self, + id: u64, + start: u64, + end: u64, + offset: u64, + filename: &str, + build_id: &str, + ) -> u64 { + let mapping = pprof::Mapping { + id, + memory_start: start, + memory_limit: end, + file_offset: offset, + filename: self.get_or_insert_string(filename), + build_id: self.get_or_insert_string(build_id), + has_functions: false, + has_filenames: false, + has_line_numbers: false, + has_inline_frames: false, + }; + + match self.known_mappings.entry(mapping.id) { + Entry::Occupied(o) => { + return *o.get(); + } + Entry::Vacant(v) => { + let id = self.mappings.len() as u64 + 1; + v.insert(id); + self.mappings.push(mapping); + id + } + } + } + pub fn add_sample(&mut self, location_ids: Vec, count: i64, labels: Vec) { + let sample = pprof::Sample { + location_id: location_ids, // from the source code: `The leaf is at location_id\[0\].` + value: vec![count, count * 1_000_000_000 / self.freq_in_hz], + label: labels, + }; + + self.samples.push(sample); + } + + pub fn new_label(&mut self, key: &str, value: LabelStringOrNumber) -> pprof::Label { + let mut label = pprof::Label { + key: self.get_or_insert_string(key), + ..Default::default() + }; + + match value { + LabelStringOrNumber::String(string) => { + label.str = self.get_or_insert_string(&string); + } + LabelStringOrNumber::Number(num, unit) => { + label.num = num; + label.num_unit = self.get_or_insert_string(&unit); + } + } + + label + } + + pub fn profile(mut self) -> pprof::Profile { + let sample_type = pprof::ValueType { + r#type: self.get_or_insert_string("samples"), + unit: self.get_or_insert_string("count"), + }; + + let period_type = pprof::ValueType { + r#type: self.get_or_insert_string("cpu"), + unit: self.get_or_insert_string("nanoseconds"), + }; + + // Used to identify profiles generated by lightswitch. + // This is useful because the mapping ID is used in a non-standard way + // which should not be interpreted like this by other pprof sources. + let comments = vec![self.get_or_insert_string("lightswitch")]; + + pprof::Profile { + sample_type: vec![sample_type, period_type], + sample: self.samples, + mapping: self.mappings, + location: self.locations, + function: self.functions, + string_table: self.string_table, + drop_frames: 0, + keep_frames: 0, + // TODO: change this to send the time when the profile was collected. + time_nanos: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos() as i64, + duration_nanos: self.duration.as_nanos() as i64, + period_type: Some(period_type), + period: 1_000_000_000 / self.freq_in_hz, + comment: comments, + default_sample_type: 0, + } + } +} + +#[cfg(test)] +mod tests { + // Cheat sheet: + // - decode protobuf: `protoc --decode perftools.profiles.Profile src/proto/profile.proto < profile.pb` + // - validate it: (in pprof's codebase) `go tool pprof profile.pb` + // - print it: `go tool pprof -raw profile.pb` + // - http server: `go tool pprof -http=:8080 profile.pb` + use prost::Message; + + use super::*; + + #[test] + fn test_string_table() { + let mut pprof = PprofBuilder::new(Duration::from_secs(5), 27); + assert_eq!(pprof.get_or_insert_string("hi"), 1); + assert_eq!(pprof.get_or_insert_string("salut"), 2); + assert_eq!(pprof.string_table, vec!["", "hi", "salut"]); + + assert!(pprof.string_id("").is_some()); + assert!(pprof.string_id("hi").is_some()); + assert!(pprof.string_id("salut").is_some()); + assert!(pprof.string_id("-_-").is_none()); + } + + #[test] + fn test_mappings() { + let mut pprof = PprofBuilder::new(Duration::from_secs(5), 27); + assert_eq!( + pprof.add_mapping(0, 0x100, 0x200, 0x0, "file.so", "sha256-abc"), + 1 + ); + assert_eq!( + pprof.add_mapping(1, 0x200, 0x400, 0x100, "libc.so", "sha256-bad"), + 2 + ); + assert_eq!(pprof.mappings[0].memory_start, 0x100); + assert_eq!( + pprof.mappings[0].filename, + pprof.string_id("file.so").unwrap() + ); + } + + #[test] + fn test_locations() { + let mut pprof = PprofBuilder::new(Duration::from_secs(5), 27); + let _ = pprof.add_line("hahahaha-first-line"); + let (line, function_id) = pprof.add_line("test-line"); + + assert_eq!(pprof.add_location(0x123, 0x1111, vec![line.clone()]), 1); + assert_eq!(pprof.add_location(0x123, 0x1111, vec![line.clone()]), 1); + assert_eq!(pprof.add_location(0x256, 0x2222, vec![line.clone()]), 2); + assert_eq!(pprof.add_location(0x512, 0x3333, vec![line.clone()]), 3); + + assert_eq!(pprof.locations.len(), 3); + assert_eq!( + pprof.locations[0], + pprof::Location { + id: 1, // The IDs are incremental and start with 1. + mapping_id: 0x1111, + address: 0x123, + line: vec![pprof::Line { + function_id, + ..Default::default() + }], + is_folded: false + } + ); + } + + #[test] + fn test_sample() { + let mut pprof = PprofBuilder::new(Duration::from_secs(5), 27); + let labels = vec![ + pprof.new_label("key", LabelStringOrNumber::String("value".into())), + pprof.new_label("key", LabelStringOrNumber::Number(123, "pid".into())), + ]; + pprof.add_sample(vec![1, 2, 3], 100, labels.clone()); + pprof.add_sample(vec![1, 2, 3], 100, labels); + + assert_eq!(pprof.samples.len(), 2); + assert_eq!( + pprof.samples[0].label, + vec![ + pprof::Label { + key: pprof.string_id("key").unwrap(), + str: pprof.string_id("value").unwrap(), + ..Default::default() + }, + pprof::Label { + key: pprof.string_id("key").unwrap(), + num: 123, + num_unit: pprof.string_id("pid").unwrap(), + ..Default::default() + } + ] + ); + } + + #[test] + fn test_profile() { + use rand::Rng; + use std::fs::File; + use std::io::Write; + + let mut rng = rand::thread_rng(); + let mut pprof = PprofBuilder::new(Duration::from_secs(5), 27); + let raw_samples = vec![ + (vec![123], 200), + (vec![0, 20, 30, 40, 50], 900), + (vec![1, 2, 3, 4, 5, 99999], 2000), + ]; + + for raw_sample in raw_samples { + let mut location_ids = Vec::new(); + let count = raw_sample.1; + + for addr in raw_sample.0 { + let mapping_id: u64 = pprof.add_mapping( + if addr == 0 { 1 } else { addr }, // id 0 is reserved and can't be used. + rng.gen(), + rng.gen(), + rng.gen(), + if addr % 2 == 0 { "fake.so" } else { "test.so" }, + if addr % 2 == 0 { + "sha256-fake" + } else { + "golang-fake" + }, + ); + location_ids.push(pprof.add_location(addr, mapping_id, vec![])); + } + + pprof.add_sample(location_ids, count, vec![]); + } + + assert!(pprof.validate().is_ok()); + pprof.profile(); + } +} diff --git a/lightswitch-proto/src/protos/profile.proto b/lightswitch-proto/src/protos/profile.proto new file mode 100644 index 0000000..c7a475a --- /dev/null +++ b/lightswitch-proto/src/protos/profile.proto @@ -0,0 +1,227 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Profile is a common stacktrace profile format. +// +// Measurements represented with this format should follow the +// following conventions: +// +// - Consumers should treat unset optional fields as if they had been +// set with their default value. +// +// - When possible, measurements should be stored in "unsampled" form +// that is most useful to humans. There should be enough +// information present to determine the original sampled values. +// +// - On-disk, the serialized proto must be gzip-compressed. +// +// - The profile is represented as a set of samples, where each sample +// references a sequence of locations, and where each location belongs +// to a mapping. +// - There is a N->1 relationship from sample.location_id entries to +// locations. For every sample.location_id entry there must be a +// unique Location with that id. +// - There is an optional N->1 relationship from locations to +// mappings. For every nonzero Location.mapping_id there must be a +// unique Mapping with that id. + +syntax = "proto3"; + +package perftools.profiles; + +option java_outer_classname = "ProfileProto"; +option java_package = "com.google.perftools.profiles"; + +message Profile { + // A description of the samples associated with each Sample.value. + // For a cpu profile this might be: + // [["cpu","nanoseconds"]] or [["wall","seconds"]] or [["syscall","count"]] + // For a heap profile, this might be: + // [["allocations","count"], ["space","bytes"]], + // If one of the values represents the number of events represented + // by the sample, by convention it should be at index 0 and use + // sample_type.unit == "count". + repeated ValueType sample_type = 1; + // The set of samples recorded in this profile. + repeated Sample sample = 2; + // Mapping from address ranges to the image/binary/library mapped + // into that address range. mapping[0] will be the main binary. + repeated Mapping mapping = 3; + // Locations referenced by samples. + repeated Location location = 4; + // Functions referenced by locations. + repeated Function function = 5; + // A common table for strings referenced by various messages. + // string_table[0] must always be "". + repeated string string_table = 6; + // frames with Function.function_name fully matching the following + // regexp will be dropped from the samples, along with their successors. + int64 drop_frames = 7; // Index into string table. + // frames with Function.function_name fully matching the following + // regexp will be kept, even if it matches drop_frames. + int64 keep_frames = 8; // Index into string table. + + // The following fields are informational, do not affect + // interpretation of results. + + // Time of collection (UTC) represented as nanoseconds past the epoch. + int64 time_nanos = 9; + // Duration of the profile, if a duration makes sense. + int64 duration_nanos = 10; + // The kind of events between sampled occurrences. + // e.g [ "cpu","cycles" ] or [ "heap","bytes" ] + ValueType period_type = 11; + // The number of events between sampled occurrences. + int64 period = 12; + // Free-form text associated with the profile. The text is displayed as is + // to the user by the tools that read profiles (e.g. by pprof). This field + // should not be used to store any machine-readable information, it is only + // for human-friendly content. The profile must stay functional if this field + // is cleaned. + repeated int64 comment = 13; // Indices into string table. + // Index into the string table of the type of the preferred sample + // value. If unset, clients should default to the last sample value. + int64 default_sample_type = 14; +} + +// ValueType describes the semantics and measurement units of a value. +message ValueType { + int64 type = 1; // Index into string table. + int64 unit = 2; // Index into string table. +} + +// Each Sample records values encountered in some program +// context. The program context is typically a stack trace, perhaps +// augmented with auxiliary information like the thread-id, some +// indicator of a higher level request being handled etc. +message Sample { + // The ids recorded here correspond to a Profile.location.id. + // The leaf is at location_id[0]. + repeated uint64 location_id = 1; + // The type and unit of each value is defined by the corresponding + // entry in Profile.sample_type. All samples must have the same + // number of values, the same as the length of Profile.sample_type. + // When aggregating multiple samples into a single sample, the + // result has a list of values that is the element-wise sum of the + // lists of the originals. + repeated int64 value = 2; + // label includes additional context for this sample. It can include + // things like a thread id, allocation size, etc. + // + // NOTE: While possible, having multiple values for the same label key is + // strongly discouraged and should never be used. Most tools (e.g. pprof) do + // not have good (or any) support for multi-value labels. And an even more + // discouraged case is having a string label and a numeric label of the same + // name on a sample. Again, possible to express, but should not be used. + repeated Label label = 3; +} + +message Label { + // Index into string table. An annotation for a sample (e.g. + // "allocation_size") with an associated value. + // Keys with "pprof::" prefix are reserved for internal use by pprof. + int64 key = 1; + + // At most one of the following must be present + int64 str = 2; // Index into string table + int64 num = 3; + + // Should only be present when num is present. + // Specifies the units of num. + // Use arbitrary string (for example, "requests") as a custom count unit. + // If no unit is specified, consumer may apply heuristic to deduce the unit. + // Consumers may also interpret units like "bytes" and "kilobytes" as memory + // units and units like "seconds" and "nanoseconds" as time units, + // and apply appropriate unit conversions to these. + int64 num_unit = 4; // Index into string table +} + +message Mapping { + // Unique nonzero id for the mapping. + uint64 id = 1; + // Address at which the binary (or DLL) is loaded into memory. + uint64 memory_start = 2; + // The limit of the address range occupied by this mapping. + uint64 memory_limit = 3; + // Offset in the binary that corresponds to the first mapped address. + uint64 file_offset = 4; + // The object this entry is loaded from. This can be a filename on + // disk for the main binary and shared libraries, or virtual + // abstractions like "[vdso]". + int64 filename = 5; // Index into string table + // A string that uniquely identifies a particular program version + // with high probability. E.g., for binaries generated by GNU tools, + // it could be the contents of the .note.gnu.build-id field. + int64 build_id = 6; // Index into string table + + // The following fields indicate the resolution of symbolic info. + bool has_functions = 7; + bool has_filenames = 8; + bool has_line_numbers = 9; + bool has_inline_frames = 10; +} + +// Describes function and line table debug information. +message Location { + // Unique nonzero id for the location. A profile could use + // instruction addresses or any integer sequence as ids. + uint64 id = 1; + // The id of the corresponding profile.Mapping for this location. + // It can be unset if the mapping is unknown or not applicable for + // this profile type. + uint64 mapping_id = 2; + // The instruction address for this location, if available. It + // should be within [Mapping.memory_start...Mapping.memory_limit] + // for the corresponding mapping. A non-leaf address may be in the + // middle of a call instruction. It is up to display tools to find + // the beginning of the instruction if necessary. + uint64 address = 3; + // Multiple line indicates this location has inlined functions, + // where the last entry represents the caller into which the + // preceding entries were inlined. + // + // E.g., if memcpy() is inlined into printf: + // line[0].function_name == "memcpy" + // line[1].function_name == "printf" + repeated Line line = 4; + // Provides an indication that multiple symbols map to this location's + // address, for example due to identical code folding by the linker. In that + // case the line information above represents one of the multiple + // symbols. This field must be recomputed when the symbolization state of the + // profile changes. + bool is_folded = 5; +} + +message Line { + // The id of the corresponding profile.Function for this line. + uint64 function_id = 1; + // Line number in source code. + int64 line = 2; + // Column number in source code. + int64 column = 3; +} + +message Function { + // Unique nonzero id for the function. + uint64 id = 1; + // Name of the function, in human-readable form if available. + int64 name = 2; // Index into string table + // Name of the function, as identified by the system. + // For instance, it can be a C++ mangled name. + int64 system_name = 3; // Index into string table + // Source file containing the function. + int64 filename = 4; // Index into string table + // Line number in source file. + int64 start_line = 5; +} diff --git a/src/collector.rs b/src/collector.rs index c8edc29..910df50 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -1,30 +1,135 @@ +use prost::Message; use std::collections::HashMap; use std::sync::{Arc, Mutex}; +use std::time::Duration; use tracing::{debug, span, Level}; use crate::object::ExecutableId; +use crate::profile::{symbolize_profile, to_proto}; use crate::profiler::ProcessInfo; use crate::profiler::RawAggregatedProfile; use crate::profiler::{ObjectFileInfo, RawAggregatedSample}; -pub struct Collector { - profiles: Vec, +pub trait Collector { + fn collect( + &mut self, + profile: RawAggregatedProfile, + procs: &HashMap, + objs: &HashMap, + ); + fn finish( + &self, + ) -> ( + RawAggregatedProfile, + &HashMap, + &HashMap, + ); +} + +pub type ThreadSafeCollector = Arc>>; + +#[derive(Default)] +pub struct NullCollector { procs: HashMap, objs: HashMap, } -type ThreadSafeCollector = Arc>; +impl NullCollector { + pub fn new() -> Self { + Self::default() + } +} -impl Collector { - pub fn new() -> ThreadSafeCollector { - Arc::new(Mutex::new(Self { - profiles: Vec::new(), - procs: HashMap::new(), - objs: HashMap::new(), - })) +/// Discards the profile, useful for testing. +impl Collector for NullCollector { + fn collect( + &mut self, + _profile: RawAggregatedProfile, + _procs: &HashMap, + _objs: &HashMap, + ) { } - pub fn collect( + fn finish( + &self, + ) -> ( + RawAggregatedProfile, + &HashMap, + &HashMap, + ) { + (RawAggregatedProfile::new(), &self.procs, &self.objs) + } +} + +#[derive(Default)] +pub struct StreamingCollector { + pprof_ingest_url: String, + timeout: Duration, + procs: HashMap, + objs: HashMap, +} + +impl StreamingCollector { + pub fn new(pprof_ingest_url: &str) -> Self { + Self { + pprof_ingest_url: pprof_ingest_url.into(), + timeout: Duration::from_secs(30), + ..Default::default() + } + } +} + +/// POSTs the pprof formatted profiles to the given url. +impl Collector for StreamingCollector { + fn collect( + &mut self, + profile: RawAggregatedProfile, + procs: &HashMap, + objs: &HashMap, + ) { + let _span = span!(Level::DEBUG, "StreamingCollector.finish").entered(); + + let symbolized_profile = symbolize_profile(&profile, procs, objs); + let pprof_builder = to_proto(symbolized_profile, procs, objs); + let pprof = pprof_builder.profile(); + + let client_builder = reqwest::blocking::Client::builder().timeout(self.timeout); + let client = client_builder.build().unwrap(); + let response = client + .post(self.pprof_ingest_url.clone()) + .body(pprof.encode_to_vec()) + .send(); + + tracing::debug!("http response: {:?}", response); + } + + fn finish( + &self, + ) -> ( + RawAggregatedProfile, + &HashMap, + &HashMap, + ) { + (RawAggregatedProfile::new(), &self.procs, &self.objs) + } +} + +#[derive(Default)] +pub struct AggregatorCollector { + profiles: Vec, + procs: HashMap, + objs: HashMap, +} + +impl AggregatorCollector { + pub fn new() -> Self { + Self::default() + } +} + +/// Aggregagates the samples in memory, which might be acceptable when profiling for short amounts of time. +impl Collector for AggregatorCollector { + fn collect( &mut self, profile: RawAggregatedProfile, procs: &HashMap, @@ -51,14 +156,14 @@ impl Collector { } } - pub fn finish( + fn finish( &self, ) -> ( RawAggregatedProfile, &HashMap, &HashMap, ) { - let _span: span::EnteredSpan = span!(Level::DEBUG, "symbolize_profiles").entered(); + let _span = span!(Level::DEBUG, "AggregatorCollector.finish").entered(); let mut samples_count = HashMap::new(); for profile in &self.profiles { diff --git a/src/main.rs b/src/main.rs index 6dd8971..49cd36b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,26 @@ use std::error::Error; use std::fs::File; use std::io::IsTerminal; +use std::io::Write; use std::ops::RangeInclusive; use std::panic; use std::path::PathBuf; +use std::sync::{Arc, Mutex}; use std::time::Duration; use clap::Parser; - use inferno::flamegraph; +use lightswitch::collector::{AggregatorCollector, Collector, NullCollector, StreamingCollector}; use nix::unistd::Uid; use primal::is_prime; +use prost::Message; use tracing::{error, Level}; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::FmtSubscriber; -use lightswitch::collector::Collector; use lightswitch::object::ObjectFile; -use lightswitch::profile::fold_profile; use lightswitch::profile::symbolize_profile; +use lightswitch::profile::{fold_profile, to_proto}; use lightswitch::profiler::Profiler; use lightswitch::unwind_info::in_memory_unwind_info; use lightswitch::unwind_info::remove_redundant; @@ -26,6 +28,7 @@ use lightswitch::unwind_info::remove_unnecesary_markers; use lightswitch::unwind_info::UnwindInfoBuilder; const SAMPLE_FREQ_RANGE: RangeInclusive = 1..=1009; +const PPROF_INGEST_URL: &str = "http://localhost:4567/pprof/new"; fn parse_duration(arg: &str) -> Result { let seconds = arg.parse()?; @@ -86,10 +89,19 @@ enum LoggingLevel { #[derive(clap::ValueEnum, Debug, Clone, Default)] enum ProfileFormat { + None, #[default] FlameGraph, - /// Do not produce a profile. Used for kernel tests. + Pprof, +} + +#[derive(PartialEq, clap::ValueEnum, Debug, Clone, Default)] +enum ProfileSender { + /// Discard the profile. Used for kernel tests. None, + #[default] + LocalDisk, + Remote, } #[derive(Parser, Debug)] @@ -135,9 +147,11 @@ struct Cli { #[arg(long, default_value_t, value_enum)] profile_format: ProfileFormat, /// Name for the generated profile. - // TODO: change suffix depending on the format. - #[arg(long, default_value = "flame.svg")] - profile_name: PathBuf, + #[arg(long)] + profile_name: Option, + /// Where to write the profile. + #[arg(long, default_value_t, value_enum)] + sender: ProfileSender, } /// Exit the main thread if any thread panics. We prefer this behaviour because pretty much every @@ -203,7 +217,15 @@ fn main() -> Result<(), Box> { std::process::exit(1); } - let collector = Collector::new(); + let collector = Arc::new(Mutex::new(match args.sender { + ProfileSender::None => Box::new(NullCollector::new()) as Box, + ProfileSender::LocalDisk => { + Box::new(AggregatorCollector::new()) as Box + } + ProfileSender::Remote => { + Box::new(StreamingCollector::new(PPROF_INGEST_URL)) as Box + } + })); let mut p: Profiler<'_> = Profiler::new( args.libbpf_logs, @@ -216,6 +238,16 @@ fn main() -> Result<(), Box> { let collector = collector.lock().unwrap(); let (raw_profile, procs, objs) = collector.finish(); + + // If we need to send the profile to the backend there's nothing else to do. + match args.sender { + ProfileSender::Remote | ProfileSender::None => { + return Ok(()); + } + _ => {} + } + + // Otherwise let's symbolize the profile and write it to disk. let symbolized_profile = symbolize_profile(&raw_profile, procs, objs); match args.profile_format { @@ -223,13 +255,30 @@ fn main() -> Result<(), Box> { let folded = fold_profile(symbolized_profile); let mut options: flamegraph::Options<'_> = flamegraph::Options::default(); let data = folded.as_bytes(); - let f = File::create(args.profile_name).unwrap(); + let f = File::create(args.profile_name.unwrap_or_else(|| "flame.svg".into())).unwrap(); match flamegraph::from_reader(&mut options, data, f) { Ok(_) => { - eprintln!("Profile successfully written to disk"); + eprintln!("Flamegraph profile successfully written to disk"); + } + Err(e) => { + error!("Failed generate flamegraph: {:?}", e); + } + } + } + ProfileFormat::Pprof => { + let mut buffer = Vec::new(); + let proto = to_proto(symbolized_profile, procs, objs); + proto.validate().unwrap(); + proto.profile().encode(&mut buffer).unwrap(); + let mut pprof_file = + File::create(args.profile_name.unwrap_or_else(|| "profile.pb".into())).unwrap(); + + match pprof_file.write_all(&buffer) { + Ok(_) => { + eprintln!("Pprof profile successfully written to disk"); } Err(e) => { - error!("Failed generate profile: {:?}", e); + error!("Failed generate pprof: {:?}", e); } } } @@ -263,7 +312,7 @@ mod tests { let actual = String::from_utf8(cmd.unwrap().stdout).unwrap(); insta::assert_yaml_snapshot!(actual, @r###" --- - "Usage: lightswitch [OPTIONS]\n\nOptions:\n --pids \n Specific PIDs to profile\n\n --tids \n Specific TIDs to profile (these can be outside the PIDs selected above)\n\n --show-unwind-info \n Show unwind info for given binary\n\n --show-info \n Show build ID for given binary\n\n -D, --duration \n How long this agent will run in seconds\n \n [default: 18446744073709551615]\n\n --libbpf-logs\n Enable libbpf logs. This includes the BPF verifier output\n\n --bpf-logging\n Enable BPF programs logging\n\n --logging \n Set lightswitch's logging level\n \n [default: info]\n [possible values: trace, debug, info, warn, error]\n\n --sample-freq \n Per-CPU Sampling Frequency in Hz\n \n [default: 19]\n\n --profile-format \n Output file for Flame Graph in SVG format\n \n [default: flame-graph]\n\n Possible values:\n - flame-graph\n - none: Do not produce a profile. Used for kernel tests\n\n --profile-name \n Name for the generated profile\n \n [default: flame.svg]\n\n -h, --help\n Print help (see a summary with '-h')\n" + "Usage: lightswitch [OPTIONS]\n\nOptions:\n --pids \n Specific PIDs to profile\n\n --tids \n Specific TIDs to profile (these can be outside the PIDs selected above)\n\n --show-unwind-info \n Show unwind info for given binary\n\n --show-info \n Show build ID for given binary\n\n -D, --duration \n How long this agent will run in seconds\n \n [default: 18446744073709551615]\n\n --libbpf-logs\n Enable libbpf logs. This includes the BPF verifier output\n\n --bpf-logging\n Enable BPF programs logging\n\n --logging \n Set lightswitch's logging level\n \n [default: info]\n [possible values: trace, debug, info, warn, error]\n\n --sample-freq \n Per-CPU Sampling Frequency in Hz\n \n [default: 19]\n\n --profile-format \n Output file for Flame Graph in SVG format\n \n [default: flame-graph]\n [possible values: none, flame-graph, pprof]\n\n --profile-name \n Name for the generated profile\n\n --sender \n Where to write the profile\n \n [default: local-disk]\n\n Possible values:\n - none: Discard the profile. Used for kernel tests\n - local-disk\n - remote\n\n -h, --help\n Print help (see a summary with '-h')\n" "###); } diff --git a/src/object.rs b/src/object.rs index 01e3059..20ef043 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,3 +1,6 @@ +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; use std::fs; use std::io::Read; use std::path::PathBuf; @@ -30,6 +33,22 @@ pub struct ElfLoad { pub vaddr: u64, } +impl Display for BuildId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + BuildId::Gnu(build_id) => { + write!(f, "gnu-{}", build_id) + } + BuildId::Go(build_id) => { + write!(f, "go-{}", build_id) + } + BuildId::Sha256(build_id) => { + write!(f, "sha256-{}", build_id) + } + } + } +} + #[derive(Debug)] pub struct ObjectFile<'a> { leaked_mmap_ptr: *const memmap2::Mmap, diff --git a/src/profile.rs b/src/profile.rs index 1fef0b3..adc8ba9 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -1,12 +1,14 @@ +use lightswitch_proto::profile::LabelStringOrNumber; +use lightswitch_proto::profile::PprofBuilder; use std::collections::HashMap; use std::fmt::Write; use std::path::PathBuf; +use std::time::Duration; use tracing::{debug, error, span, Level}; use crate::bpf::profiler_bindings::native_stack_t; use crate::ksym::KsymIter; use crate::object::ExecutableId; -use crate::profiler::ExecutableMapping; use crate::profiler::Frame; use crate::profiler::FrameAddress; use crate::profiler::ObjectFileInfo; @@ -16,7 +18,102 @@ use crate::profiler::SymbolizedAggregatedProfile; use crate::profiler::SymbolizedAggregatedSample; use crate::usym::symbolize_native_stack_blaze; -/// Converts a symbolized aggregated profiles to their folded representation that most flamegraph renderers use. +/// Converts a given symbolized profile to Google's pprof. +pub fn to_proto( + profile: SymbolizedAggregatedProfile, + procs: &HashMap, + objs: &HashMap, +) -> PprofBuilder { + let mut pprof = PprofBuilder::new(Duration::from_secs(5), 27); + + for sample in profile { + let pid = sample.pid; + let ustack = sample.ustack; + let kstack = sample.kstack; + let mut location_ids = Vec::new(); + + for kframe in kstack { + // TODO: Add real values, read kernel build ID, etc. + let mapping_id: u64 = pprof.add_mapping( + 0x1000000, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0x0, + "[kernel]", // This is a special marker. + "fake_kernel_build_id", + ); + + let (line, _) = pprof.add_line(&kframe.name); + let location = pprof.add_location(kframe.virtual_address, mapping_id, vec![line]); + location_ids.push(location); + } + + for uframe in ustack { + let addr = uframe.virtual_address; + + let Some(info) = procs.get(&pid) else { + // r.push("".to_string()); + continue; + }; + + let Some(mapping) = info.mappings.for_address(addr) else { + // r.push("".to_string()); + continue; + }; + + match objs.get(&mapping.executable_id) { + Some(obj) => { + let normalized_addr = addr - mapping.start_addr + mapping.offset + - obj.load_offset + + obj.load_vaddr; + + let build_id = match mapping.build_id { + Some(build_id) => { + format!("{}", build_id) + } + None => "no-build-id".into(), + }; + let mapping_id: u64 = pprof.add_mapping( + mapping.executable_id, + mapping.start_addr, + mapping.end_addr, + mapping.offset, + obj.path.to_str().expect("convert path to str"), + &build_id, + ); + + // TODO, ensure address normalization is correct + // normalized_addr == uframe.file_offset.unwrap() + + let (line, _) = pprof.add_line(&uframe.name); + let location = pprof.add_location(normalized_addr, mapping_id, vec![line]); + location_ids.push(location); + } + None => { + debug!("build id not found"); + } + } + } + + let labels = vec![ + pprof.new_label( + "pid", + LabelStringOrNumber::Number(pid.into(), "task-tgid".into()), + ), + pprof.new_label( + "pid", + LabelStringOrNumber::Number(pid.into(), "task-id".into()), + ), + pprof.new_label("comm", LabelStringOrNumber::String("fake-comm".into())), + ]; + + pprof.add_sample(location_ids, sample.count as i64, labels); + } + + pprof +} + +/// Converts a collection of symbolized aggregated profiles to their folded representation that most flamegraph renderers use. /// Folded stacks look like this: /// /// > base_frame;other_frame;top_frame 100 @@ -182,17 +279,7 @@ pub fn symbolize_profile( r } -fn find_mapping(mappings: &[ExecutableMapping], addr: u64) -> Option { - for mapping in mappings { - if mapping.start_addr <= addr && addr <= mapping.end_addr { - return Some(mapping.clone()); - } - } - - None -} - -fn fetch_symbols_for_profile( +pub fn fetch_symbols_for_profile( profile: &RawAggregatedProfile, procs: &HashMap, objs: &HashMap, @@ -214,7 +301,7 @@ fn fetch_symbols_for_profile( continue; } - let Some(mapping) = find_mapping(&info.mappings, addr) else { + let Some(mapping) = info.mappings.for_address(addr) else { continue; //return Err(anyhow!("could not find mapping")); }; @@ -279,7 +366,7 @@ fn symbolize_native_stack( continue; }; - let Some(mapping) = find_mapping(&info.mappings, addr) else { + let Some(mapping) = info.mappings.for_address(addr) else { r.push(Frame::with_error("".to_string())); continue; }; diff --git a/src/profiler.rs b/src/profiler.rs index fe6414a..f18c274 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -49,7 +49,7 @@ pub enum ProcessStatus { #[derive(Clone)] pub struct ProcessInfo { pub status: ProcessStatus, - pub mappings: Vec, + pub mappings: ExecutableMappings, } pub struct ObjectFileInfo { @@ -84,6 +84,21 @@ pub struct ExecutableMapping { // Add (inode, ctime) and whether the file is in the root namespace } +#[derive(Clone)] +pub struct ExecutableMappings(Vec); + +impl ExecutableMappings { + pub fn for_address(&self, addr: u64) -> Option { + for mapping in &self.0 { + if mapping.start_addr <= addr && addr <= mapping.end_addr { + return Some(mapping.clone()); + } + } + + None + } +} + impl ExecutableMapping { fn mark_as_deleted(&mut self, object_files: &mut HashMap) { // Avoid decrementing the reference count logic more than once if called multiple times. @@ -205,7 +220,7 @@ pub struct FrameAddress { pub file_offset: u64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct Frame { /// Address from the process, as collected from the BPF program. pub virtual_address: u64, @@ -233,7 +248,7 @@ impl Frame { } } -#[derive(Default, Debug)] +#[derive(Default, Debug, Hash, Eq, PartialEq)] pub struct SymbolizedAggregatedSample { pub pid: i32, pub tid: i32, @@ -362,7 +377,7 @@ impl Profiler<'_> { .expect("handle send"); } - pub fn run(mut self, collector: Arc>) { + pub fn run(mut self, collector: ThreadSafeCollector) { // In this case, we only want to calculate maximum sampling buffer sizes based on the // number of online CPUs, NOT possible CPUs, when they differ - which is often. let num_cpus = get_online_cpus().expect("get online CPUs").len() as u64; @@ -513,7 +528,7 @@ impl Profiler<'_> { Some(proc_info) => { debug!("marking process {} as exited", pid); proc_info.status = ProcessStatus::Exited; - for mapping in &mut proc_info.mappings { + for mapping in &mut proc_info.mappings.0 { let mut object_files = self.object_files.lock().expect("lock"); mapping.mark_as_deleted(&mut object_files); } @@ -529,7 +544,7 @@ impl Profiler<'_> { match procs.get_mut(&pid) { Some(proc_info) => { - for mapping in &mut proc_info.mappings { + for mapping in &mut proc_info.mappings.0 { if mapping.start_addr <= start_address && start_address <= mapping.end_addr { debug!("found memory mapping starting at {:x} for pid {} while handling munmap", start_address, pid); let mut object_files = self.object_files.lock().expect("lock"); @@ -765,6 +780,7 @@ impl Profiler<'_> { .get(&pid) .unwrap() .mappings + .0 .iter() { if self.native_unwind_state.shard_index > MAX_SHARDS { @@ -1229,7 +1245,7 @@ impl Profiler<'_> { mappings.sort_by_key(|k| k.start_addr.cmp(&k.start_addr)); let proc_info = ProcessInfo { status: ProcessStatus::Running, - mappings, + mappings: ExecutableMappings(mappings), }; self.procs .clone() diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 94e6212..7e8c4cb 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,9 +1,10 @@ use std::io; use std::io::Write; use std::process::{Child, Command, Stdio}; +use std::sync::{Arc, Mutex}; use std::time::Duration; -use lightswitch::collector::Collector; +use lightswitch::collector::{AggregatorCollector, Collector}; use lightswitch::profile::symbolize_profile; use lightswitch::profiler::Profiler; use lightswitch::profiler::SymbolizedAggregatedProfile; @@ -93,7 +94,9 @@ fn test_integration() { build_test_binary("cpp-progs"); let cpp_proc = TestProcess::new("main_cpp_clang_O1"); - let collector = Collector::new(); + let collector = Arc::new(Mutex::new( + Box::new(AggregatorCollector::new()) as Box + )); let mut p = Profiler::new(bpf_test_debug, bpf_test_debug, Duration::from_secs(5), 999); p.profile_pids(vec![cpp_proc.pid()]); p.run(collector.clone()); diff --git a/vm.nix b/vm.nix index 8a7311a..c91e81f 100644 --- a/vm.nix +++ b/vm.nix @@ -87,32 +87,32 @@ let [[target]] name = "Fedora 5.15" kernel = "${kernel_5_15}/bzImage" - command = "${lightswitch}/bin/lightswitch --duration 0 --profile-format=none" + command = "${lightswitch}/bin/lightswitch --duration 0 --sender=none" [[target]] name = "Fedora 6.0" kernel = "${kernel_6_0}/bzImage" - command = "${lightswitch}/bin/lightswitch --duration 0 --profile-format=none" + command = "${lightswitch}/bin/lightswitch --duration 0 --sender=none" [[target]] name = "Fedora 6.2" kernel = "${kernel_6_2}/bzImage" - command = "${lightswitch}/bin/lightswitch --duration 0 --profile-format=none" + command = "${lightswitch}/bin/lightswitch --duration 0 --sender=none" [[target]] name = "Fedora 6.6" kernel = "${kernel_6_6}/bzImage" - command = "${lightswitch}/bin/lightswitch --duration 0 --profile-format=none" + command = "${lightswitch}/bin/lightswitch --duration 0 --sender=none" [[target]] name = "Upstream 6.8.7" kernel = "${kernel_6_8_7}/bzImage" - command = "${lightswitch}/bin/lightswitch --duration 0 --profile-format=none" + command = "${lightswitch}/bin/lightswitch --duration 0 --sender=none" [[target]] name = "Upstream v6.9-rc5" kernel = "${kernel_6_9_rc5}/bzImage" - command = "${lightswitch}/bin/lightswitch --duration 0 --profile-format=none" + command = "${lightswitch}/bin/lightswitch --duration 0 --sender=none" ''; nativeBuildInputs = [ ]; installPhase = ''