mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 10:14:44 +08:00
feat: initialize profile tool with Rust and BPF integration
- Removed the legacy C implementation of the profiling tool. - Added a new Rust-based implementation that utilizes BPF for stack tracing. - Introduced a Makefile for building the project and managing dependencies. - Created a Cargo.toml file to manage Rust dependencies and project metadata. - Implemented event handling and performance monitoring using the libbpf library. - Added support for both standard and extended output formats for stack traces. - Included a .gitignore file to exclude build artifacts and output directories. - Established a new directory structure for organizing source files and BPF code.
This commit is contained in:
@@ -1 +0,0 @@
|
||||
level=Advance
|
||||
2
src/12-profile/.gitignore
vendored
Normal file
2
src/12-profile/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/src/bpf/.output
|
||||
/target
|
||||
909
src/12-profile/Cargo.lock
generated
Normal file
909
src/12-profile/Cargo.lock
generated
Normal file
@@ -0,0 +1,909 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "blazesym"
|
||||
version = "0.2.0-rc.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a810b7e5f883ad3c711208237841f051061bf59b6ee698ac4dc1fe12a3a5db"
|
||||
dependencies = [
|
||||
"cpp_demangle",
|
||||
"gimli",
|
||||
"libc",
|
||||
"memmap2 0.9.8",
|
||||
"miniz_oxide",
|
||||
"rustc-demangle",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "cpp_demangle"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6298e594375a7fead9efd5568f0a46e6a154fb6a9bdcbe3c06946ffd81a5f6"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"indexmap",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libbpf-cargo"
|
||||
version = "0.24.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704727a07f185a76c58faa7b8ed08fba3194661c212183aea1174fe2970ee185"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_metadata",
|
||||
"clap",
|
||||
"libbpf-rs",
|
||||
"memmap2 0.5.10",
|
||||
"regex",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libbpf-rs"
|
||||
version = "0.24.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93edd9cd673087fa7518fd63ad6c87be2cd9b4e35034b1873f3e3258c018275b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libbpf-sys",
|
||||
"libc",
|
||||
"vsprintf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libbpf-sys"
|
||||
version = "1.6.1+v1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e351855cbd724ac341b2a1c163568808e72acd930c491a921331c2e5347390d3"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"nix 0.30.1",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profile"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"blazesym",
|
||||
"clap",
|
||||
"libbpf-cargo",
|
||||
"libbpf-rs",
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-syntax 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vsprintf"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
19
src/12-profile/Cargo.toml
Normal file
19
src/12-profile/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "profile"
|
||||
version = "0.1.0"
|
||||
authors = ["Kuifeng Lee <kuifeng@fb.com>"]
|
||||
license = "GPL-2.0 OR BSD-3-Clause"
|
||||
edition = "2021"
|
||||
rust-version = "1.71"
|
||||
|
||||
[dependencies]
|
||||
blazesym = { version = "0.2.0-rc.4",features = ["tracing"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
libbpf-rs = "0.24"
|
||||
libc = "*"
|
||||
nix = "0.29.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = {version = "0.3", features = ["ansi", "env-filter", "fmt"]}
|
||||
|
||||
[build-dependencies]
|
||||
libbpf-cargo = "0.24"
|
||||
@@ -1,14 +1,14 @@
|
||||
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
OUTPUT := .output
|
||||
CLANG ?= clang
|
||||
CARGO ?= cargo
|
||||
TARGET_DIR := target/release
|
||||
BINARY_NAME := profile
|
||||
LIBBPF_SRC := $(abspath ../third_party/libbpf/src)
|
||||
BPFTOOL_SRC := $(abspath ../third_party/bpftool/src)
|
||||
LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a)
|
||||
BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool)
|
||||
BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool
|
||||
LIBBLAZESYM_SRC := $(abspath ../third_party/blazesym/)
|
||||
LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a)
|
||||
LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h)
|
||||
ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \
|
||||
| sed 's/arm.*/arm/' \
|
||||
| sed 's/aarch64/arm64/' \
|
||||
@@ -17,36 +17,13 @@ ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \
|
||||
| sed 's/riscv64/riscv/' \
|
||||
| sed 's/loongarch64/loongarch/')
|
||||
VMLINUX := ../third_party/vmlinux/$(ARCH)/vmlinux.h
|
||||
# Use our own libbpf API headers and Linux UAPI headers distributed with
|
||||
# libbpf to avoid dependency on system-wide headers, which could be missing or
|
||||
# outdated
|
||||
INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX))
|
||||
CFLAGS := -g -Wall
|
||||
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)
|
||||
|
||||
APPS = # minimal minimal_legacy bootstrap uprobe kprobe fentry usdt sockfilter tc ksyscall
|
||||
|
||||
CARGO ?= $(shell which cargo)
|
||||
ifeq ($(strip $(CARGO)),)
|
||||
BZS_APPS :=
|
||||
else
|
||||
BZS_APPS := profile
|
||||
APPS += $(BZS_APPS)
|
||||
# Required by libblazesym
|
||||
ALL_LDFLAGS += -lrt -ldl -lpthread -lm
|
||||
endif
|
||||
|
||||
# Get Clang's default includes on this system. We'll explicitly add these dirs
|
||||
# to the includes list when compiling with `-target bpf` because otherwise some
|
||||
# architecture-specific dirs will be "missing" on some architectures/distros -
|
||||
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
|
||||
# sys/cdefs.h etc. might be missing.
|
||||
#
|
||||
# Use '-idirafter': Don't interfere with include mechanics except where the
|
||||
# build would have failed anyways.
|
||||
# Get Clang's default includes on this system
|
||||
CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - </dev/null 2>&1 \
|
||||
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
|
||||
|
||||
INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX))
|
||||
|
||||
ifeq ($(V),1)
|
||||
Q =
|
||||
msg =
|
||||
@@ -59,22 +36,14 @@ else
|
||||
MAKEFLAGS += --no-print-directory
|
||||
endif
|
||||
|
||||
define allow-override
|
||||
$(if $(or $(findstring environment,$(origin $(1))),\
|
||||
$(findstring command line,$(origin $(1)))),,\
|
||||
$(eval $(1) = $(2)))
|
||||
endef
|
||||
|
||||
$(call allow-override,CC,$(CROSS_COMPILE)cc)
|
||||
$(call allow-override,LD,$(CROSS_COMPILE)ld)
|
||||
|
||||
.PHONY: all
|
||||
all: $(APPS)
|
||||
all: $(BINARY_NAME)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(call msg,CLEAN)
|
||||
$(Q)rm -rf $(OUTPUT) $(APPS)
|
||||
$(Q)rm -rf $(OUTPUT) $(TARGET_DIR) $(BINARY_NAME)
|
||||
$(Q)$(CARGO) clean
|
||||
|
||||
$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
|
||||
$(call msg,MKDIR,$@)
|
||||
@@ -93,49 +62,46 @@ $(BPFTOOL): | $(BPFTOOL_OUTPUT)
|
||||
$(call msg,BPFTOOL,$@)
|
||||
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap
|
||||
|
||||
|
||||
$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
|
||||
$(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release
|
||||
|
||||
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
|
||||
$(call msg,LIB, $@)
|
||||
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
|
||||
|
||||
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
|
||||
$(call msg,LIB,$@)
|
||||
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
|
||||
|
||||
# Build BPF code
|
||||
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
|
||||
# Build BPF object files from C source
|
||||
$(OUTPUT)/%.bpf.o: src/bpf/%.bpf.c $(LIBBPF_OBJ) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
|
||||
$(call msg,BPF,$@)
|
||||
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
|
||||
$(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \
|
||||
-c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
-c $< -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
$(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
|
||||
# Generate BPF skeletons
|
||||
# Generate BPF skeletons (if needed for Rust)
|
||||
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL)
|
||||
$(call msg,GEN-SKEL,$@)
|
||||
$(Q)$(BPFTOOL) gen skeleton $< > $@
|
||||
|
||||
# Build user-space code
|
||||
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
|
||||
# Build the Rust binary
|
||||
$(BINARY_NAME): $(OUTPUT)/profile.bpf.o | $(OUTPUT)
|
||||
$(call msg,CARGO,$@)
|
||||
$(Q)$(CARGO) build --release
|
||||
$(Q)cp $(TARGET_DIR)/$(BINARY_NAME) ./
|
||||
|
||||
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
|
||||
$(call msg,CC,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
|
||||
# Build in debug mode
|
||||
.PHONY: debug
|
||||
debug: $(OUTPUT)/profile.bpf.o | $(OUTPUT)
|
||||
$(call msg,CARGO,debug)
|
||||
$(Q)$(CARGO) build
|
||||
$(Q)cp target/debug/$(BINARY_NAME) ./$(BINARY_NAME)-debug
|
||||
|
||||
$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
|
||||
# Run cargo check
|
||||
.PHONY: check
|
||||
check: $(OUTPUT)/profile.bpf.o | $(OUTPUT)
|
||||
$(call msg,CARGO,check)
|
||||
$(Q)$(CARGO) check
|
||||
|
||||
$(BZS_APPS): $(LIBBLAZESYM_OBJ)
|
||||
|
||||
# Build application binary
|
||||
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
|
||||
$(call msg,BINARY,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@
|
||||
# Run cargo test
|
||||
.PHONY: test
|
||||
test: $(OUTPUT)/profile.bpf.o | $(OUTPUT)
|
||||
$(call msg,CARGO,test)
|
||||
$(Q)$(CARGO) test
|
||||
|
||||
# delete failed targets
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
# keep intermediate (.skel.h, .bpf.o, etc) targets
|
||||
.SECONDARY:
|
||||
.SECONDARY:
|
||||
@@ -1,50 +1,55 @@
|
||||
# eBPF Tutorial by Example 12: Using eBPF Program Profile for Performance Analysis
|
||||
|
||||
This tutorial will guide you on using libbpf and eBPF programs for performance analysis. We will leverage the perf mechanism in the kernel to learn how to capture the execution time of functions and view performance data.
|
||||
This tutorial will guide you on using eBPF programs for performance analysis with a Rust implementation. We will leverage the perf mechanism in the kernel to learn how to capture the execution time of functions and view performance data.
|
||||
|
||||
libbpf is a C library for interacting with eBPF. It provides the basic functionality for creating, loading, and using eBPF programs. In this tutorial, we will mainly use libbpf for development. Perf is a performance analysis tool in the Linux kernel that allows users to measure and analyze the performance of kernel and user space programs, as well as obtain corresponding call stacks. It collects performance data using hardware counters and software events in the kernel.
|
||||
This implementation uses libbpf-rs, a Rust wrapper around libbpf, along with blazesym for symbol resolution. Perf is a performance analysis tool in the Linux kernel that allows users to measure and analyze the performance of kernel and user space programs, as well as obtain corresponding call stacks. It collects performance data using hardware counters and software events in the kernel.
|
||||
|
||||
## eBPF Tool: profile Performance Analysis Example
|
||||
|
||||
The `profile` tool is implemented based on eBPF and utilizes the perf events in the Linux kernel for performance analysis. The `profile` tool periodically samples each processor to capture the execution of kernel and user space functions. It provides the following information for stack traces:
|
||||
The `profile` tool is implemented based on eBPF and utilizes the perf events in the Linux kernel for performance analysis. The `profile` tool periodically samples each processor to capture the execution of kernel and user space functions.
|
||||
|
||||
- Address: memory address of the function call
|
||||
- Symbol: function name
|
||||
- File Name: name of the source code file
|
||||
- Line Number: line number in the source code
|
||||
In the stack trace information, the tool displays the memory addresses of function calls, which represent the most primitive location information. Through symbol resolution, these addresses are converted to corresponding function names, allowing developers to directly identify which functions are being executed. Furthermore, when debug information is available, the tool can provide source code file names and specific line numbers, pinpointing the exact location in the code. This complete information chain from addresses to symbols to source code locations provides developers with a comprehensive perspective for performance analysis.
|
||||
|
||||
This information helps developers locate performance bottlenecks and optimize code. Furthermore, flame graphs can be generated based on this information for a more intuitive view of performance data.
|
||||
This detailed information helps developers quickly locate performance bottlenecks and optimize code. By analyzing which functions are frequently called and which code paths consume the most CPU time, developers can perform targeted optimizations. Additionally, this stack trace information can be converted into flame graph format, providing a visual representation of program execution hotspots that makes performance issues immediately apparent.
|
||||
|
||||
In this example, you can compile and run it with the libbpf library (using Ubuntu/Debian as an example):
|
||||
In this example, you can compile and run it with Rust and Cargo:
|
||||
|
||||
**NOTE:** To compile the `profile`, you first need to install `Cargo`, as shown in ["The Cargo Book"](https://rustwiki.org/en/cargo/getting-started/installation.html)
|
||||
**Prerequisites:**
|
||||
- Rust and Cargo installed (see ["The Cargo Book"](https://rustwiki.org/en/cargo/getting-started/installation.html))
|
||||
- Clang and development libraries
|
||||
|
||||
```console
|
||||
$ git submodule update --init --recursive
|
||||
$ sudo apt install clang libelf1 libelf-dev zlib1g-dev
|
||||
$ make
|
||||
$ sudo ./profile
|
||||
COMM: chronyd (pid=156) @ CPU 1
|
||||
Kernel:
|
||||
0 [<ffffffff81ee9f56>] _raw_spin_lock_irqsave+0x16
|
||||
1 [<ffffffff811527b4>] remove_wait_queue+0x14
|
||||
2 [<ffffffff8132611d>] poll_freewait+0x3d
|
||||
3 [<ffffffff81326d3f>] do_select+0x7bf
|
||||
4 [<ffffffff81327af2>] core_sys_select+0x182
|
||||
5 [<ffffffff81327f3a>] __x64_sys_pselect6+0xea
|
||||
6 [<ffffffff81ed9e38>] do_syscall_64+0x38
|
||||
7 [<ffffffff82000099>] entry_SYSCALL_64_after_hwframe+0x61
|
||||
Userspace:
|
||||
0 [<00007fab187bfe09>]
|
||||
1 [<000000000ee6ae98>]
|
||||
$ sudo ./profile
|
||||
```
|
||||
|
||||
COMM: profile (pid=9843) @ CPU 6
|
||||
**Sample Output:**
|
||||
```console
|
||||
[1756723652.366364804] COMM: node (pid=285503) @ CPU 64
|
||||
No Kernel Stack
|
||||
Userspace:
|
||||
0 [<0000556deb068ac8>]
|
||||
1 [<0000556dec34cad0>]
|
||||
0x0072e2a97f4be0: v8::internal::Scanner::Next() @ 0x15f47c0+0x420
|
||||
0x0072e2a97d9051: v8::internal::ParserBase<v8::internal::PreParser>::ParseBlock(...) @ 0x15d8fc0+0x91
|
||||
0x0072e2a97d6df0: v8::internal::ParserBase<v8::internal::PreParser>::ParseStatement(...) @ 0x15d6ce0+0x110
|
||||
...
|
||||
|
||||
[1756723657.337170411] COMM: qemu-system-x86 (pid=4166437) @ CPU 70
|
||||
Kernel:
|
||||
0xffffffff95f403d5: _raw_spin_lock_irq @ 0xffffffff95f403b0+0x25
|
||||
0xffffffff94e2b6d8: __flush_work @ 0xffffffff94e2b630+0xa8
|
||||
0xffffffff94e2ba5c: flush_work @ 0xffffffff94e2ba40+0x1c
|
||||
0xffffffff95852672: tty_buffer_flush_work @ 0xffffffff95852660+0x12
|
||||
...
|
||||
Userspace:
|
||||
0x005849a7fbbd33: qemu_poll_ns @ 0xc63c4c+0xe7 /home/victoryang00/CXLMemSim/lib/qemu/build/../util/qemu-timer.c:347:1
|
||||
0x005849a7fb64d7: os_host_main_loop_wait @ 0xc5e473+0x64 /home/victoryang00/CXLMemSim/lib/qemu/build/../util/main-loop.c:305:11
|
||||
...
|
||||
```
|
||||
|
||||
The tool provides detailed stack traces with symbol resolution, including function names, offsets, and source file locations when available.
|
||||
|
||||
## Implementation Principle
|
||||
|
||||
The `profile` tool consists of two parts: the eBPF program in kernel space and the `profile` symbol handling program in user space. The `profile` symbol handling program is responsible for loading the eBPF program and processing the data outputted by the eBPF program.
|
||||
@@ -84,13 +89,15 @@ int profile(void *ctx)
|
||||
|
||||
event->pid = pid;
|
||||
event->cpu_id = cpu_id;
|
||||
event->timestamp = bpf_ktime_get_ns(); // Capture timestamp
|
||||
|
||||
if (bpf_get_current_comm(event->comm, sizeof(event->comm)))
|
||||
event->comm[0] = 0;
|
||||
|
||||
event->kstack_sz = bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0);
|
||||
|
||||
event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
event->ustack_sz =
|
||||
bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
|
||||
bpf_ringbuf_submit(event, 0);
|
||||
|
||||
@@ -98,6 +105,37 @@ int profile(void *ctx)
|
||||
}
|
||||
```
|
||||
|
||||
The header file `profile.h` defines the event structure:
|
||||
|
||||
```c
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __PROFILE_H_
|
||||
#define __PROFILE_H_
|
||||
|
||||
#ifndef TASK_COMM_LEN
|
||||
#define TASK_COMM_LEN 16
|
||||
#endif
|
||||
|
||||
#ifndef MAX_STACK_DEPTH
|
||||
#define MAX_STACK_DEPTH 128
|
||||
#endif
|
||||
|
||||
typedef __u64 stack_trace_t[MAX_STACK_DEPTH];
|
||||
|
||||
struct stacktrace_event {
|
||||
__u32 pid;
|
||||
__u32 cpu_id;
|
||||
__u64 timestamp; // Kernel timestamp in nanoseconds
|
||||
char comm[TASK_COMM_LEN];
|
||||
__s32 kstack_sz;
|
||||
__s32 ustack_sz;
|
||||
stack_trace_t kstack;
|
||||
stack_trace_t ustack;
|
||||
};
|
||||
|
||||
#endif /* __PROFILE_H_ */
|
||||
```
|
||||
|
||||
Next, we will focus on the key part of the kernel code.
|
||||
|
||||
1. Define eBPF maps `events`:
|
||||
@@ -176,44 +214,88 @@ Finally, use the `bpf_ringbuf_submit()` function to submit the event to the Ring
|
||||
|
||||
This kernel mode eBPF program captures the program's execution flow by sampling the kernel stack and user space stack of the program periodically. These data are stored in the Ring Buffer for the user mode `profile` program to read.
|
||||
|
||||
### User Mode Section
|
||||
### User Mode Section (Rust Implementation)
|
||||
|
||||
This code is mainly responsible for setting up perf events for each online CPU and attaching eBPF programs:
|
||||
The user-space portion is implemented in Rust using libbpf-rs and blazesym. The main components include:
|
||||
|
||||
```c
|
||||
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags)
|
||||
{
|
||||
int ret;
|
||||
**Main Entry Point (src/main.rs):**
|
||||
```rust
|
||||
use std::mem::MaybeUninit;
|
||||
use std::time::Duration;
|
||||
use clap::{ArgAction, Parser};
|
||||
use libbpf_rs::skel::{OpenSkel, SkelBuilder};
|
||||
|
||||
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
|
||||
return ret;
|
||||
mod profile {
|
||||
include!(concat!(env!("OUT_DIR"), "/profile.skel.rs"));
|
||||
}
|
||||
mod syscall;
|
||||
mod event;
|
||||
mod perf;
|
||||
|
||||
use profile::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
/// Sampling frequency
|
||||
#[arg(short, default_value_t = 50)]
|
||||
freq: u64,
|
||||
|
||||
/// Increase verbosity (can be supplied multiple times)
|
||||
#[arg(short = 'v', long = "verbose", global = true, action = ArgAction::Count)]
|
||||
verbosity: u8,
|
||||
|
||||
/// Use software event for triggering stack trace capture
|
||||
#[arg(long = "sw-event")]
|
||||
sw_event: bool,
|
||||
|
||||
/// Filter by PID (optional)
|
||||
#[arg(short = 'p', long = "pid")]
|
||||
pid: Option<i32>,
|
||||
|
||||
/// Output in extended folded format
|
||||
#[arg(short = 'E', long = "fold-extend")]
|
||||
fold_extend: bool,
|
||||
}
|
||||
|
||||
int main(){
|
||||
...
|
||||
for (cpu = 0; cpu < num_cpus; cpu++) {
|
||||
/* skip offline/not present CPUs */
|
||||
if (cpu >= num_online_cpus || !online_mask[cpu])
|
||||
continue;
|
||||
|
||||
/* Set up performance monitoring on a CPU/Core */
|
||||
pefd = perf_event_open(&attr, pid, cpu, -1, PERF_FLAG_FD_CLOEXEC);
|
||||
if (pefd < 0) {
|
||||
fprintf(stderr, "Fail to set up performance monitor on a CPU/Core\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
pefds[cpu] = pefd;
|
||||
|
||||
/* Attach a BPF program on a CPU */
|
||||
links[cpu] = bpf_program__attach_perf_event(skel->progs.profile, pefd);
|
||||
if (!links[cpu]) {
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
...
|
||||
fn main() -> Result<(), libbpf_rs::Error> {
|
||||
let args = Args::parse();
|
||||
|
||||
// Set up logging based on verbosity
|
||||
let level = match args.verbosity {
|
||||
0 => LevelFilter::WARN,
|
||||
1 => LevelFilter::INFO,
|
||||
2 => LevelFilter::DEBUG,
|
||||
_ => LevelFilter::TRACE,
|
||||
};
|
||||
|
||||
// Initialize BPF skeleton
|
||||
let skel_builder = ProfileSkelBuilder::default();
|
||||
let mut open_object = MaybeUninit::uninit();
|
||||
let open_skel = skel_builder.open(&mut open_object)?;
|
||||
let skel = open_skel.load()?;
|
||||
|
||||
// Set up perf events and attach BPF program
|
||||
let pefds = perf::init_perf_monitor(args.freq, args.sw_event, args.pid)?;
|
||||
let _links = perf::attach_perf_event(&pefds, &skel.progs.profile);
|
||||
|
||||
// Set up ring buffer with event handler
|
||||
let mut builder = libbpf_rs::RingBufferBuilder::new();
|
||||
let output_format = if args.fold_extend {
|
||||
event::OutputFormat::FoldedExtended
|
||||
} else {
|
||||
event::OutputFormat::Standard
|
||||
};
|
||||
|
||||
let event_handler = event::EventHandler::new(output_format);
|
||||
builder.add(&skel.maps.events, move |data| {
|
||||
event_handler.handle(data)
|
||||
})?;
|
||||
|
||||
let ringbuf = builder.build()?;
|
||||
while ringbuf.poll(Duration::MAX).is_ok() {}
|
||||
|
||||
perf::close_perf_events(pefds)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@@ -229,99 +311,114 @@ for (cpu = 0; cpu < num_cpus; cpu++) {
|
||||
|
||||
This loop sets up perf events and attaches eBPF programs for each online CPU. Firstly, it checks if the current CPU is online and skips if it's not. Then, it uses the perf_event_open() function to set up perf events for the current CPU and stores the returned file descriptor in the pefds array. Finally, it attaches the eBPF program to the perf event using the bpf_program__attach_perf_event() function. The links array is used to store the BPF links for each CPU so that they can be destroyed when the program ends.By doing so, user-mode programs set perf events for each online CPU and attach eBPF programs to these perf events to monitor all online CPUs in the system.
|
||||
|
||||
The following two functions are used to display stack traces and handle events received from the ring buffer:
|
||||
**Event Processing and Symbol Resolution (src/event.rs):**
|
||||
```rust
|
||||
use std::mem;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use blazesym::symbolize;
|
||||
use nix::sys::sysinfo;
|
||||
|
||||
```c
|
||||
static void show_stack_trace(__u64 *stack, int stack_sz, pid_t pid)
|
||||
{
|
||||
const struct blazesym_result *result;
|
||||
const struct blazesym_csym *sym;
|
||||
sym_src_cfg src;
|
||||
int i, j;
|
||||
pub const MAX_STACK_DEPTH: usize = 128;
|
||||
pub const TASK_COMM_LEN: usize = 16;
|
||||
|
||||
if (pid) {
|
||||
src.src_type = SRC_T_PROCESS;
|
||||
src.params.process.pid = pid;
|
||||
} else {
|
||||
src.src_type = SRC_T_KERNEL;
|
||||
src.params.kernel.kallsyms = NULL;
|
||||
src.params.kernel.kernel_image = NULL;
|
||||
}
|
||||
|
||||
result = blazesym_symbolize(symbolizer, &src, 1, (const uint64_t *)stack, stack_sz);
|
||||
|
||||
for (i = 0; i < stack_sz; i++) {
|
||||
if (!result || result->size <= i || !result->entries[i].size) {
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result->entries[i].size == 1) {
|
||||
sym = &result->entries[i].syms[0];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %d [<%016llx>] %s+0x%llx %s:%ld\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %d [<%016llx>] %s+0x%llx\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
for (j = 0; j < result->entries[i].size; j++) {
|
||||
sym = &result->entries[i].syms[j];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %s+0x%llx %s:%ld\n",
|
||||
sym->symbol, stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %s+0x%llx\n", sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blazesym_result_free(result);
|
||||
// A Rust version of stacktrace_event in profile.h
|
||||
#[repr(C)]
|
||||
pub struct StacktraceEvent {
|
||||
pub pid: u32,
|
||||
pub cpu_id: u32,
|
||||
pub timestamp: u64, // Kernel timestamp in nanoseconds
|
||||
pub comm: [u8; TASK_COMM_LEN],
|
||||
pub kstack_size: i32,
|
||||
pub ustack_size: i32,
|
||||
pub kstack: [u64; MAX_STACK_DEPTH],
|
||||
pub ustack: [u64; MAX_STACK_DEPTH],
|
||||
}
|
||||
|
||||
/* Receive events from the ring buffer. */
|
||||
static int event_handler(void *_ctx, void *data, size_t size)
|
||||
{
|
||||
struct stacktrace_event *event = data;
|
||||
pub enum OutputFormat {
|
||||
Standard,
|
||||
FoldedExtended, // For flame graph generation
|
||||
}
|
||||
|
||||
if (event->kstack_sz <= 0 && event->ustack_sz <= 0)
|
||||
return 1;
|
||||
pub struct EventHandler {
|
||||
symbolizer: symbolize::Symbolizer,
|
||||
format: OutputFormat,
|
||||
boot_time_ns: u64, // System boot time for timestamp conversion
|
||||
}
|
||||
|
||||
printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id);
|
||||
|
||||
if (event->kstack_sz > 0) {
|
||||
printf("Kernel:\n");
|
||||
show_stack_trace(event->kstack, event->kstack_sz / sizeof(__u64), 0);
|
||||
} else {
|
||||
printf("No Kernel Stack\n");
|
||||
impl EventHandler {
|
||||
pub fn new(format: OutputFormat) -> Self {
|
||||
let boot_time_ns = Self::get_boot_time_ns();
|
||||
Self {
|
||||
symbolizer: symbolize::Symbolizer::new(),
|
||||
format,
|
||||
boot_time_ns,
|
||||
}
|
||||
}
|
||||
|
||||
if (event->ustack_sz > 0) {
|
||||
printf("Userspace:\n");
|
||||
show_stack_trace(event->ustack, event->ustack_sz / sizeof(__u64), event->pid);
|
||||
} else {
|
||||
printf("No Userspace Stack\n");
|
||||
|
||||
fn get_boot_time_ns() -> u64 {
|
||||
// Calculate boot time from current time minus uptime
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("System time before Unix epoch");
|
||||
let now_ns = now.as_nanos() as u64;
|
||||
|
||||
let info = sysinfo::sysinfo().expect("Failed to get sysinfo");
|
||||
let uptime_ns = (info.uptime().as_secs_f64() * 1_000_000_000.0) as u64;
|
||||
|
||||
now_ns - uptime_ns
|
||||
}
|
||||
|
||||
pub fn handle(&self, data: &[u8]) -> ::std::os::raw::c_int {
|
||||
let event = unsafe { &*(data.as_ptr() as *const StacktraceEvent) };
|
||||
|
||||
if event.kstack_size <= 0 && event.ustack_size <= 0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
match self.format {
|
||||
OutputFormat::Standard => self.handle_standard(event),
|
||||
OutputFormat::FoldedExtended => self.handle_folded_extended(event),
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn handle_standard(&self, event: &StacktraceEvent) {
|
||||
let comm = std::str::from_utf8(&event.comm)
|
||||
.unwrap_or("<unknown>")
|
||||
.trim_end_matches('\0');
|
||||
|
||||
// Convert kernel timestamp to Unix timestamp
|
||||
let unix_timestamp_ns = event.timestamp + self.boot_time_ns;
|
||||
let timestamp_sec = unix_timestamp_ns / 1_000_000_000;
|
||||
let timestamp_nsec = unix_timestamp_ns % 1_000_000_000;
|
||||
|
||||
println!("[{}.{:09}] COMM: {} (pid={}) @ CPU {}",
|
||||
timestamp_sec, timestamp_nsec, comm, event.pid, event.cpu_id);
|
||||
|
||||
// Process and symbolize stacks...
|
||||
// (implementation continues with symbolization logic)
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The `show_stack_trace()` function is used to display the stack trace of the kernel or userspace. It takes a `stack` parameter, which is a pointer to the kernel or userspace stack, and a `stack_sz` parameter, which represents the size of the stack. The `pid` parameter represents the ID of the process to be displayed (set to 0 when displaying the kernel stack). In the function, the source of the stack (kernel or userspace) is determined based on the `pid` parameter, and then the `blazesym_symbolize()` function is called to resolve the addresses in the stack to symbol names and source code locations. Finally, the resolved results are traversed and the symbol names and source code location information are outputted.
|
||||
**Key Features of the Rust Implementation:**
|
||||
|
||||
The `event_handler()` function is used to handle events received from the ring buffer. It takes a `data` parameter, which points to the data in the ring buffer, and a `size` parameter, which represents the size of the data. The function first converts the `data` pointer to a pointer of type `stacktrace_event`, and then checks the sizes of the kernel and userspace stacks. If the stacks are empty, it returns directly. Next, the function outputs the process name, process ID, and CPU ID information. Then it displays the stack traces of the kernel and userspace respectively. When calling the `show_stack_trace()` function, the addresses, sizes, and process ID of the kernel and userspace stacks are passed in separately.
|
||||
1. **Type Safety**: The Rust implementation provides strong type safety through the type system, preventing memory safety issues common in C.
|
||||
|
||||
These two functions are part of the eBPF profiling tool, used to display and process stack trace information collected by eBPF programs, helping users understand program performance and bottlenecks.
|
||||
2. **Blazesym Integration**: Uses blazesym-rs for efficient symbol resolution with support for DWARF debug information, providing detailed source file and line number information.
|
||||
|
||||
3. **Error Handling**: Comprehensive error handling using Rust's `Result` type, ensuring robust operation.
|
||||
|
||||
4. **Structured Logging**: Integration with the `tracing` crate for structured logging with configurable verbosity levels.
|
||||
|
||||
5. **Command-line Interface**: Uses `clap` for a user-friendly CLI with options for:
|
||||
- Adjustable sampling frequency (`-f`)
|
||||
- Software vs hardware perf events (`--sw-event`)
|
||||
- PID filtering (`-p`)
|
||||
- Extended output format (`-E`)
|
||||
|
||||
This integrated approach combines the performance of eBPF with the safety and expressiveness of Rust, providing a robust profiling tool for system performance analysis.
|
||||
|
||||
### Summary
|
||||
|
||||
|
||||
@@ -1,50 +1,55 @@
|
||||
# eBPF 入门实践教程十二:使用 eBPF 程序 profile 进行性能分析
|
||||
|
||||
本教程将指导您使用 libbpf 和 eBPF 程序进行性能分析。我们将利用内核中的 perf 机制,学习如何捕获函数的执行时间以及如何查看性能数据。
|
||||
本教程将指导您使用 eBPF 程序和 Rust 实现进行性能分析。我们将利用内核中的 perf 机制,学习如何捕获函数的执行时间以及如何查看性能数据。
|
||||
|
||||
libbpf 是一个用于与 eBPF 交互的 C 库。它提供了创建、加载和使用 eBPF 程序所需的基本功能。本教程中,我们将主要使用 libbpf 完成开发工作。perf 是 Linux 内核中的性能分析工具,允许用户测量和分析内核及用户空间程序的性能,以及获取对应的调用堆栈。它利用内核中的硬件计数器和软件事件来收集性能数据。
|
||||
本实现使用 libbpf-rs(libbpf 的 Rust 封装)以及 blazesym 进行符号解析。perf 是 Linux 内核中的性能分析工具,允许用户测量和分析内核及用户空间程序的性能,以及获取对应的调用堆栈。它利用内核中的硬件计数器和软件事件来收集性能数据。
|
||||
|
||||
## eBPF 工具:profile 性能分析示例
|
||||
|
||||
`profile` 工具基于 eBPF 实现,利用 Linux 内核中的 perf 事件进行性能分析。`profile` 工具会定期对每个处理器进行采样,以便捕获内核函数和用户空间函数的执行。它可以显示栈回溯的以下信息:
|
||||
`profile` 工具基于 eBPF 实现,利用 Linux 内核中的 perf 事件进行性能分析。`profile` 工具会定期对每个处理器进行采样,以便捕获内核函数和用户空间函数的执行。
|
||||
|
||||
- 地址:函数调用的内存地址
|
||||
- 符号:函数名称
|
||||
- 文件名:源代码文件名称
|
||||
- 行号:源代码中的行号
|
||||
在栈回溯信息中,工具会显示函数调用的内存地址,这是最原始的位置信息。通过符号解析,这些地址会被转换为对应的函数名称,让开发者能够直接识别是哪个函数在执行。更进一步,如果调试信息可用,工具还能提供源代码文件名称和具体的行号,精确定位到代码的具体位置。这种从地址到符号,再到源代码位置的完整信息链,为开发人员提供了全方位的性能分析视角。
|
||||
|
||||
这些信息有助于开发人员定位性能瓶颈和优化代码。更进一步,可以通过这些对应的信息生成火焰图,以便更直观的查看性能数据。
|
||||
这些详细的信息有助于开发人员快速定位性能瓶颈和优化代码。通过分析哪些函数被频繁调用,哪些代码路径消耗了最多的 CPU 时间,开发者可以有针对性地进行优化。更进一步,这些栈回溯信息可以被转换成火焰图格式,通过可视化的方式直观展示程序的执行热点,让性能问题一目了然。
|
||||
|
||||
在本示例中,可以通过 libbpf 库编译运行它(以 Ubuntu/Debian 为例):
|
||||
在本示例中,可以通过 Rust 和 Cargo 编译运行:
|
||||
|
||||
**NOTE:** 首先需要安装 `cargo` 才能编译得到 `profile`, 安装方法可以参考[Cargo 手册](https://rustwiki.org/en/cargo/getting-started/installation.html)
|
||||
**前提条件:**
|
||||
- 安装 Rust 和 Cargo(参考 [Cargo 手册](https://rustwiki.org/en/cargo/getting-started/installation.html))
|
||||
- 安装 Clang 和开发库
|
||||
|
||||
```console
|
||||
$ git submodule update --init --recursive
|
||||
$ sudo apt install clang libelf1 libelf-dev zlib1g-dev
|
||||
$ make
|
||||
$ sudo ./profile
|
||||
COMM: chronyd (pid=156) @ CPU 1
|
||||
Kernel:
|
||||
0 [<ffffffff81ee9f56>] _raw_spin_lock_irqsave+0x16
|
||||
1 [<ffffffff811527b4>] remove_wait_queue+0x14
|
||||
2 [<ffffffff8132611d>] poll_freewait+0x3d
|
||||
3 [<ffffffff81326d3f>] do_select+0x7bf
|
||||
4 [<ffffffff81327af2>] core_sys_select+0x182
|
||||
5 [<ffffffff81327f3a>] __x64_sys_pselect6+0xea
|
||||
6 [<ffffffff81ed9e38>] do_syscall_64+0x38
|
||||
7 [<ffffffff82000099>] entry_SYSCALL_64_after_hwframe+0x61
|
||||
Userspace:
|
||||
0 [<00007fab187bfe09>]
|
||||
1 [<000000000ee6ae98>]
|
||||
$ sudo ./profile
|
||||
```
|
||||
|
||||
COMM: profile (pid=9843) @ CPU 6
|
||||
**示例输出:**
|
||||
```console
|
||||
[1756723652.366364804] COMM: node (pid=285503) @ CPU 64
|
||||
No Kernel Stack
|
||||
Userspace:
|
||||
0 [<0000556deb068ac8>]
|
||||
1 [<0000556dec34cad0>]
|
||||
0x0072e2a97f4be0: v8::internal::Scanner::Next() @ 0x15f47c0+0x420
|
||||
0x0072e2a97d9051: v8::internal::ParserBase<v8::internal::PreParser>::ParseBlock(...) @ 0x15d8fc0+0x91
|
||||
0x0072e2a97d6df0: v8::internal::ParserBase<v8::internal::PreParser>::ParseStatement(...) @ 0x15d6ce0+0x110
|
||||
...
|
||||
|
||||
[1756723657.337170411] COMM: qemu-system-x86 (pid=4166437) @ CPU 70
|
||||
Kernel:
|
||||
0xffffffff95f403d5: _raw_spin_lock_irq @ 0xffffffff95f403b0+0x25
|
||||
0xffffffff94e2b6d8: __flush_work @ 0xffffffff94e2b630+0xa8
|
||||
0xffffffff94e2ba5c: flush_work @ 0xffffffff94e2ba40+0x1c
|
||||
0xffffffff95852672: tty_buffer_flush_work @ 0xffffffff95852660+0x12
|
||||
...
|
||||
Userspace:
|
||||
0x005849a7fbbd33: qemu_poll_ns @ 0xc63c4c+0xe7 /home/victoryang00/CXLMemSim/lib/qemu/build/../util/qemu-timer.c:347:1
|
||||
0x005849a7fb64d7: os_host_main_loop_wait @ 0xc5e473+0x64 /home/victoryang00/CXLMemSim/lib/qemu/build/../util/main-loop.c:305:11
|
||||
...
|
||||
```
|
||||
|
||||
该工具提供详细的堆栈跟踪和符号解析,包括函数名、偏移量以及可用时的源文件位置。
|
||||
|
||||
## 实现原理
|
||||
|
||||
profile 工具由两个部分组成,内核态中的 eBPF 程序和用户态中的 `profile` 符号处理程序。`profile` 符号处理程序负责加载 eBPF 程序,以及处理 eBPF 程序输出的数据。
|
||||
@@ -84,13 +89,15 @@ int profile(void *ctx)
|
||||
|
||||
event->pid = pid;
|
||||
event->cpu_id = cpu_id;
|
||||
event->timestamp = bpf_ktime_get_ns(); // 捕获时间戳
|
||||
|
||||
if (bpf_get_current_comm(event->comm, sizeof(event->comm)))
|
||||
event->comm[0] = 0;
|
||||
|
||||
event->kstack_sz = bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0);
|
||||
|
||||
event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
event->ustack_sz =
|
||||
bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
|
||||
bpf_ringbuf_submit(event, 0);
|
||||
|
||||
@@ -150,16 +157,23 @@ int profile(void *ctx)
|
||||
|
||||
使用 `bpf_get_current_comm()` 函数获取当前进程名并将其存储到 `event->comm`。
|
||||
|
||||
6. 获取内核栈信息:
|
||||
6. 获取时间戳:
|
||||
|
||||
```c
|
||||
event->timestamp = bpf_ktime_get_ns();
|
||||
```
|
||||
|
||||
使用 `bpf_ktime_get_ns()` 函数获取内核时间戳(以纳秒为单位)。
|
||||
|
||||
7. 获取内核栈信息:
|
||||
|
||||
```c
|
||||
event->kstack_sz = bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0);
|
||||
```
|
||||
|
||||
使用 `bpf_get_stack()` 函数获取内核栈信息。将结果存储在 `event->kstack`,并将其大小存储在 `event->kstack_sz`。
|
||||
|
||||
7. 获取用户空间栈信息:
|
||||
8. 获取用户空间栈信息:
|
||||
|
||||
```c
|
||||
event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
@@ -167,7 +181,7 @@ int profile(void *ctx)
|
||||
|
||||
同样使用 `bpf_get_stack()` 函数,但传递 `BPF_F_USER_STACK` 标志以获取用户空间栈信息。将结果存储在 `event->ustack`,并将其大小存储在 `event->ustack_sz`。
|
||||
|
||||
8. 将事件提交到 Ring Buffer:
|
||||
9. 将事件提交到 Ring Buffer:
|
||||
|
||||
```c
|
||||
bpf_ringbuf_submit(event, 0);
|
||||
@@ -177,44 +191,60 @@ int profile(void *ctx)
|
||||
|
||||
这个内核态 eBPF 程序通过定期采样程序的内核栈和用户空间栈来捕获程序的执行流程。这些数据将存储在 Ring Buffer 中,以便用户态的 `profile` 程序能读取。
|
||||
|
||||
### 用户态部分
|
||||
### 用户态部分(Rust 实现)
|
||||
|
||||
这段代码主要负责为每个在线 CPU 设置 perf event 并附加 eBPF 程序:
|
||||
用户空间部分使用 Rust 实现,利用 libbpf-rs 和 blazesym 库来处理 eBPF 程序的加载、管理和数据处理。整个用户态程序的架构分为几个核心模块:主程序入口负责参数解析和整体流程控制,perf 模块处理性能事件的设置和管理,event 模块负责事件数据的处理和符号解析,syscall 模块封装了底层的系统调用接口。这种模块化的设计使得代码结构清晰,易于维护和扩展。
|
||||
|
||||
```c
|
||||
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags)
|
||||
{
|
||||
int ret;
|
||||
**主入口点(src/main.rs):**
|
||||
|
||||
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
|
||||
return ret;
|
||||
主程序是整个工具的入口点,负责协调各个模块的工作。它首先解析命令行参数,根据用户指定的选项配置采样频率、事件类型等参数。然后初始化 BPF 程序,设置性能监控事件,并启动事件循环来持续处理来自内核的性能数据。程序使用了 Rust 的所有权系统来确保资源的正确管理,在程序退出时自动清理所有分配的资源。
|
||||
```rust
|
||||
use clap::Parser;
|
||||
use libbpf_rs::PerfBufferBuilder;
|
||||
use tracing::{info, warn};
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
#[arg(short = 'f', default_value = "50")]
|
||||
/// 采样频率
|
||||
freq: u64,
|
||||
|
||||
#[arg(long, action = clap::ArgAction::SetTrue)]
|
||||
/// 使用软件事件触发
|
||||
sw_event: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
/// 按 PID 过滤(可选)
|
||||
pid: Option<i32>,
|
||||
}
|
||||
|
||||
int main(){
|
||||
...
|
||||
for (cpu = 0; cpu < num_cpus; cpu++) {
|
||||
/* skip offline/not present CPUs */
|
||||
if (cpu >= num_online_cpus || !online_mask[cpu])
|
||||
continue;
|
||||
|
||||
/* Set up performance monitoring on a CPU/Core */
|
||||
pefd = perf_event_open(&attr, pid, cpu, -1, PERF_FLAG_FD_CLOEXEC);
|
||||
if (pefd < 0) {
|
||||
fprintf(stderr, "Fail to set up performance monitor on a CPU/Core\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
pefds[cpu] = pefd;
|
||||
|
||||
/* Attach a BPF program on a CPU */
|
||||
links[cpu] = bpf_program__attach_perf_event(skel->progs.profile, pefd);
|
||||
if (!links[cpu]) {
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
// 初始化日志系统
|
||||
let filter = EnvFilter::from_default_env();
|
||||
fmt().with_env_filter(filter).init();
|
||||
|
||||
// 设置 perf 事件并附加 BPF 程序
|
||||
let mut skel_builder = ProfileSkelBuilder::default();
|
||||
let mut open_skel = skel_builder.open()?;
|
||||
let mut skel = open_skel.load()?;
|
||||
|
||||
// 在所有 CPU 上附加 perf 事件
|
||||
for cpu in online_cpus()? {
|
||||
let perf_fd = perf_event_open(cpu, args.pid, args.freq, args.sw_event)?;
|
||||
skel.progs().profile().attach_perf_event(perf_fd)?;
|
||||
}
|
||||
|
||||
// 处理来自 ring buffer 的事件
|
||||
let mut builder = PerfBufferBuilder::new(skel.maps().events());
|
||||
builder.add_callback(handle_event);
|
||||
let perf_buffer = builder.build()?;
|
||||
|
||||
loop {
|
||||
perf_buffer.poll(Duration::from_millis(100))?;
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
@@ -232,103 +262,92 @@ for (cpu = 0; cpu < num_cpus; cpu++) {
|
||||
|
||||
通过这种方式,用户态程序为每个在线 CPU 设置 perf event,并将 eBPF 程序附加到这些 perf event 上,从而实现对系统中所有在线 CPU 的监控。
|
||||
|
||||
以下这两个函数分别用于显示栈回溯和处理从 ring buffer 接收到的事件:
|
||||
**事件处理和符号解析(src/event.rs):**
|
||||
|
||||
```c
|
||||
static void show_stack_trace(__u64 *stack, int stack_sz, pid_t pid)
|
||||
{
|
||||
const struct blazesym_result *result;
|
||||
const struct blazesym_csym *sym;
|
||||
sym_src_cfg src;
|
||||
int i, j;
|
||||
事件处理模块是整个性能分析工具的核心组件之一。它负责接收来自内核的原始事件数据,进行必要的转换和解析,最终输出人类可读的性能信息。这个模块的设计考虑了多种输出格式的需求,既支持标准的详细输出,也支持适合生成火焰图的折叠格式输出。模块中的时间戳转换逻辑将内核的单调时间戳转换为 Unix 时间戳,使得输出更容易理解和分析。符号解析功能则将原始的内存地址转换为函数名和源代码位置,大大提高了性能数据的可读性和实用性。
|
||||
```rust
|
||||
use blazesym::{Addr, Pid, Source, Symbolizer};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
if (pid) {
|
||||
src.src_type = SRC_T_PROCESS;
|
||||
src.params.process.pid = pid;
|
||||
} else {
|
||||
src.src_type = SRC_T_KERNEL;
|
||||
src.params.kernel.kallsyms = NULL;
|
||||
src.params.kernel.kernel_image = NULL;
|
||||
}
|
||||
|
||||
result = blazesym_symbolize(symbolizer, &src, 1, (const uint64_t *)stack, stack_sz);
|
||||
|
||||
for (i = 0; i < stack_sz; i++) {
|
||||
if (!result || result->size <= i || !result->entries[i].size) {
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result->entries[i].size == 1) {
|
||||
sym = &result->entries[i].syms[0];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %d [<%016llx>] %s+0x%llx %s:%ld\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %d [<%016llx>] %s+0x%llx\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
for (j = 0; j < result->entries[i].size; j++) {
|
||||
sym = &result->entries[i].syms[j];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %s+0x%llx %s:%ld\n",
|
||||
sym->symbol, stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %s+0x%llx\n", sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blazesym_result_free(result);
|
||||
pub struct Event {
|
||||
pub timestamp: SystemTime,
|
||||
pub pid: i32,
|
||||
pub cpu_id: u32,
|
||||
pub comm: String,
|
||||
pub kstack: Vec<u64>,
|
||||
pub ustack: Vec<u64>,
|
||||
}
|
||||
|
||||
/* Receive events from the ring buffer. */
|
||||
static int event_handler(void *_ctx, void *data, size_t size)
|
||||
{
|
||||
struct stacktrace_event *event = data;
|
||||
|
||||
if (event->kstack_sz <= 0 && event->ustack_sz <= 0)
|
||||
return 1;
|
||||
|
||||
printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id);
|
||||
|
||||
if (event->kstack_sz > 0) {
|
||||
printf("Kernel:\n");
|
||||
show_stack_trace(event->kstack, event->kstack_sz / sizeof(__u64), 0);
|
||||
} else {
|
||||
printf("No Kernel Stack\n");
|
||||
impl Event {
|
||||
pub fn symbolize_and_print(&self, symbolizer: &Symbolizer) {
|
||||
println!("[{:?}] COMM: {} (pid={}) @ CPU {}",
|
||||
self.timestamp, self.comm, self.pid, self.cpu_id);
|
||||
|
||||
// 符号化内核栈
|
||||
if !self.kstack.is_empty() {
|
||||
println!("Kernel:");
|
||||
let src = Source::Kernel(Default::default());
|
||||
let syms = symbolizer.symbolize(&src, &self.kstack).unwrap();
|
||||
print_stack_trace(&self.kstack, &syms);
|
||||
} else {
|
||||
println!("No Kernel Stack");
|
||||
}
|
||||
|
||||
// 符号化用户栈
|
||||
if !self.ustack.is_empty() {
|
||||
println!("Userspace:");
|
||||
let src = Source::Process(Pid::from(self.pid as u32));
|
||||
let syms = symbolizer.symbolize(&src, &self.ustack).unwrap();
|
||||
print_stack_trace(&self.ustack, &syms);
|
||||
} else {
|
||||
println!("No Userspace Stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event->ustack_sz > 0) {
|
||||
printf("Userspace:\n");
|
||||
show_stack_trace(event->ustack, event->ustack_sz / sizeof(__u64), event->pid);
|
||||
} else {
|
||||
printf("No Userspace Stack\n");
|
||||
fn print_stack_trace(addrs: &[u64], symbols: &[Vec<blazesym::SymbolInfo>]) {
|
||||
for (addr, syms) in addrs.iter().zip(symbols.iter()) {
|
||||
if syms.is_empty() {
|
||||
println!("0x{:016x}: <no-symbol>", addr);
|
||||
} else {
|
||||
for sym in syms {
|
||||
if let Some(name) = &sym.name {
|
||||
if let Some(file) = &sym.file_name {
|
||||
println!("0x{:016x}: {} @ 0x{:x}+0x{:x} {}:{}",
|
||||
addr, name, sym.addr, addr - sym.addr,
|
||||
file, sym.line_no.unwrap_or(0));
|
||||
} else {
|
||||
println!("0x{:016x}: {} @ 0x{:x}+0x{:x}",
|
||||
addr, name, sym.addr, addr - sym.addr);
|
||||
}
|
||||
} else {
|
||||
println!("0x{:016x}: <unknown>", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
`show_stack_trace()` 函数用于显示内核或用户空间的栈回溯。它接收一个 stack 参数,是一个指向内核或用户空间栈的指针,stack_sz 参数表示栈的大小,pid 参数表示要显示的进程的 ID(当显示内核栈时,设置为 0)。函数中首先根据 pid 参数确定栈的来源(内核或用户空间),然后调用 blazesym_symbolize() 函数将栈中的地址解析为符号名和源代码位置。最后,遍历解析结果,输出符号名和源代码位置信息。
|
||||
**Rust 实现的主要特性:**
|
||||
|
||||
`event_handler()` 函数用于处理从 ring buffer 接收到的事件。它接收一个 data 参数,指向 ring buffer 中的数据,size 参数表示数据的大小。函数首先将 data 指针转换为 stacktrace_event 结构体指针,然后检查内核和用户空间栈的大小。如果栈为空,则直接返回。接下来,函数输出进程名称、进程 ID 和 CPU ID 信息。然后分别显示内核栈和用户空间栈的回溯。调用 show_stack_trace() 函数时,分别传入内核栈和用户空间栈的地址、大小和进程 ID。
|
||||
Rust 实现通过类型系统提供强类型安全,有效防止了 C 语言中常见的内存安全问题,如缓冲区溢出、空指针解引用等。这种安全性保证让开发者能够专注于业务逻辑而不必担心底层的内存管理问题。
|
||||
|
||||
符号解析是性能分析工具的核心功能之一。本实现集成了 blazesym 库,它能够高效地将内存地址转换为可读的函数名和源代码位置。blazesym 支持 DWARF 调试信息的解析,这意味着即使是经过优化的二进制文件,也能获得准确的源文件路径和行号信息。这对于定位性能瓶颈的具体代码位置至关重要。
|
||||
|
||||
错误处理方面,Rust 的 `Result` 类型提供了显式的错误处理机制。每个可能失败的操作都返回 Result 类型,强制开发者处理潜在的错误情况。这种设计避免了未处理错误导致的程序崩溃,提高了工具的稳定性和可靠性。
|
||||
|
||||
日志系统使用了 `tracing` crate,它提供了结构化的日志记录能力。通过环境变量或命令行参数,用户可以动态调整日志级别,从 WARN、INFO、DEBUG 到 TRACE,方便在不同场景下获取适当详细程度的诊断信息。这种灵活性对于调试和问题排查非常有用。
|
||||
|
||||
命令行界面通过 `clap` 库实现,提供了直观的参数解析和帮助信息生成。用户可以通过 `-f` 参数调整采样频率,使用 `--sw-event` 在虚拟机等不支持硬件性能计数器的环境中切换到软件事件,通过 `-p` 参数过滤特定进程,以及使用 `-E` 参数输出适合生成火焰图的扩展格式。
|
||||
|
||||
|
||||
这种集成方法结合了 eBPF 的性能与 Rust 的安全性和表达力,提供了一个强大的系统性能分析工具。
|
||||
|
||||
这两个函数作为 eBPF profile 工具的一部分,用于显示和处理 eBPF 程序收集到的栈回溯信息,帮助用户了解程序的运行情况和性能瓶颈。
|
||||
|
||||
### 总结
|
||||
|
||||
通过本篇 eBPF 入门实践教程,我们学习了如何使用 eBPF 程序进行性能分析。在这个过程中,我们详细讲解了如何创建 eBPF 程序,监控进程的性能,并从 ring buffer 中获取数据以分析栈回溯。我们还学习了如何使用 perf_event_open() 函数设置性能监控,并将 BPF 程序附加到性能事件上。在本教程中,我们还展示了如何编写 eBPF 程序来捕获进程的内核和用户空间栈信息,进而分析程序性能瓶颈。通过这个例子,您可以了解到 eBPF 在性能分析方面的强大功能。
|
||||
本实现展示了如何将 eBPF 的高性能监控能力与 Rust 的安全性和表达力相结合,创建一个强大而可靠的性能分析工具。通过这个例子,您可以了解到 eBPF 在性能分析方面的强大功能,以及如何使用现代系统编程语言来构建 eBPF 工具。
|
||||
|
||||
如果您希望学习更多关于 eBPF 的知识和实践,请查阅 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 。您还可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 或网站 <https://eunomia.dev/zh/tutorials/> 以获取更多示例和完整的教程。
|
||||
|
||||
|
||||
44
src/12-profile/build.rs
Normal file
44
src/12-profile/build.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libbpf_cargo::SkeletonBuilder;
|
||||
|
||||
const SRC: &str = "src/bpf/profile.bpf.c";
|
||||
|
||||
fn main() {
|
||||
let mut out =
|
||||
PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR must be set in build script"));
|
||||
out.push("profile.skel.rs");
|
||||
|
||||
let _arch = env::var("CARGO_CFG_TARGET_ARCH")
|
||||
.unwrap_or_else(|_| "x86_64".to_string());
|
||||
|
||||
// Map Rust target arch to kernel arch naming
|
||||
let kernel_arch = match _arch.as_str() {
|
||||
"x86_64" => "x86",
|
||||
"aarch64" => "arm64",
|
||||
"arm" => "arm",
|
||||
"powerpc64" => "powerpc",
|
||||
"mips" | "mips64" => "mips",
|
||||
"riscv64" => "riscv",
|
||||
"loongarch64" => "loongarch",
|
||||
_ => "x86", // default to x86
|
||||
};
|
||||
|
||||
// Path to the vmlinux directory containing arch-specific headers
|
||||
let vmlinux_path = format!("../third_party/vmlinux/{}", kernel_arch);
|
||||
|
||||
SkeletonBuilder::new()
|
||||
.source(SRC)
|
||||
.clang_args([
|
||||
OsStr::new("-I"),
|
||||
Path::new(&vmlinux_path).as_os_str(),
|
||||
OsStr::new("-I"),
|
||||
Path::new(".output").as_os_str(),
|
||||
])
|
||||
.build_and_generate(out)
|
||||
.expect("bpf compilation failed");
|
||||
println!("cargo:rerun-if-changed={}", SRC);
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2022 Facebook */
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/bpf.h>
|
||||
|
||||
#include "profile.skel.h"
|
||||
#include "profile.h"
|
||||
#include "blazesym.h"
|
||||
|
||||
/*
|
||||
* This function is from libbpf, but it is not a public API and can only be
|
||||
* used for demonstration. We can use this here because we statically link
|
||||
* against the libbpf built from submodule during build.
|
||||
*/
|
||||
extern int parse_cpu_mask_file(const char *fcpu, bool **mask, int *mask_sz);
|
||||
|
||||
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct blazesym *symbolizer;
|
||||
|
||||
static void show_stack_trace(__u64 *stack, int stack_sz, pid_t pid)
|
||||
{
|
||||
const struct blazesym_result *result;
|
||||
const struct blazesym_csym *sym;
|
||||
sym_src_cfg src;
|
||||
int i, j;
|
||||
|
||||
if (pid) {
|
||||
src.src_type = SRC_T_PROCESS;
|
||||
src.params.process.pid = pid;
|
||||
} else {
|
||||
src.src_type = SRC_T_KERNEL;
|
||||
src.params.kernel.kallsyms = NULL;
|
||||
src.params.kernel.kernel_image = NULL;
|
||||
}
|
||||
|
||||
result = blazesym_symbolize(symbolizer, &src, 1, (const uint64_t *)stack, stack_sz);
|
||||
|
||||
for (i = 0; i < stack_sz; i++) {
|
||||
if (!result || result->size <= i || !result->entries[i].size) {
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result->entries[i].size == 1) {
|
||||
sym = &result->entries[i].syms[0];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %d [<%016llx>] %s+0x%llx %s:%ld\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %d [<%016llx>] %s+0x%llx\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
for (j = 0; j < result->entries[i].size; j++) {
|
||||
sym = &result->entries[i].syms[j];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %s+0x%llx %s:%ld\n",
|
||||
sym->symbol, stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %s+0x%llx\n", sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blazesym_result_free(result);
|
||||
}
|
||||
|
||||
/* Receive events from the ring buffer. */
|
||||
static int event_handler(void *_ctx, void *data, size_t size)
|
||||
{
|
||||
struct stacktrace_event *event = data;
|
||||
|
||||
if (event->kstack_sz <= 0 && event->ustack_sz <= 0)
|
||||
return 1;
|
||||
|
||||
printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id);
|
||||
|
||||
if (event->kstack_sz > 0) {
|
||||
printf("Kernel:\n");
|
||||
show_stack_trace(event->kstack, event->kstack_sz / sizeof(__u64), 0);
|
||||
} else {
|
||||
printf("No Kernel Stack\n");
|
||||
}
|
||||
|
||||
if (event->ustack_sz > 0) {
|
||||
printf("Userspace:\n");
|
||||
show_stack_trace(event->ustack, event->ustack_sz / sizeof(__u64), event->pid);
|
||||
} else {
|
||||
printf("No Userspace Stack\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void show_help(const char *progname)
|
||||
{
|
||||
printf("Usage: %s [-f <frequency>] [-p <pid>] [-h]\n", progname);
|
||||
printf(" -f <frequency> sampling frequency (default: 1)\n");
|
||||
printf(" -p <pid> target PID to profile (default: all processes)\n");
|
||||
printf(" -h show this help\n");
|
||||
}
|
||||
|
||||
int main(int argc, char * const argv[])
|
||||
{
|
||||
const char *online_cpus_file = "/sys/devices/system/cpu/online";
|
||||
int freq = 1, pid = -1, cpu;
|
||||
struct profile_bpf *skel = NULL;
|
||||
struct perf_event_attr attr;
|
||||
struct bpf_link **links = NULL;
|
||||
struct ring_buffer *ring_buf = NULL;
|
||||
int num_cpus, num_online_cpus;
|
||||
int *pefds = NULL, pefd;
|
||||
int argp, i, err = 0;
|
||||
bool *online_mask = NULL;
|
||||
__u32 target_pid = 0; /* 0 means profile all processes */
|
||||
__u32 key = 0;
|
||||
|
||||
while ((argp = getopt(argc, argv, "hf:p:")) != -1) {
|
||||
switch (argp) {
|
||||
case 'f':
|
||||
freq = atoi(optarg);
|
||||
if (freq < 1)
|
||||
freq = 1;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
target_pid = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
default:
|
||||
show_help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
err = parse_cpu_mask_file(online_cpus_file, &online_mask, &num_online_cpus);
|
||||
if (err) {
|
||||
fprintf(stderr, "Fail to get online CPU numbers: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
num_cpus = libbpf_num_possible_cpus();
|
||||
if (num_cpus <= 0) {
|
||||
fprintf(stderr, "Fail to get the number of processors\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
skel = profile_bpf__open_and_load();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "Fail to open and load BPF skeleton\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Set the target PID in the BPF map */
|
||||
if (target_pid) {
|
||||
skel->bss->target_pid = target_pid;
|
||||
printf("Profiling process with PID %u only\n", target_pid);
|
||||
} else {
|
||||
printf("Profiling all processes\n");
|
||||
}
|
||||
|
||||
symbolizer = blazesym_new();
|
||||
if (!symbolizer) {
|
||||
fprintf(stderr, "Fail to create a symbolizer\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Prepare ring buffer to receive events from the BPF program. */
|
||||
ring_buf = ring_buffer__new(bpf_map__fd(skel->maps.events), event_handler, NULL, NULL);
|
||||
if (!ring_buf) {
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pefds = malloc(num_cpus * sizeof(int));
|
||||
for (i = 0; i < num_cpus; i++) {
|
||||
pefds[i] = -1;
|
||||
}
|
||||
|
||||
links = calloc(num_cpus, sizeof(struct bpf_link *));
|
||||
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
attr.type = PERF_TYPE_HARDWARE;
|
||||
attr.size = sizeof(attr);
|
||||
attr.config = PERF_COUNT_HW_CPU_CYCLES;
|
||||
attr.sample_freq = freq;
|
||||
attr.freq = 1;
|
||||
|
||||
for (cpu = 0; cpu < num_cpus; cpu++) {
|
||||
/* skip offline/not present CPUs */
|
||||
if (cpu >= num_online_cpus || !online_mask[cpu])
|
||||
continue;
|
||||
|
||||
/* Set up performance monitoring on a CPU/Core */
|
||||
pefd = perf_event_open(&attr, pid, cpu, -1, PERF_FLAG_FD_CLOEXEC);
|
||||
if (pefd < 0) {
|
||||
fprintf(stderr, "Fail to set up performance monitor on a CPU/Core\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
pefds[cpu] = pefd;
|
||||
|
||||
/* Attach a BPF program on a CPU */
|
||||
links[cpu] = bpf_program__attach_perf_event(skel->progs.profile, pefd);
|
||||
if (!links[cpu]) {
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait and receive stack traces */
|
||||
while (ring_buffer__poll(ring_buf, -1) >= 0) {
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (links) {
|
||||
for (cpu = 0; cpu < num_cpus; cpu++)
|
||||
bpf_link__destroy(links[cpu]);
|
||||
free(links);
|
||||
}
|
||||
if (pefds) {
|
||||
for (i = 0; i < num_cpus; i++) {
|
||||
if (pefds[i] >= 0)
|
||||
close(pefds[i]);
|
||||
}
|
||||
free(pefds);
|
||||
}
|
||||
ring_buffer__free(ring_buf);
|
||||
profile_bpf__destroy(skel);
|
||||
blazesym_free(symbolizer);
|
||||
free(online_mask);
|
||||
return -err;
|
||||
}
|
||||
@@ -14,8 +14,6 @@ struct {
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} events SEC(".maps");
|
||||
|
||||
__u32 target_pid = 0;
|
||||
|
||||
SEC("perf_event")
|
||||
int profile(void *ctx)
|
||||
{
|
||||
@@ -24,22 +22,21 @@ int profile(void *ctx)
|
||||
struct stacktrace_event *event;
|
||||
int cp;
|
||||
|
||||
/* Check if we need to filter by PID */
|
||||
if (target_pid != 0 && target_pid != pid)
|
||||
return 0;
|
||||
|
||||
event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
|
||||
if (!event)
|
||||
return 1;
|
||||
|
||||
event->pid = pid;
|
||||
event->cpu_id = cpu_id;
|
||||
event->timestamp = bpf_ktime_get_ns();
|
||||
|
||||
if (bpf_get_current_comm(event->comm, sizeof(event->comm)))
|
||||
event->comm[0] = 0;
|
||||
|
||||
event->kstack_sz = bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0);
|
||||
event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
|
||||
event->ustack_sz =
|
||||
bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), BPF_F_USER_STACK);
|
||||
|
||||
bpf_ringbuf_submit(event, 0);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef MAX_STACK_DEPTH
|
||||
#define MAX_STACK_DEPTH 128
|
||||
#define MAX_STACK_DEPTH 128
|
||||
#endif
|
||||
|
||||
typedef __u64 stack_trace_t[MAX_STACK_DEPTH];
|
||||
@@ -16,6 +16,7 @@ typedef __u64 stack_trace_t[MAX_STACK_DEPTH];
|
||||
struct stacktrace_event {
|
||||
__u32 pid;
|
||||
__u32 cpu_id;
|
||||
__u64 timestamp;
|
||||
char comm[TASK_COMM_LEN];
|
||||
__s32 kstack_sz;
|
||||
__s32 ustack_sz;
|
||||
309
src/12-profile/src/event.rs
Normal file
309
src/12-profile/src/event.rs
Normal file
@@ -0,0 +1,309 @@
|
||||
use std::mem;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use blazesym::symbolize;
|
||||
use nix::sys::sysinfo;
|
||||
|
||||
pub const MAX_STACK_DEPTH: usize = 128;
|
||||
pub const TASK_COMM_LEN: usize = 16;
|
||||
const ADDR_WIDTH: usize = 16;
|
||||
|
||||
// A Rust version of stacktrace_event in profile.h
|
||||
#[repr(C)]
|
||||
pub struct StacktraceEvent {
|
||||
pub pid: u32,
|
||||
pub cpu_id: u32,
|
||||
pub timestamp: u64,
|
||||
pub comm: [u8; TASK_COMM_LEN],
|
||||
pub kstack_size: i32,
|
||||
pub ustack_size: i32,
|
||||
pub kstack: [u64; MAX_STACK_DEPTH],
|
||||
pub ustack: [u64; MAX_STACK_DEPTH],
|
||||
}
|
||||
|
||||
pub enum OutputFormat {
|
||||
Standard,
|
||||
FoldedExtended,
|
||||
}
|
||||
|
||||
pub struct EventHandler {
|
||||
symbolizer: symbolize::Symbolizer,
|
||||
format: OutputFormat,
|
||||
boot_time_ns: u64,
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
pub fn new(format: OutputFormat) -> Self {
|
||||
// Get system uptime to calculate boot time
|
||||
let boot_time_ns = Self::get_boot_time_ns();
|
||||
|
||||
Self {
|
||||
symbolizer: symbolize::Symbolizer::new(),
|
||||
format,
|
||||
boot_time_ns,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_boot_time_ns() -> u64 {
|
||||
// Get current Unix timestamp in nanoseconds
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("System time before Unix epoch");
|
||||
let now_ns = now.as_nanos() as u64;
|
||||
|
||||
// Get system uptime in nanoseconds
|
||||
let info = sysinfo::sysinfo().expect("Failed to get sysinfo");
|
||||
let uptime_ns = (info.uptime().as_secs_f64() * 1_000_000_000.0) as u64;
|
||||
|
||||
// Boot time = current time - uptime
|
||||
now_ns - uptime_ns
|
||||
}
|
||||
|
||||
pub fn handle(&self, data: &[u8]) -> ::std::os::raw::c_int {
|
||||
if data.len() != mem::size_of::<StacktraceEvent>() {
|
||||
eprintln!(
|
||||
"Invalid size {} != {}",
|
||||
data.len(),
|
||||
mem::size_of::<StacktraceEvent>()
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let event = unsafe { &*(data.as_ptr() as *const StacktraceEvent) };
|
||||
|
||||
if event.kstack_size <= 0 && event.ustack_size <= 0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
match self.format {
|
||||
OutputFormat::Standard => self.handle_standard(event),
|
||||
OutputFormat::FoldedExtended => self.handle_folded_extended(event),
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
// Helper to extract stack slice
|
||||
fn get_stack_slice<'a>(stack: &'a [u64; MAX_STACK_DEPTH], size: i32) -> &'a [u64] {
|
||||
if size > 0 {
|
||||
&stack[0..(size as usize / mem::size_of::<u64>())]
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get command name
|
||||
fn get_comm_str(comm: &[u8; TASK_COMM_LEN]) -> &str {
|
||||
std::str::from_utf8(comm)
|
||||
.unwrap_or("<unknown>")
|
||||
.trim_end_matches('\0')
|
||||
}
|
||||
|
||||
fn handle_standard(&self, event: &StacktraceEvent) {
|
||||
let comm = Self::get_comm_str(&event.comm);
|
||||
// Convert kernel timestamp to Unix timestamp
|
||||
let unix_timestamp_ns = event.timestamp + self.boot_time_ns;
|
||||
let timestamp_sec = unix_timestamp_ns / 1_000_000_000;
|
||||
let timestamp_nsec = unix_timestamp_ns % 1_000_000_000;
|
||||
println!("[{}.{:09}] COMM: {} (pid={}) @ CPU {}",
|
||||
timestamp_sec, timestamp_nsec, comm, event.pid, event.cpu_id);
|
||||
|
||||
if event.kstack_size > 0 {
|
||||
println!("Kernel:");
|
||||
let kstack = Self::get_stack_slice(&event.kstack, event.kstack_size);
|
||||
show_stack_trace(kstack, &self.symbolizer, 0);
|
||||
} else {
|
||||
println!("No Kernel Stack");
|
||||
}
|
||||
|
||||
if event.ustack_size > 0 {
|
||||
println!("Userspace:");
|
||||
let ustack = Self::get_stack_slice(&event.ustack, event.ustack_size);
|
||||
show_stack_trace(ustack, &self.symbolizer, event.pid);
|
||||
} else {
|
||||
println!("No Userspace Stack");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
fn handle_folded_extended(&self, event: &StacktraceEvent) {
|
||||
let comm = Self::get_comm_str(&event.comm);
|
||||
let tid = event.pid; // For single-threaded processes, TID = PID
|
||||
|
||||
let mut stack_frames = Vec::new();
|
||||
|
||||
// Process user stack (if present)
|
||||
if event.ustack_size > 0 {
|
||||
let ustack = Self::get_stack_slice(&event.ustack, event.ustack_size);
|
||||
let user_frames = symbolize_stack_to_vec(&self.symbolizer, ustack, event.pid);
|
||||
|
||||
// Add user frames in reverse order (top to bottom)
|
||||
for frame in user_frames.iter().rev() {
|
||||
stack_frames.push(frame.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Process kernel stack (if present)
|
||||
if event.kstack_size > 0 {
|
||||
let kstack = Self::get_stack_slice(&event.kstack, event.kstack_size);
|
||||
let kernel_frames = symbolize_stack_to_vec(&self.symbolizer, kstack, 0);
|
||||
|
||||
// Add kernel frames with [k] suffix in reverse order (top to bottom)
|
||||
for frame in kernel_frames.iter().rev() {
|
||||
stack_frames.push(format!("{}_[k]", frame));
|
||||
}
|
||||
}
|
||||
|
||||
// Format: timestamp_ns comm pid tid cpu stack1;stack2;stack3
|
||||
// Convert kernel timestamp to Unix timestamp
|
||||
let unix_timestamp_ns = event.timestamp + self.boot_time_ns;
|
||||
println!(
|
||||
"{} {} {} {} {} {}",
|
||||
unix_timestamp_ns,
|
||||
comm,
|
||||
event.pid,
|
||||
tid,
|
||||
event.cpu_id,
|
||||
stack_frames.join(";")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn print_frame(
|
||||
name: &str,
|
||||
addr_info: Option<(blazesym::Addr, blazesym::Addr, usize)>,
|
||||
code_info: &Option<symbolize::CodeInfo>,
|
||||
) {
|
||||
let code_info = code_info.as_ref().map(|code_info| {
|
||||
let path = code_info.to_path();
|
||||
let path = path.display();
|
||||
|
||||
match (code_info.line, code_info.column) {
|
||||
(Some(line), Some(col)) => format!(" {path}:{line}:{col}"),
|
||||
(Some(line), None) => format!(" {path}:{line}"),
|
||||
(None, _) => format!(" {path}"),
|
||||
}
|
||||
});
|
||||
|
||||
if let Some((input_addr, addr, offset)) = addr_info {
|
||||
// If we have various address information bits we have a new symbol.
|
||||
println!(
|
||||
"{input_addr:#0width$x}: {name} @ {addr:#x}+{offset:#x}{code_info}",
|
||||
code_info = code_info.as_deref().unwrap_or(""),
|
||||
width = ADDR_WIDTH
|
||||
)
|
||||
} else {
|
||||
// Otherwise we are dealing with an inlined call.
|
||||
println!(
|
||||
"{:width$} {name}{code_info} [inlined]",
|
||||
" ",
|
||||
code_info = code_info
|
||||
.map(|info| format!(" @{info}"))
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
width = ADDR_WIDTH
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to convert stack addresses for blazesym
|
||||
fn convert_stack_addresses(stack: &[u64]) -> Vec<blazesym::Addr> {
|
||||
if mem::size_of::<blazesym::Addr>() != mem::size_of::<u64>() {
|
||||
stack
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|addr| addr as blazesym::Addr)
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
// For same-sized types, still need to return owned data for consistency
|
||||
stack.iter().copied().map(|addr| addr as blazesym::Addr).collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Get the stack addresses as a slice (avoiding lifetime issues)
|
||||
fn get_stack_slice<'a>(stack: &'a [u64], converted: &'a [blazesym::Addr]) -> &'a [blazesym::Addr] {
|
||||
if mem::size_of::<blazesym::Addr>() != mem::size_of::<u64>() {
|
||||
converted
|
||||
} else {
|
||||
// SAFETY: `Addr` has the same size as `u64`, so it can be trivially and
|
||||
// safely converted.
|
||||
unsafe { mem::transmute::<_, &[blazesym::Addr]>(stack) }
|
||||
}
|
||||
}
|
||||
|
||||
// Get source for symbolization based on PID (0 means kernel)
|
||||
fn get_symbolize_source(pid: u32) -> symbolize::source::Source<'static> {
|
||||
if pid == 0 {
|
||||
symbolize::source::Source::from(symbolize::source::Kernel::default())
|
||||
} else {
|
||||
symbolize::source::Source::from(symbolize::source::Process::new(pid.into()))
|
||||
}
|
||||
}
|
||||
|
||||
// Symbolize stack and return as vector of strings for folded format
|
||||
fn symbolize_stack_to_vec(symbolizer: &symbolize::Symbolizer, stack: &[u64], pid: u32) -> Vec<String> {
|
||||
let converted = convert_stack_addresses(stack);
|
||||
let stack_addrs = get_stack_slice(stack, &converted);
|
||||
let src = get_symbolize_source(pid);
|
||||
|
||||
let syms = match symbolizer.symbolize(&src, symbolize::Input::AbsAddr(stack_addrs)) {
|
||||
Ok(syms) => syms,
|
||||
Err(_) => {
|
||||
// Return addresses if symbolization fails
|
||||
return stack_addrs.iter().map(|addr| format!("{:#x}", addr)).collect();
|
||||
}
|
||||
};
|
||||
|
||||
let mut result = Vec::new();
|
||||
for (addr, sym) in stack_addrs.iter().copied().zip(syms) {
|
||||
match sym {
|
||||
symbolize::Symbolized::Sym(symbolize::Sym {
|
||||
name,
|
||||
..
|
||||
}) => {
|
||||
result.push(name.to_string());
|
||||
}
|
||||
symbolize::Symbolized::Unknown(..) => {
|
||||
result.push(format!("{:#x}", addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// Pid 0 means a kernel space stack.
|
||||
fn show_stack_trace(stack: &[u64], symbolizer: &symbolize::Symbolizer, pid: u32) {
|
||||
let converted = convert_stack_addresses(stack);
|
||||
let stack_addrs = get_stack_slice(stack, &converted);
|
||||
let src = get_symbolize_source(pid);
|
||||
|
||||
let syms = match symbolizer.symbolize(&src, symbolize::Input::AbsAddr(stack_addrs)) {
|
||||
Ok(syms) => syms,
|
||||
Err(err) => {
|
||||
eprintln!(" failed to symbolize addresses: {err:#}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for (input_addr, sym) in stack_addrs.iter().copied().zip(syms) {
|
||||
match sym {
|
||||
symbolize::Symbolized::Sym(symbolize::Sym {
|
||||
name,
|
||||
addr,
|
||||
offset,
|
||||
code_info,
|
||||
inlined,
|
||||
..
|
||||
}) => {
|
||||
print_frame(&name, Some((input_addr, addr, offset)), &code_info);
|
||||
for frame in inlined.iter() {
|
||||
print_frame(&frame.name, None, &frame.code_info);
|
||||
}
|
||||
}
|
||||
symbolize::Symbolized::Unknown(..) => {
|
||||
println!("{input_addr:#0width$x}: <no-symbol>", width = ADDR_WIDTH)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/12-profile/src/main.rs
Normal file
95
src/12-profile/src/main.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::mem::MaybeUninit;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::ArgAction;
|
||||
use clap::Parser;
|
||||
|
||||
use libbpf_rs::skel::OpenSkel as _;
|
||||
use libbpf_rs::skel::SkelBuilder as _;
|
||||
|
||||
use tracing::subscriber::set_global_default as set_global_subscriber;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
use tracing_subscriber::fmt::time::SystemTime;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
mod profile {
|
||||
include!(concat!(env!("OUT_DIR"), "/profile.skel.rs"));
|
||||
}
|
||||
mod syscall;
|
||||
mod event;
|
||||
mod perf;
|
||||
|
||||
use profile::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
/// Sampling frequency
|
||||
#[arg(short, default_value_t = 50)]
|
||||
freq: u64,
|
||||
/// Increase verbosity (can be supplied multiple times).
|
||||
#[arg(short = 'v', long = "verbose", global = true, action = ArgAction::Count)]
|
||||
verbosity: u8,
|
||||
/// Use software event for triggering stack trace capture.
|
||||
///
|
||||
/// This can be useful for compatibility reasons if hardware event is not available
|
||||
/// (which could happen in a virtual machine, for example).
|
||||
#[arg(long = "sw-event")]
|
||||
sw_event: bool,
|
||||
/// Filter by PID (optional)
|
||||
#[arg(short = 'p', long = "pid")]
|
||||
pid: Option<i32>,
|
||||
/// Output in extended folded format (timestamp_ns comm pid tid cpu stack1;stack2;...)
|
||||
#[arg(short = 'E', long = "fold-extend")]
|
||||
fold_extend: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), libbpf_rs::Error> {
|
||||
let args = Args::parse();
|
||||
let level = match args.verbosity {
|
||||
0 => LevelFilter::WARN,
|
||||
1 => LevelFilter::INFO,
|
||||
2 => LevelFilter::DEBUG,
|
||||
_ => LevelFilter::TRACE,
|
||||
};
|
||||
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(level)
|
||||
.with_span_events(FmtSpan::FULL)
|
||||
.with_timer(SystemTime)
|
||||
.finish();
|
||||
let () = set_global_subscriber(subscriber).expect("failed to set tracing subscriber");
|
||||
|
||||
let freq = if args.freq < 1 { 1 } else { args.freq };
|
||||
|
||||
let skel_builder = ProfileSkelBuilder::default();
|
||||
let mut open_object = MaybeUninit::uninit();
|
||||
let open_skel = skel_builder.open(&mut open_object).unwrap();
|
||||
let skel = open_skel.load().unwrap();
|
||||
|
||||
let pefds = perf::init_perf_monitor(freq, args.sw_event, args.pid)?;
|
||||
let _links = perf::attach_perf_event(&pefds, &skel.progs.profile);
|
||||
|
||||
let mut builder = libbpf_rs::RingBufferBuilder::new();
|
||||
|
||||
let output_format = if args.fold_extend {
|
||||
event::OutputFormat::FoldedExtended
|
||||
} else {
|
||||
event::OutputFormat::Standard
|
||||
};
|
||||
|
||||
let event_handler = event::EventHandler::new(output_format);
|
||||
|
||||
builder
|
||||
.add(&skel.maps.events, move |data| {
|
||||
event_handler.handle(data)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let ringbuf = builder.build().unwrap();
|
||||
while ringbuf.poll(Duration::MAX).is_ok() {}
|
||||
|
||||
perf::close_perf_events(pefds)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
63
src/12-profile/src/perf.rs
Normal file
63
src/12-profile/src/perf.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use nix::unistd::close;
|
||||
use libbpf_rs::ErrorExt as _;
|
||||
|
||||
use crate::syscall;
|
||||
|
||||
pub fn init_perf_monitor(freq: u64, sw_event: bool, pid_filter: Option<i32>) -> Result<Vec<i32>, libbpf_rs::Error> {
|
||||
let nprocs = libbpf_rs::num_possible_cpus().unwrap();
|
||||
let pid = pid_filter.unwrap_or(-1);
|
||||
let attr = syscall::perf_event_attr {
|
||||
_type: if sw_event {
|
||||
syscall::PERF_TYPE_SOFTWARE
|
||||
} else {
|
||||
syscall::PERF_TYPE_HARDWARE
|
||||
},
|
||||
size: mem::size_of::<syscall::perf_event_attr>() as u32,
|
||||
config: if sw_event {
|
||||
syscall::PERF_COUNT_SW_CPU_CLOCK
|
||||
} else {
|
||||
syscall::PERF_COUNT_HW_CPU_CYCLES
|
||||
},
|
||||
sample: syscall::sample_un { sample_freq: freq },
|
||||
flags: 1 << 10, // freq = 1
|
||||
..Default::default()
|
||||
};
|
||||
(0..nprocs)
|
||||
.map(|cpu| {
|
||||
let fd = syscall::perf_event_open(&attr, pid, cpu as i32, -1, 0) as i32;
|
||||
if fd == -1 {
|
||||
let mut error_context = "Failed to open perf event.";
|
||||
let os_error = io::Error::last_os_error();
|
||||
if !sw_event && os_error.kind() == io::ErrorKind::NotFound {
|
||||
error_context = "Failed to open perf event.\n\
|
||||
Try running the profile example with the `--sw-event` option.";
|
||||
}
|
||||
Err(libbpf_rs::Error::from(os_error)).context(error_context)
|
||||
} else {
|
||||
Ok(fd)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn attach_perf_event(
|
||||
pefds: &[i32],
|
||||
prog: &libbpf_rs::ProgramMut,
|
||||
) -> Vec<Result<libbpf_rs::Link, libbpf_rs::Error>> {
|
||||
pefds
|
||||
.iter()
|
||||
.map(|pefd| prog.attach_perf_event(*pefd))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn close_perf_events(pefds: Vec<i32>) -> Result<(), libbpf_rs::Error> {
|
||||
for pefd in pefds {
|
||||
close(pefd)
|
||||
.map_err(io::Error::from)
|
||||
.map_err(libbpf_rs::Error::from)
|
||||
.context("failed to close perf event")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
90
src/12-profile/src/syscall.rs
Normal file
90
src/12-profile/src/syscall.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::mem;
|
||||
|
||||
extern crate libc;
|
||||
|
||||
#[repr(C)]
|
||||
pub union sample_un {
|
||||
pub sample_period: u64,
|
||||
pub sample_freq: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union wakeup_un {
|
||||
pub wakeup_events: u32,
|
||||
pub wakeup_atermark: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union bp_1_un {
|
||||
pub bp_addr: u64,
|
||||
pub kprobe_func: u64,
|
||||
pub uprobe_path: u64,
|
||||
pub config1: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union bp_2_un {
|
||||
pub bp_len: u64,
|
||||
pub kprobe_addr: u64,
|
||||
pub probe_offset: u64,
|
||||
pub config2: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct perf_event_attr {
|
||||
pub _type: u32,
|
||||
pub size: u32,
|
||||
pub config: u64,
|
||||
pub sample: sample_un,
|
||||
pub sample_type: u64,
|
||||
pub read_format: u64,
|
||||
pub flags: u64,
|
||||
pub wakeup: wakeup_un,
|
||||
pub bp_type: u32,
|
||||
pub bp_1: bp_1_un,
|
||||
pub bp_2: bp_2_un,
|
||||
pub branch_sample_type: u64,
|
||||
pub sample_regs_user: u64,
|
||||
pub sample_stack_user: u32,
|
||||
pub clockid: i32,
|
||||
pub sample_regs_intr: u64,
|
||||
pub aux_watermark: u32,
|
||||
pub sample_max_stack: u16,
|
||||
pub __reserved_2: u16,
|
||||
pub aux_sample_size: u32,
|
||||
pub __reserved_3: u32,
|
||||
}
|
||||
|
||||
impl Default for perf_event_attr {
|
||||
fn default() -> Self {
|
||||
unsafe { mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
pub const PERF_TYPE_HARDWARE: u32 = 0;
|
||||
pub const PERF_TYPE_SOFTWARE: u32 = 1;
|
||||
pub const PERF_COUNT_HW_CPU_CYCLES: u64 = 0;
|
||||
pub const PERF_COUNT_SW_CPU_CLOCK: u64 = 0;
|
||||
|
||||
extern "C" {
|
||||
fn syscall(number: libc::c_long, ...) -> libc::c_long;
|
||||
}
|
||||
|
||||
pub fn perf_event_open(
|
||||
hw_event: &perf_event_attr,
|
||||
pid: libc::pid_t,
|
||||
cpu: libc::c_int,
|
||||
group_fd: libc::c_int,
|
||||
flags: libc::c_ulong,
|
||||
) -> libc::c_long {
|
||||
unsafe {
|
||||
syscall(
|
||||
libc::SYS_perf_event_open,
|
||||
hw_event as *const perf_event_attr,
|
||||
pid,
|
||||
cpu,
|
||||
group_fd,
|
||||
flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user