diff --git a/.gitignore b/.gitignore index 810a477..142cfac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target flame.svg -src/bpf/*_skel.rs \ No newline at end of file +src/bpf/*_skel.rs diff --git a/Cargo.lock b/Cargo.lock index 3a6cd61..bab61ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arrayvec" @@ -105,6 +105,21 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "assert_cmd" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -113,17 +128,17 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bindgen" -version = "0.69.2" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags 2.4.2", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", @@ -158,6 +173,17 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -234,9 +260,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -259,9 +285,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", "clap_derive", @@ -269,9 +295,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -281,11 +307,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.48", @@ -293,9 +319,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -372,6 +398,24 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "dissimilar" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.9.0" @@ -403,6 +447,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "expect-test" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" +dependencies = [ + "dissimilar", + "once_cell", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -425,6 +479,101 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -453,6 +602,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hamming" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" + [[package]] name = "hashbrown" version = "0.14.3" @@ -465,6 +620,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.4" @@ -553,6 +714,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -633,9 +803,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" @@ -652,12 +822,14 @@ name = "lightswitch" version = "0.1.0" dependencies = [ "anyhow", + "assert_cmd", "bindgen", "blazesym", "chrono", "clap", "data-encoding", "errno", + "expect-test", "gimli", "inferno", "lazy_static", @@ -670,8 +842,10 @@ dependencies = [ "page_size", "perf-event-open-sys", "plain", + "primal", "procfs", "ring", + "rstest", "thiserror", "tracing", "tracing-subscriber", @@ -811,6 +985,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -853,9 +1036,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "d7090bae93f8585aad99e595b7073c5de9ba89fbd6b4e9f0cdd7a10177273ac8" dependencies = [ "flate2", "memchr", @@ -897,12 +1080,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "perf-event-open-sys" version = "4.0.0" @@ -936,6 +1113,33 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "prettyplease" version = "0.2.16" @@ -946,6 +1150,52 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "primal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b53cc99c892c461727618e8a63806c94b09ae13c494dc5fc70a7557b3a2f071" +dependencies = [ + "primal-check", + "primal-estimate", + "primal-sieve", +] + +[[package]] +name = "primal-bit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce4fe11b2a87850ca3bd5dc9c7cb9f66e32a09edab221be406ac5ff677f2241" +dependencies = [ + "hamming", +] + +[[package]] +name = "primal-check" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" +dependencies = [ + "num-integer", +] + +[[package]] +name = "primal-estimate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7374f14c76f23e1271e6be806981ac5dd9e52b59132b0a2f10bcc412495f9159" + +[[package]] +name = "primal-sieve" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2a14766f8c543620824b5b2cec356abf2681b76966a7ac4b4ed2c0011e696a" +dependencies = [ + "primal-bit", + "primal-estimate", + "smallvec", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1047,6 +1297,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "rgb" version = "0.8.37" @@ -1058,16 +1314,46 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.48", + "unicode-ident", ] [[package]] @@ -1082,6 +1368,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.30" @@ -1103,9 +1398,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ruzstd" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" dependencies = [ "byteorder", "derive_more", @@ -1196,6 +1491,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -1228,9 +1532,9 @@ checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "strum_macros" @@ -1238,7 +1542,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -1280,20 +1584,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", @@ -1434,6 +1744,15 @@ dependencies = [ "libc", ] +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 4c5f6d3..83a102f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,30 +4,35 @@ version = "0.1.0" edition = "2021" [dependencies] -gimli = "*" -object = "*" -memmap2 = "*" -lazy_static = "*" -anyhow = "*" -thiserror = "*" -libbpf-rs = {version = "*", features=["static"]} -num_cpus = "*" -perf-event-open-sys = "*" -libc = "*" -errno = "*" -plain = "*" -procfs = "*" -ring = "*" -data-encoding = "*" -page_size = "*" -clap = { version = "*", features = ["derive"] } +gimli = "0.28.1" +object = "0.34.0" +memmap2 = "0.9.4" +lazy_static = "1.4.0" +anyhow = "1.0.81" +thiserror = "1.0.58" +libbpf-rs = { version = "0.22.1", features = ["static"] } +num_cpus = "1.16.0" +perf-event-open-sys = "4.0.0" +libc = "0.2.153" +errno = "0.3.8" +plain = "0.2.3" +procfs = "0.16.0" +ring = "0.17.8" +data-encoding = "2.5.0" +page_size = "0.6.0" +clap = { version = "4.5.3", features = ["derive", "string"] } blazesym = "0.2.0-alpha.10" -tracing = "*" -tracing-subscriber = "*" -chrono = "*" -inferno = "*" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +chrono = "0.4.35" +inferno = "0.11.19" +primal = "0.3.2" +[dev-dependencies] +assert_cmd = { version = "2.0.14" } +expect-test = { version = "1.4.1" } +rstest = "0.18.2" [build-dependencies] -bindgen = "*" -libbpf-cargo = "*" +bindgen = "0.69.4" +libbpf-cargo = "0.22.1" diff --git a/src/main.rs b/src/main.rs index d2017ec..61753d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use clap::Parser; use inferno::flamegraph; use std::fmt::Write; use std::fs::File; +use std::ops::RangeInclusive; use tracing::Level; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::FmtSubscriber; @@ -12,27 +13,77 @@ use lightswitch::object::build_id; use lightswitch::profiler::Collector; use lightswitch::profiler::Profiler; use lightswitch::unwind_info::{compact_printing_callback, UnwindInfoBuilder}; +use primal::is_prime; use std::error::Error; use std::path::PathBuf; use std::time::Duration; +const SAMPLE_FREQ_RANGE: RangeInclusive = 1..=1009; + +fn parse_duration(arg: &str) -> Result { + let seconds = arg.parse()?; + Ok(Duration::from_secs(seconds)) +} + +fn sample_freq_in_range(s: &str) -> Result { + let sample_freq: usize = s + .parse() + .map_err(|_| format!("`{s}' isn't a valid frequency"))?; + if !SAMPLE_FREQ_RANGE.contains(&sample_freq) { + return Err(format!( + "sample frequency not in allowed range {}-{}", + SAMPLE_FREQ_RANGE.start(), + SAMPLE_FREQ_RANGE.end() + )); + } + if !is_prime(sample_freq.try_into().unwrap()) { + return Err("sample frequency is not prime".to_string()); + } + Ok(sample_freq as u16) +} + #[derive(Parser, Debug)] -struct Args { +struct Cli { + /// Specific PIDs to profile #[arg(long)] pids: Vec, + /// Specific TIDs to profile (these can be outside the PIDs selected above) #[arg(long)] + tids: Vec, + /// Show unwind info for given binary + #[arg(long, value_name = "PATH_TO_BINARY", + conflicts_with_all = ["pids", "tids", "show_info", "duration", + "filter_logs", "sample_freq", "flamegraph_file"] + )] show_unwind_info: Option, - #[arg(long)] + /// Show build ID for given binary + #[arg(long, value_name = "PATH_TO_BINARY", + conflicts_with_all = ["pids", "tids", "duration", "filter_logs", + "sample_freq", "flamegraph_file"] + )] show_info: Option, - #[arg(long)] - continuous: bool, + /// How long this agent will run in seconds + #[arg(short='D', long, default_value = Duration::MAX.as_secs().to_string(), + value_parser = parse_duration)] + duration: Duration, + /// Enable TRACE (max) level logging - defaults to INFO level otherwise #[arg(long, action=ArgAction::SetFalse)] filter_logs: bool, + // Verification for this option guarantees the only possible selections + // are prime numbers up to and including 1001 + /// Per-CPU Sampling Frequency in Hz + #[arg(long, default_value_t = 19, value_name = "SAMPLE_FREQ_IN_HZ", + value_parser = sample_freq_in_range, + )] + sample_freq: u16, + /// Output file for Flame Graph in SVG format + #[arg(long, default_value = "flame.svg")] + flamegraph_file: PathBuf, } fn main() -> Result<(), Box> { - let args = Args::parse(); + let args = Cli::parse(); let subscriber = FmtSubscriber::builder() .with_max_level(if args.filter_logs { @@ -59,16 +110,11 @@ fn main() -> Result<(), Box> { return Ok(()); } - let mut duration = Duration::MAX; - if !args.continuous { - duration = Duration::from_secs(5); - } - let collector = Collector::new(); - let mut p: Profiler<'_> = Profiler::new(false); + let mut p: Profiler<'_> = Profiler::new(false, args.duration, args.sample_freq); p.profile_pids(args.pids); - p.run(duration, collector.clone()); + p.run(collector.clone()); let profiles = collector.lock().unwrap().finish(); @@ -90,9 +136,74 @@ fn main() -> Result<(), Box> { let mut options: flamegraph::Options<'_> = flamegraph::Options::default(); let data = folded.as_bytes(); - let flame_path = "flame.svg"; + let flame_path = args.flamegraph_file; let f = File::create(flame_path).unwrap(); flamegraph::from_reader(&mut options, data, f).unwrap(); Ok(()) } + +#[cfg(test)] +mod tests { + use super::Cli; + use assert_cmd::Command; + use clap::Parser; + use expect_test::expect; + use rstest::rstest; + + #[test] + fn verify_cli() { + use clap::CommandFactory; + Cli::command().debug_assert() + } + + #[test] + fn cli_help() { + let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); + + cmd.arg("--help"); + let expected = expect![[r#" + "Usage: lightswitch [OPTIONS]\n\nOptions:\n --pids \n Specific PIDs to profile\n --tids \n Specific TIDs to profile (these can be outside the PIDs selected above)\n --show-unwind-info \n Show unwind info for given binary\n --show-info \n Show build ID for given binary\n -D, --duration \n How long this agent will run in seconds [default: 18446744073709551615]\n --filter-logs\n \n --sample-freq \n Per-CPU Sampling Frequency in Hz [default: 19]\n --flamegraph-file \n Output file for Flame Graph in SVG format [default: flame.svg]\n -h, --help\n Print help\n" + "#]]; + cmd.assert().success(); + let actual = String::from_utf8(cmd.unwrap().stdout).unwrap(); + expected.assert_debug_eq(&actual); + // cmd.assert().stdout(predicate::str::contains("junk")); + } + + #[rstest] + // The case tuples are: (string frequency to try, error string - if expected ) + // Test a frequency below the range, which is also prime + // Interestingly, this one panics, and thinks you sent -1, not -101 + #[should_panic] + #[case::neg101("-101", "sample frequency not in allowed range")] + #[case::prime_19("19", "")] + #[case::non_prime_20("20", "sample frequency is not prime")] + #[case::prime_47("47", "")] + #[case::non_prime_49("49", "sample frequency is not prime")] + #[case::prime_101("101", "")] + #[case::prime_1009("1009", "")] + #[case::non_prime_out_of_range1010("1010", "sample frequency not in allowed range")] + #[case::prime_out_of_range_1013("1013", "sample frequency not in allowed range")] + fn sample_freq_successes(#[case] desired_freq: String, #[case] expected_msg: String) { + let execname = env!("CARGO_PKG_NAME"); + let argname = "--sample-freq"; + let baseargs = vec![execname, argname]; + + let mut myargs = baseargs.clone(); + myargs.push(desired_freq.as_str()); + let result = Cli::try_parse_from(myargs.iter()); + match result { + Ok(config) => { + // println!("{:?}", config); + assert_eq!(config.sample_freq, desired_freq.parse::().unwrap()); + } + Err(err) => { + let actual_message = err.to_string(); + // println!("Errored with: {}", actual_message); + assert!(actual_message.contains(expected_msg.as_str())); + } + } + // Expected output/results for various inputs + } +} diff --git a/src/profiler.rs b/src/profiler.rs index ea94f9b..5293b3a 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -384,6 +384,10 @@ pub struct Profiler<'bpf> { // Profile channel profile_send: Arc>>, profile_receive: Arc>>, + // Duration of this profile + duration: Duration, + // Per-CPU Sampling Frequency of this profile in Hz + sample_freq: u16, session_duration: Duration, } @@ -443,7 +447,6 @@ impl Collector { } // Static config -const SAMPLE_PERIOD_HZ: u64 = 97; const MAX_UNWIND_INFO_SHARDS: u64 = 50; const SHARD_CAPACITY: usize = MAX_UNWIND_TABLE_SIZE as usize; const PERF_BUFFER_PAGES: usize = 512 * 1024; @@ -470,12 +473,12 @@ pub type SymbolizedAggregatedProfile = Vec; impl Default for Profiler<'_> { fn default() -> Self { - Self::new(false) + Self::new(false, Duration::MAX, 19) } } impl Profiler<'_> { - pub fn new(bpf_debug: bool) -> Self { + pub fn new(bpf_debug: bool, duration: Duration, sample_freq: u16) -> Self { let mut skel_builder: ProfilerSkelBuilder = ProfilerSkelBuilder::default(); skel_builder.obj_builder.debug(bpf_debug); let open_skel = skel_builder.open().expect("open skel"); @@ -517,6 +520,8 @@ impl Profiler<'_> { filter_pids, profile_send, profile_receive, + duration, + sample_freq, session_duration: Duration::from_secs(5), } } @@ -535,9 +540,9 @@ impl Profiler<'_> { .expect("handle send"); } - pub fn run(mut self, duration: Duration, collector: Arc>) { + pub fn run(mut self, collector: Arc>) { let max_samples_per_session = - SAMPLE_PERIOD_HZ * num_cpus::get() as u64 * self.session_duration.as_secs(); + self.sample_freq as u64 * num_cpus::get() as u64 * self.session_duration.as_secs(); if max_samples_per_session >= MAX_AGGREGATED_STACKS_ENTRIES.into() { warn!("samples might be lost due to too many samples in a profile session"); } @@ -583,7 +588,7 @@ impl Profiler<'_> { let mut time_since_last_scheduled_collection: Instant = Instant::now(); loop { - if start_time.elapsed() >= duration { + if start_time.elapsed() >= self.duration { debug!("done after running for {:?}", start_time.elapsed()); let profile = self.collect_profile(); self.send_profile(profile); @@ -614,7 +619,7 @@ impl Profiler<'_> { } } */ } else { - error!("unknow event {}", event.type_); + error!("unknown event {}", event.type_); } } Err(_) => { @@ -1225,8 +1230,9 @@ impl Profiler<'_> { pub fn setup_perf_events(&mut self) { let mut prog_fds = Vec::new(); for i in 0..num_cpus::get() { - let perf_fd = unsafe { setup_perf_event(i.try_into().unwrap(), SAMPLE_PERIOD_HZ) } - .expect("setup perf event"); + let perf_fd = + unsafe { setup_perf_event(i.try_into().unwrap(), self.sample_freq as u64) } + .expect("setup perf event"); prog_fds.push(perf_fd); }